diff options
author | Peter Kim <pk15950@gmail.com> | 2021-10-03 06:22:05 +0300 |
---|---|---|
committer | Peter Kim <pk15950@gmail.com> | 2021-10-03 06:22:05 +0300 |
commit | 6fc81d6bca6424a1e44305df7cdc3598e03b00ba (patch) | |
tree | a66f17c5378f2a68f4c5d8b09f56687c3d9bf888 | |
parent | 85e1f28fcaafd137a546bf192777b00f96851e80 (diff) | |
parent | d3afe0c1265c9ebb53053de68f176b30f0132281 (diff) |
Merge branch 'master' into xr-controller-supportxr-controller-support
867 files changed, 27159 insertions, 6890 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 8e807b84e22..c4b8bf6dcd4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -419,6 +419,8 @@ mark_as_advanced(WITH_CYCLES_NATIVE_ONLY) option(WITH_CYCLES_DEVICE_CUDA "Enable Cycles CUDA compute support" ON) option(WITH_CYCLES_DEVICE_OPTIX "Enable Cycles OptiX support" ON) +option(WITH_CYCLES_DEVICE_HIP "Enable Cycles HIP support" OFF) +mark_as_advanced(WITH_CYCLES_DEVICE_HIP) mark_as_advanced(WITH_CYCLES_DEVICE_CUDA) option(WITH_CUDA_DYNLOAD "Dynamically load CUDA libraries at runtime" ON) @@ -821,6 +823,11 @@ if(NOT WITH_CUDA_DYNLOAD) endif() endif() +if(WITH_CYCLES_DEVICE_HIP) + # Currently HIP must be dynamically loaded, this may change in future toolkits + set(WITH_HIP_DYNLOAD ON) +endif() + #----------------------------------------------------------------------------- # Check check if submodules are cloned @@ -1850,6 +1857,9 @@ elseif(WITH_CYCLES_STANDALONE) if(WITH_CUDA_DYNLOAD) add_subdirectory(extern/cuew) endif() + if(WITH_HIP_DYNLOAD) + add_subdirectory(extern/hipew) + endif() if(NOT WITH_SYSTEM_GLEW) add_subdirectory(extern/glew) endif() diff --git a/doc/python_api/examples/bpy.types.Bone.convert_local_to_pose.py b/doc/python_api/examples/bpy.types.Bone.convert_local_to_pose.py new file mode 100644 index 00000000000..f3cc95dec61 --- /dev/null +++ b/doc/python_api/examples/bpy.types.Bone.convert_local_to_pose.py @@ -0,0 +1,40 @@ +""" +This method enables conversions between Local and Pose space for bones in +the middle of updating the armature without having to update dependencies +after each change, by manually carrying updated matrices in a recursive walk. +""" + +def set_pose_matrices(obj, matrix_map): + "Assign pose space matrices of all bones at once, ignoring constraints." + + def rec(pbone, parent_matrix): + matrix = matrix_map[pbone.name] + + ## Instead of: + # pbone.matrix = matrix + # bpy.context.view_layer.update() + + # Compute and assign local matrix, using the new parent matrix + if pbone.parent: + pbone.matrix_basis = pbone.bone.convert_local_to_pose( + matrix, + pbone.bone.matrix_local, + parent_matrix=parent_matrix, + parent_matrix_local=pbone.parent.bone.matrix_local, + invert=True + ) + else: + pbone.matrix_basis = pbone.bone.convert_local_to_pose( + matrix, + pbone.bone.matrix_local, + invert=True + ) + + # Recursively process children, passing the new matrix through + for child in pbone.children: + rec(child, matrix) + + # Scan all bone trees from their roots + for pbone in obj.pose.bones: + if not pbone.parent: + rec(pbone, None) diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index 7f7d91f0765..1fdc8e60167 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -67,9 +67,12 @@ endif() if(WITH_CYCLES OR WITH_COMPOSITOR OR WITH_OPENSUBDIV) add_subdirectory(clew) - if(WITH_CUDA_DYNLOAD) + if((WITH_CYCLES_DEVICE_CUDA OR WITH_CYCLES_DEVICE_OPTIX) AND WITH_CUDA_DYNLOAD) add_subdirectory(cuew) endif() + if(WITH_CYCLES_DEVICE_HIP AND WITH_HIP_DYNLOAD) + add_subdirectory(hipew) + endif() endif() if(WITH_GHOST_X11 AND WITH_GHOST_XDND) diff --git a/extern/audaspace/bindings/C/AUD_Sound.cpp b/extern/audaspace/bindings/C/AUD_Sound.cpp index aa246b9a047..8a3c9d1bbc9 100644 --- a/extern/audaspace/bindings/C/AUD_Sound.cpp +++ b/extern/audaspace/bindings/C/AUD_Sound.cpp @@ -102,26 +102,30 @@ AUD_API int AUD_Sound_getFileStreams(AUD_Sound* sound, AUD_StreamInfo **stream_i if(file) { - auto streams = file->queryStreams(); + try + { + auto streams = file->queryStreams(); - size_t size = sizeof(AUD_StreamInfo) * streams.size(); + size_t size = sizeof(AUD_StreamInfo) * streams.size(); - if(!size) - { - *stream_infos = nullptr; - return 0; - } + if(!size) + { + *stream_infos = nullptr; + return 0; + } - *stream_infos = reinterpret_cast<AUD_StreamInfo*>(std::malloc(size)); - std::memcpy(*stream_infos, streams.data(), size); + *stream_infos = reinterpret_cast<AUD_StreamInfo*>(std::malloc(size)); + std::memcpy(*stream_infos, streams.data(), size); - return streams.size(); - } - else - { - *stream_infos = nullptr; - return 0; + return streams.size(); + } + catch(Exception&) + { + } } + + *stream_infos = nullptr; + return 0; } AUD_API sample_t* AUD_Sound_data(AUD_Sound* sound, int* length, AUD_Specs* specs) diff --git a/extern/hipew/CMakeLists.txt b/extern/hipew/CMakeLists.txt new file mode 100644 index 00000000000..d215ea8c691 --- /dev/null +++ b/extern/hipew/CMakeLists.txt @@ -0,0 +1,39 @@ +# ***** BEGIN GPL LICENSE BLOCK ***** +# +# 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) 2021, Blender Foundation +# All rights reserved. +# ***** END GPL LICENSE BLOCK ***** + +set(INC + . + include +) + +set(INC_SYS + +) + +set(SRC + src/hipew.c + + include/hipew.h +) + +set(LIB +) + +blender_add_lib(extern_hipew "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") diff --git a/extern/hipew/include/hipew.h b/extern/hipew/include/hipew.h new file mode 100644 index 00000000000..02fffc331bf --- /dev/null +++ b/extern/hipew/include/hipew.h @@ -0,0 +1,1207 @@ +/* + * Copyright 2011-2021 Blender Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +#ifndef __HIPEW_H__ +#define __HIPEW_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdlib.h> + +#define HIP_IPC_HANDLE_SIZE 64 +#define hipHostMallocPortable 0x01 +#define hipHostMallocMapped 0x02 +#define hipHostMallocWriteCombined 0x04 +#define hipHostRegisterPortable 0x01 +#define hipHostRegisterMapped 0x02 +#define hipHostRegisterIoMemory 0x04 +#define hipCooperativeLaunchMultiDeviceNoPreSync 0x01 +#define hipCooperativeLaunchMultiDeviceNoPostSync 0x02 +#define hipArrayLayered 0x01 +#define hipArraySurfaceLoadStore 0x02 +#define hipArrayCubemap 0x04 +#define hipArrayTextureGather 0x08 +#define HIP_TRSA_OVERRIDE_FORMAT 0x01 +#define HIP_TRSF_READ_AS_INTEGER 0x01 +#define HIP_TRSF_NORMALIZED_COORDINATES 0x02 +#define HIP_LAUNCH_PARAM_END ((void*)0x00) +#define HIP_LAUNCH_PARAM_BUFFER_POINTER ((void*)0x01) +#define HIP_LAUNCH_PARAM_BUFFER_SIZE ((void*)0x02) + +/* Functions which changed 3.1 -> 3.2 for 64 bit stuff, + * the cuda library has both the old ones for compatibility and new + * ones with _v2 postfix, + */ +#define hipModuleGetGlobal hipModuleGetGlobal +#define hipMemGetInfo hipMemGetInfo +#define hipMemAllocPitch hipMemAllocPitch +#define hipMemGetAddressRange hipMemGetAddressRange +#define hipMemcpyHtoD hipMemcpyHtoD +#define hipMemcpyDtoH hipMemcpyDtoH +#define hipMemcpyDtoD hipMemcpyDtoD +#define hipMemcpyHtoA hipMemcpyHtoA +#define hipMemcpyAtoH hipMemcpyAtoH +#define hipMemcpyHtoDAsync hipMemcpyHtoDAsync +#define hipMemcpyDtoHAsync hipMemcpyDtoHAsync +#define hipMemcpyDtoDAsync hipMemcpyDtoDAsync +#define hipMemsetD8 hipMemsetD8 +#define hipMemsetD16 hipMemsetD16 +#define hipMemsetD32 hipMemsetD32 +#define hipArrayCreate hipArrayCreate +#define hipArray3DCreate hipArray3DCreate +#define hipTexRefSetAddress hipTexRefSetAddress +#define hipTexRefGetAddress hipTexRefGetAddress +#define hipStreamDestroy hipStreamDestroy +#define hipEventDestroy hipEventDestroy +#define hipTexRefSetAddress2D hipTexRefSetAddress2D + +/* Types. */ +#ifdef _MSC_VER +typedef unsigned __int32 hipuint32_t; +typedef unsigned __int64 hipuint64_t; +#else +#include <stdint.h> +typedef uint32_t hipuint32_t; +typedef uint64_t hipuint64_t; +#endif + +#if defined(__x86_64) || defined(AMD64) || defined(_M_AMD64) || defined (__aarch64__) +typedef unsigned long long hipDeviceptr_t; +#else +typedef unsigned int hipDeviceptr_t; +#endif + + +#ifdef _WIN32 +# define HIPAPI __stdcall +# define HIP_CB __stdcall +#else +# define HIPAPI +# define HIP_CB +#endif + +typedef int hipDevice_t; +typedef struct ihipCtx_t* hipCtx_t; +typedef struct ihipModule_t* hipModule_t; +typedef struct ihipModuleSymbol_t* hipFunction_t; +typedef struct hipArray* hArray; +typedef struct hipMipmappedArray_st* hipMipmappedArray_t; +typedef struct ihipEvent_t* hipEvent_t; +typedef struct ihipStream_t* hipStream_t; +typedef unsigned long long hipTextureObject_t; + +typedef struct HIPuuid_st { + char bytes[16]; +} HIPuuid; + +typedef enum hipChannelFormatKind { + hipChannelFormatKindSigned = 0, + hipChannelFormatKindUnsigned = 1, + hipChannelFormatKindFloat = 2, + hipChannelFormatKindNone = 3, +}hipChannelFormatKind; + +typedef struct hipChannelFormatDesc { + int x; + int y; + int z; + int w; + enum hipChannelFormatKind f; +}hipChannelFormatDesc; + +typedef enum hipTextureFilterMode { + hipFilterModePoint = 0, + hipFilterModeLinear = 1, +} hipTextureFilterMode; + +typedef enum hipArray_Format { + HIP_AD_FORMAT_UNSIGNED_INT8 = 0x01, + HIP_AD_FORMAT_SIGNED_INT8 = 0x08, + HIP_AD_FORMAT_UNSIGNED_INT16 = 0x02, + HIP_AD_FORMAT_SIGNED_INT16 = 0x09, + HIP_AD_FORMAT_UNSIGNED_INT32 = 0x03, + HIP_AD_FORMAT_SIGNED_INT32 = 0x0a, + HIP_AD_FORMAT_HALF = 0x10, + HIP_AD_FORMAT_FLOAT = 0x20, +} hipArray_Format; + +typedef enum hipTextureAddressMode { + hipAddressModeWrap = 0, + hipAddressModeClamp = 1, + hipAddressModeMirror = 2, + hipAddressModeBorder = 3, +} hipTextureAddressMode; + +/** + * hip texture reference + */ +typedef struct textureReference { + int normalized; + //enum hipTextureReadMode readMode;// used only for driver API's + enum hipTextureFilterMode filterMode; + enum hipTextureAddressMode addressMode[3]; // Texture address mode for up to 3 dimensions + struct hipChannelFormatDesc channelDesc; + int sRGB; // Perform sRGB->linear conversion during texture read + unsigned int maxAnisotropy; // Limit to the anisotropy ratio + enum hipTextureFilterMode mipmapFilterMode; + float mipmapLevelBias; + float minMipmapLevelClamp; + float maxMipmapLevelClamp; + + hipTextureObject_t textureObject; + int numChannels; + enum hipArray_Format format; +}textureReference; + +typedef textureReference* hipTexRef; + +typedef enum hipMemoryType { + hipMemoryTypeHost = 0x00, + hipMemoryTypeDevice = 0x01, + hipMemoryTypeArray = 0x02, + hipMemoryTypeUnified = 0x03, +} hipMemoryType; + +/** + * Pointer attributes + */ +typedef struct hipPointerAttribute_t { + enum hipMemoryType memoryType; + int device; + void* devicePointer; + void* hostPointer; + int isManaged; + unsigned allocationFlags; /* flags specified when memory was allocated*/ + /* peers? */ +} hipPointerAttribute_t; + +typedef struct ihipIpcEventHandle_t { + char reserved[HIP_IPC_HANDLE_SIZE]; +} ihipIpcEventHandle_t; + +typedef struct hipIpcMemHandle_st { + char reserved[HIP_IPC_HANDLE_SIZE]; +} hipIpcMemHandle_t; + +typedef enum HIPipcMem_flags_enum { + hipIpcMemLazyEnablePeerAccess = 0x1, +} HIPipcMem_flags; + +typedef enum HIPmemAttach_flags_enum { + hipMemAttachGlobal = 0x1, + hipMemAttachHost = 0x2, + HIP_MEM_ATTACH_SINGLE = 0x4, +} HIPmemAttach_flags; + +typedef enum HIPctx_flags_enum { + hipDeviceScheduleAuto = 0x00, + hipDeviceScheduleSpin = 0x01, + hipDeviceScheduleYield = 0x02, + hipDeviceScheduleBlockingSync = 0x04, + hipDeviceScheduleMask = 0x07, + hipDeviceMapHost = 0x08, + hipDeviceLmemResizeToMax = 0x10, +} HIPctx_flags; + +typedef enum HIPstream_flags_enum { + hipStreamDefault = 0x0, + hipStreamNonBlocking = 0x1, +} HIPstream_flags; + +typedef enum HIPevent_flags_enum { + hipEventDefault = 0x0, + hipEventBlockingSync = 0x1, + hipEventDisableTiming = 0x2, + hipEventInterprocess = 0x4, +} HIPevent_flags; + +typedef enum HIPstreamWaitValue_flags_enum { + HIP_STREAM_WAIT_VALUE_GEQ = 0x0, + HIP_STREAM_WAIT_VALUE_EQ = 0x1, + HIP_STREAM_WAIT_VALUE_AND = 0x2, + HIP_STREAM_WAIT_VALUE_NOR = 0x3, + HIP_STREAM_WAIT_VALUE_FLUSH = (1 << 30), +} HIPstreamWaitValue_flags; + +typedef enum HIPstreamWriteValue_flags_enum { + HIP_STREAM_WRITE_VALUE_DEFAULT = 0x0, + HIP_STREAM_WRITE_VALUE_NO_MEMORY_BARRIER = 0x1, +} HIPstreamWriteValue_flags; + +typedef enum HIPstreamBatchMemOpType_enum { + HIP_STREAM_MEM_OP_WAIT_VALUE_32 = 1, + HIP_STREAM_MEM_OP_WRITE_VALUE_32 = 2, + HIP_STREAM_MEM_OP_WAIT_VALUE_64 = 4, + HIP_STREAM_MEM_OP_WRITE_VALUE_64 = 5, + HIP_STREAM_MEM_OP_FLUSH_REMOTE_WRITES = 3, +} HIPstreamBatchMemOpType; + + +typedef union HIPstreamBatchMemOpParams_union { + HIPstreamBatchMemOpType operation; + struct HIPstreamMemOpWaitValueParams_st { + HIPstreamBatchMemOpType operation; + hipDeviceptr_t address; + union { + hipuint32_t value; + hipuint64_t value64; + }; + unsigned int flags; + hipDeviceptr_t alias; + } waitValue; + struct HIPstreamMemOpWriteValueParams_st { + HIPstreamBatchMemOpType operation; + hipDeviceptr_t address; + union { + hipuint32_t value; + hipuint64_t value64; + }; + unsigned int flags; + hipDeviceptr_t alias; + } writeValue; + struct HIPstreamMemOpFlushRemoteWritesParams_st { + HIPstreamBatchMemOpType operation; + unsigned int flags; + } flushRemoteWrites; + hipuint64_t pad[6]; +} HIPstreamBatchMemOpParams; + +typedef enum HIPoccupancy_flags_enum { + hipOccupancyDefault = 0x0, + HIP_OCCUPANCY_DISABLE_CACHING_OVERRIDE = 0x1, +} HIPoccupancy_flags; + +typedef enum hipDeviceAttribute_t { + hipDeviceAttributeCudaCompatibleBegin = 0, + hipDeviceAttributeEccEnabled = hipDeviceAttributeCudaCompatibleBegin, ///< Whether ECC support is enabled. + hipDeviceAttributeAccessPolicyMaxWindowSize, ///< Cuda only. The maximum size of the window policy in bytes. + hipDeviceAttributeAsyncEngineCount, ///< Cuda only. Asynchronous engines number. + hipDeviceAttributeCanMapHostMemory, ///< Whether host memory can be mapped into device address space + hipDeviceAttributeCanUseHostPointerForRegisteredMem,///< Cuda only. Device can access host registered memory + ///< at the same virtual address as the CPU + hipDeviceAttributeClockRate, ///< Peak clock frequency in kilohertz. + hipDeviceAttributeComputeMode, ///< Compute mode that device is currently in. + hipDeviceAttributeComputePreemptionSupported, ///< Cuda only. Device supports Compute Preemption. + hipDeviceAttributeConcurrentKernels, ///< Device can possibly execute multiple kernels concurrently. + hipDeviceAttributeConcurrentManagedAccess, ///< Device can coherently access managed memory concurrently with the CPU + hipDeviceAttributeCooperativeLaunch, ///< Support cooperative launch + hipDeviceAttributeCooperativeMultiDeviceLaunch, ///< Support cooperative launch on multiple devices + hipDeviceAttributeDeviceOverlap, ///< Cuda only. Device can concurrently copy memory and execute a kernel. + ///< Deprecated. Use instead asyncEngineCount. + hipDeviceAttributeDirectManagedMemAccessFromHost, ///< Host can directly access managed memory on + ///< the device without migration + hipDeviceAttributeGlobalL1CacheSupported, ///< Cuda only. Device supports caching globals in L1 + hipDeviceAttributeHostNativeAtomicSupported, ///< Cuda only. Link between the device and the host supports native atomic operations + hipDeviceAttributeIntegrated, ///< Device is integrated GPU + hipDeviceAttributeIsMultiGpuBoard, ///< Multiple GPU devices. + hipDeviceAttributeKernelExecTimeout, ///< Run time limit for kernels executed on the device + hipDeviceAttributeL2CacheSize, ///< Size of L2 cache in bytes. 0 if the device doesn't have L2 cache. + hipDeviceAttributeLocalL1CacheSupported, ///< caching locals in L1 is supported + hipDeviceAttributeLuid, ///< Cuda only. 8-byte locally unique identifier in 8 bytes. Undefined on TCC and non-Windows platforms + hipDeviceAttributeLuidDeviceNodeMask, ///< Cuda only. Luid device node mask. Undefined on TCC and non-Windows platforms + hipDeviceAttributeComputeCapabilityMajor, ///< Major compute capability version number. + hipDeviceAttributeManagedMemory, ///< Device supports allocating managed memory on this system + hipDeviceAttributeMaxBlocksPerMultiProcessor, ///< Cuda only. Max block size per multiprocessor + hipDeviceAttributeMaxBlockDimX, ///< Max block size in width. + hipDeviceAttributeMaxBlockDimY, ///< Max block size in height. + hipDeviceAttributeMaxBlockDimZ, ///< Max block size in depth. + hipDeviceAttributeMaxGridDimX, ///< Max grid size in width. + hipDeviceAttributeMaxGridDimY, ///< Max grid size in height. + hipDeviceAttributeMaxGridDimZ, ///< Max grid size in depth. + hipDeviceAttributeMaxSurface1D, ///< Maximum size of 1D surface. + hipDeviceAttributeMaxSurface1DLayered, ///< Cuda only. Maximum dimensions of 1D layered surface. + hipDeviceAttributeMaxSurface2D, ///< Maximum dimension (width, height) of 2D surface. + hipDeviceAttributeMaxSurface2DLayered, ///< Cuda only. Maximum dimensions of 2D layered surface. + hipDeviceAttributeMaxSurface3D, ///< Maximum dimension (width, height, depth) of 3D surface. + hipDeviceAttributeMaxSurfaceCubemap, ///< Cuda only. Maximum dimensions of Cubemap surface. + hipDeviceAttributeMaxSurfaceCubemapLayered, ///< Cuda only. Maximum dimension of Cubemap layered surface. + hipDeviceAttributeMaxTexture1DWidth, ///< Maximum size of 1D texture. + hipDeviceAttributeMaxTexture1DLayered, ///< Cuda only. Maximum dimensions of 1D layered texture. + hipDeviceAttributeMaxTexture1DLinear, ///< Maximum number of elements allocatable in a 1D linear texture. + ///< Use cudaDeviceGetTexture1DLinearMaxWidth() instead on Cuda. + hipDeviceAttributeMaxTexture1DMipmap, ///< Cuda only. Maximum size of 1D mipmapped texture. + hipDeviceAttributeMaxTexture2DWidth, ///< Maximum dimension width of 2D texture. + hipDeviceAttributeMaxTexture2DHeight, ///< Maximum dimension hight of 2D texture. + hipDeviceAttributeMaxTexture2DGather, ///< Cuda only. Maximum dimensions of 2D texture if gather operations performed. + hipDeviceAttributeMaxTexture2DLayered, ///< Cuda only. Maximum dimensions of 2D layered texture. + hipDeviceAttributeMaxTexture2DLinear, ///< Cuda only. Maximum dimensions (width, height, pitch) of 2D textures bound to pitched memory. + hipDeviceAttributeMaxTexture2DMipmap, ///< Cuda only. Maximum dimensions of 2D mipmapped texture. + hipDeviceAttributeMaxTexture3DWidth, ///< Maximum dimension width of 3D texture. + hipDeviceAttributeMaxTexture3DHeight, ///< Maximum dimension height of 3D texture. + hipDeviceAttributeMaxTexture3DDepth, ///< Maximum dimension depth of 3D texture. + hipDeviceAttributeMaxTexture3DAlt, ///< Cuda only. Maximum dimensions of alternate 3D texture. + hipDeviceAttributeMaxTextureCubemap, ///< Cuda only. Maximum dimensions of Cubemap texture + hipDeviceAttributeMaxTextureCubemapLayered, ///< Cuda only. Maximum dimensions of Cubemap layered texture. + hipDeviceAttributeMaxThreadsDim, ///< Maximum dimension of a block + hipDeviceAttributeMaxThreadsPerBlock, ///< Maximum number of threads per block. + hipDeviceAttributeMaxThreadsPerMultiProcessor, ///< Maximum resident threads per multiprocessor. + hipDeviceAttributeMaxPitch, ///< Maximum pitch in bytes allowed by memory copies + hipDeviceAttributeMemoryBusWidth, ///< Global memory bus width in bits. + hipDeviceAttributeMemoryClockRate, ///< Peak memory clock frequency in kilohertz. + hipDeviceAttributeComputeCapabilityMinor, ///< Minor compute capability version number. + hipDeviceAttributeMultiGpuBoardGroupID, ///< Cuda only. Unique ID of device group on the same multi-GPU board + hipDeviceAttributeMultiprocessorCount, ///< Number of multiprocessors on the device. + hipDeviceAttributeName, ///< Device name. + hipDeviceAttributePageableMemoryAccess, ///< Device supports coherently accessing pageable memory + ///< without calling hipHostRegister on it + hipDeviceAttributePageableMemoryAccessUsesHostPageTables, ///< Device accesses pageable memory via the host's page tables + hipDeviceAttributePciBusId, ///< PCI Bus ID. + hipDeviceAttributePciDeviceId, ///< PCI Device ID. + hipDeviceAttributePciDomainID, ///< PCI Domain ID. + hipDeviceAttributePersistingL2CacheMaxSize, ///< Cuda11 only. Maximum l2 persisting lines capacity in bytes + hipDeviceAttributeMaxRegistersPerBlock, ///< 32-bit registers available to a thread block. This number is shared + ///< by all thread blocks simultaneously resident on a multiprocessor. + hipDeviceAttributeMaxRegistersPerMultiprocessor, ///< 32-bit registers available per block. + hipDeviceAttributeReservedSharedMemPerBlock, ///< Cuda11 only. Shared memory reserved by CUDA driver per block. + hipDeviceAttributeMaxSharedMemoryPerBlock, ///< Maximum shared memory available per block in bytes. + hipDeviceAttributeSharedMemPerBlockOptin, ///< Cuda only. Maximum shared memory per block usable by special opt in. + hipDeviceAttributeSharedMemPerMultiprocessor, ///< Cuda only. Shared memory available per multiprocessor. + hipDeviceAttributeSingleToDoublePrecisionPerfRatio, ///< Cuda only. Performance ratio of single precision to double precision. + hipDeviceAttributeStreamPrioritiesSupported, ///< Cuda only. Whether to support stream priorities. + hipDeviceAttributeSurfaceAlignment, ///< Cuda only. Alignment requirement for surfaces + hipDeviceAttributeTccDriver, ///< Cuda only. Whether device is a Tesla device using TCC driver + hipDeviceAttributeTextureAlignment, ///< Alignment requirement for textures + hipDeviceAttributeTexturePitchAlignment, ///< Pitch alignment requirement for 2D texture references bound to pitched memory; + hipDeviceAttributeTotalConstantMemory, ///< Constant memory size in bytes. + hipDeviceAttributeTotalGlobalMem, ///< Global memory available on devicice. + hipDeviceAttributeUnifiedAddressing, ///< Cuda only. An unified address space shared with the host. + hipDeviceAttributeUuid, ///< Cuda only. Unique ID in 16 byte. + hipDeviceAttributeWarpSize, ///< Warp size in threads. + hipDeviceAttributeCudaCompatibleEnd = 9999, + hipDeviceAttributeAmdSpecificBegin = 10000, + hipDeviceAttributeClockInstructionRate = hipDeviceAttributeAmdSpecificBegin, ///< Frequency in khz of the timer used by the device-side "clock*" + hipDeviceAttributeArch, ///< Device architecture + hipDeviceAttributeMaxSharedMemoryPerMultiprocessor, ///< Maximum Shared Memory PerMultiprocessor. + hipDeviceAttributeGcnArch, ///< Device gcn architecture + hipDeviceAttributeGcnArchName, ///< Device gcnArch name in 256 bytes + hipDeviceAttributeHdpMemFlushCntl, ///< Address of the HDP_MEM_COHERENCY_FLUSH_CNTL register + hipDeviceAttributeHdpRegFlushCntl, ///< Address of the HDP_REG_COHERENCY_FLUSH_CNTL register + hipDeviceAttributeCooperativeMultiDeviceUnmatchedFunc, ///< Supports cooperative launch on multiple + ///< devices with unmatched functions + hipDeviceAttributeCooperativeMultiDeviceUnmatchedGridDim, ///< Supports cooperative launch on multiple + ///< devices with unmatched grid dimensions + hipDeviceAttributeCooperativeMultiDeviceUnmatchedBlockDim, ///< Supports cooperative launch on multiple + ///< devices with unmatched block dimensions + hipDeviceAttributeCooperativeMultiDeviceUnmatchedSharedMem, ///< Supports cooperative launch on multiple + ///< devices with unmatched shared memories + hipDeviceAttributeIsLargeBar, ///< Whether it is LargeBar + hipDeviceAttributeAsicRevision, ///< Revision of the GPU in this device + hipDeviceAttributeCanUseStreamWaitValue, ///< '1' if Device supports hipStreamWaitValue32() and + ///< hipStreamWaitValue64() , '0' otherwise. + hipDeviceAttributeAmdSpecificEnd = 19999, + hipDeviceAttributeVendorSpecificBegin = 20000, + // Extended attributes for vendors +} hipDeviceAttribute_t; + +typedef struct HIPdevprop_st { + int maxThreadsPerBlock; + int maxThreadsDim[3]; + int maxGridSize[3]; + int sharedMemPerBlock; + int totalConstantMemory; + int SIMDWidth; + int memPitch; + int regsPerBlock; + int clockRate; + int textureAlign; +} HIPdevprop; + +typedef enum HIPpointer_attribute_enum { + HIP_POINTER_ATTRIBUTE_CONTEXT = 1, + HIP_POINTER_ATTRIBUTE_MEMORY_TYPE = 2, + HIP_POINTER_ATTRIBUTE_DEVICE_POINTER = 3, + HIP_POINTER_ATTRIBUTE_HOST_POINTER = 4, + HIP_POINTER_ATTRIBUTE_SYNC_MEMOPS = 6, + HIP_POINTER_ATTRIBUTE_BUFFER_ID = 7, + HIP_POINTER_ATTRIBUTE_IS_MANAGED = 8, + HIP_POINTER_ATTRIBUTE_DEVICE_ORDINAL = 9, +} HIPpointer_attribute; + +typedef enum hipFunction_attribute { + HIP_FUNC_ATTRIBUTE_MAX_THREADS_PER_BLOCK = 0, + HIP_FUNC_ATTRIBUTE_SHARED_SIZE_BYTES = 1, + HIP_FUNC_ATTRIBUTE_CONST_SIZE_BYTES = 2, + HIP_FUNC_ATTRIBUTE_LOCAL_SIZE_BYTES = 3, + HIP_FUNC_ATTRIBUTE_NUM_REGS = 4, + HIP_FUNC_ATTRIBUTE_PTX_VERSION = 5, + HIP_FUNC_ATTRIBUTE_BINARY_VERSION = 6, + HIP_FUNC_ATTRIBUTE_CACHE_MODE_CA = 7, + HIP_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES = 8, + HIP_FUNC_ATTRIBUTE_PREFERRED_SHARED_MEMORY_CARVEOUT = 9, + HIP_FUNC_ATTRIBUTE_MAX, +} hipFunction_attribute; + +typedef enum hipFuncCache_t { + hipFuncCachePreferNone = 0x00, + hipFuncCachePreferShared = 0x01, + hipFuncCachePreferL1 = 0x02, + hipFuncCachePreferEqual = 0x03, +} hipFuncCache_t; + +typedef enum hipSharedMemConfig { + hipSharedMemBankSizeDefault = 0x00, + hipSharedMemBankSizeFourByte = 0x01, + hipSharedMemBankSizeEightByte = 0x02, +} hipSharedMemConfig; + +typedef enum HIPshared_carveout_enum { + HIP_SHAREDMEM_CARVEOUT_DEFAULT, + HIP_SHAREDMEM_CARVEOUT_MAX_SHARED = 100, + HIP_SHAREDMEM_CARVEOUT_MAX_L1 = 0, +} HIPshared_carveout; + + + +typedef enum hipComputeMode { + hipComputeModeDefault = 0, + hipComputeModeProhibited = 2, + hipComputeModeExclusiveProcess = 3, +} hipComputeMode; + +typedef enum HIPmem_advise_enum { + HIP_MEM_ADVISE_SET_READ_MOSTLY = 1, + HIP_MEM_ADVISE_UNSET_READ_MOSTLY = 2, + HIP_MEM_ADVISE_SET_PREFERRED_LOCATION = 3, + HIP_MEM_ADVISE_UNSET_PREFERRED_LOCATION = 4, + HIP_MEM_ADVISE_SET_ACCESSED_BY = 5, + HIP_MEM_ADVISE_UNSET_ACCESSED_BY = 6, +} HIPmem_advise; + +typedef enum HIPmem_range_attribute_enum { + HIP_MEM_RANGE_ATTRIBUTE_READ_MOSTLY = 1, + HIP_MEM_RANGE_ATTRIBUTE_PREFERRED_LOCATION = 2, + HIP_MEM_RANGE_ATTRIBUTE_ACCESSED_BY = 3, + HIP_MEM_RANGE_ATTRIBUTE_LAST_PREFETCH_LOCATION = 4, +} HIPmem_range_attribute; + +typedef enum hipJitOption { + hipJitOptionMaxRegisters = 0, + hipJitOptionThreadsPerBlock, + hipJitOptionWallTime, + hipJitOptionInfoLogBuffer, + hipJitOptionInfoLogBufferSizeBytes, + hipJitOptionErrorLogBuffer, + hipJitOptionErrorLogBufferSizeBytes, + hipJitOptionOptimizationLevel, + hipJitOptionTargetFromContext, + hipJitOptionTarget, + hipJitOptionFallbackStrategy, + hipJitOptionGenerateDebugInfo, + hipJitOptionLogVerbose, + hipJitOptionGenerateLineInfo, + hipJitOptionCacheMode, + hipJitOptionSm3xOpt, + hipJitOptionFastCompile, + hipJitOptionNumOptions, +} hipJitOption; + +typedef enum HIPjit_target_enum { + HIP_TARGET_COMPUTE_20 = 20, + HIP_TARGET_COMPUTE_21 = 21, + HIP_TARGET_COMPUTE_30 = 30, + HIP_TARGET_COMPUTE_32 = 32, + HIP_TARGET_COMPUTE_35 = 35, + HIP_TARGET_COMPUTE_37 = 37, + HIP_TARGET_COMPUTE_50 = 50, + HIP_TARGET_COMPUTE_52 = 52, + HIP_TARGET_COMPUTE_53 = 53, + HIP_TARGET_COMPUTE_60 = 60, + HIP_TARGET_COMPUTE_61 = 61, + HIP_TARGET_COMPUTE_62 = 62, + HIP_TARGET_COMPUTE_70 = 70, + HIP_TARGET_COMPUTE_73 = 73, + HIP_TARGET_COMPUTE_75 = 75, +} HIPjit_target; + +typedef enum HIPjit_fallback_enum { + HIP_PREFER_PTX = 0, + HIP_PREFER_BINARY, +} HIPjit_fallback; + +typedef enum HIPjit_cacheMode_enum { + HIP_JIT_CACHE_OPTION_NONE = 0, + HIP_JIT_CACHE_OPTION_CG, + HIP_JIT_CACHE_OPTION_CA, +} HIPjit_cacheMode; + +typedef enum HIPjitInputType_enum { + HIP_JIT_INPUT_HIPBIN = 0, + HIP_JIT_INPUT_PTX, + HIP_JIT_INPUT_FATBINARY, + HIP_JIT_INPUT_OBJECT, + HIP_JIT_INPUT_LIBRARY, + HIP_JIT_NUM_INPUT_TYPES, +} HIPjitInputType; + +typedef struct HIPlinkState_st* HIPlinkState; + +typedef enum hipGLDeviceList { + hipGLDeviceListAll = 1, ///< All hip devices used by current OpenGL context. + hipGLDeviceListCurrentFrame = 2, ///< Hip devices used by current OpenGL context in current + ///< frame + hipGLDeviceListNextFrame = 3 ///< Hip devices used by current OpenGL context in next + ///< frame. +} hipGLDeviceList; + +typedef enum hipGraphicsRegisterFlags { + hipGraphicsRegisterFlagsNone = 0, + hipGraphicsRegisterFlagsReadOnly = 1, ///< HIP will not write to this registered resource + hipGraphicsRegisterFlagsWriteDiscard = + 2, ///< HIP will only write and will not read from this registered resource + hipGraphicsRegisterFlagsSurfaceLoadStore = 4, ///< HIP will bind this resource to a surface + hipGraphicsRegisterFlagsTextureGather = + 8 ///< HIP will perform texture gather operations on this registered resource +} hipGraphicsRegisterFlags; + +typedef enum HIPgraphicsRegisterFlags_enum { + HIP_GRAPHICS_REGISTER_FLAGS_NONE = 0x00, + HIP_GRAPHICS_REGISTER_FLAGS_READ_ONLY = 0x01, + HIP_GRAPHICS_REGISTER_FLAGS_WRITE_DISCARD = 0x02, + HIP_GRAPHICS_REGISTER_FLAGS_SURFACE_LDST = 0x04, + HIP_GRAPHICS_REGISTER_FLAGS_TEXTURE_GATHER = 0x08, +} HIPgraphicsRegisterFlags; + +typedef enum HIPgraphicsMapResourceFlags_enum { + HIP_GRAPHICS_MAP_RESOURCE_FLAGS_NONE = 0x00, + HIP_GRAPHICS_MAP_RESOURCE_FLAGS_READ_ONLY = 0x01, + HIP_GRAPHICS_MAP_RESOURCE_FLAGS_WRITE_DISCARD = 0x02, +} HIPgraphicsMapResourceFlags; + +typedef enum HIParray_cubemap_face_enum { + HIP_HIPBEMAP_FACE_POSITIVE_X = 0x00, + HIP_HIPBEMAP_FACE_NEGATIVE_X = 0x01, + HIP_HIPBEMAP_FACE_POSITIVE_Y = 0x02, + HIP_HIPBEMAP_FACE_NEGATIVE_Y = 0x03, + HIP_HIPBEMAP_FACE_POSITIVE_Z = 0x04, + HIP_HIPBEMAP_FACE_NEGATIVE_Z = 0x05, +} HIParray_cubemap_face; + +typedef enum hipLimit_t { + HIP_LIMIT_STACK_SIZE = 0x00, + HIP_LIMIT_PRINTF_FIFO_SIZE = 0x01, + hipLimitMallocHeapSize = 0x02, + HIP_LIMIT_DEV_RUNTIME_SYNC_DEPTH = 0x03, + HIP_LIMIT_DEV_RUNTIME_PENDING_LAUNCH_COUNT = 0x04, + HIP_LIMIT_MAX, +} hipLimit_t; + +typedef enum hipResourceType { + hipResourceTypeArray = 0x00, + hipResourceTypeMipmappedArray = 0x01, + hipResourceTypeLinear = 0x02, + hipResourceTypePitch2D = 0x03, +} hipResourceType; + +typedef enum hipError_t { + hipSuccess = 0, + hipErrorInvalidValue = 1, + hipErrorOutOfMemory = 2, + hipErrorNotInitialized = 3, + hipErrorDeinitialized = 4, + hipErrorProfilerDisabled = 5, + hipErrorProfilerNotInitialized = 6, + hipErrorProfilerAlreadyStarted = 7, + hipErrorProfilerAlreadyStopped = 8, + hipErrorNoDevice = 100, + hipErrorInvalidDevice = 101, + hipErrorInvalidImage = 200, + hipErrorInvalidContext = 201, + hipErrorContextAlreadyCurrent = 202, + hipErrorMapFailed = 205, + hipErrorUnmapFailed = 206, + hipErrorArrayIsMapped = 207, + hipErrorAlreadyMapped = 208, + hipErrorNoBinaryForGpu = 209, + hipErrorAlreadyAcquired = 210, + hipErrorNotMapped = 211, + hipErrorNotMappedAsArray = 212, + hipErrorNotMappedAsPointer = 213, + hipErrorECCNotCorrectable = 214, + hipErrorUnsupportedLimit = 215, + hipErrorContextAlreadyInUse = 216, + hipErrorPeerAccessUnsupported = 217, + hipErrorInvalidKernelFile = 218, + hipErrorInvalidGraphicsContext = 219, + hipErrorInvalidSource = 300, + hipErrorFileNotFound = 301, + hipErrorSharedObjectSymbolNotFound = 302, + hipErrorSharedObjectInitFailed = 303, + hipErrorOperatingSystem = 304, + hipErrorInvalidHandle = 400, + hipErrorNotFound = 500, + hipErrorNotReady = 600, + hipErrorIllegalAddress = 700, + hipErrorLaunchOutOfResources = 701, + hipErrorLaunchTimeOut = 702, + hipErrorPeerAccessAlreadyEnabled = 704, + hipErrorPeerAccessNotEnabled = 705, + hipErrorSetOnActiveProcess = 708, + hipErrorAssert = 710, + hipErrorHostMemoryAlreadyRegistered = 712, + hipErrorHostMemoryNotRegistered = 713, + hipErrorLaunchFailure = 719, + hipErrorCooperativeLaunchTooLarge = 720, + hipErrorNotSupported = 801, + hipErrorUnknown = 999, +} hipError_t; + +/** + * Stream CallBack struct + */ +typedef void (*hipStreamCallback_t)(hipStream_t stream, hipError_t status, void* userData); + +typedef enum HIPdevice_P2PAttribute_enum { + HIP_DEVICE_P2P_ATTRIBUTE_PERFORMANCE_RANK = 0x01, + HIP_DEVICE_P2P_ATTRIBUTE_ACCESS_SUPPORTED = 0x02, + HIP_DEVICE_P2P_ATTRIBUTE_NATIVE_ATOMIC_SUPPORTED = 0x03, + HIP_DEVICE_P2P_ATTRIBUTE_ARRAY_ACCESS_ACCESS_SUPPORTED = 0x04, +} HIPdevice_P2PAttribute; + +typedef struct hipGraphicsResource_st* hipGraphicsResource; + +typedef struct hip_Memcpy2D { + size_t srcXInBytes; + size_t srcY; + hipMemoryType srcMemoryType; + const void* srcHost; + hipDeviceptr_t srcDevice; + hArray * srcArray; + size_t srcPitch; + size_t dstXInBytes; + size_t dstY; + hipMemoryType dstMemoryType; + void* dstHost; + hipDeviceptr_t dstDevice; + hArray * dstArray; + size_t dstPitch; + size_t WidthInBytes; + size_t Height; +} hip_Memcpy2D; + +typedef enum hipDeviceP2PAttr { + hipDevP2PAttrPerformanceRank = 0, + hipDevP2PAttrAccessSupported, + hipDevP2PAttrNativeAtomicSupported, + hipDevP2PAttrHipArrayAccessSupported +} hipDeviceP2PAttr; + +typedef struct HIP_MEMCPY3D { + size_t srcXInBytes; + size_t srcY; + size_t srcZ; + size_t srcLOD; + hipMemoryType srcMemoryType; + const void* srcHost; + hipDeviceptr_t srcDevice; + hArray * srcArray; + void* reserved0; + size_t srcPitch; + size_t srcHeight; + size_t dstXInBytes; + size_t dstY; + size_t dstZ; + size_t dstLOD; + hipMemoryType dstMemoryType; + void* dstHost; + hipDeviceptr_t dstDevice; + hArray * dstArray; + void* reserved1; + size_t dstPitch; + size_t dstHeight; + size_t WidthInBytes; + size_t Height; + size_t Depth; +} HIP_MEMCPY3D; + +typedef struct HIP_MEMCPY3D_PEER_st { + size_t srcXInBytes; + size_t srcY; + size_t srcZ; + size_t srcLOD; + hipMemoryType srcMemoryType; + const void* srcHost; + hipDeviceptr_t srcDevice; + hArray * srcArray; + hipCtx_t srcContext; + size_t srcPitch; + size_t srcHeight; + size_t dstXInBytes; + size_t dstY; + size_t dstZ; + size_t dstLOD; + hipMemoryType dstMemoryType; + void* dstHost; + hipDeviceptr_t dstDevice; + hArray * dstArray; + hipCtx_t dstContext; + size_t dstPitch; + size_t dstHeight; + size_t WidthInBytes; + size_t Height; + size_t Depth; +} HIP_MEMCPY3D_PEER; + +typedef struct HIP_ARRAY_DESCRIPTOR { + size_t Width; + size_t Height; + hipArray_Format Format; + unsigned int NumChannels; +} HIP_ARRAY_DESCRIPTOR; + +typedef struct HIP_ARRAY3D_DESCRIPTOR { + size_t Width; + size_t Height; + size_t Depth; + hipArray_Format Format; + unsigned int NumChannels; + unsigned int Flags; +} HIP_ARRAY3D_DESCRIPTOR; + +typedef struct HIP_RESOURCE_DESC_st { + hipResourceType resType; + union { + struct { + hArray * h_Array; + } array; + struct { + hipMipmappedArray_t hMipmappedArray; + } mipmap; + struct { + hipDeviceptr_t devPtr; + hipArray_Format format; + unsigned int numChannels; + size_t sizeInBytes; + } linear; + struct { + hipDeviceptr_t devPtr; + hipArray_Format format; + unsigned int numChannels; + size_t width; + size_t height; + size_t pitchInBytes; + } pitch2D; + struct { + int reserved[32]; + } reserved; + } res; + unsigned int flags; +} hipResourceDesc; + +/** + * hip texture resource view formats + */ +typedef enum hipResourceViewFormat { + hipResViewFormatNone = 0x00, + hipResViewFormatUnsignedChar1 = 0x01, + hipResViewFormatUnsignedChar2 = 0x02, + hipResViewFormatUnsignedChar4 = 0x03, + hipResViewFormatSignedChar1 = 0x04, + hipResViewFormatSignedChar2 = 0x05, + hipResViewFormatSignedChar4 = 0x06, + hipResViewFormatUnsignedShort1 = 0x07, + hipResViewFormatUnsignedShort2 = 0x08, + hipResViewFormatUnsignedShort4 = 0x09, + hipResViewFormatSignedShort1 = 0x0a, + hipResViewFormatSignedShort2 = 0x0b, + hipResViewFormatSignedShort4 = 0x0c, + hipResViewFormatUnsignedInt1 = 0x0d, + hipResViewFormatUnsignedInt2 = 0x0e, + hipResViewFormatUnsignedInt4 = 0x0f, + hipResViewFormatSignedInt1 = 0x10, + hipResViewFormatSignedInt2 = 0x11, + hipResViewFormatSignedInt4 = 0x12, + hipResViewFormatHalf1 = 0x13, + hipResViewFormatHalf2 = 0x14, + hipResViewFormatHalf4 = 0x15, + hipResViewFormatFloat1 = 0x16, + hipResViewFormatFloat2 = 0x17, + hipResViewFormatFloat4 = 0x18, + hipResViewFormatUnsignedBlockCompressed1 = 0x19, + hipResViewFormatUnsignedBlockCompressed2 = 0x1a, + hipResViewFormatUnsignedBlockCompressed3 = 0x1b, + hipResViewFormatUnsignedBlockCompressed4 = 0x1c, + hipResViewFormatSignedBlockCompressed4 = 0x1d, + hipResViewFormatUnsignedBlockCompressed5 = 0x1e, + hipResViewFormatSignedBlockCompressed5 = 0x1f, + hipResViewFormatUnsignedBlockCompressed6H = 0x20, + hipResViewFormatSignedBlockCompressed6H = 0x21, + hipResViewFormatUnsignedBlockCompressed7 = 0x22 +}hipResourceViewFormat; + +typedef enum HIPresourceViewFormat_enum +{ + HIP_RES_VIEW_FORMAT_NONE = 0x00, /**< No resource view format (use underlying resource format) */ + HIP_RES_VIEW_FORMAT_UINT_1X8 = 0x01, /**< 1 channel unsigned 8-bit integers */ + HIP_RES_VIEW_FORMAT_UINT_2X8 = 0x02, /**< 2 channel unsigned 8-bit integers */ + HIP_RES_VIEW_FORMAT_UINT_4X8 = 0x03, /**< 4 channel unsigned 8-bit integers */ + HIP_RES_VIEW_FORMAT_SINT_1X8 = 0x04, /**< 1 channel signed 8-bit integers */ + HIP_RES_VIEW_FORMAT_SINT_2X8 = 0x05, /**< 2 channel signed 8-bit integers */ + HIP_RES_VIEW_FORMAT_SINT_4X8 = 0x06, /**< 4 channel signed 8-bit integers */ + HIP_RES_VIEW_FORMAT_UINT_1X16 = 0x07, /**< 1 channel unsigned 16-bit integers */ + HIP_RES_VIEW_FORMAT_UINT_2X16 = 0x08, /**< 2 channel unsigned 16-bit integers */ + HIP_RES_VIEW_FORMAT_UINT_4X16 = 0x09, /**< 4 channel unsigned 16-bit integers */ + HIP_RES_VIEW_FORMAT_SINT_1X16 = 0x0a, /**< 1 channel signed 16-bit integers */ + HIP_RES_VIEW_FORMAT_SINT_2X16 = 0x0b, /**< 2 channel signed 16-bit integers */ + HIP_RES_VIEW_FORMAT_SINT_4X16 = 0x0c, /**< 4 channel signed 16-bit integers */ + HIP_RES_VIEW_FORMAT_UINT_1X32 = 0x0d, /**< 1 channel unsigned 32-bit integers */ + HIP_RES_VIEW_FORMAT_UINT_2X32 = 0x0e, /**< 2 channel unsigned 32-bit integers */ + HIP_RES_VIEW_FORMAT_UINT_4X32 = 0x0f, /**< 4 channel unsigned 32-bit integers */ + HIP_RES_VIEW_FORMAT_SINT_1X32 = 0x10, /**< 1 channel signed 32-bit integers */ + HIP_RES_VIEW_FORMAT_SINT_2X32 = 0x11, /**< 2 channel signed 32-bit integers */ + HIP_RES_VIEW_FORMAT_SINT_4X32 = 0x12, /**< 4 channel signed 32-bit integers */ + HIP_RES_VIEW_FORMAT_FLOAT_1X16 = 0x13, /**< 1 channel 16-bit floating point */ + HIP_RES_VIEW_FORMAT_FLOAT_2X16 = 0x14, /**< 2 channel 16-bit floating point */ + HIP_RES_VIEW_FORMAT_FLOAT_4X16 = 0x15, /**< 4 channel 16-bit floating point */ + HIP_RES_VIEW_FORMAT_FLOAT_1X32 = 0x16, /**< 1 channel 32-bit floating point */ + HIP_RES_VIEW_FORMAT_FLOAT_2X32 = 0x17, /**< 2 channel 32-bit floating point */ + HIP_RES_VIEW_FORMAT_FLOAT_4X32 = 0x18, /**< 4 channel 32-bit floating point */ + HIP_RES_VIEW_FORMAT_UNSIGNED_BC1 = 0x19, /**< Block compressed 1 */ + HIP_RES_VIEW_FORMAT_UNSIGNED_BC2 = 0x1a, /**< Block compressed 2 */ + HIP_RES_VIEW_FORMAT_UNSIGNED_BC3 = 0x1b, /**< Block compressed 3 */ + HIP_RES_VIEW_FORMAT_UNSIGNED_BC4 = 0x1c, /**< Block compressed 4 unsigned */ + HIP_RES_VIEW_FORMAT_SIGNED_BC4 = 0x1d, /**< Block compressed 4 signed */ + HIP_RES_VIEW_FORMAT_UNSIGNED_BC5 = 0x1e, /**< Block compressed 5 unsigned */ + HIP_RES_VIEW_FORMAT_SIGNED_BC5 = 0x1f, /**< Block compressed 5 signed */ + HIP_RES_VIEW_FORMAT_UNSIGNED_BC6H = 0x20, /**< Block compressed 6 unsigned half-float */ + HIP_RES_VIEW_FORMAT_SIGNED_BC6H = 0x21, /**< Block compressed 6 signed half-float */ + HIP_RES_VIEW_FORMAT_UNSIGNED_BC7 = 0x22 /**< Block compressed 7 */ +} HIPresourceViewFormat; + +/** + * hip resource view descriptor + */ +struct hipResourceViewDesc { + enum hipResourceViewFormat format; + size_t width; + size_t height; + size_t depth; + unsigned int firstMipmapLevel; + unsigned int lastMipmapLevel; + unsigned int firstLayer; + unsigned int lastLayer; +}; + + +typedef struct hipTextureDesc_st { + hipTextureAddressMode addressMode[3]; + hipTextureFilterMode filterMode; + unsigned int flags; + unsigned int maxAnisotropy; + hipTextureFilterMode mipmapFilterMode; + float mipmapLevelBias; + float minMipmapLevelClamp; + float maxMipmapLevelClamp; + float borderColor[4]; + int reserved[12]; +} hipTextureDesc; + +/** + * Resource view descriptor + */ +typedef struct HIP_RESOURCE_VIEW_DESC_st { + hipResourceViewFormat format; + size_t width; + size_t height; + size_t depth; + unsigned int firstMipmapLevel; + unsigned int lastMipmapLevel; + unsigned int firstLayer; + unsigned int lastLayer; + unsigned int reserved[16]; +} HIP_RESOURCE_VIEW_DESC; + +typedef struct HIP_POINTER_ATTRIBUTE_P2P_TOKENS_st { + unsigned long long p2pToken; + unsigned int vaSpaceToken; +} HIP_POINTER_ATTRIBUTE_P2P_TOKENS; + + +typedef unsigned int GLenum; +typedef unsigned int GLuint; +typedef int GLint; + +typedef enum HIPGLDeviceList_enum { + HIP_GL_DEVICE_LIST_ALL = 0x01, + HIP_GL_DEVICE_LIST_CURRENT_FRAME = 0x02, + HIP_GL_DEVICE_LIST_NEXT_FRAME = 0x03, +} HIPGLDeviceList; + +typedef enum HIPGLmap_flags_enum { + HIP_GL_MAP_RESOURCE_FLAGS_NONE = 0x00, + HIP_GL_MAP_RESOURCE_FLAGS_READ_ONLY = 0x01, + HIP_GL_MAP_RESOURCE_FLAGS_WRITE_DISCARD = 0x02, +} HIPGLmap_flags; + + +/* Function types. */ +typedef hipError_t HIPAPI thipGetErrorName(hipError_t error, const char** pStr); +typedef hipError_t HIPAPI thipInit(unsigned int Flags); +typedef hipError_t HIPAPI thipDriverGetVersion(int* driverVersion); +typedef hipError_t HIPAPI thipGetDevice(hipDevice_t* device, int ordinal); +typedef hipError_t HIPAPI thipGetDeviceCount(int* count); +typedef hipError_t HIPAPI thipDeviceGetName(char* name, int len, hipDevice_t dev); +typedef hipError_t HIPAPI thipDeviceGetAttribute(int* pi, hipDeviceAttribute_t attrib, hipDevice_t dev); +typedef hipError_t HIPAPI thipDeviceComputeCapability(int* major, int* minor, hipDevice_t dev); +typedef hipError_t HIPAPI thipDevicePrimaryCtxRetain(hipCtx_t* pctx, hipDevice_t dev); +typedef hipError_t HIPAPI thipDevicePrimaryCtxRelease(hipDevice_t dev); +typedef hipError_t HIPAPI thipDevicePrimaryCtxSetFlags(hipDevice_t dev, unsigned int flags); +typedef hipError_t HIPAPI thipDevicePrimaryCtxGetState(hipDevice_t dev, unsigned int* flags, int* active); +typedef hipError_t HIPAPI thipDevicePrimaryCtxReset(hipDevice_t dev); +typedef hipError_t HIPAPI thipCtxCreate(hipCtx_t* pctx, unsigned int flags, hipDevice_t dev); +typedef hipError_t HIPAPI thipCtxDestroy(hipCtx_t ctx); +typedef hipError_t HIPAPI thipCtxPushCurrent(hipCtx_t ctx); +typedef hipError_t HIPAPI thipCtxPopCurrent(hipCtx_t* pctx); +typedef hipError_t HIPAPI thipCtxSetCurrent(hipCtx_t ctx); +typedef hipError_t HIPAPI thipCtxGetCurrent(hipCtx_t* pctx); +typedef hipError_t HIPAPI thipCtxGetDevice(hipDevice_t* device); +typedef hipError_t HIPAPI thipCtxGetFlags(unsigned int* flags); +typedef hipError_t HIPAPI thipCtxSynchronize(void); +typedef hipError_t HIPAPI thipDeviceSynchronize(void); +typedef hipError_t HIPAPI thipCtxGetCacheConfig(hipFuncCache_t* pconfig); +typedef hipError_t HIPAPI thipCtxSetCacheConfig(hipFuncCache_t config); +typedef hipError_t HIPAPI thipCtxGetSharedMemConfig(hipSharedMemConfig* pConfig); +typedef hipError_t HIPAPI thipCtxSetSharedMemConfig(hipSharedMemConfig config); +typedef hipError_t HIPAPI thipCtxGetApiVersion(hipCtx_t ctx, unsigned int* version); +typedef hipError_t HIPAPI thipModuleLoad(hipModule_t* module, const char* fname); +typedef hipError_t HIPAPI thipModuleLoadData(hipModule_t* module, const void* image); +typedef hipError_t HIPAPI thipModuleLoadDataEx(hipModule_t* module, const void* image, unsigned int numOptions, hipJitOption* options, void** optionValues); +typedef hipError_t HIPAPI thipModuleUnload(hipModule_t hmod); +typedef hipError_t HIPAPI thipModuleGetFunction(hipFunction_t* hfunc, hipModule_t hmod, const char* name); +typedef hipError_t HIPAPI thipModuleGetGlobal(hipDeviceptr_t* dptr, size_t* bytes, hipModule_t hmod, const char* name); +typedef hipError_t HIPAPI thipModuleGetTexRef(textureReference** pTexRef, hipModule_t hmod, const char* name); +typedef hipError_t HIPAPI thipMemGetInfo(size_t* free, size_t* total); +typedef hipError_t HIPAPI thipMalloc(hipDeviceptr_t* dptr, size_t bytesize); +typedef hipError_t HIPAPI thipMemAllocPitch(hipDeviceptr_t* dptr, size_t* pPitch, size_t WidthInBytes, size_t Height, unsigned int ElementSizeBytes); +typedef hipError_t HIPAPI thipFree(hipDeviceptr_t dptr); +typedef hipError_t HIPAPI thipMemGetAddressRange(hipDeviceptr_t* pbase, size_t* psize, hipDeviceptr_t dptr); +typedef hipError_t HIPAPI thipHostMalloc(void** pp, size_t bytesize); +typedef hipError_t HIPAPI thipHostFree(void* p); +typedef hipError_t HIPAPI thipMemHostAlloc(void** pp, size_t bytesize, unsigned int Flags); +typedef hipError_t HIPAPI thipHostGetDevicePointer(hipDeviceptr_t* pdptr, void* p, unsigned int Flags); +typedef hipError_t HIPAPI thipHostGetFlags(unsigned int* pFlags, void* p); +typedef hipError_t HIPAPI thipMallocManaged(hipDeviceptr_t* dptr, size_t bytesize, unsigned int flags); +typedef hipError_t HIPAPI thipDeviceGetByPCIBusId(hipDevice_t* dev, const char* pciBusId); +typedef hipError_t HIPAPI thipDeviceGetPCIBusId(char* pciBusId, int len, hipDevice_t dev); +typedef hipError_t HIPAPI thipMemHostUnregister(void* p); +typedef hipError_t HIPAPI thipMemcpy(hipDeviceptr_t dst, hipDeviceptr_t src, size_t ByteCount); +typedef hipError_t HIPAPI thipMemcpyPeer(hipDeviceptr_t dstDevice, hipCtx_t dstContext, hipDeviceptr_t srcDevice, hipCtx_t srcContext, size_t ByteCount); +typedef hipError_t HIPAPI thipMemcpyHtoD(hipDeviceptr_t dstDevice, void* srcHost, size_t ByteCount); +typedef hipError_t HIPAPI thipMemcpyDtoH(void* dstHost, hipDeviceptr_t srcDevice, size_t ByteCount); +typedef hipError_t HIPAPI thipMemcpyDtoD(hipDeviceptr_t dstDevice, hipDeviceptr_t srcDevice, size_t ByteCount); +typedef hipError_t HIPAPI thipDrvMemcpy2DUnaligned(const hip_Memcpy2D* pCopy); +typedef hipError_t HIPAPI thipMemcpyParam2D(const hip_Memcpy2D* pCopy); +typedef hipError_t HIPAPI thipDrvMemcpy3D(const HIP_MEMCPY3D* pCopy); +typedef hipError_t HIPAPI thipMemcpyHtoDAsync(hipDeviceptr_t dstDevice, const void* srcHost, size_t ByteCount, hipStream_t hStream); +typedef hipError_t HIPAPI thipMemcpyDtoHAsync(void* dstHost, hipDeviceptr_t srcDevice, size_t ByteCount, hipStream_t hStream); +typedef hipError_t HIPAPI thipMemcpyParam2DAsync(const hip_Memcpy2D* pCopy, hipStream_t hStream); +typedef hipError_t HIPAPI thipDrvMemcpy3DAsync(const HIP_MEMCPY3D* pCopy, hipStream_t hStream); +typedef hipError_t HIPAPI thipMemsetD8(hipDeviceptr_t dstDevice, unsigned char uc, size_t N); +typedef hipError_t HIPAPI thipMemsetD16(hipDeviceptr_t dstDevice, unsigned short us, size_t N); +typedef hipError_t HIPAPI thipMemsetD32(hipDeviceptr_t dstDevice, unsigned int ui, size_t N); +typedef hipError_t HIPAPI thipMemsetD8Async(hipDeviceptr_t dstDevice, unsigned char uc, size_t N, hipStream_t hStream); +typedef hipError_t HIPAPI thipMemsetD16Async(hipDeviceptr_t dstDevice, unsigned short us, size_t N, hipStream_t hStream); +typedef hipError_t HIPAPI thipMemsetD32Async(hipDeviceptr_t dstDevice, unsigned int ui, size_t N, hipStream_t hStream); +typedef hipError_t HIPAPI thipMemsetD2D8Async(hipDeviceptr_t dstDevice, size_t dstPitch, unsigned char uc, size_t Width, size_t Height, hipStream_t hStream); +typedef hipError_t HIPAPI thipMemsetD2D16Async(hipDeviceptr_t dstDevice, size_t dstPitch, unsigned short us, size_t Width, size_t Height, hipStream_t hStream); +typedef hipError_t HIPAPI thipMemsetD2D32Async(hipDeviceptr_t dstDevice, size_t dstPitch, unsigned int ui, size_t Width, size_t Height, hipStream_t hStream); +typedef hipError_t HIPAPI thipArrayCreate(hArray ** pHandle, const HIP_ARRAY_DESCRIPTOR* pAllocateArray); +typedef hipError_t HIPAPI thipArrayDestroy(hArray hArray); +typedef hipError_t HIPAPI thipArray3DCreate(hArray * pHandle, const HIP_ARRAY3D_DESCRIPTOR* pAllocateArray); +typedef hipError_t HIPAPI hipPointerGetAttributes(hipPointerAttribute_t* attributes, const void* ptr); +typedef hipError_t HIPAPI thipStreamCreateWithFlags(hipStream_t* phStream, unsigned int Flags); +typedef hipError_t HIPAPI thipStreamCreateWithPriority(hipStream_t* phStream, unsigned int flags, int priority); +typedef hipError_t HIPAPI thipStreamGetPriority(hipStream_t hStream, int* priority); +typedef hipError_t HIPAPI thipStreamGetFlags(hipStream_t hStream, unsigned int* flags); +typedef hipError_t HIPAPI thipStreamWaitEvent(hipStream_t hStream, hipEvent_t hEvent, unsigned int Flags); +typedef hipError_t HIPAPI thipStreamAddCallback(hipStream_t hStream, hipStreamCallback_t callback, void* userData, unsigned int flags); +typedef hipError_t HIPAPI thipStreamQuery(hipStream_t hStream); +typedef hipError_t HIPAPI thipStreamSynchronize(hipStream_t hStream); +typedef hipError_t HIPAPI thipStreamDestroy(hipStream_t hStream); +typedef hipError_t HIPAPI thipEventCreateWithFlags(hipEvent_t* phEvent, unsigned int Flags); +typedef hipError_t HIPAPI thipEventRecord(hipEvent_t hEvent, hipStream_t hStream); +typedef hipError_t HIPAPI thipEventQuery(hipEvent_t hEvent); +typedef hipError_t HIPAPI thipEventSynchronize(hipEvent_t hEvent); +typedef hipError_t HIPAPI thipEventDestroy(hipEvent_t hEvent); +typedef hipError_t HIPAPI thipEventElapsedTime(float* pMilliseconds, hipEvent_t hStart, hipEvent_t hEnd); +typedef hipError_t HIPAPI thipFuncGetAttribute(int* pi, hipFunction_attribute attrib, hipFunction_t hfunc); +typedef hipError_t HIPAPI thipFuncSetCacheConfig(hipFunction_t hfunc, hipFuncCache_t config); +typedef hipError_t HIPAPI thipModuleLaunchKernel(hipFunction_t f, unsigned int gridDimX, unsigned int gridDimY, unsigned int gridDimZ, unsigned int blockDimX, unsigned int blockDimY, unsigned int blockDimZ, unsigned int sharedMemBytes, hipStream_t hStream, void** kernelParams, void** extra); +typedef hipError_t HIPAPI thipDrvOccupancyMaxActiveBlocksPerMultiprocessor(int* numBlocks, hipFunction_t func, int blockSize, size_t dynamicSMemSize); +typedef hipError_t HIPAPI thipDrvOccupancyMaxActiveBlocksPerMultiprocessorWithFlags(int* numBlocks, hipFunction_t func, int blockSize, size_t dynamicSMemSize, unsigned int flags); +typedef hipError_t HIPAPI thipModuleOccupancyMaxPotentialBlockSize(int* minGridSize, int* blockSize, hipFunction_t func, size_t dynamicSMemSize, int blockSizeLimit); +typedef hipError_t HIPAPI thipTexRefSetArray(hipTexRef hTexRef, hArray * hArray, unsigned int Flags); +typedef hipError_t HIPAPI thipTexRefSetAddress(size_t* ByteOffset, hipTexRef hTexRef, hipDeviceptr_t dptr, size_t bytes); +typedef hipError_t HIPAPI thipTexRefSetAddress2D(hipTexRef hTexRef, const HIP_ARRAY_DESCRIPTOR* desc, hipDeviceptr_t dptr, size_t Pitch); +typedef hipError_t HIPAPI thipTexRefSetFormat(hipTexRef hTexRef, hipArray_Format fmt, int NumPackedComponents); +typedef hipError_t HIPAPI thipTexRefSetAddressMode(hipTexRef hTexRef, int dim, hipTextureAddressMode am); +typedef hipError_t HIPAPI thipTexRefSetFilterMode(hipTexRef hTexRef, hipTextureFilterMode fm); +typedef hipError_t HIPAPI thipTexRefSetFlags(hipTexRef hTexRef, unsigned int Flags); +typedef hipError_t HIPAPI thipTexRefGetAddress(hipDeviceptr_t* pdptr, hipTexRef hTexRef); +typedef hipError_t HIPAPI thipTexRefGetArray(hArray ** phArray, hipTexRef hTexRef); +typedef hipError_t HIPAPI thipTexRefGetAddressMode(hipTextureAddressMode* pam, hipTexRef hTexRef, int dim); +typedef hipError_t HIPAPI thipTexObjectCreate(hipTextureObject_t* pTexObject, const hipResourceDesc* pResDesc, const hipTextureDesc* pTexDesc, const HIP_RESOURCE_VIEW_DESC* pResViewDesc); +typedef hipError_t HIPAPI thipTexObjectDestroy(hipTextureObject_t texObject); +typedef hipError_t HIPAPI thipDeviceCanAccessPeer(int* canAccessPeer, hipDevice_t dev, hipDevice_t peerDev); +typedef hipError_t HIPAPI thipCtxEnablePeerAccess(hipCtx_t peerContext, unsigned int Flags); +typedef hipError_t HIPAPI thipCtxDisablePeerAccess(hipCtx_t peerContext); +typedef hipError_t HIPAPI thipDeviceGetP2PAttribute(int* value, hipDeviceP2PAttr attrib, hipDevice_t srcDevice, hipDevice_t dstDevice); +typedef hipError_t HIPAPI thipGraphicsUnregisterResource(hipGraphicsResource resource); +typedef hipError_t HIPAPI thipGraphicsResourceGetMappedMipmappedArray(hipMipmappedArray_t* pMipmappedArray, hipGraphicsResource resource); +typedef hipError_t HIPAPI thipGraphicsResourceGetMappedPointer(hipDeviceptr_t* pDevPtr, size_t* pSize, hipGraphicsResource resource); +typedef hipError_t HIPAPI thipGraphicsMapResources(unsigned int count, hipGraphicsResource* resources, hipStream_t hStream); +typedef hipError_t HIPAPI thipGraphicsUnmapResources(unsigned int count, hipGraphicsResource* resources, hipStream_t hStream); +typedef hipError_t HIPAPI thipGraphicsGLRegisterBuffer(hipGraphicsResource* pCudaResource, GLuint buffer, unsigned int Flags); +typedef hipError_t HIPAPI thipGLGetDevices(unsigned int* pHipDeviceCount, int* pHipDevices, unsigned int hipDeviceCount, hipGLDeviceList deviceList); + + +/* Function declarations. */ +extern thipGetErrorName *hipGetErrorName; +extern thipInit *hipInit; +extern thipDriverGetVersion *hipDriverGetVersion; +extern thipGetDevice *hipGetDevice; +extern thipGetDeviceCount *hipGetDeviceCount; +extern thipDeviceGetName *hipDeviceGetName; +extern thipDeviceGetAttribute *hipDeviceGetAttribute; +extern thipDeviceComputeCapability *hipDeviceComputeCapability; +extern thipDevicePrimaryCtxRetain *hipDevicePrimaryCtxRetain; +extern thipDevicePrimaryCtxRelease *hipDevicePrimaryCtxRelease; +extern thipDevicePrimaryCtxSetFlags *hipDevicePrimaryCtxSetFlags; +extern thipDevicePrimaryCtxGetState *hipDevicePrimaryCtxGetState; +extern thipDevicePrimaryCtxReset *hipDevicePrimaryCtxReset; +extern thipCtxCreate *hipCtxCreate; +extern thipCtxDestroy *hipCtxDestroy; +extern thipCtxPushCurrent *hipCtxPushCurrent; +extern thipCtxPopCurrent *hipCtxPopCurrent; +extern thipCtxSetCurrent *hipCtxSetCurrent; +extern thipCtxGetCurrent *hipCtxGetCurrent; +extern thipCtxGetDevice *hipCtxGetDevice; +extern thipCtxGetFlags *hipCtxGetFlags; +extern thipCtxSynchronize *hipCtxSynchronize; +extern thipDeviceSynchronize *hipDeviceSynchronize; +extern thipCtxGetCacheConfig *hipCtxGetCacheConfig; +extern thipCtxSetCacheConfig *hipCtxSetCacheConfig; +extern thipCtxGetSharedMemConfig *hipCtxGetSharedMemConfig; +extern thipCtxSetSharedMemConfig *hipCtxSetSharedMemConfig; +extern thipCtxGetApiVersion *hipCtxGetApiVersion; +extern thipModuleLoad *hipModuleLoad; +extern thipModuleLoadData *hipModuleLoadData; +extern thipModuleLoadDataEx *hipModuleLoadDataEx; +extern thipModuleUnload *hipModuleUnload; +extern thipModuleGetFunction *hipModuleGetFunction; +extern thipModuleGetGlobal *hipModuleGetGlobal; +extern thipModuleGetTexRef *hipModuleGetTexRef; +extern thipMemGetInfo *hipMemGetInfo; +extern thipMalloc *hipMalloc; +extern thipMemAllocPitch *hipMemAllocPitch; +extern thipFree *hipFree; +extern thipMemGetAddressRange *hipMemGetAddressRange; +extern thipHostMalloc *hipHostMalloc; +extern thipHostFree *hipHostFree; +extern thipHostGetDevicePointer *hipHostGetDevicePointer; +extern thipHostGetFlags *hipHostGetFlags; +extern thipMallocManaged *hipMallocManaged; +extern thipDeviceGetByPCIBusId *hipDeviceGetByPCIBusId; +extern thipDeviceGetPCIBusId *hipDeviceGetPCIBusId; +extern thipMemcpyPeer *hipMemcpyPeer; +extern thipMemcpyHtoD *hipMemcpyHtoD; +extern thipMemcpyDtoH *hipMemcpyDtoH; +extern thipMemcpyDtoD *hipMemcpyDtoD; +extern thipDrvMemcpy2DUnaligned *hipDrvMemcpy2DUnaligned; +extern thipMemcpyParam2D *hipMemcpyParam2D; +extern thipDrvMemcpy3D *hipDrvMemcpy3D; +extern thipMemcpyHtoDAsync *hipMemcpyHtoDAsync; +extern thipMemcpyDtoHAsync *hipMemcpyDtoHAsync; +extern thipMemcpyParam2DAsync *hipMemcpyParam2DAsync; +extern thipDrvMemcpy3DAsync *hipDrvMemcpy3DAsync; +extern thipMemsetD8 *hipMemsetD8; +extern thipMemsetD16 *hipMemsetD16; +extern thipMemsetD32 *hipMemsetD32; +extern thipMemsetD8Async *hipMemsetD8Async; +extern thipMemsetD16Async *hipMemsetD16Async; +extern thipMemsetD32Async *hipMemsetD32Async; +extern thipArrayCreate *hipArrayCreate; +extern thipArrayDestroy *hipArrayDestroy; +extern thipArray3DCreate *hipArray3DCreate; +extern thipStreamCreateWithFlags *hipStreamCreateWithFlags; +extern thipStreamCreateWithPriority *hipStreamCreateWithPriority; +extern thipStreamGetPriority *hipStreamGetPriority; +extern thipStreamGetFlags *hipStreamGetFlags; +extern thipStreamWaitEvent *hipStreamWaitEvent; +extern thipStreamAddCallback *hipStreamAddCallback; +extern thipStreamQuery *hipStreamQuery; +extern thipStreamSynchronize *hipStreamSynchronize; +extern thipStreamDestroy *hipStreamDestroy; +extern thipEventCreateWithFlags *hipEventCreateWithFlags; +extern thipEventRecord *hipEventRecord; +extern thipEventQuery *hipEventQuery; +extern thipEventSynchronize *hipEventSynchronize; +extern thipEventDestroy *hipEventDestroy; +extern thipEventElapsedTime *hipEventElapsedTime; +extern thipFuncGetAttribute *hipFuncGetAttribute; +extern thipFuncSetCacheConfig *hipFuncSetCacheConfig; +extern thipModuleLaunchKernel *hipModuleLaunchKernel; +extern thipDrvOccupancyMaxActiveBlocksPerMultiprocessor *hipDrvOccupancyMaxActiveBlocksPerMultiprocessor; +extern thipDrvOccupancyMaxActiveBlocksPerMultiprocessorWithFlags *hipDrvOccupancyMaxActiveBlocksPerMultiprocessorWithFlags; +extern thipModuleOccupancyMaxPotentialBlockSize *hipModuleOccupancyMaxPotentialBlockSize; +extern thipTexRefSetArray *hipTexRefSetArray; +extern thipTexRefSetAddress *hipTexRefSetAddress; +extern thipTexRefSetAddress2D *hipTexRefSetAddress2D; +extern thipTexRefSetFormat *hipTexRefSetFormat; +extern thipTexRefSetAddressMode *hipTexRefSetAddressMode; +extern thipTexRefSetFilterMode *hipTexRefSetFilterMode; +extern thipTexRefSetFlags *hipTexRefSetFlags; +extern thipTexRefGetAddress *hipTexRefGetAddress; +extern thipTexRefGetArray *hipTexRefGetArray; +extern thipTexRefGetAddressMode *hipTexRefGetAddressMode; +extern thipTexObjectCreate *hipTexObjectCreate; +extern thipTexObjectDestroy *hipTexObjectDestroy; +extern thipDeviceCanAccessPeer *hipDeviceCanAccessPeer; +extern thipCtxEnablePeerAccess *hipCtxEnablePeerAccess; +extern thipCtxDisablePeerAccess *hipCtxDisablePeerAccess; +extern thipDeviceGetP2PAttribute *hipDeviceGetP2PAttribute; +extern thipGraphicsUnregisterResource *hipGraphicsUnregisterResource; +extern thipGraphicsResourceGetMappedMipmappedArray *hipGraphicsResourceGetMappedMipmappedArray; +extern thipGraphicsResourceGetMappedPointer *hipGraphicsResourceGetMappedPointer; +extern thipGraphicsMapResources *hipGraphicsMapResources; +extern thipGraphicsUnmapResources *hipGraphicsUnmapResources; + +extern thipGraphicsGLRegisterBuffer *hipGraphicsGLRegisterBuffer; +extern thipGLGetDevices *hipGLGetDevices; + + +enum { + HIPEW_SUCCESS = 0, + HIPEW_ERROR_OPEN_FAILED = -1, + HIPEW_ERROR_ATEXIT_FAILED = -2, +}; + +enum { + HIPEW_INIT_HIP = 1, +}; + +int hipewInit(hipuint32_t flags); +const char *hipewErrorString(hipError_t result); +const char *hipewCompilerPath(void); +int hipewCompilerVersion(void); +int hipewNvrtcVersion(void); + +#ifdef __cplusplus +} +#endif + +#endif /* __HIPEW_H__ */ diff --git a/extern/hipew/src/hipew.c b/extern/hipew/src/hipew.c new file mode 100644 index 00000000000..9d5a63f869a --- /dev/null +++ b/extern/hipew/src/hipew.c @@ -0,0 +1,533 @@ +/* + * Copyright 2011-2021 Blender Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ +#ifdef _MSC_VER +# if _MSC_VER < 1900 +# define snprintf _snprintf +# endif +# define popen _popen +# define pclose _pclose +# define _CRT_SECURE_NO_WARNINGS +#endif + +#include <hipew.h> +#include <assert.h> +#include <stdio.h> +#include <string.h> +#include <sys/stat.h> + +#ifdef _WIN32 +# define WIN32_LEAN_AND_MEAN +# define VC_EXTRALEAN +# include <windows.h> + +/* Utility macros. */ + +typedef HMODULE DynamicLibrary; + +# define dynamic_library_open(path) LoadLibraryA(path) +# define dynamic_library_close(lib) FreeLibrary(lib) +# define dynamic_library_find(lib, symbol) GetProcAddress(lib, symbol) +#else +# include <dlfcn.h> + +typedef void* DynamicLibrary; + +# define dynamic_library_open(path) dlopen(path, RTLD_NOW) +# define dynamic_library_close(lib) dlclose(lib) +# define dynamic_library_find(lib, symbol) dlsym(lib, symbol) +#endif + +#define _LIBRARY_FIND_CHECKED(lib, name) \ + name = (t##name *)dynamic_library_find(lib, #name); \ + assert(name); + +#define _LIBRARY_FIND(lib, name) \ + name = (t##name *)dynamic_library_find(lib, #name); + +#define HIP_LIBRARY_FIND_CHECKED(name) \ + _LIBRARY_FIND_CHECKED(hip_lib, name) +#define HIP_LIBRARY_FIND(name) _LIBRARY_FIND(hip_lib, name) + + +static DynamicLibrary hip_lib; + +/* Function definitions. */ +thipGetErrorName *hipGetErrorName; +thipInit *hipInit; +thipDriverGetVersion *hipDriverGetVersion; +thipGetDevice *hipGetDevice; +thipGetDeviceCount *hipGetDeviceCount; +thipDeviceGetName *hipDeviceGetName; +thipDeviceGetAttribute *hipDeviceGetAttribute; +thipDeviceComputeCapability *hipDeviceComputeCapability; +thipDevicePrimaryCtxRetain *hipDevicePrimaryCtxRetain; +thipDevicePrimaryCtxRelease *hipDevicePrimaryCtxRelease; +thipDevicePrimaryCtxSetFlags *hipDevicePrimaryCtxSetFlags; +thipDevicePrimaryCtxGetState *hipDevicePrimaryCtxGetState; +thipDevicePrimaryCtxReset *hipDevicePrimaryCtxReset; +thipCtxCreate *hipCtxCreate; +thipCtxDestroy *hipCtxDestroy; +thipCtxPushCurrent *hipCtxPushCurrent; +thipCtxPopCurrent *hipCtxPopCurrent; +thipCtxSetCurrent *hipCtxSetCurrent; +thipCtxGetCurrent *hipCtxGetCurrent; +thipCtxGetDevice *hipCtxGetDevice; +thipCtxGetFlags *hipCtxGetFlags; +thipCtxSynchronize *hipCtxSynchronize; +thipDeviceSynchronize *hipDeviceSynchronize; +thipCtxGetCacheConfig *hipCtxGetCacheConfig; +thipCtxSetCacheConfig *hipCtxSetCacheConfig; +thipCtxGetSharedMemConfig *hipCtxGetSharedMemConfig; +thipCtxSetSharedMemConfig *hipCtxSetSharedMemConfig; +thipCtxGetApiVersion *hipCtxGetApiVersion; +thipModuleLoad *hipModuleLoad; +thipModuleLoadData *hipModuleLoadData; +thipModuleLoadDataEx *hipModuleLoadDataEx; +thipModuleUnload *hipModuleUnload; +thipModuleGetFunction *hipModuleGetFunction; +thipModuleGetGlobal *hipModuleGetGlobal; +thipModuleGetTexRef *hipModuleGetTexRef; +thipMemGetInfo *hipMemGetInfo; +thipMalloc *hipMalloc; +thipMemAllocPitch *hipMemAllocPitch; +thipFree *hipFree; +thipMemGetAddressRange *hipMemGetAddressRange; +thipHostMalloc *hipHostMalloc; +thipHostFree *hipHostFree; +thipHostGetDevicePointer *hipHostGetDevicePointer; +thipHostGetFlags *hipHostGetFlags; +thipMallocManaged *hipMallocManaged; +thipDeviceGetByPCIBusId *hipDeviceGetByPCIBusId; +thipDeviceGetPCIBusId *hipDeviceGetPCIBusId; +thipMemcpyPeer *hipMemcpyPeer; +thipMemcpyHtoD *hipMemcpyHtoD; +thipMemcpyDtoH *hipMemcpyDtoH; +thipMemcpyDtoD *hipMemcpyDtoD; +thipDrvMemcpy2DUnaligned *hipDrvMemcpy2DUnaligned; +thipMemcpyParam2D *hipMemcpyParam2D; +thipDrvMemcpy3D *hipDrvMemcpy3D; +thipMemcpyHtoDAsync *hipMemcpyHtoDAsync; +thipMemcpyDtoHAsync *hipMemcpyDtoHAsync; +thipMemcpyParam2DAsync *hipMemcpyParam2DAsync; +thipDrvMemcpy3DAsync *hipDrvMemcpy3DAsync; +thipMemsetD8 *hipMemsetD8; +thipMemsetD16 *hipMemsetD16; +thipMemsetD32 *hipMemsetD32; +thipMemsetD8Async *hipMemsetD8Async; +thipMemsetD16Async *hipMemsetD16Async; +thipMemsetD32Async *hipMemsetD32Async; +thipArrayCreate *hipArrayCreate; +thipArrayDestroy *hipArrayDestroy; +thipArray3DCreate *hipArray3DCreate; +thipStreamCreateWithFlags *hipStreamCreateWithFlags; +thipStreamCreateWithPriority *hipStreamCreateWithPriority; +thipStreamGetPriority *hipStreamGetPriority; +thipStreamGetFlags *hipStreamGetFlags; +thipStreamWaitEvent *hipStreamWaitEvent; +thipStreamAddCallback *hipStreamAddCallback; +thipStreamQuery *hipStreamQuery; +thipStreamSynchronize *hipStreamSynchronize; +thipStreamDestroy *hipStreamDestroy; +thipEventCreateWithFlags *hipEventCreateWithFlags; +thipEventRecord *hipEventRecord; +thipEventQuery *hipEventQuery; +thipEventSynchronize *hipEventSynchronize; +thipEventDestroy *hipEventDestroy; +thipEventElapsedTime *hipEventElapsedTime; +thipFuncGetAttribute *hipFuncGetAttribute; +thipFuncSetCacheConfig *hipFuncSetCacheConfig; +thipModuleLaunchKernel *hipModuleLaunchKernel; +thipDrvOccupancyMaxActiveBlocksPerMultiprocessor *hipDrvOccupancyMaxActiveBlocksPerMultiprocessor; +thipDrvOccupancyMaxActiveBlocksPerMultiprocessorWithFlags *hipDrvOccupancyMaxActiveBlocksPerMultiprocessorWithFlags; +thipModuleOccupancyMaxPotentialBlockSize *hipModuleOccupancyMaxPotentialBlockSize; +thipTexRefSetArray *hipTexRefSetArray; +thipTexRefSetAddress *hipTexRefSetAddress; +thipTexRefSetAddress2D *hipTexRefSetAddress2D; +thipTexRefSetFormat *hipTexRefSetFormat; +thipTexRefSetAddressMode *hipTexRefSetAddressMode; +thipTexRefSetFilterMode *hipTexRefSetFilterMode; +thipTexRefSetFlags *hipTexRefSetFlags; +thipTexRefGetAddress *hipTexRefGetAddress; +thipTexRefGetArray *hipTexRefGetArray; +thipTexRefGetAddressMode *hipTexRefGetAddressMode; +thipTexObjectCreate *hipTexObjectCreate; +thipTexObjectDestroy *hipTexObjectDestroy; +thipDeviceCanAccessPeer *hipDeviceCanAccessPeer; + +thipCtxEnablePeerAccess *hipCtxEnablePeerAccess; +thipCtxDisablePeerAccess *hipCtxDisablePeerAccess; +thipDeviceGetP2PAttribute *hipDeviceGetP2PAttribute; +thipGraphicsUnregisterResource *hipGraphicsUnregisterResource; +thipGraphicsMapResources *hipGraphicsMapResources; +thipGraphicsUnmapResources *hipGraphicsUnmapResources; +thipGraphicsResourceGetMappedPointer *hipGraphicsResourceGetMappedPointer; + +thipGraphicsGLRegisterBuffer *hipGraphicsGLRegisterBuffer; +thipGLGetDevices *hipGLGetDevices; + + + +static DynamicLibrary dynamic_library_open_find(const char **paths) { + int i = 0; + while (paths[i] != NULL) { + DynamicLibrary lib = dynamic_library_open(paths[i]); + if (lib != NULL) { + return lib; + } + ++i; + } + return NULL; +} + +/* Implementation function. */ +static void hipewHipExit(void) { + if (hip_lib != NULL) { + /* Ignore errors. */ + dynamic_library_close(hip_lib); + hip_lib = NULL; + } +} + +static int hipewHipInit(void) { + /* Library paths. */ +#ifdef _WIN32 + /* Expected in c:/windows/system or similar, no path needed. */ + const char *hip_paths[] = {"amdhip64.dll", NULL}; +#elif defined(__APPLE__) + /* Default installation path. */ + const char *hip_paths[] = {"", NULL}; +#else + const char *hip_paths[] = {"/opt/rocm/hip/lib/libamdhip64.so", NULL}; +#endif + static int initialized = 0; + static int result = 0; + int error, driver_version; + + if (initialized) { + return result; + } + + initialized = 1; + + error = atexit(hipewHipExit); + if (error) { + result = HIPEW_ERROR_ATEXIT_FAILED; + return result; + } + + /* Load library. */ + hip_lib = dynamic_library_open_find(hip_paths); + + if (hip_lib == NULL) { + result = HIPEW_ERROR_OPEN_FAILED; + return result; + } + + /* Fetch all function pointers. */ + HIP_LIBRARY_FIND_CHECKED(hipGetErrorName); + HIP_LIBRARY_FIND_CHECKED(hipInit); + HIP_LIBRARY_FIND_CHECKED(hipDriverGetVersion); + HIP_LIBRARY_FIND_CHECKED(hipGetDevice); + HIP_LIBRARY_FIND_CHECKED(hipGetDeviceCount); + HIP_LIBRARY_FIND_CHECKED(hipDeviceGetName); + HIP_LIBRARY_FIND_CHECKED(hipDeviceGetAttribute); + HIP_LIBRARY_FIND_CHECKED(hipDeviceComputeCapability); + HIP_LIBRARY_FIND_CHECKED(hipDevicePrimaryCtxRetain); + HIP_LIBRARY_FIND_CHECKED(hipDevicePrimaryCtxRelease); + HIP_LIBRARY_FIND_CHECKED(hipDevicePrimaryCtxSetFlags); + HIP_LIBRARY_FIND_CHECKED(hipDevicePrimaryCtxGetState); + HIP_LIBRARY_FIND_CHECKED(hipDevicePrimaryCtxReset); + HIP_LIBRARY_FIND_CHECKED(hipCtxCreate); + HIP_LIBRARY_FIND_CHECKED(hipCtxDestroy); + HIP_LIBRARY_FIND_CHECKED(hipCtxPushCurrent); + HIP_LIBRARY_FIND_CHECKED(hipCtxPopCurrent); + HIP_LIBRARY_FIND_CHECKED(hipCtxSetCurrent); + HIP_LIBRARY_FIND_CHECKED(hipCtxGetCurrent); + HIP_LIBRARY_FIND_CHECKED(hipCtxGetDevice); + HIP_LIBRARY_FIND_CHECKED(hipCtxGetFlags); + HIP_LIBRARY_FIND_CHECKED(hipCtxSynchronize); + HIP_LIBRARY_FIND_CHECKED(hipDeviceSynchronize); + HIP_LIBRARY_FIND_CHECKED(hipCtxGetCacheConfig); + HIP_LIBRARY_FIND_CHECKED(hipCtxSetCacheConfig); + HIP_LIBRARY_FIND_CHECKED(hipCtxGetSharedMemConfig); + HIP_LIBRARY_FIND_CHECKED(hipCtxSetSharedMemConfig); + HIP_LIBRARY_FIND_CHECKED(hipCtxGetApiVersion); + HIP_LIBRARY_FIND_CHECKED(hipModuleLoad); + HIP_LIBRARY_FIND_CHECKED(hipModuleLoadData); + HIP_LIBRARY_FIND_CHECKED(hipModuleLoadDataEx); + HIP_LIBRARY_FIND_CHECKED(hipModuleUnload); + HIP_LIBRARY_FIND_CHECKED(hipModuleGetFunction); + HIP_LIBRARY_FIND_CHECKED(hipModuleGetGlobal); + HIP_LIBRARY_FIND_CHECKED(hipModuleGetTexRef); + HIP_LIBRARY_FIND_CHECKED(hipMemGetInfo); + HIP_LIBRARY_FIND_CHECKED(hipMalloc); + HIP_LIBRARY_FIND_CHECKED(hipMemAllocPitch); + HIP_LIBRARY_FIND_CHECKED(hipFree); + HIP_LIBRARY_FIND_CHECKED(hipMemGetAddressRange); + HIP_LIBRARY_FIND_CHECKED(hipHostMalloc); + HIP_LIBRARY_FIND_CHECKED(hipHostFree); + HIP_LIBRARY_FIND_CHECKED(hipHostGetDevicePointer); + HIP_LIBRARY_FIND_CHECKED(hipHostGetFlags); + HIP_LIBRARY_FIND_CHECKED(hipMallocManaged); + HIP_LIBRARY_FIND_CHECKED(hipDeviceGetByPCIBusId); + HIP_LIBRARY_FIND_CHECKED(hipDeviceGetPCIBusId); + HIP_LIBRARY_FIND_CHECKED(hipMemcpyPeer); + HIP_LIBRARY_FIND_CHECKED(hipMemcpyHtoD); + HIP_LIBRARY_FIND_CHECKED(hipMemcpyDtoH); + HIP_LIBRARY_FIND_CHECKED(hipMemcpyDtoD); + HIP_LIBRARY_FIND_CHECKED(hipMemcpyParam2D); + HIP_LIBRARY_FIND_CHECKED(hipDrvMemcpy3D); + HIP_LIBRARY_FIND_CHECKED(hipMemcpyHtoDAsync); + HIP_LIBRARY_FIND_CHECKED(hipMemcpyDtoHAsync); + HIP_LIBRARY_FIND_CHECKED(hipDrvMemcpy2DUnaligned); + HIP_LIBRARY_FIND_CHECKED(hipMemcpyParam2DAsync); + HIP_LIBRARY_FIND_CHECKED(hipDrvMemcpy3DAsync); + HIP_LIBRARY_FIND_CHECKED(hipMemsetD8); + HIP_LIBRARY_FIND_CHECKED(hipMemsetD16); + HIP_LIBRARY_FIND_CHECKED(hipMemsetD32); + HIP_LIBRARY_FIND_CHECKED(hipMemsetD8Async); + HIP_LIBRARY_FIND_CHECKED(hipMemsetD16Async); + HIP_LIBRARY_FIND_CHECKED(hipMemsetD32Async); + HIP_LIBRARY_FIND_CHECKED(hipArrayCreate); + HIP_LIBRARY_FIND_CHECKED(hipArrayDestroy); + HIP_LIBRARY_FIND_CHECKED(hipArray3DCreate); + HIP_LIBRARY_FIND_CHECKED(hipStreamCreateWithFlags); + HIP_LIBRARY_FIND_CHECKED(hipStreamCreateWithPriority); + HIP_LIBRARY_FIND_CHECKED(hipStreamGetPriority); + HIP_LIBRARY_FIND_CHECKED(hipStreamGetFlags); + HIP_LIBRARY_FIND_CHECKED(hipStreamWaitEvent); + HIP_LIBRARY_FIND_CHECKED(hipStreamAddCallback); + HIP_LIBRARY_FIND_CHECKED(hipStreamQuery); + HIP_LIBRARY_FIND_CHECKED(hipStreamSynchronize); + HIP_LIBRARY_FIND_CHECKED(hipStreamDestroy); + HIP_LIBRARY_FIND_CHECKED(hipEventCreateWithFlags); + HIP_LIBRARY_FIND_CHECKED(hipEventRecord); + HIP_LIBRARY_FIND_CHECKED(hipEventQuery); + HIP_LIBRARY_FIND_CHECKED(hipEventSynchronize); + HIP_LIBRARY_FIND_CHECKED(hipEventDestroy); + HIP_LIBRARY_FIND_CHECKED(hipEventElapsedTime); + HIP_LIBRARY_FIND_CHECKED(hipFuncGetAttribute); + HIP_LIBRARY_FIND_CHECKED(hipFuncSetCacheConfig); + HIP_LIBRARY_FIND_CHECKED(hipModuleLaunchKernel); + HIP_LIBRARY_FIND_CHECKED(hipModuleOccupancyMaxPotentialBlockSize); + HIP_LIBRARY_FIND_CHECKED(hipTexRefSetArray); + HIP_LIBRARY_FIND_CHECKED(hipTexRefSetAddress); + HIP_LIBRARY_FIND_CHECKED(hipTexRefSetAddress2D); + HIP_LIBRARY_FIND_CHECKED(hipTexRefSetFormat); + HIP_LIBRARY_FIND_CHECKED(hipTexRefSetAddressMode); + HIP_LIBRARY_FIND_CHECKED(hipTexRefSetFilterMode); + HIP_LIBRARY_FIND_CHECKED(hipTexRefSetFlags); + HIP_LIBRARY_FIND_CHECKED(hipTexRefGetAddress); + HIP_LIBRARY_FIND_CHECKED(hipTexRefGetAddressMode); + HIP_LIBRARY_FIND_CHECKED(hipTexObjectCreate); + HIP_LIBRARY_FIND_CHECKED(hipTexObjectDestroy); + HIP_LIBRARY_FIND_CHECKED(hipDeviceCanAccessPeer); + HIP_LIBRARY_FIND_CHECKED(hipCtxEnablePeerAccess); + HIP_LIBRARY_FIND_CHECKED(hipCtxDisablePeerAccess); + HIP_LIBRARY_FIND_CHECKED(hipDeviceGetP2PAttribute); +#ifdef _WIN32 + HIP_LIBRARY_FIND_CHECKED(hipGraphicsUnregisterResource); + HIP_LIBRARY_FIND_CHECKED(hipGraphicsMapResources); + HIP_LIBRARY_FIND_CHECKED(hipGraphicsUnmapResources); + HIP_LIBRARY_FIND_CHECKED(hipGraphicsResourceGetMappedPointer); + HIP_LIBRARY_FIND_CHECKED(hipGraphicsGLRegisterBuffer); + HIP_LIBRARY_FIND_CHECKED(hipGLGetDevices); +#endif + result = HIPEW_SUCCESS; + return result; +} + + + +int hipewInit(hipuint32_t flags) { + int result = HIPEW_SUCCESS; + + if (flags & HIPEW_INIT_HIP) { + result = hipewHipInit(); + if (result != HIPEW_SUCCESS) { + return result; + } + } + + return result; +} + + +const char *hipewErrorString(hipError_t result) { + switch (result) { + case hipSuccess: return "No errors"; + case hipErrorInvalidValue: return "Invalid value"; + case hipErrorOutOfMemory: return "Out of memory"; + case hipErrorNotInitialized: return "Driver not initialized"; + case hipErrorDeinitialized: return "Driver deinitialized"; + case hipErrorProfilerDisabled: return "Profiler disabled"; + case hipErrorProfilerNotInitialized: return "Profiler not initialized"; + case hipErrorProfilerAlreadyStarted: return "Profiler already started"; + case hipErrorProfilerAlreadyStopped: return "Profiler already stopped"; + case hipErrorNoDevice: return "No HIP-capable device available"; + case hipErrorInvalidDevice: return "Invalid device"; + case hipErrorInvalidImage: return "Invalid kernel image"; + case hipErrorInvalidContext: return "Invalid context"; + case hipErrorContextAlreadyCurrent: return "Context already current"; + case hipErrorMapFailed: return "Map failed"; + case hipErrorUnmapFailed: return "Unmap failed"; + case hipErrorArrayIsMapped: return "Array is mapped"; + case hipErrorAlreadyMapped: return "Already mapped"; + case hipErrorNoBinaryForGpu: return "No binary for GPU"; + case hipErrorAlreadyAcquired: return "Already acquired"; + case hipErrorNotMapped: return "Not mapped"; + case hipErrorNotMappedAsArray: return "Mapped resource not available for access as an array"; + case hipErrorNotMappedAsPointer: return "Mapped resource not available for access as a pointer"; + case hipErrorECCNotCorrectable: return "Uncorrectable ECC error detected"; + case hipErrorUnsupportedLimit: return "hipLimit_t not supported by device"; + case hipErrorContextAlreadyInUse: return "Context already in use"; + case hipErrorPeerAccessUnsupported: return "Peer access unsupported"; + case hipErrorInvalidKernelFile: return "Invalid ptx"; + case hipErrorInvalidGraphicsContext: return "Invalid graphics context"; + case hipErrorInvalidSource: return "Invalid source"; + case hipErrorFileNotFound: return "File not found"; + case hipErrorSharedObjectSymbolNotFound: return "Link to a shared object failed to resolve"; + case hipErrorSharedObjectInitFailed: return "Shared object initialization failed"; + case hipErrorOperatingSystem: return "Operating system"; + case hipErrorInvalidHandle: return "Invalid handle"; + case hipErrorNotFound: return "Not found"; + case hipErrorNotReady: return "HIP not ready"; + case hipErrorIllegalAddress: return "Illegal address"; + case hipErrorLaunchOutOfResources: return "Launch exceeded resources"; + case hipErrorLaunchTimeOut: return "Launch exceeded timeout"; + case hipErrorPeerAccessAlreadyEnabled: return "Peer access already enabled"; + case hipErrorPeerAccessNotEnabled: return "Peer access not enabled"; + case hipErrorSetOnActiveProcess: return "Primary context active"; + case hipErrorAssert: return "Assert"; + case hipErrorHostMemoryAlreadyRegistered: return "Host memory already registered"; + case hipErrorHostMemoryNotRegistered: return "Host memory not registered"; + case hipErrorLaunchFailure: return "Launch failed"; + case hipErrorCooperativeLaunchTooLarge: return "Cooperative launch too large"; + case hipErrorNotSupported: return "Not supported"; + case hipErrorUnknown: return "Unknown error"; + default: return "Unknown HIP error value"; + } +} + +static void path_join(const char *path1, + const char *path2, + int maxlen, + char *result) { +#if defined(WIN32) || defined(_WIN32) + const char separator = '\\'; +#else + const char separator = '/'; +#endif + int n = snprintf(result, maxlen, "%s%c%s", path1, separator, path2); + if (n != -1 && n < maxlen) { + result[n] = '\0'; + } + else { + result[maxlen - 1] = '\0'; + } +} + +static int path_exists(const char *path) { + struct stat st; + if (stat(path, &st)) { + return 0; + } + return 1; +} + +const char *hipewCompilerPath(void) { + #ifdef _WIN32 + const char *hipPath = getenv("HIP_ROCCLR_HOME"); + const char *windowsCommand = "perl "; + const char *executable = "bin/hipcc"; + + static char hipcc[65536]; + static char finalCommand[65536]; + if(hipPath) { + path_join(hipPath, executable, sizeof(hipcc), hipcc); + if(path_exists(hipcc)) { + snprintf(finalCommand, sizeof(hipcc), "%s %s", windowsCommand, hipcc); + return finalCommand; + } else { + printf("Could not find hipcc. Make sure HIP_ROCCLR_HOME points to the directory holding /bin/hipcc"); + } + } + #else + const char *hipPath = "opt/rocm/hip/bin"; + const char *executable = "hipcc"; + + static char hipcc[65536]; + if(hipPath) { + path_join(hipPath, executable, sizeof(hipcc), hipcc); + if(path_exists(hipcc)){ + return hipcc; + } + } + #endif + + { +#ifdef _WIN32 + FILE *handle = popen("where hipcc", "r"); +#else + FILE *handle = popen("which hipcc", "r"); +#endif + if (handle) { + char buffer[4096] = {0}; + int len = fread(buffer, 1, sizeof(buffer) - 1, handle); + buffer[len] = '\0'; + pclose(handle); + if (buffer[0]) { + return "hipcc"; + } + } + } + + return NULL; +} + +int hipewCompilerVersion(void) { + const char *path = hipewCompilerPath(); + const char *marker = "Hip compilation tools, release "; + FILE *pipe; + int major, minor; + char *versionstr; + char buf[128]; + char output[65536] = "\0"; + char command[65536] = "\0"; + + if (path == NULL) { + return 0; + } + + /* get --version output */ + strcat(command, "\""); + strncat(command, path, sizeof(command) - 1); + strncat(command, "\" --version", sizeof(command) - strlen(path) - 1); + pipe = popen(command, "r"); + if (!pipe) { + fprintf(stderr, "HIP: failed to run compiler to retrieve version"); + return 0; + } + + while (!feof(pipe)) { + if (fgets(buf, sizeof(buf), pipe) != NULL) { + strncat(output, buf, sizeof(output) - strlen(output) - 1); + } + } + + pclose(pipe); + return 40; +} diff --git a/intern/cycles/CMakeLists.txt b/intern/cycles/CMakeLists.txt index 17096d441f0..2018c1d9648 100644 --- a/intern/cycles/CMakeLists.txt +++ b/intern/cycles/CMakeLists.txt @@ -297,6 +297,7 @@ endif() if(WITH_CYCLES_STANDALONE) set(WITH_CYCLES_DEVICE_CUDA TRUE) + set(WITH_CYCLES_DEVICE_HIP TRUE) endif() # TODO(sergey): Consider removing it, only causes confusion in interface. set(WITH_CYCLES_DEVICE_MULTI TRUE) diff --git a/intern/cycles/app/CMakeLists.txt b/intern/cycles/app/CMakeLists.txt index f9dc5f00802..3ed3f54ef9f 100644 --- a/intern/cycles/app/CMakeLists.txt +++ b/intern/cycles/app/CMakeLists.txt @@ -64,6 +64,8 @@ if(WITH_CYCLES_STANDALONE) cycles_standalone.cpp cycles_xml.cpp cycles_xml.h + oiio_output_driver.cpp + oiio_output_driver.h ) add_executable(cycles ${SRC} ${INC} ${INC_SYS}) unset(SRC) @@ -73,7 +75,7 @@ if(WITH_CYCLES_STANDALONE) if(APPLE) if(WITH_OPENCOLORIO) - set_property(TARGET cycles APPEND_STRING PROPERTY LINK_FLAGS " -framework IOKit") + set_property(TARGET cycles APPEND_STRING PROPERTY LINK_FLAGS " -framework IOKit -framework Carbon") endif() if(WITH_OPENIMAGEDENOISE AND "${CMAKE_OSX_ARCHITECTURES}" STREQUAL "arm64") # OpenImageDenoise uses BNNS from the Accelerate framework. diff --git a/intern/cycles/app/cycles_standalone.cpp b/intern/cycles/app/cycles_standalone.cpp index 270096d70b0..00dc140648a 100644 --- a/intern/cycles/app/cycles_standalone.cpp +++ b/intern/cycles/app/cycles_standalone.cpp @@ -36,6 +36,9 @@ #include "util/util_unique_ptr.h" #include "util/util_version.h" +#include "app/cycles_xml.h" +#include "app/oiio_output_driver.h" + #ifdef WITH_CYCLES_STANDALONE_GUI # include "util/util_view.h" #endif @@ -53,7 +56,8 @@ struct Options { SessionParams session_params; bool quiet; bool show_help, interactive, pause; - string output_path; + string output_filepath; + string output_pass; } options; static void session_print(const string &str) @@ -89,30 +93,6 @@ static void session_print_status() session_print(status); } -static bool write_render(const uchar *pixels, int w, int h, int channels) -{ - string msg = string_printf("Writing image %s", options.output_path.c_str()); - session_print(msg); - - unique_ptr<ImageOutput> out = unique_ptr<ImageOutput>(ImageOutput::create(options.output_path)); - if (!out) { - return false; - } - - ImageSpec spec(w, h, channels, TypeDesc::UINT8); - if (!out->open(options.output_path, spec)) { - return false; - } - - /* conversion for different top/bottom convention */ - out->write_image( - TypeDesc::UINT8, pixels + (h - 1) * w * channels, AutoStride, -w * channels, AutoStride); - - out->close(); - - return true; -} - static BufferParams &session_buffer_params() { static BufferParams buffer_params; @@ -147,9 +127,14 @@ static void scene_init() static void session_init() { - options.session_params.write_render_cb = write_render; + options.output_pass = "combined"; options.session = new Session(options.session_params, options.scene_params); + if (!options.output_filepath.empty()) { + options.session->set_output_driver(make_unique<OIIOOutputDriver>( + options.output_filepath, options.output_pass, session_print)); + } + if (options.session_params.background && !options.quiet) options.session->progress.set_update_callback(function_bind(&session_print_status)); #ifdef WITH_CYCLES_STANDALONE_GUI @@ -160,7 +145,12 @@ static void session_init() /* load scene */ scene_init(); - options.session->reset(session_buffer_params(), options.session_params.samples); + /* add pass for output. */ + Pass *pass = options.scene->create_node<Pass>(); + pass->set_name(ustring(options.output_pass.c_str())); + pass->set_type(PASS_COMBINED); + + options.session->reset(options.session_params, session_buffer_params()); options.session->start(); } @@ -222,9 +212,7 @@ static void display_info(Progress &progress) static void display() { - static DeviceDrawParams draw_params = DeviceDrawParams(); - - options.session->draw(session_buffer_params(), draw_params); + options.session->draw(); display_info(options.session->progress); } @@ -254,7 +242,7 @@ static void motion(int x, int y, int button) options.session->scene->camera->need_flags_update = true; options.session->scene->camera->need_device_update = true; - options.session->reset(session_buffer_params(), options.session_params.samples); + options.session->reset(options.session_params, session_buffer_params()); } } @@ -271,7 +259,7 @@ static void resize(int width, int height) options.session->scene->camera->need_flags_update = true; options.session->scene->camera->need_device_update = true; - options.session->reset(session_buffer_params(), options.session_params.samples); + options.session->reset(options.session_params, session_buffer_params()); } } @@ -283,7 +271,7 @@ static void keyboard(unsigned char key) /* Reset */ else if (key == 'r') - options.session->reset(session_buffer_params(), options.session_params.samples); + options.session->reset(options.session_params, session_buffer_params()); /* Cancel */ else if (key == 27) // escape @@ -320,7 +308,7 @@ static void keyboard(unsigned char key) options.session->scene->camera->need_flags_update = true; options.session->scene->camera->need_device_update = true; - options.session->reset(session_buffer_params(), options.session_params.samples); + options.session->reset(options.session_params, session_buffer_params()); } /* Set Max Bounces */ @@ -346,7 +334,7 @@ static void keyboard(unsigned char key) options.session->scene->integrator->set_max_bounce(bounce); - options.session->reset(session_buffer_params(), options.session_params.samples); + options.session->reset(options.session_params, session_buffer_params()); } } #endif @@ -361,11 +349,13 @@ static int files_parse(int argc, const char *argv[]) static void options_parse(int argc, const char **argv) { - options.width = 0; - options.height = 0; + options.width = 1024; + options.height = 512; options.filepath = ""; options.session = NULL; options.quiet = false; + options.session_params.use_auto_tile = false; + options.session_params.tile_size = 0; /* device names */ string device_names = ""; @@ -411,7 +401,7 @@ static void options_parse(int argc, const char **argv) &options.session_params.samples, "Number of samples to render", "--output %s", - &options.output_path, + &options.output_filepath, "File path to write output image", "--threads %d", &options.session_params.threads, @@ -422,12 +412,9 @@ static void options_parse(int argc, const char **argv) "--height %d", &options.height, "Window height in pixel", - "--tile-width %d", - &options.session_params.tile_size.x, - "Tile width in pixels", - "--tile-height %d", - &options.session_params.tile_size.y, - "Tile height in pixels", + "--tile-size %d", + &options.session_params.tile_size, + "Tile size in pixels", "--list-devices", &list, "List information about all available devices", @@ -489,8 +476,9 @@ static void options_parse(int argc, const char **argv) options.session_params.background = true; #endif - /* Use progressive rendering */ - options.session_params.progressive = true; + if (options.session_params.tile_size > 0) { + options.session_params.use_auto_tile = true; + } /* find matching device */ DeviceType device_type = Device::type_from_string(devicename.c_str()); diff --git a/intern/cycles/app/cycles_xml.cpp b/intern/cycles/app/cycles_xml.cpp index 54f97fddbd9..0b83c60f32d 100644 --- a/intern/cycles/app/cycles_xml.cpp +++ b/intern/cycles/app/cycles_xml.cpp @@ -333,6 +333,7 @@ static void xml_read_shader_graph(XMLReadState &state, Shader *shader, xml_node } snode = (ShaderNode *)node_type->create(node_type); + snode->set_owner(graph); } xml_read_node(graph_reader, snode, node); diff --git a/intern/cycles/app/oiio_output_driver.cpp b/intern/cycles/app/oiio_output_driver.cpp new file mode 100644 index 00000000000..d791c89772f --- /dev/null +++ b/intern/cycles/app/oiio_output_driver.cpp @@ -0,0 +1,71 @@ +/* + * Copyright 2021 Blender Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "app/oiio_output_driver.h" + +CCL_NAMESPACE_BEGIN + +OIIOOutputDriver::OIIOOutputDriver(const string_view filepath, + const string_view pass, + LogFunction log) + : filepath_(filepath), pass_(pass), log_(log) +{ +} + +OIIOOutputDriver::~OIIOOutputDriver() +{ +} + +void OIIOOutputDriver::write_render_tile(const Tile &tile) +{ + /* Only write the full buffer, no intermediate tiles. */ + if (!(tile.size == tile.full_size)) { + return; + } + + log_(string_printf("Writing image %s", filepath_.c_str())); + + unique_ptr<ImageOutput> image_output(ImageOutput::create(filepath_)); + if (image_output == nullptr) { + log_("Failed to create image file"); + return; + } + + const int width = tile.size.x; + const int height = tile.size.y; + + ImageSpec spec(width, height, 4, TypeDesc::FLOAT); + if (!image_output->open(filepath_, spec)) { + log_("Failed to create image file"); + return; + } + + vector<float> pixels(width * height * 4); + if (!tile.get_pass_pixels(pass_, 4, pixels.data())) { + log_("Failed to read render pass pixels"); + return; + } + + /* Manipulate offset and stride to convert from bottom-up to top-down convention. */ + image_output->write_image(TypeDesc::FLOAT, + pixels.data() + (height - 1) * width * 4, + AutoStride, + -width * 4 * sizeof(float), + AutoStride); + image_output->close(); +} + +CCL_NAMESPACE_END diff --git a/intern/cycles/app/oiio_output_driver.h b/intern/cycles/app/oiio_output_driver.h new file mode 100644 index 00000000000..cdc4085d962 --- /dev/null +++ b/intern/cycles/app/oiio_output_driver.h @@ -0,0 +1,42 @@ +/* + * Copyright 2021 Blender Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "render/output_driver.h" + +#include "util/util_function.h" +#include "util/util_image.h" +#include "util/util_string.h" +#include "util/util_unique_ptr.h" +#include "util/util_vector.h" + +CCL_NAMESPACE_BEGIN + +class OIIOOutputDriver : public OutputDriver { + public: + typedef function<void(const string &)> LogFunction; + + OIIOOutputDriver(const string_view filepath, const string_view pass, LogFunction log); + virtual ~OIIOOutputDriver(); + + void write_render_tile(const Tile &tile) override; + + protected: + string filepath_; + string pass_; + LogFunction log_; +}; + +CCL_NAMESPACE_END diff --git a/intern/cycles/blender/CMakeLists.txt b/intern/cycles/blender/CMakeLists.txt index 5bdcfd56a4d..a0442b3394b 100644 --- a/intern/cycles/blender/CMakeLists.txt +++ b/intern/cycles/blender/CMakeLists.txt @@ -31,13 +31,14 @@ set(INC_SYS set(SRC blender_camera.cpp blender_device.cpp + blender_display_driver.cpp blender_image.cpp blender_geometry.cpp - blender_gpu_display.cpp blender_light.cpp blender_mesh.cpp blender_object.cpp blender_object_cull.cpp + blender_output_driver.cpp blender_particles.cpp blender_curves.cpp blender_logging.cpp @@ -51,10 +52,11 @@ set(SRC CCL_api.h blender_device.h - blender_gpu_display.h + blender_display_driver.h blender_id_map.h blender_image.h blender_object_cull.h + blender_output_driver.h blender_sync.h blender_session.h blender_texture.h @@ -95,6 +97,9 @@ set(ADDON_FILES add_definitions(${GL_DEFINITIONS}) +if(WITH_CYCLES_DEVICE_HIP) + add_definitions(-DWITH_HIP) +endif() if(WITH_MOD_FLUID) add_definitions(-DWITH_FLUID) endif() diff --git a/intern/cycles/blender/addon/engine.py b/intern/cycles/blender/addon/engine.py index e0e8ca10bef..d729cb1ee69 100644 --- a/intern/cycles/blender/addon/engine.py +++ b/intern/cycles/blender/addon/engine.py @@ -28,7 +28,7 @@ def _configure_argument_parser(): action='store_true') parser.add_argument("--cycles-device", help="Set the device to use for Cycles, overriding user preferences and the scene setting." - "Valid options are 'CPU', 'CUDA' or 'OPTIX'." + "Valid options are 'CPU', 'CUDA', 'OPTIX', or 'HIP'" "Additionally, you can append '+CPU' to any GPU type for hybrid rendering.", default=None) return parser diff --git a/intern/cycles/blender/addon/properties.py b/intern/cycles/blender/addon/properties.py index 5fb0eeed925..cea70033784 100644 --- a/intern/cycles/blender/addon/properties.py +++ b/intern/cycles/blender/addon/properties.py @@ -111,6 +111,7 @@ enum_device_type = ( ('CPU', "CPU", "CPU", 0), ('CUDA', "CUDA", "CUDA", 1), ('OPTIX', "OptiX", "OptiX", 3), + ("HIP", "HIP", "HIP", 4) ) enum_texture_limit = ( @@ -123,7 +124,7 @@ enum_texture_limit = ( ('4096', "4096", "Limit texture size to 4096 pixels", 6), ('8192', "8192", "Limit texture size to 8192 pixels", 7), ) - + # NOTE: Identifiers are expected to be an upper case version of identifiers from `Pass::get_type_enum()` enum_view3d_shading_render_pass = ( ('', "General", ""), @@ -739,7 +740,7 @@ class CyclesRenderSettings(bpy.types.PropertyGroup): use_auto_tile: BoolProperty( name="Auto Tiles", - description="Automatically split image into tiles", + description="Automatically render high resolution images in tiles to reduce memory usage, using the specified tile size. Tiles are cached to disk while rendering to save memory", default=True, ) tile_size: IntProperty( @@ -1266,12 +1267,16 @@ class CyclesPreferences(bpy.types.AddonPreferences): def get_device_types(self, context): import _cycles - has_cuda, has_optix = _cycles.get_device_types() + has_cuda, has_optix, has_hip = _cycles.get_device_types() + list = [('NONE', "None", "Don't use compute device", 0)] if has_cuda: list.append(('CUDA', "CUDA", "Use CUDA for GPU acceleration", 1)) if has_optix: list.append(('OPTIX', "OptiX", "Use OptiX for GPU acceleration", 3)) + if has_hip: + list.append(('HIP', "HIP", "Use HIP for GPU acceleration", 4)) + return list compute_device_type: EnumProperty( @@ -1296,7 +1301,7 @@ class CyclesPreferences(bpy.types.AddonPreferences): def update_device_entries(self, device_list): for device in device_list: - if not device[1] in {'CUDA', 'OPTIX', 'CPU'}: + if not device[1] in {'CUDA', 'OPTIX', 'CPU', 'HIP'}: continue # Try to find existing Device entry entry = self.find_existing_device_entry(device) @@ -1330,7 +1335,7 @@ class CyclesPreferences(bpy.types.AddonPreferences): elif entry.type == 'CPU': cpu_devices.append(entry) # Extend all GPU devices with CPU. - if compute_device_type != 'CPU': + if compute_device_type != 'CPU' and compute_device_type != 'HIP': devices.extend(cpu_devices) return devices @@ -1340,7 +1345,7 @@ class CyclesPreferences(bpy.types.AddonPreferences): import _cycles # Ensure `self.devices` is not re-allocated when the second call to # get_devices_for_type is made, freeing items from the first list. - for device_type in ('CUDA', 'OPTIX', 'OPENCL'): + for device_type in ('CUDA', 'OPTIX', 'HIP'): self.update_device_entries(_cycles.available_devices(device_type)) # Deprecated: use refresh_devices instead. diff --git a/intern/cycles/blender/addon/ui.py b/intern/cycles/blender/addon/ui.py index d02627b9936..c4a1844480c 100644 --- a/intern/cycles/blender/addon/ui.py +++ b/intern/cycles/blender/addon/ui.py @@ -99,6 +99,11 @@ def use_cuda(context): return (get_device_type(context) == 'CUDA' and cscene.device == 'GPU') +def use_hip(context): + cscene = context.scene.cycles + + return (get_device_type(context) == 'HIP' and cscene.device == 'GPU') + def use_optix(context): cscene = context.scene.cycles @@ -613,8 +618,8 @@ class CYCLES_RENDER_PT_performance_threads(CyclesButtonsPanel, Panel): sub.prop(rd, "threads") -class CYCLES_RENDER_PT_performance_tiles(CyclesButtonsPanel, Panel): - bl_label = "Tiles" +class CYCLES_RENDER_PT_performance_memory(CyclesButtonsPanel, Panel): + bl_label = "Memory" bl_parent_id = "CYCLES_RENDER_PT_performance" def draw(self, context): @@ -2107,7 +2112,7 @@ classes = ( CYCLES_RENDER_PT_film_transparency, CYCLES_RENDER_PT_performance, CYCLES_RENDER_PT_performance_threads, - CYCLES_RENDER_PT_performance_tiles, + CYCLES_RENDER_PT_performance_memory, CYCLES_RENDER_PT_performance_acceleration_structure, CYCLES_RENDER_PT_performance_final_render, CYCLES_RENDER_PT_performance_viewport, diff --git a/intern/cycles/blender/blender_curves.cpp b/intern/cycles/blender/blender_curves.cpp index 6fe5ea41fff..b6b4f206620 100644 --- a/intern/cycles/blender/blender_curves.cpp +++ b/intern/cycles/blender/blender_curves.cpp @@ -283,10 +283,13 @@ static void ExportCurveSegments(Scene *scene, Hair *hair, ParticleCurveData *CDa return; Attribute *attr_intercept = NULL; + Attribute *attr_length = NULL; Attribute *attr_random = NULL; if (hair->need_attribute(scene, ATTR_STD_CURVE_INTERCEPT)) attr_intercept = hair->attributes.add(ATTR_STD_CURVE_INTERCEPT); + if (hair->need_attribute(scene, ATTR_STD_CURVE_LENGTH)) + attr_length = hair->attributes.add(ATTR_STD_CURVE_LENGTH); if (hair->need_attribute(scene, ATTR_STD_CURVE_RANDOM)) attr_random = hair->attributes.add(ATTR_STD_CURVE_RANDOM); @@ -336,6 +339,10 @@ static void ExportCurveSegments(Scene *scene, Hair *hair, ParticleCurveData *CDa num_curve_keys++; } + if (attr_length != NULL) { + attr_length->add(CData->curve_length[curve]); + } + if (attr_random != NULL) { attr_random->add(hash_uint2_to_float(num_curves, 0)); } @@ -657,11 +664,15 @@ static void export_hair_curves(Scene *scene, Hair *hair, BL::Hair b_hair) /* Add requested attributes. */ Attribute *attr_intercept = NULL; + Attribute *attr_length = NULL; Attribute *attr_random = NULL; if (hair->need_attribute(scene, ATTR_STD_CURVE_INTERCEPT)) { attr_intercept = hair->attributes.add(ATTR_STD_CURVE_INTERCEPT); } + if (hair->need_attribute(scene, ATTR_STD_CURVE_LENGTH)) { + attr_length = hair->attributes.add(ATTR_STD_CURVE_LENGTH); + } if (hair->need_attribute(scene, ATTR_STD_CURVE_RANDOM)) { attr_random = hair->attributes.add(ATTR_STD_CURVE_RANDOM); } @@ -714,6 +725,10 @@ static void export_hair_curves(Scene *scene, Hair *hair, BL::Hair b_hair) } } + if (attr_length) { + attr_length->add(length); + } + /* Random number per curve. */ if (attr_random != NULL) { attr_random->add(hash_uint2_to_float(b_curve.index(), 0)); diff --git a/intern/cycles/blender/blender_device.cpp b/intern/cycles/blender/blender_device.cpp index ce1770f18a3..7bed33855c2 100644 --- a/intern/cycles/blender/blender_device.cpp +++ b/intern/cycles/blender/blender_device.cpp @@ -26,6 +26,7 @@ enum ComputeDevice { COMPUTE_DEVICE_CPU = 0, COMPUTE_DEVICE_CUDA = 1, COMPUTE_DEVICE_OPTIX = 3, + COMPUTE_DEVICE_HIP = 4, COMPUTE_DEVICE_NUM }; @@ -81,6 +82,9 @@ DeviceInfo blender_device_info(BL::Preferences &b_preferences, BL::Scene &b_scen else if (compute_device == COMPUTE_DEVICE_OPTIX) { mask |= DEVICE_MASK_OPTIX; } + else if (compute_device == COMPUTE_DEVICE_HIP) { + mask |= DEVICE_MASK_HIP; + } vector<DeviceInfo> devices = Device::available_devices(mask); /* Match device preferences and available devices. */ diff --git a/intern/cycles/blender/blender_gpu_display.cpp b/intern/cycles/blender/blender_display_driver.cpp index c5c3a2bd155..f55a8ce8c4e 100644 --- a/intern/cycles/blender/blender_gpu_display.cpp +++ b/intern/cycles/blender/blender_display_driver.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "blender/blender_gpu_display.h" +#include "blender/blender_display_driver.h" #include "device/device.h" #include "util/util_logging.h" @@ -273,17 +273,17 @@ uint BlenderDisplaySpaceShader::get_shader_program() } /* -------------------------------------------------------------------- - * BlenderGPUDisplay. + * BlenderDisplayDriver. */ -BlenderGPUDisplay::BlenderGPUDisplay(BL::RenderEngine &b_engine, BL::Scene &b_scene) +BlenderDisplayDriver::BlenderDisplayDriver(BL::RenderEngine &b_engine, BL::Scene &b_scene) : b_engine_(b_engine), display_shader_(BlenderDisplayShader::create(b_engine, b_scene)) { /* Create context while on the main thread. */ gl_context_create(); } -BlenderGPUDisplay::~BlenderGPUDisplay() +BlenderDisplayDriver::~BlenderDisplayDriver() { gl_resources_destroy(); } @@ -292,19 +292,18 @@ BlenderGPUDisplay::~BlenderGPUDisplay() * Update procedure. */ -bool BlenderGPUDisplay::do_update_begin(const GPUDisplayParams ¶ms, +bool BlenderDisplayDriver::update_begin(const Params ¶ms, int texture_width, int texture_height) { - /* Note that it's the responsibility of BlenderGPUDisplay to ensure updating and drawing + /* Note that it's the responsibility of BlenderDisplayDriver to ensure updating and drawing * the texture does not happen at the same time. This is achieved indirectly. * * When enabling the OpenGL context, it uses an internal mutex lock DST.gl_context_lock. * This same lock is also held when do_draw() is called, which together ensure mutual * exclusion. * - * This locking is not performed at the GPU display level, because that would cause lock - * inversion. */ + * This locking is not performed on the Cycles side, because that would cause lock inversion. */ if (!gl_context_enable()) { return false; } @@ -361,7 +360,7 @@ bool BlenderGPUDisplay::do_update_begin(const GPUDisplayParams ¶ms, return true; } -void BlenderGPUDisplay::do_update_end() +void BlenderDisplayDriver::update_end() { gl_upload_sync_ = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); glFlush(); @@ -370,53 +369,17 @@ void BlenderGPUDisplay::do_update_end() } /* -------------------------------------------------------------------- - * Texture update from CPU buffer. - */ - -void BlenderGPUDisplay::do_copy_pixels_to_texture( - const half4 *rgba_pixels, int texture_x, int texture_y, int pixels_width, int pixels_height) -{ - /* This call copies pixels to a Pixel Buffer Object (PBO) which is much cheaper from CPU time - * point of view than to copy data directly to the OpenGL texture. - * - * The possible downside of this approach is that it might require a higher peak memory when - * doing partial updates of the texture (although, in practice even partial updates might peak - * with a full-frame buffer stored on the CPU if the GPU is currently occupied). */ - - half4 *mapped_rgba_pixels = map_texture_buffer(); - if (!mapped_rgba_pixels) { - return; - } - - if (texture_x == 0 && texture_y == 0 && pixels_width == texture_.width && - pixels_height == texture_.height) { - const size_t size_in_bytes = sizeof(half4) * texture_.width * texture_.height; - memcpy(mapped_rgba_pixels, rgba_pixels, size_in_bytes); - } - else { - const half4 *rgba_row = rgba_pixels; - half4 *mapped_rgba_row = mapped_rgba_pixels + texture_y * texture_.width + texture_x; - for (int y = 0; y < pixels_height; - ++y, rgba_row += pixels_width, mapped_rgba_row += texture_.width) { - memcpy(mapped_rgba_row, rgba_row, sizeof(half4) * pixels_width); - } - } - - unmap_texture_buffer(); -} - -/* -------------------------------------------------------------------- * Texture buffer mapping. */ -half4 *BlenderGPUDisplay::do_map_texture_buffer() +half4 *BlenderDisplayDriver::map_texture_buffer() { glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture_.gl_pbo_id); half4 *mapped_rgba_pixels = reinterpret_cast<half4 *>( glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY)); if (!mapped_rgba_pixels) { - LOG(ERROR) << "Error mapping BlenderGPUDisplay pixel buffer object."; + LOG(ERROR) << "Error mapping BlenderDisplayDriver pixel buffer object."; } if (texture_.need_clear) { @@ -431,7 +394,7 @@ half4 *BlenderGPUDisplay::do_map_texture_buffer() return mapped_rgba_pixels; } -void BlenderGPUDisplay::do_unmap_texture_buffer() +void BlenderDisplayDriver::unmap_texture_buffer() { glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); @@ -442,9 +405,9 @@ void BlenderGPUDisplay::do_unmap_texture_buffer() * Graphics interoperability. */ -DeviceGraphicsInteropDestination BlenderGPUDisplay::do_graphics_interop_get() +BlenderDisplayDriver::GraphicsInterop BlenderDisplayDriver::graphics_interop_get() { - DeviceGraphicsInteropDestination interop_dst; + GraphicsInterop interop_dst; interop_dst.buffer_width = texture_.buffer_width; interop_dst.buffer_height = texture_.buffer_height; @@ -456,12 +419,12 @@ DeviceGraphicsInteropDestination BlenderGPUDisplay::do_graphics_interop_get() return interop_dst; } -void BlenderGPUDisplay::graphics_interop_activate() +void BlenderDisplayDriver::graphics_interop_activate() { gl_context_enable(); } -void BlenderGPUDisplay::graphics_interop_deactivate() +void BlenderDisplayDriver::graphics_interop_deactivate() { gl_context_disable(); } @@ -470,27 +433,21 @@ void BlenderGPUDisplay::graphics_interop_deactivate() * Drawing. */ -void BlenderGPUDisplay::clear() +void BlenderDisplayDriver::clear() { texture_.need_clear = true; } -void BlenderGPUDisplay::set_zoom(float zoom_x, float zoom_y) +void BlenderDisplayDriver::set_zoom(float zoom_x, float zoom_y) { zoom_ = make_float2(zoom_x, zoom_y); } -void BlenderGPUDisplay::do_draw(const GPUDisplayParams ¶ms) +void BlenderDisplayDriver::draw(const Params ¶ms) { /* See do_update_begin() for why no locking is required here. */ const bool transparent = true; // TODO(sergey): Derive this from Film. - if (texture_.need_clear) { - /* Texture is requested to be cleared and was not yet cleared. - * Do early return which should be equivalent of drawing all-zero texture. */ - return; - } - if (!gl_draw_resources_ensure()) { return; } @@ -499,6 +456,16 @@ void BlenderGPUDisplay::do_draw(const GPUDisplayParams ¶ms) gl_context_mutex_.lock(); } + if (texture_.need_clear) { + /* Texture is requested to be cleared and was not yet cleared. + * + * Do early return which should be equivalent of drawing all-zero texture. + * Watch out for the lock though so that the clear happening during update is properly + * synchronized here. */ + gl_context_mutex_.unlock(); + return; + } + if (gl_upload_sync_) { glWaitSync((GLsync)gl_upload_sync_, 0, GL_TIMEOUT_IGNORED); } @@ -524,7 +491,7 @@ void BlenderGPUDisplay::do_draw(const GPUDisplayParams ¶ms) const float zoomed_width = params.size.x * zoom_.x; const float zoomed_height = params.size.y * zoom_.y; if (texture_.width != params.size.x || texture_.height != params.size.y) { - /* Resolution divider is different from 1, force enarest interpolation. */ + /* Resolution divider is different from 1, force nearest interpolation. */ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); } else if (zoomed_width - params.size.x > 0.5f || zoomed_height - params.size.y > 0.5f) { @@ -580,7 +547,7 @@ void BlenderGPUDisplay::do_draw(const GPUDisplayParams ¶ms) } } -void BlenderGPUDisplay::gl_context_create() +void BlenderDisplayDriver::gl_context_create() { /* When rendering in viewport there is no render context available via engine. * Check whether own context is to be created here. @@ -609,7 +576,7 @@ void BlenderGPUDisplay::gl_context_create() } } -bool BlenderGPUDisplay::gl_context_enable() +bool BlenderDisplayDriver::gl_context_enable() { if (use_gl_context_) { if (!gl_context_) { @@ -624,7 +591,7 @@ bool BlenderGPUDisplay::gl_context_enable() return true; } -void BlenderGPUDisplay::gl_context_disable() +void BlenderDisplayDriver::gl_context_disable() { if (use_gl_context_) { if (gl_context_) { @@ -637,7 +604,7 @@ void BlenderGPUDisplay::gl_context_disable() RE_engine_render_context_disable(reinterpret_cast<RenderEngine *>(b_engine_.ptr.data)); } -void BlenderGPUDisplay::gl_context_dispose() +void BlenderDisplayDriver::gl_context_dispose() { if (gl_context_) { const bool drw_state = DRW_opengl_context_release(); @@ -649,7 +616,7 @@ void BlenderGPUDisplay::gl_context_dispose() } } -bool BlenderGPUDisplay::gl_draw_resources_ensure() +bool BlenderDisplayDriver::gl_draw_resources_ensure() { if (!texture_.gl_id) { /* If there is no texture allocated, there is nothing to draw. Inform the draw call that it can @@ -676,7 +643,7 @@ bool BlenderGPUDisplay::gl_draw_resources_ensure() return true; } -void BlenderGPUDisplay::gl_resources_destroy() +void BlenderDisplayDriver::gl_resources_destroy() { gl_context_enable(); @@ -699,7 +666,7 @@ void BlenderGPUDisplay::gl_resources_destroy() gl_context_dispose(); } -bool BlenderGPUDisplay::gl_texture_resources_ensure() +bool BlenderDisplayDriver::gl_texture_resources_ensure() { if (texture_.creation_attempted) { return texture_.is_created; @@ -736,7 +703,7 @@ bool BlenderGPUDisplay::gl_texture_resources_ensure() return true; } -void BlenderGPUDisplay::texture_update_if_needed() +void BlenderDisplayDriver::texture_update_if_needed() { if (!texture_.need_update) { return; @@ -750,7 +717,7 @@ void BlenderGPUDisplay::texture_update_if_needed() texture_.need_update = false; } -void BlenderGPUDisplay::vertex_buffer_update(const GPUDisplayParams ¶ms) +void BlenderDisplayDriver::vertex_buffer_update(const Params ¶ms) { /* Invalidate old contents - avoids stalling if the buffer is still waiting in queue to be * rendered. */ @@ -763,23 +730,23 @@ void BlenderGPUDisplay::vertex_buffer_update(const GPUDisplayParams ¶ms) vpointer[0] = 0.0f; vpointer[1] = 0.0f; - vpointer[2] = params.offset.x; - vpointer[3] = params.offset.y; + vpointer[2] = params.full_offset.x; + vpointer[3] = params.full_offset.y; vpointer[4] = 1.0f; vpointer[5] = 0.0f; - vpointer[6] = (float)params.size.x + params.offset.x; - vpointer[7] = params.offset.y; + vpointer[6] = (float)params.size.x + params.full_offset.x; + vpointer[7] = params.full_offset.y; vpointer[8] = 1.0f; vpointer[9] = 1.0f; - vpointer[10] = (float)params.size.x + params.offset.x; - vpointer[11] = (float)params.size.y + params.offset.y; + vpointer[10] = (float)params.size.x + params.full_offset.x; + vpointer[11] = (float)params.size.y + params.full_offset.y; vpointer[12] = 0.0f; vpointer[13] = 1.0f; - vpointer[14] = params.offset.x; - vpointer[15] = (float)params.size.y + params.offset.y; + vpointer[14] = params.full_offset.x; + vpointer[15] = (float)params.size.y + params.full_offset.y; glUnmapBuffer(GL_ARRAY_BUFFER); } diff --git a/intern/cycles/blender/blender_gpu_display.h b/intern/cycles/blender/blender_display_driver.h index 89420567037..558997c6b4f 100644 --- a/intern/cycles/blender/blender_gpu_display.h +++ b/intern/cycles/blender/blender_display_driver.h @@ -22,12 +22,14 @@ #include "RNA_blender_cpp.h" -#include "render/gpu_display.h" +#include "render/display_driver.h" + +#include "util/util_thread.h" #include "util/util_unique_ptr.h" CCL_NAMESPACE_BEGIN -/* Base class of shader used for GPU display rendering. */ +/* Base class of shader used for display driver rendering. */ class BlenderDisplayShader { public: static constexpr const char *position_attribute_name = "pos"; @@ -96,11 +98,11 @@ class BlenderDisplaySpaceShader : public BlenderDisplayShader { uint shader_program_ = 0; }; -/* GPU display implementation which is specific for Blender viewport integration. */ -class BlenderGPUDisplay : public GPUDisplay { +/* Display driver implementation which is specific for Blender viewport integration. */ +class BlenderDisplayDriver : public DisplayDriver { public: - BlenderGPUDisplay(BL::RenderEngine &b_engine, BL::Scene &b_scene); - ~BlenderGPUDisplay(); + BlenderDisplayDriver(BL::RenderEngine &b_engine, BL::Scene &b_scene); + ~BlenderDisplayDriver(); virtual void graphics_interop_activate() override; virtual void graphics_interop_deactivate() override; @@ -110,22 +112,15 @@ class BlenderGPUDisplay : public GPUDisplay { void set_zoom(float zoom_x, float zoom_y); protected: - virtual bool do_update_begin(const GPUDisplayParams ¶ms, - int texture_width, - int texture_height) override; - virtual void do_update_end() override; + virtual bool update_begin(const Params ¶ms, int texture_width, int texture_height) override; + virtual void update_end() override; - virtual void do_copy_pixels_to_texture(const half4 *rgba_pixels, - int texture_x, - int texture_y, - int pixels_width, - int pixels_height) override; - virtual void do_draw(const GPUDisplayParams ¶ms) override; + virtual half4 *map_texture_buffer() override; + virtual void unmap_texture_buffer() override; - virtual half4 *do_map_texture_buffer() override; - virtual void do_unmap_texture_buffer() override; + virtual GraphicsInterop graphics_interop_get() override; - virtual DeviceGraphicsInteropDestination do_graphics_interop_get() override; + virtual void draw(const Params ¶ms) override; /* Helper function which allocates new GPU context. */ void gl_context_create(); @@ -152,13 +147,13 @@ class BlenderGPUDisplay : public GPUDisplay { * This buffer is used to render texture in the viewport. * * NOTE: The buffer needs to be bound. */ - void vertex_buffer_update(const GPUDisplayParams ¶ms); + void vertex_buffer_update(const Params ¶ms); BL::RenderEngine b_engine_; /* OpenGL context which is used the render engine doesn't have its own. */ void *gl_context_ = nullptr; - /* The when Blender RenderEngine side context is not available and the GPUDisplay is to create + /* The when Blender RenderEngine side context is not available and the DisplayDriver is to create * its own context. */ bool use_gl_context_ = false; /* Mutex used to guard the `gl_context_`. */ diff --git a/intern/cycles/blender/blender_geometry.cpp b/intern/cycles/blender/blender_geometry.cpp index fca8cb9eda3..7b49bb7fbb7 100644 --- a/intern/cycles/blender/blender_geometry.cpp +++ b/intern/cycles/blender/blender_geometry.cpp @@ -80,8 +80,10 @@ Geometry *BlenderSync::sync_geometry(BL::Depsgraph &b_depsgraph, { /* Test if we can instance or if the object is modified. */ Geometry::Type geom_type = determine_geom_type(b_ob_info, use_particle_hair); - BL::ID b_key_id = (BKE_object_is_modified(b_ob_info.real_object)) ? b_ob_info.real_object : - b_ob_info.object_data; + BL::ID b_key_id = (b_ob_info.is_real_object_data() && + BKE_object_is_modified(b_ob_info.real_object)) ? + b_ob_info.real_object : + b_ob_info.object_data; GeometryKey key(b_key_id.ptr.data, geom_type); /* Find shader indices. */ diff --git a/intern/cycles/blender/blender_output_driver.cpp b/intern/cycles/blender/blender_output_driver.cpp new file mode 100644 index 00000000000..f380b7b3bb1 --- /dev/null +++ b/intern/cycles/blender/blender_output_driver.cpp @@ -0,0 +1,127 @@ +/* + * Copyright 2021 Blender Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "blender/blender_output_driver.h" + +CCL_NAMESPACE_BEGIN + +BlenderOutputDriver::BlenderOutputDriver(BL::RenderEngine &b_engine) : b_engine_(b_engine) +{ +} + +BlenderOutputDriver::~BlenderOutputDriver() +{ +} + +bool BlenderOutputDriver::read_render_tile(const Tile &tile) +{ + /* Get render result. */ + BL::RenderResult b_rr = b_engine_.begin_result(tile.offset.x, + tile.offset.y, + tile.size.x, + tile.size.y, + tile.layer.c_str(), + tile.view.c_str()); + + /* Can happen if the intersected rectangle gives 0 width or height. */ + if (b_rr.ptr.data == NULL) { + return false; + } + + BL::RenderResult::layers_iterator b_single_rlay; + b_rr.layers.begin(b_single_rlay); + + /* layer will be missing if it was disabled in the UI */ + if (b_single_rlay == b_rr.layers.end()) { + return false; + } + + BL::RenderLayer b_rlay = *b_single_rlay; + + vector<float> pixels(tile.size.x * tile.size.y * 4); + + /* Copy each pass. + * TODO:copy only the required ones for better performance? */ + for (BL::RenderPass &b_pass : b_rlay.passes) { + tile.set_pass_pixels(b_pass.name(), b_pass.channels(), (float *)b_pass.rect()); + } + + b_engine_.end_result(b_rr, false, false, false); + + return true; +} + +bool BlenderOutputDriver::update_render_tile(const Tile &tile) +{ + /* Use final write for preview renders, otherwise render result wouldn't be be updated + * quickly on Blender side. For all other cases we use the display driver. */ + if (b_engine_.is_preview()) { + write_render_tile(tile); + return true; + } + else { + /* Don't highlight full-frame tile. */ + if (!(tile.size == tile.full_size)) { + b_engine_.tile_highlight_clear_all(); + b_engine_.tile_highlight_set(tile.offset.x, tile.offset.y, tile.size.x, tile.size.y, true); + } + + return false; + } +} + +void BlenderOutputDriver::write_render_tile(const Tile &tile) +{ + b_engine_.tile_highlight_clear_all(); + + /* Get render result. */ + BL::RenderResult b_rr = b_engine_.begin_result(tile.offset.x, + tile.offset.y, + tile.size.x, + tile.size.y, + tile.layer.c_str(), + tile.view.c_str()); + + /* Can happen if the intersected rectangle gives 0 width or height. */ + if (b_rr.ptr.data == NULL) { + return; + } + + BL::RenderResult::layers_iterator b_single_rlay; + b_rr.layers.begin(b_single_rlay); + + /* Layer will be missing if it was disabled in the UI. */ + if (b_single_rlay == b_rr.layers.end()) { + return; + } + + BL::RenderLayer b_rlay = *b_single_rlay; + + vector<float> pixels(tile.size.x * tile.size.y * 4); + + /* Copy each pass. */ + for (BL::RenderPass &b_pass : b_rlay.passes) { + if (!tile.get_pass_pixels(b_pass.name(), b_pass.channels(), &pixels[0])) { + memset(&pixels[0], 0, pixels.size() * sizeof(float)); + } + + b_pass.rect(&pixels[0]); + } + + b_engine_.end_result(b_rr, true, false, true); +} + +CCL_NAMESPACE_END diff --git a/intern/cycles/blender/blender_output_driver.h b/intern/cycles/blender/blender_output_driver.h new file mode 100644 index 00000000000..8a1cf92d7c7 --- /dev/null +++ b/intern/cycles/blender/blender_output_driver.h @@ -0,0 +1,40 @@ +/* + * Copyright 2021 Blender Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "MEM_guardedalloc.h" + +#include "RNA_blender_cpp.h" + +#include "render/output_driver.h" + +CCL_NAMESPACE_BEGIN + +class BlenderOutputDriver : public OutputDriver { + public: + BlenderOutputDriver(BL::RenderEngine &b_engine); + ~BlenderOutputDriver(); + + virtual void write_render_tile(const Tile &tile) override; + virtual bool update_render_tile(const Tile &tile) override; + virtual bool read_render_tile(const Tile &tile) override; + + protected: + BL::RenderEngine b_engine_; +}; + +CCL_NAMESPACE_END diff --git a/intern/cycles/blender/blender_python.cpp b/intern/cycles/blender/blender_python.cpp index 694d8454422..d681517c9e1 100644 --- a/intern/cycles/blender/blender_python.cpp +++ b/intern/cycles/blender/blender_python.cpp @@ -911,14 +911,16 @@ static PyObject *enable_print_stats_func(PyObject * /*self*/, PyObject * /*args* static PyObject *get_device_types_func(PyObject * /*self*/, PyObject * /*args*/) { vector<DeviceType> device_types = Device::available_types(); - bool has_cuda = false, has_optix = false; + bool has_cuda = false, has_optix = false, has_hip = false; foreach (DeviceType device_type, device_types) { has_cuda |= (device_type == DEVICE_CUDA); has_optix |= (device_type == DEVICE_OPTIX); + has_hip |= (device_type == DEVICE_HIP); } - PyObject *list = PyTuple_New(2); + PyObject *list = PyTuple_New(3); PyTuple_SET_ITEM(list, 0, PyBool_FromLong(has_cuda)); PyTuple_SET_ITEM(list, 1, PyBool_FromLong(has_optix)); + PyTuple_SET_ITEM(list, 2, PyBool_FromLong(has_hip)); return list; } @@ -944,6 +946,9 @@ static PyObject *set_device_override_func(PyObject * /*self*/, PyObject *arg) else if (override == "OPTIX") { BlenderSession::device_override = DEVICE_MASK_OPTIX; } + else if (override == "HIP") { + BlenderSession::device_override = DEVICE_MASK_HIP; + } else { printf("\nError: %s is not a valid Cycles device.\n", override.c_str()); Py_RETURN_FALSE; diff --git a/intern/cycles/blender/blender_session.cpp b/intern/cycles/blender/blender_session.cpp index d65d89a7ddd..3be7ff32bd8 100644 --- a/intern/cycles/blender/blender_session.cpp +++ b/intern/cycles/blender/blender_session.cpp @@ -42,7 +42,8 @@ #include "util/util_progress.h" #include "util/util_time.h" -#include "blender/blender_gpu_display.h" +#include "blender/blender_display_driver.h" +#include "blender/blender_output_driver.h" #include "blender/blender_session.h" #include "blender/blender_sync.h" #include "blender/blender_util.h" @@ -71,7 +72,8 @@ BlenderSession::BlenderSession(BL::RenderEngine &b_engine, width(0), height(0), preview_osl(preview_osl), - python_thread_state(NULL) + python_thread_state(NULL), + use_developer_ui(false) { /* offline render */ background = true; @@ -156,11 +158,13 @@ void BlenderSession::create_session() b_v3d, b_rv3d, scene->camera, width, height); session->reset(session_params, buffer_params); - /* Create GPU display. */ + /* Create GPU display. + * TODO(sergey): Investigate whether DisplayDriver can be used for the preview as well. */ if (!b_engine.is_preview() && !headless) { - unique_ptr<BlenderGPUDisplay> gpu_display = make_unique<BlenderGPUDisplay>(b_engine, b_scene); - gpu_display_ = gpu_display.get(); - session->set_gpu_display(move(gpu_display)); + unique_ptr<BlenderDisplayDriver> display_driver = make_unique<BlenderDisplayDriver>(b_engine, + b_scene); + display_driver_ = display_driver.get(); + session->set_display_driver(move(display_driver)); } /* Viewport and preview (as in, material preview) does not do tiled rendering, so can inform @@ -277,94 +281,6 @@ void BlenderSession::free_session() session = nullptr; } -void BlenderSession::read_render_tile() -{ - const int2 tile_offset = session->get_render_tile_offset(); - const int2 tile_size = session->get_render_tile_size(); - - /* get render result */ - BL::RenderResult b_rr = b_engine.begin_result(tile_offset.x, - tile_offset.y, - tile_size.x, - tile_size.y, - b_rlay_name.c_str(), - b_rview_name.c_str()); - - /* can happen if the intersected rectangle gives 0 width or height */ - if (b_rr.ptr.data == NULL) { - return; - } - - BL::RenderResult::layers_iterator b_single_rlay; - b_rr.layers.begin(b_single_rlay); - - /* layer will be missing if it was disabled in the UI */ - if (b_single_rlay == b_rr.layers.end()) - return; - - BL::RenderLayer b_rlay = *b_single_rlay; - - vector<float> pixels(tile_size.x * tile_size.y * 4); - - /* Copy each pass. - * TODO:copy only the required ones for better performance? */ - for (BL::RenderPass &b_pass : b_rlay.passes) { - session->set_render_tile_pixels(b_pass.name(), b_pass.channels(), (float *)b_pass.rect()); - } -} - -void BlenderSession::write_render_tile() -{ - const int2 tile_offset = session->get_render_tile_offset(); - const int2 tile_size = session->get_render_tile_size(); - - const string_view render_layer_name = session->get_render_tile_layer(); - const string_view render_view_name = session->get_render_tile_view(); - - b_engine.tile_highlight_clear_all(); - - /* get render result */ - BL::RenderResult b_rr = b_engine.begin_result(tile_offset.x, - tile_offset.y, - tile_size.x, - tile_size.y, - render_layer_name.c_str(), - render_view_name.c_str()); - - /* can happen if the intersected rectangle gives 0 width or height */ - if (b_rr.ptr.data == NULL) { - return; - } - - BL::RenderResult::layers_iterator b_single_rlay; - b_rr.layers.begin(b_single_rlay); - - /* layer will be missing if it was disabled in the UI */ - if (b_single_rlay == b_rr.layers.end()) { - return; - } - - BL::RenderLayer b_rlay = *b_single_rlay; - - write_render_result(b_rlay); - - b_engine.end_result(b_rr, true, false, true); -} - -void BlenderSession::update_render_tile() -{ - if (!session->has_multiple_render_tiles()) { - /* Don't highlight full-frame tile. */ - return; - } - - const int2 tile_offset = session->get_render_tile_offset(); - const int2 tile_size = session->get_render_tile_size(); - - b_engine.tile_highlight_clear_all(); - b_engine.tile_highlight_set(tile_offset.x, tile_offset.y, tile_size.x, tile_size.y, true); -} - void BlenderSession::full_buffer_written(string_view filename) { full_buffer_files_.emplace_back(filename); @@ -438,18 +354,8 @@ void BlenderSession::render(BL::Depsgraph &b_depsgraph_) return; } - /* set callback to write out render results */ - session->write_render_tile_cb = [&]() { write_render_tile(); }; - - /* Use final write for preview renders, otherwise render result wouldn't be be updated on Blender - * side. */ - /* TODO(sergey): Investigate whether GPUDisplay can be used for the preview as well. */ - if (b_engine.is_preview()) { - session->update_render_tile_cb = [&]() { write_render_tile(); }; - } - else { - session->update_render_tile_cb = [&]() { update_render_tile(); }; - } + /* Create driver to write out render results. */ + session->set_output_driver(make_unique<BlenderOutputDriver>(b_engine)); session->full_buffer_written_cb = [&](string_view filename) { full_buffer_written(filename); }; @@ -557,6 +463,11 @@ void BlenderSession::render(BL::Depsgraph &b_depsgraph_) /* free result without merging */ b_engine.end_result(b_rr, true, false, false); + /* When tiled rendering is used there will be no "write" done for the tile. Forcefully clear + * highlighted tiles now, so that the highlight will be removed while processing full frame from + * file. */ + b_engine.tile_highlight_clear_all(); + double total_time, render_time; session->progress.get_time(total_time, render_time); VLOG(1) << "Total render time: " << total_time; @@ -581,12 +492,17 @@ void BlenderSession::render_frame_finish() for (string_view filename : full_buffer_files_) { session->process_full_buffer_from_disk(filename); + if (check_and_report_session_error()) { + break; + } + } + + for (string_view filename : full_buffer_files_) { path_remove(filename); } - /* clear callback */ - session->write_render_tile_cb = function_null; - session->update_render_tile_cb = function_null; + /* Clear driver. */ + session->set_output_driver(nullptr); session->full_buffer_written_cb = function_null; } @@ -692,9 +608,8 @@ void BlenderSession::bake(BL::Depsgraph &b_depsgraph_, pass->set_type(bake_type_to_pass(bake_type, bake_filter)); pass->set_include_albedo((bake_filter & BL::BakeSettings::pass_filter_COLOR)); - session->read_render_tile_cb = [&]() { read_render_tile(); }; - session->write_render_tile_cb = [&]() { write_render_tile(); }; - session->set_gpu_display(nullptr); + session->set_display_driver(nullptr); + session->set_output_driver(make_unique<BlenderOutputDriver>(b_engine)); if (!session->progress.get_cancel()) { /* Sync scene. */ @@ -737,43 +652,7 @@ void BlenderSession::bake(BL::Depsgraph &b_depsgraph_, session->wait(); } - session->read_render_tile_cb = function_null; - session->write_render_tile_cb = function_null; -} - -void BlenderSession::write_render_result(BL::RenderLayer &b_rlay) -{ - if (!session->copy_render_tile_from_device()) { - return; - } - - const int2 tile_size = session->get_render_tile_size(); - vector<float> pixels(tile_size.x * tile_size.y * 4); - - /* Copy each pass. */ - for (BL::RenderPass &b_pass : b_rlay.passes) { - if (!session->get_render_tile_pixels(b_pass.name(), b_pass.channels(), &pixels[0])) { - memset(&pixels[0], 0, pixels.size() * sizeof(float)); - } - - b_pass.rect(&pixels[0]); - } -} - -void BlenderSession::update_render_result(BL::RenderLayer &b_rlay) -{ - if (!session->copy_render_tile_from_device()) { - return; - } - - const int2 tile_size = session->get_render_tile_size(); - vector<float> pixels(tile_size.x * tile_size.y * 4); - - /* Copy combined pass. */ - BL::RenderPass b_combined_pass(b_rlay.passes.find_by_name("Combined", b_rview_name.c_str())); - if (session->get_render_tile_pixels("Combined", b_combined_pass.channels(), &pixels[0])) { - b_combined_pass.rect(&pixels[0]); - } + session->set_output_driver(nullptr); } void BlenderSession::synchronize(BL::Depsgraph &b_depsgraph_) @@ -881,7 +760,7 @@ void BlenderSession::draw(BL::SpaceImageEditor &space_image) } BL::Array<float, 2> zoom = space_image.zoom(); - gpu_display_->set_zoom(zoom[0], zoom[1]); + display_driver_->set_zoom(zoom[0], zoom[1]); session->draw(); } @@ -988,8 +867,9 @@ void BlenderSession::update_status_progress() get_status(status, substatus); get_progress(progress, total_time, render_time); - if (progress > 0) - remaining_time = (1.0 - (double)progress) * (render_time / (double)progress); + if (progress > 0) { + remaining_time = session->get_estimated_remaining_time(); + } if (background) { if (scene) @@ -1027,20 +907,27 @@ void BlenderSession::update_status_progress() last_progress = progress; } - if (session->progress.get_error()) { - string error = session->progress.get_error_message(); - if (error != last_error) { - /* TODO(sergey): Currently C++ RNA API doesn't let us to - * use mnemonic name for the variable. Would be nice to - * have this figured out. - * - * For until then, 1 << 5 means RPT_ERROR. - */ - b_engine.report(1 << 5, error.c_str()); - b_engine.error_set(error.c_str()); - last_error = error; - } + check_and_report_session_error(); +} + +bool BlenderSession::check_and_report_session_error() +{ + if (!session->progress.get_error()) { + return false; } + + const string error = session->progress.get_error_message(); + if (error != last_error) { + /* TODO(sergey): Currently C++ RNA API doesn't let us to use mnemonic name for the variable. + * Would be nice to have this figured out. + * + * For until then, 1 << 5 means RPT_ERROR. */ + b_engine.report(1 << 5, error.c_str()); + b_engine.error_set(error.c_str()); + last_error = error; + } + + return true; } void BlenderSession::tag_update() diff --git a/intern/cycles/blender/blender_session.h b/intern/cycles/blender/blender_session.h index 11e2657a325..fef6ad1adfc 100644 --- a/intern/cycles/blender/blender_session.h +++ b/intern/cycles/blender/blender_session.h @@ -29,7 +29,7 @@ CCL_NAMESPACE_BEGIN -class BlenderGPUDisplay; +class BlenderDisplayDriver; class BlenderSync; class ImageMetaData; class Scene; @@ -70,20 +70,7 @@ class BlenderSession { const int bake_width, const int bake_height); - void write_render_result(BL::RenderLayer &b_rlay); - void write_render_tile(); - - void update_render_tile(); - void full_buffer_written(string_view filename); - - /* update functions are used to update display buffer only after sample was rendered - * only needed for better visual feedback */ - void update_render_result(BL::RenderLayer &b_rlay); - - /* read functions for baking input */ - void read_render_tile(); - /* interactive updates */ void synchronize(BL::Depsgraph &b_depsgraph); @@ -110,8 +97,7 @@ class BlenderSession { BL::RenderSettings b_render; BL::Depsgraph b_depsgraph; /* NOTE: Blender's scene might become invalid after call - * free_blender_memory_if_possible(). - */ + * #free_blender_memory_if_possible(). */ BL::Scene b_scene; BL::SpaceView3D b_v3d; BL::RegionView3D b_rv3d; @@ -147,6 +133,11 @@ class BlenderSession { protected: void stamp_view_layer_metadata(Scene *scene, const string &view_layer_name); + /* Check whether session error happened. + * If so, it is reported to the render engine and true is returned. + * Otherwise false is returned. */ + bool check_and_report_session_error(); + void builtin_images_load(); /* Is used after each render layer synchronization is done with the goal @@ -160,8 +151,8 @@ class BlenderSession { int last_pass_index = -1; } draw_state_; - /* NOTE: The BlenderSession references the GPU display. */ - BlenderGPUDisplay *gpu_display_ = nullptr; + /* NOTE: The BlenderSession references the display driver. */ + BlenderDisplayDriver *display_driver_ = nullptr; vector<string> full_buffer_files_; }; diff --git a/intern/cycles/blender/blender_shader.cpp b/intern/cycles/blender/blender_shader.cpp index 8c4f789ffd0..0b8aea15d6c 100644 --- a/intern/cycles/blender/blender_shader.cpp +++ b/intern/cycles/blender/blender_shader.cpp @@ -279,7 +279,7 @@ static ShaderNode *add_node(Scene *scene, array<float3> curve_mapping_curves; float min_x, max_x; curvemapping_color_to_array(mapping, curve_mapping_curves, RAMP_TABLE_SIZE, true); - curvemapping_minmax(mapping, true, &min_x, &max_x); + curvemapping_minmax(mapping, 4, &min_x, &max_x); curves->set_min_x(min_x); curves->set_max_x(max_x); curves->set_curves(curve_mapping_curves); @@ -292,12 +292,25 @@ static ShaderNode *add_node(Scene *scene, array<float3> curve_mapping_curves; float min_x, max_x; curvemapping_color_to_array(mapping, curve_mapping_curves, RAMP_TABLE_SIZE, false); - curvemapping_minmax(mapping, false, &min_x, &max_x); + curvemapping_minmax(mapping, 3, &min_x, &max_x); curves->set_min_x(min_x); curves->set_max_x(max_x); curves->set_curves(curve_mapping_curves); node = curves; } + else if (b_node.is_a(&RNA_ShaderNodeFloatCurve)) { + BL::ShaderNodeFloatCurve b_curve_node(b_node); + BL::CurveMapping mapping(b_curve_node.mapping()); + FloatCurveNode *curve = graph->create_node<FloatCurveNode>(); + array<float> curve_mapping_curve; + float min_x, max_x; + curvemapping_float_to_array(mapping, curve_mapping_curve, RAMP_TABLE_SIZE); + curvemapping_minmax(mapping, 1, &min_x, &max_x); + curve->set_min_x(min_x); + curve->set_max_x(max_x); + curve->set_curve(curve_mapping_curve); + node = curve; + } else if (b_node.is_a(&RNA_ShaderNodeValToRGB)) { RGBRampNode *ramp = graph->create_node<RGBRampNode>(); BL::ShaderNodeValToRGB b_ramp_node(b_node); diff --git a/intern/cycles/blender/blender_util.h b/intern/cycles/blender/blender_util.h index 04008d77d89..77b2bd5ac4f 100644 --- a/intern/cycles/blender/blender_util.h +++ b/intern/cycles/blender/blender_util.h @@ -90,26 +90,27 @@ static inline BL::Mesh object_to_mesh(BL::BlendData & /*data*/, } #endif - BL::Mesh mesh(PointerRNA_NULL); - if (b_ob_info.object_data.is_a(&RNA_Mesh)) { - /* TODO: calc_undeformed is not used. */ - mesh = BL::Mesh(b_ob_info.object_data); - - /* Make a copy to split faces if we use autosmooth, otherwise not needed. - * Also in edit mode do we need to make a copy, to ensure data layers like - * UV are not empty. */ - if (mesh.is_editmode() || - (mesh.use_auto_smooth() && subdivision_type == Mesh::SUBDIVISION_NONE)) { + BL::Mesh mesh = (b_ob_info.object_data.is_a(&RNA_Mesh)) ? BL::Mesh(b_ob_info.object_data) : + BL::Mesh(PointerRNA_NULL); + + if (b_ob_info.is_real_object_data()) { + if (mesh) { + /* Make a copy to split faces if we use autosmooth, otherwise not needed. + * Also in edit mode do we need to make a copy, to ensure data layers like + * UV are not empty. */ + if (mesh.is_editmode() || + (mesh.use_auto_smooth() && subdivision_type == Mesh::SUBDIVISION_NONE)) { + BL::Depsgraph depsgraph(PointerRNA_NULL); + mesh = b_ob_info.real_object.to_mesh(false, depsgraph); + } + } + else { BL::Depsgraph depsgraph(PointerRNA_NULL); - assert(b_ob_info.is_real_object_data()); mesh = b_ob_info.real_object.to_mesh(false, depsgraph); } } else { - BL::Depsgraph depsgraph(PointerRNA_NULL); - if (b_ob_info.is_real_object_data()) { - mesh = b_ob_info.real_object.to_mesh(false, depsgraph); - } + /* TODO: what to do about non-mesh geometry instances? */ } #if 0 @@ -170,12 +171,11 @@ static inline void curvemap_minmax_curve(/*const*/ BL::CurveMap &curve, float *m } static inline void curvemapping_minmax(/*const*/ BL::CurveMapping &cumap, - bool rgb_curve, + int num_curves, float *min_x, float *max_x) { // const int num_curves = cumap.curves.length(); /* Gives linking error so far. */ - const int num_curves = rgb_curve ? 4 : 3; *min_x = FLT_MAX; *max_x = -FLT_MAX; for (int i = 0; i < num_curves; ++i) { @@ -195,6 +195,28 @@ static inline void curvemapping_to_array(BL::CurveMapping &cumap, array<float> & } } +static inline void curvemapping_float_to_array(BL::CurveMapping &cumap, + array<float> &data, + int size) +{ + float min = 0.0f, max = 1.0f; + + curvemapping_minmax(cumap, 1, &min, &max); + + const float range = max - min; + + cumap.update(); + + BL::CurveMap map = cumap.curves[0]; + + data.resize(size); + + for (int i = 0; i < size; i++) { + float t = min + (float)i / (float)(size - 1) * range; + data[i] = cumap.evaluate(map, t); + } +} + static inline void curvemapping_color_to_array(BL::CurveMapping &cumap, array<float3> &data, int size, @@ -213,7 +235,8 @@ static inline void curvemapping_color_to_array(BL::CurveMapping &cumap, * * There might be some better estimations here tho. */ - curvemapping_minmax(cumap, rgb_curve, &min_x, &max_x); + const int num_curves = rgb_curve ? 4 : 3; + curvemapping_minmax(cumap, num_curves, &min_x, &max_x); const float range_x = max_x - min_x; diff --git a/intern/cycles/bvh/bvh_embree.cpp b/intern/cycles/bvh/bvh_embree.cpp index 96852510b63..20430cb164c 100644 --- a/intern/cycles/bvh/bvh_embree.cpp +++ b/intern/cycles/bvh/bvh_embree.cpp @@ -213,7 +213,7 @@ static void rtc_filter_occluded_func(const RTCFilterFunctionNArguments *args) if (ctx->num_hits < ctx->max_hits) { Intersection current_isect; kernel_embree_convert_hit(kg, ray, hit, ¤t_isect); - for (size_t i = 0; i < ctx->max_hits; ++i) { + for (size_t i = 0; i < ctx->num_hits; ++i) { if (current_isect.object == ctx->isect_s[i].object && current_isect.prim == ctx->isect_s[i].prim && current_isect.t == ctx->isect_s[i].t) { /* This intersection was already recorded, skip it. */ diff --git a/intern/cycles/cmake/external_libs.cmake b/intern/cycles/cmake/external_libs.cmake index da259171844..b966edd4298 100644 --- a/intern/cycles/cmake/external_libs.cmake +++ b/intern/cycles/cmake/external_libs.cmake @@ -532,4 +532,13 @@ if(WITH_CYCLES_CUDA_BINARIES OR NOT WITH_CUDA_DYNLOAD) endif() endif() + +########################################################################### +# HIP +########################################################################### + +if(NOT WITH_HIP_DYNLOAD) + set(WITH_HIP_DYNLOAD ON) +endif() + unset(_cycles_lib_dir) diff --git a/intern/cycles/cmake/macros.cmake b/intern/cycles/cmake/macros.cmake index 47196dfd1ce..a470fb9c574 100644 --- a/intern/cycles/cmake/macros.cmake +++ b/intern/cycles/cmake/macros.cmake @@ -156,10 +156,16 @@ macro(cycles_target_link_libraries target) ${PLATFORM_LINKLIBS} ) - if(WITH_CUDA_DYNLOAD) - target_link_libraries(${target} extern_cuew) - else() - target_link_libraries(${target} ${CUDA_CUDA_LIBRARY}) + if(WITH_CYCLES_DEVICE_CUDA OR WITH_CYCLES_DEVICE_OPTIX) + if(WITH_CUDA_DYNLOAD) + target_link_libraries(${target} extern_cuew) + else() + target_link_libraries(${target} ${CUDA_CUDA_LIBRARY}) + endif() + endif() + + if(WITH_CYCLES_DEVICE_HIP AND WITH_HIP_DYNLOAD) + target_link_libraries(${target} extern_hipew) endif() if(CYCLES_STANDALONE_REPOSITORY) diff --git a/intern/cycles/device/CMakeLists.txt b/intern/cycles/device/CMakeLists.txt index d18f4360aef..6d33a6f107f 100644 --- a/intern/cycles/device/CMakeLists.txt +++ b/intern/cycles/device/CMakeLists.txt @@ -22,16 +22,25 @@ set(INC_SYS ../../../extern/clew/include ) -if(WITH_CUDA_DYNLOAD) +if(WITH_CYCLES_DEVICE_OPTIX OR WITH_CYCLES_DEVICE_CUDA) + if(WITH_CUDA_DYNLOAD) + list(APPEND INC + ../../../extern/cuew/include + ) + add_definitions(-DWITH_CUDA_DYNLOAD) + else() + list(APPEND INC_SYS + ${CUDA_TOOLKIT_INCLUDE} + ) + add_definitions(-DCYCLES_CUDA_NVCC_EXECUTABLE="${CUDA_NVCC_EXECUTABLE}") + endif() +endif() + +if(WITH_CYCLES_DEVICE_HIP AND WITH_HIP_DYNLOAD) list(APPEND INC - ../../../extern/cuew/include - ) - add_definitions(-DWITH_CUDA_DYNLOAD) -else() - list(APPEND INC_SYS - ${CUDA_TOOLKIT_INCLUDE} + ../../../extern/hipew/include ) - add_definitions(-DCYCLES_CUDA_NVCC_EXECUTABLE="${CUDA_NVCC_EXECUTABLE}") + add_definitions(-DWITH_HIP_DYNLOAD) endif() set(SRC @@ -70,6 +79,21 @@ set(SRC_CUDA cuda/util.h ) +set(SRC_HIP + hip/device.cpp + hip/device.h + hip/device_impl.cpp + hip/device_impl.h + hip/graphics_interop.cpp + hip/graphics_interop.h + hip/kernel.cpp + hip/kernel.h + hip/queue.cpp + hip/queue.h + hip/util.cpp + hip/util.h +) + set(SRC_DUMMY dummy/device.cpp dummy/device.h @@ -105,13 +129,21 @@ set(LIB ${CYCLES_GL_LIBRARIES} ) -if(WITH_CUDA_DYNLOAD) - list(APPEND LIB - extern_cuew - ) -else() +if(WITH_CYCLES_DEVICE_OPTIX OR WITH_CYCLES_DEVICE_CUDA) + if(WITH_CUDA_DYNLOAD) + list(APPEND LIB + extern_cuew + ) + else() + list(APPEND LIB + ${CUDA_CUDA_LIBRARY} + ) + endif() +endif() + +if(WITH_CYCLES_DEVICE_HIP AND WITH_HIP_DYNLOAD) list(APPEND LIB - ${CUDA_CUDA_LIBRARY} + extern_hipew ) endif() @@ -120,6 +152,9 @@ add_definitions(${GL_DEFINITIONS}) if(WITH_CYCLES_DEVICE_CUDA) add_definitions(-DWITH_CUDA) endif() +if(WITH_CYCLES_DEVICE_HIP) + add_definitions(-DWITH_HIP) +endif() if(WITH_CYCLES_DEVICE_OPTIX) add_definitions(-DWITH_OPTIX) endif() @@ -140,6 +175,7 @@ cycles_add_library(cycles_device "${LIB}" ${SRC} ${SRC_CPU} ${SRC_CUDA} + ${SRC_HIP} ${SRC_DUMMY} ${SRC_MULTI} ${SRC_OPTIX} diff --git a/intern/cycles/device/cpu/device_impl.cpp b/intern/cycles/device/cpu/device_impl.cpp index 3b0db6bdd0e..d02c18daee9 100644 --- a/intern/cycles/device/cpu/device_impl.cpp +++ b/intern/cycles/device/cpu/device_impl.cpp @@ -54,7 +54,6 @@ #include "util/util_function.h" #include "util/util_logging.h" #include "util/util_map.h" -#include "util/util_opengl.h" #include "util/util_openimagedenoise.h" #include "util/util_optimization.h" #include "util/util_progress.h" @@ -170,7 +169,7 @@ void CPUDevice::mem_copy_to(device_memory &mem) } void CPUDevice::mem_copy_from( - device_memory & /*mem*/, int /*y*/, int /*w*/, int /*h*/, int /*elem*/) + device_memory & /*mem*/, size_t /*y*/, size_t /*w*/, size_t /*h*/, size_t /*elem*/) { /* no-op */ } @@ -204,7 +203,7 @@ void CPUDevice::mem_free(device_memory &mem) } } -device_ptr CPUDevice::mem_alloc_sub_ptr(device_memory &mem, int offset, int /*size*/) +device_ptr CPUDevice::mem_alloc_sub_ptr(device_memory &mem, size_t offset, size_t /*size*/) { return (device_ptr)(((char *)mem.device_pointer) + mem.memory_elements_size(offset)); } @@ -298,154 +297,6 @@ void CPUDevice::build_bvh(BVH *bvh, Progress &progress, bool refit) Device::build_bvh(bvh, progress, refit); } -#if 0 -void CPUDevice::render(DeviceTask &task, RenderTile &tile, KernelGlobals *kg) -{ - const bool use_coverage = kernel_data.film.cryptomatte_passes & CRYPT_ACCURATE; - - scoped_timer timer(&tile.buffers->render_time); - - Coverage coverage(kg, tile); - if (use_coverage) { - coverage.init_path_trace(); - } - - float *render_buffer = (float *)tile.buffer; - int start_sample = tile.start_sample; - int end_sample = tile.start_sample + tile.num_samples; - - /* Needed for Embree. */ - SIMD_SET_FLUSH_TO_ZERO; - - for (int sample = start_sample; sample < end_sample; sample++) { - if (task.get_cancel() || TaskPool::canceled()) { - if (task.need_finish_queue == false) - break; - } - - if (tile.stealing_state == RenderTile::CAN_BE_STOLEN && task.get_tile_stolen()) { - tile.stealing_state = RenderTile::WAS_STOLEN; - break; - } - - if (tile.task == RenderTile::PATH_TRACE) { - for (int y = tile.y; y < tile.y + tile.h; y++) { - for (int x = tile.x; x < tile.x + tile.w; x++) { - if (use_coverage) { - coverage.init_pixel(x, y); - } - kernels.path_trace(kg, render_buffer, sample, x, y, tile.offset, tile.stride); - } - } - } - else { - for (int y = tile.y; y < tile.y + tile.h; y++) { - for (int x = tile.x; x < tile.x + tile.w; x++) { - kernels.bake(kg, render_buffer, sample, x, y, tile.offset, tile.stride); - } - } - } - tile.sample = sample + 1; - - if (task.adaptive_sampling.use && task.adaptive_sampling.need_filter(sample)) { - const bool stop = adaptive_sampling_filter(kg, tile, sample); - if (stop) { - const int num_progress_samples = end_sample - sample; - tile.sample = end_sample; - task.update_progress(&tile, tile.w * tile.h * num_progress_samples); - break; - } - } - - task.update_progress(&tile, tile.w * tile.h); - } - if (use_coverage) { - coverage.finalize(); - } - - if (task.adaptive_sampling.use && (tile.stealing_state != RenderTile::WAS_STOLEN)) { - adaptive_sampling_post(tile, kg); - } -} - -void CPUDevice::thread_render(DeviceTask &task) -{ - if (TaskPool::canceled()) { - if (task.need_finish_queue == false) - return; - } - - /* allocate buffer for kernel globals */ - CPUKernelThreadGlobals kg(kernel_globals, get_cpu_osl_memory()); - - profiler.add_state(&kg.profiler); - - /* NLM denoiser. */ - DenoisingTask *denoising = NULL; - - /* OpenImageDenoise: we can only denoise with one thread at a time, so to - * avoid waiting with mutex locks in the denoiser, we let only a single - * thread acquire denoising tiles. */ - uint tile_types = task.tile_types; - bool hold_denoise_lock = false; - if ((tile_types & RenderTile::DENOISE) && task.denoising.type == DENOISER_OPENIMAGEDENOISE) { - if (!oidn_task_lock.try_lock()) { - tile_types &= ~RenderTile::DENOISE; - hold_denoise_lock = true; - } - } - - RenderTile tile; - while (task.acquire_tile(this, tile, tile_types)) { - if (tile.task == RenderTile::PATH_TRACE) { - render(task, tile, &kg); - } - else if (tile.task == RenderTile::BAKE) { - render(task, tile, &kg); - } - else if (tile.task == RenderTile::DENOISE) { - denoise_openimagedenoise(task, tile); - task.update_progress(&tile, tile.w * tile.h); - } - - task.release_tile(tile); - - if (TaskPool::canceled()) { - if (task.need_finish_queue == false) - break; - } - } - - if (hold_denoise_lock) { - oidn_task_lock.unlock(); - } - - profiler.remove_state(&kg.profiler); - - delete denoising; -} - -void CPUDevice::thread_denoise(DeviceTask &task) -{ - RenderTile tile; - tile.x = task.x; - tile.y = task.y; - tile.w = task.w; - tile.h = task.h; - tile.buffer = task.buffer; - tile.sample = task.sample + task.num_samples; - tile.num_samples = task.num_samples; - tile.start_sample = task.sample; - tile.offset = task.offset; - tile.stride = task.stride; - tile.buffers = task.buffers; - - denoise_openimagedenoise(task, tile); - - task.update_progress(&tile, tile.w * tile.h); -} -#endif - const CPUKernels *CPUDevice::get_cpu_kernels() const { return &kernels; diff --git a/intern/cycles/device/cpu/device_impl.h b/intern/cycles/device/cpu/device_impl.h index 7d222808652..371d2258104 100644 --- a/intern/cycles/device/cpu/device_impl.h +++ b/intern/cycles/device/cpu/device_impl.h @@ -72,10 +72,13 @@ class CPUDevice : public Device { virtual void mem_alloc(device_memory &mem) override; virtual void mem_copy_to(device_memory &mem) override; - virtual void mem_copy_from(device_memory &mem, int y, int w, int h, int elem) override; + virtual void mem_copy_from( + device_memory &mem, size_t y, size_t w, size_t h, size_t elem) override; virtual void mem_zero(device_memory &mem) override; virtual void mem_free(device_memory &mem) override; - virtual device_ptr mem_alloc_sub_ptr(device_memory &mem, int offset, int /*size*/) override; + virtual device_ptr mem_alloc_sub_ptr(device_memory &mem, + size_t offset, + size_t /*size*/) override; virtual void const_copy_to(const char *name, void *host, size_t size) override; diff --git a/intern/cycles/device/cuda/device_impl.cpp b/intern/cycles/device/cuda/device_impl.cpp index 37fab8f8293..5e1a63c04df 100644 --- a/intern/cycles/device/cuda/device_impl.cpp +++ b/intern/cycles/device/cuda/device_impl.cpp @@ -31,7 +31,6 @@ # include "util/util_logging.h" # include "util/util_map.h" # include "util/util_md5.h" -# include "util/util_opengl.h" # include "util/util_path.h" # include "util/util_string.h" # include "util/util_system.h" @@ -837,7 +836,7 @@ void CUDADevice::mem_copy_to(device_memory &mem) } } -void CUDADevice::mem_copy_from(device_memory &mem, int y, int w, int h, int elem) +void CUDADevice::mem_copy_from(device_memory &mem, size_t y, size_t w, size_t h, size_t elem) { if (mem.type == MEM_TEXTURE || mem.type == MEM_GLOBAL) { assert(!"mem_copy_from not supported for textures."); @@ -891,7 +890,7 @@ void CUDADevice::mem_free(device_memory &mem) } } -device_ptr CUDADevice::mem_alloc_sub_ptr(device_memory &mem, int offset, int /*size*/) +device_ptr CUDADevice::mem_alloc_sub_ptr(device_memory &mem, size_t offset, size_t /*size*/) { return (device_ptr)(((char *)mem.device_pointer) + mem.memory_elements_size(offset)); } @@ -1169,141 +1168,6 @@ void CUDADevice::tex_free(device_texture &mem) } } -# if 0 -void CUDADevice::render(DeviceTask &task, - RenderTile &rtile, - device_vector<KernelWorkTile> &work_tiles) -{ - scoped_timer timer(&rtile.buffers->render_time); - - if (have_error()) - return; - - CUDAContextScope scope(this); - CUfunction cuRender; - - /* Get kernel function. */ - if (rtile.task == RenderTile::BAKE) { - cuda_assert(cuModuleGetFunction(&cuRender, cuModule, "kernel_cuda_bake")); - } - else { - cuda_assert(cuModuleGetFunction(&cuRender, cuModule, "kernel_cuda_path_trace")); - } - - if (have_error()) { - return; - } - - cuda_assert(cuFuncSetCacheConfig(cuRender, CU_FUNC_CACHE_PREFER_L1)); - - /* Allocate work tile. */ - work_tiles.alloc(1); - - KernelWorkTile *wtile = work_tiles.data(); - wtile->x = rtile.x; - wtile->y = rtile.y; - wtile->w = rtile.w; - wtile->h = rtile.h; - wtile->offset = rtile.offset; - wtile->stride = rtile.stride; - wtile->buffer = (float *)(CUdeviceptr)rtile.buffer; - - /* Prepare work size. More step samples render faster, but for now we - * remain conservative for GPUs connected to a display to avoid driver - * timeouts and display freezing. */ - int min_blocks, num_threads_per_block; - cuda_assert( - cuOccupancyMaxPotentialBlockSize(&min_blocks, &num_threads_per_block, cuRender, NULL, 0, 0)); - if (!info.display_device) { - min_blocks *= 8; - } - - uint step_samples = divide_up(min_blocks * num_threads_per_block, wtile->w * wtile->h); - - /* Render all samples. */ - uint start_sample = rtile.start_sample; - uint end_sample = rtile.start_sample + rtile.num_samples; - - for (int sample = start_sample; sample < end_sample;) { - /* Setup and copy work tile to device. */ - wtile->start_sample = sample; - wtile->num_samples = step_samples; - if (task.adaptive_sampling.use) { - wtile->num_samples = task.adaptive_sampling.align_samples(sample, step_samples); - } - wtile->num_samples = min(wtile->num_samples, end_sample - sample); - work_tiles.copy_to_device(); - - CUdeviceptr d_work_tiles = (CUdeviceptr)work_tiles.device_pointer; - uint total_work_size = wtile->w * wtile->h * wtile->num_samples; - uint num_blocks = divide_up(total_work_size, num_threads_per_block); - - /* Launch kernel. */ - void *args[] = {&d_work_tiles, &total_work_size}; - - cuda_assert( - cuLaunchKernel(cuRender, num_blocks, 1, 1, num_threads_per_block, 1, 1, 0, 0, args, 0)); - - /* Run the adaptive sampling kernels at selected samples aligned to step samples. */ - uint filter_sample = sample + wtile->num_samples - 1; - if (task.adaptive_sampling.use && task.adaptive_sampling.need_filter(filter_sample)) { - adaptive_sampling_filter(filter_sample, wtile, d_work_tiles); - } - - cuda_assert(cuCtxSynchronize()); - - /* Update progress. */ - sample += wtile->num_samples; - rtile.sample = sample; - task.update_progress(&rtile, rtile.w * rtile.h * wtile->num_samples); - - if (task.get_cancel()) { - if (task.need_finish_queue == false) - break; - } - } - - /* Finalize adaptive sampling. */ - if (task.adaptive_sampling.use) { - CUdeviceptr d_work_tiles = (CUdeviceptr)work_tiles.device_pointer; - adaptive_sampling_post(rtile, wtile, d_work_tiles); - cuda_assert(cuCtxSynchronize()); - task.update_progress(&rtile, rtile.w * rtile.h * wtile->num_samples); - } -} - -void CUDADevice::thread_run(DeviceTask &task) -{ - CUDAContextScope scope(this); - - if (task.type == DeviceTask::RENDER) { - device_vector<KernelWorkTile> work_tiles(this, "work_tiles", MEM_READ_ONLY); - - /* keep rendering tiles until done */ - RenderTile tile; - DenoisingTask denoising(this, task); - - while (task.acquire_tile(this, tile, task.tile_types)) { - if (tile.task == RenderTile::PATH_TRACE) { - render(task, tile, work_tiles); - } - else if (tile.task == RenderTile::BAKE) { - render(task, tile, work_tiles); - } - - task.release_tile(tile); - - if (task.get_cancel()) { - if (task.need_finish_queue == false) - break; - } - } - - work_tiles.free(); - } -} -# endif - unique_ptr<DeviceQueue> CUDADevice::gpu_queue_create() { return make_unique<CUDADeviceQueue>(this); diff --git a/intern/cycles/device/cuda/device_impl.h b/intern/cycles/device/cuda/device_impl.h index 6b27db54ab4..c0316d18ba0 100644 --- a/intern/cycles/device/cuda/device_impl.h +++ b/intern/cycles/device/cuda/device_impl.h @@ -26,7 +26,6 @@ # ifdef WITH_CUDA_DYNLOAD # include "cuew.h" # else -# include "util/util_opengl.h" # include <cuda.h> # include <cudaGL.h> # endif @@ -120,13 +119,13 @@ class CUDADevice : public Device { void mem_copy_to(device_memory &mem) override; - void mem_copy_from(device_memory &mem, int y, int w, int h, int elem) override; + void mem_copy_from(device_memory &mem, size_t y, size_t w, size_t h, size_t elem) override; void mem_zero(device_memory &mem) override; void mem_free(device_memory &mem) override; - device_ptr mem_alloc_sub_ptr(device_memory &mem, int offset, int /*size*/) override; + device_ptr mem_alloc_sub_ptr(device_memory &mem, size_t offset, size_t /*size*/) override; virtual void const_copy_to(const char *name, void *host, size_t size) override; diff --git a/intern/cycles/device/cuda/graphics_interop.cpp b/intern/cycles/device/cuda/graphics_interop.cpp index e8ca8b90eae..30efefd9b6b 100644 --- a/intern/cycles/device/cuda/graphics_interop.cpp +++ b/intern/cycles/device/cuda/graphics_interop.cpp @@ -37,14 +37,15 @@ CUDADeviceGraphicsInterop::~CUDADeviceGraphicsInterop() } } -void CUDADeviceGraphicsInterop::set_destination( - const DeviceGraphicsInteropDestination &destination) +void CUDADeviceGraphicsInterop::set_display_interop( + const DisplayDriver::GraphicsInterop &display_interop) { - const int64_t new_buffer_area = int64_t(destination.buffer_width) * destination.buffer_height; + const int64_t new_buffer_area = int64_t(display_interop.buffer_width) * + display_interop.buffer_height; - need_clear_ = destination.need_clear; + need_clear_ = display_interop.need_clear; - if (opengl_pbo_id_ == destination.opengl_pbo_id && buffer_area_ == new_buffer_area) { + if (opengl_pbo_id_ == display_interop.opengl_pbo_id && buffer_area_ == new_buffer_area) { return; } @@ -55,12 +56,12 @@ void CUDADeviceGraphicsInterop::set_destination( } const CUresult result = cuGraphicsGLRegisterBuffer( - &cu_graphics_resource_, destination.opengl_pbo_id, CU_GRAPHICS_MAP_RESOURCE_FLAGS_NONE); + &cu_graphics_resource_, display_interop.opengl_pbo_id, CU_GRAPHICS_MAP_RESOURCE_FLAGS_NONE); if (result != CUDA_SUCCESS) { LOG(ERROR) << "Error registering OpenGL buffer: " << cuewErrorString(result); } - opengl_pbo_id_ = destination.opengl_pbo_id; + opengl_pbo_id_ = display_interop.opengl_pbo_id; buffer_area_ = new_buffer_area; } diff --git a/intern/cycles/device/cuda/graphics_interop.h b/intern/cycles/device/cuda/graphics_interop.h index 8a70c8aa71d..ec480f20c86 100644 --- a/intern/cycles/device/cuda/graphics_interop.h +++ b/intern/cycles/device/cuda/graphics_interop.h @@ -41,7 +41,7 @@ class CUDADeviceGraphicsInterop : public DeviceGraphicsInterop { CUDADeviceGraphicsInterop &operator=(const CUDADeviceGraphicsInterop &other) = delete; CUDADeviceGraphicsInterop &operator=(CUDADeviceGraphicsInterop &&other) = delete; - virtual void set_destination(const DeviceGraphicsInteropDestination &destination) override; + virtual void set_display_interop(const DisplayDriver::GraphicsInterop &display_interop) override; virtual device_ptr map() override; virtual void unmap() override; diff --git a/intern/cycles/device/cuda/queue.cpp b/intern/cycles/device/cuda/queue.cpp index b7f86c10553..1149a835b14 100644 --- a/intern/cycles/device/cuda/queue.cpp +++ b/intern/cycles/device/cuda/queue.cpp @@ -116,18 +116,18 @@ bool CUDADeviceQueue::enqueue(DeviceKernel kernel, const int work_size, void *ar } /* Launch kernel. */ - cuda_device_assert(cuda_device_, - cuLaunchKernel(cuda_kernel.function, - num_blocks, - 1, - 1, - num_threads_per_block, - 1, - 1, - shared_mem_bytes, - cuda_stream_, - args, - 0)); + assert_success(cuLaunchKernel(cuda_kernel.function, + num_blocks, + 1, + 1, + num_threads_per_block, + 1, + 1, + shared_mem_bytes, + cuda_stream_, + args, + 0), + "enqueue"); return !(cuda_device_->have_error()); } @@ -139,7 +139,8 @@ bool CUDADeviceQueue::synchronize() } const CUDAContextScope scope(cuda_device_); - cuda_device_assert(cuda_device_, cuStreamSynchronize(cuda_stream_)); + assert_success(cuStreamSynchronize(cuda_stream_), "synchronize"); + debug_synchronize(); return !(cuda_device_->have_error()); @@ -162,9 +163,9 @@ void CUDADeviceQueue::zero_to_device(device_memory &mem) assert(mem.device_pointer != 0); const CUDAContextScope scope(cuda_device_); - cuda_device_assert( - cuda_device_, - cuMemsetD8Async((CUdeviceptr)mem.device_pointer, 0, mem.memory_size(), cuda_stream_)); + assert_success( + cuMemsetD8Async((CUdeviceptr)mem.device_pointer, 0, mem.memory_size(), cuda_stream_), + "zero_to_device"); } void CUDADeviceQueue::copy_to_device(device_memory &mem) @@ -185,10 +186,10 @@ void CUDADeviceQueue::copy_to_device(device_memory &mem) /* Copy memory to device. */ const CUDAContextScope scope(cuda_device_); - cuda_device_assert( - cuda_device_, + assert_success( cuMemcpyHtoDAsync( - (CUdeviceptr)mem.device_pointer, mem.host_pointer, mem.memory_size(), cuda_stream_)); + (CUdeviceptr)mem.device_pointer, mem.host_pointer, mem.memory_size(), cuda_stream_), + "copy_to_device"); } void CUDADeviceQueue::copy_from_device(device_memory &mem) @@ -204,10 +205,19 @@ void CUDADeviceQueue::copy_from_device(device_memory &mem) /* Copy memory from device. */ const CUDAContextScope scope(cuda_device_); - cuda_device_assert( - cuda_device_, + assert_success( cuMemcpyDtoHAsync( - mem.host_pointer, (CUdeviceptr)mem.device_pointer, mem.memory_size(), cuda_stream_)); + mem.host_pointer, (CUdeviceptr)mem.device_pointer, mem.memory_size(), cuda_stream_), + "copy_from_device"); +} + +void CUDADeviceQueue::assert_success(CUresult result, const char *operation) +{ + if (result != CUDA_SUCCESS) { + const char *name = cuewErrorString(result); + cuda_device_->set_error(string_printf( + "%s in CUDA queue %s (%s)", name, operation, debug_active_kernels().c_str())); + } } unique_ptr<DeviceGraphicsInterop> CUDADeviceQueue::graphics_interop_create() diff --git a/intern/cycles/device/cuda/queue.h b/intern/cycles/device/cuda/queue.h index 62e3aa3d6c2..4d1995ed69e 100644 --- a/intern/cycles/device/cuda/queue.h +++ b/intern/cycles/device/cuda/queue.h @@ -60,6 +60,8 @@ class CUDADeviceQueue : public DeviceQueue { protected: CUDADevice *cuda_device_; CUstream cuda_stream_; + + void assert_success(CUresult result, const char *operation); }; CCL_NAMESPACE_END diff --git a/intern/cycles/device/device.cpp b/intern/cycles/device/device.cpp index 6ccedcf54ef..81574e8b184 100644 --- a/intern/cycles/device/device.cpp +++ b/intern/cycles/device/device.cpp @@ -25,6 +25,7 @@ #include "device/cpu/device.h" #include "device/cuda/device.h" #include "device/dummy/device.h" +#include "device/hip/device.h" #include "device/multi/device.h" #include "device/optix/device.h" @@ -32,7 +33,6 @@ #include "util/util_half.h" #include "util/util_logging.h" #include "util/util_math.h" -#include "util/util_opengl.h" #include "util/util_string.h" #include "util/util_system.h" #include "util/util_time.h" @@ -47,6 +47,7 @@ thread_mutex Device::device_mutex; vector<DeviceInfo> Device::cuda_devices; vector<DeviceInfo> Device::optix_devices; vector<DeviceInfo> Device::cpu_devices; +vector<DeviceInfo> Device::hip_devices; uint Device::devices_initialized_mask = 0; /* Device */ @@ -97,6 +98,14 @@ Device *Device::create(const DeviceInfo &info, Stats &stats, Profiler &profiler) device = device_optix_create(info, stats, profiler); break; #endif + +#ifdef WITH_HIP + case DEVICE_HIP: + if (device_hip_init()) + device = device_hip_create(info, stats, profiler); + break; +#endif + default: break; } @@ -118,6 +127,8 @@ DeviceType Device::type_from_string(const char *name) return DEVICE_OPTIX; else if (strcmp(name, "MULTI") == 0) return DEVICE_MULTI; + else if (strcmp(name, "HIP") == 0) + return DEVICE_HIP; return DEVICE_NONE; } @@ -132,6 +143,8 @@ string Device::string_from_type(DeviceType type) return "OPTIX"; else if (type == DEVICE_MULTI) return "MULTI"; + else if (type == DEVICE_HIP) + return "HIP"; return ""; } @@ -146,6 +159,10 @@ vector<DeviceType> Device::available_types() #ifdef WITH_OPTIX types.push_back(DEVICE_OPTIX); #endif +#ifdef WITH_HIP + types.push_back(DEVICE_HIP); +#endif + return types; } @@ -187,6 +204,20 @@ vector<DeviceInfo> Device::available_devices(uint mask) } #endif +#ifdef WITH_HIP + if (mask & DEVICE_MASK_HIP) { + if (!(devices_initialized_mask & DEVICE_MASK_HIP)) { + if (device_hip_init()) { + device_hip_info(hip_devices); + } + devices_initialized_mask |= DEVICE_MASK_HIP; + } + foreach (DeviceInfo &info, hip_devices) { + devices.push_back(info); + } + } +#endif + if (mask & DEVICE_MASK_CPU) { if (!(devices_initialized_mask & DEVICE_MASK_CPU)) { device_cpu_info(cpu_devices); @@ -227,6 +258,15 @@ string Device::device_capabilities(uint mask) } #endif +#ifdef WITH_HIP + if (mask & DEVICE_MASK_HIP) { + if (device_hip_init()) { + capabilities += "\nHIP device capabilities:\n"; + capabilities += device_hip_capabilities(); + } + } +#endif + return capabilities; } @@ -315,6 +355,7 @@ void Device::free_memory() devices_initialized_mask = 0; cuda_devices.free_memory(); optix_devices.free_memory(); + hip_devices.free_memory(); cpu_devices.free_memory(); } diff --git a/intern/cycles/device/device.h b/intern/cycles/device/device.h index 399d5eb91df..c73d74cdccc 100644 --- a/intern/cycles/device/device.h +++ b/intern/cycles/device/device.h @@ -51,6 +51,7 @@ enum DeviceType { DEVICE_CUDA, DEVICE_MULTI, DEVICE_OPTIX, + DEVICE_HIP, DEVICE_DUMMY, }; @@ -58,6 +59,7 @@ enum DeviceTypeMask { DEVICE_MASK_CPU = (1 << DEVICE_CPU), DEVICE_MASK_CUDA = (1 << DEVICE_CUDA), DEVICE_MASK_OPTIX = (1 << DEVICE_OPTIX), + DEVICE_MASK_HIP = (1 << DEVICE_HIP), DEVICE_MASK_ALL = ~0 }; @@ -119,7 +121,7 @@ class Device { string error_msg; - virtual device_ptr mem_alloc_sub_ptr(device_memory & /*mem*/, int /*offset*/, int /*size*/) + virtual device_ptr mem_alloc_sub_ptr(device_memory & /*mem*/, size_t /*offset*/, size_t /*size*/) { /* Only required for devices that implement denoising. */ assert(false); @@ -273,7 +275,7 @@ class Device { virtual void mem_alloc(device_memory &mem) = 0; virtual void mem_copy_to(device_memory &mem) = 0; - virtual void mem_copy_from(device_memory &mem, int y, int w, int h, int elem) = 0; + virtual void mem_copy_from(device_memory &mem, size_t y, size_t w, size_t h, size_t elem) = 0; virtual void mem_zero(device_memory &mem) = 0; virtual void mem_free(device_memory &mem) = 0; @@ -284,6 +286,7 @@ class Device { static vector<DeviceInfo> cuda_devices; static vector<DeviceInfo> optix_devices; static vector<DeviceInfo> cpu_devices; + static vector<DeviceInfo> hip_devices; static uint devices_initialized_mask; }; diff --git a/intern/cycles/device/device_graphics_interop.h b/intern/cycles/device/device_graphics_interop.h index 671b1c189d7..eaf76077141 100644 --- a/intern/cycles/device/device_graphics_interop.h +++ b/intern/cycles/device/device_graphics_interop.h @@ -16,25 +16,12 @@ #pragma once +#include "render/display_driver.h" + #include "util/util_types.h" CCL_NAMESPACE_BEGIN -/* Information about interoperability destination. - * Is provided by the GPUDisplay. */ -class DeviceGraphicsInteropDestination { - public: - /* Dimensions of the buffer, in pixels. */ - int buffer_width = 0; - int buffer_height = 0; - - /* OpenGL pixel buffer object. */ - int opengl_pbo_id = 0; - - /* Clear the entire destination before doing partial write to it. */ - bool need_clear = false; -}; - /* Device-side graphics interoperability support. * * Takes care of holding all the handlers needed by the device to implement interoperability with @@ -46,7 +33,7 @@ class DeviceGraphicsInterop { /* Update this device-side graphics interoperability object with the given destination resource * information. */ - virtual void set_destination(const DeviceGraphicsInteropDestination &destination) = 0; + virtual void set_display_interop(const DisplayDriver::GraphicsInterop &display_interop) = 0; virtual device_ptr map() = 0; virtual void unmap() = 0; diff --git a/intern/cycles/device/device_memory.cpp b/intern/cycles/device/device_memory.cpp index c4d45829b83..c0ab2e17cae 100644 --- a/intern/cycles/device/device_memory.cpp +++ b/intern/cycles/device/device_memory.cpp @@ -136,7 +136,7 @@ void device_memory::device_copy_to() } } -void device_memory::device_copy_from(int y, int w, int h, int elem) +void device_memory::device_copy_from(size_t y, size_t w, size_t h, size_t elem) { assert(type != MEM_TEXTURE && type != MEM_READ_ONLY && type != MEM_GLOBAL); device->mem_copy_from(*this, y, w, h, elem); @@ -181,7 +181,7 @@ bool device_memory::is_resident(Device *sub_device) const /* Device Sub Ptr */ -device_sub_ptr::device_sub_ptr(device_memory &mem, int offset, int size) : device(mem.device) +device_sub_ptr::device_sub_ptr(device_memory &mem, size_t offset, size_t size) : device(mem.device) { ptr = device->mem_alloc_sub_ptr(mem, offset, size); } diff --git a/intern/cycles/device/device_memory.h b/intern/cycles/device/device_memory.h index c51594b8580..be6123e09b2 100644 --- a/intern/cycles/device/device_memory.h +++ b/intern/cycles/device/device_memory.h @@ -81,154 +81,154 @@ static constexpr size_t datatype_size(DataType datatype) template<typename T> struct device_type_traits { static const DataType data_type = TYPE_UNKNOWN; - static const int num_elements_cpu = sizeof(T); - static const int num_elements_gpu = sizeof(T); + static const size_t num_elements_cpu = sizeof(T); + static const size_t num_elements_gpu = sizeof(T); }; template<> struct device_type_traits<uchar> { static const DataType data_type = TYPE_UCHAR; - static const int num_elements_cpu = 1; - static const int num_elements_gpu = 1; + static const size_t num_elements_cpu = 1; + static const size_t num_elements_gpu = 1; static_assert(sizeof(uchar) == num_elements_cpu * datatype_size(data_type)); }; template<> struct device_type_traits<uchar2> { static const DataType data_type = TYPE_UCHAR; - static const int num_elements_cpu = 2; - static const int num_elements_gpu = 2; + static const size_t num_elements_cpu = 2; + static const size_t num_elements_gpu = 2; static_assert(sizeof(uchar2) == num_elements_cpu * datatype_size(data_type)); }; template<> struct device_type_traits<uchar3> { static const DataType data_type = TYPE_UCHAR; - static const int num_elements_cpu = 3; - static const int num_elements_gpu = 3; + static const size_t num_elements_cpu = 3; + static const size_t num_elements_gpu = 3; static_assert(sizeof(uchar3) == num_elements_cpu * datatype_size(data_type)); }; template<> struct device_type_traits<uchar4> { static const DataType data_type = TYPE_UCHAR; - static const int num_elements_cpu = 4; - static const int num_elements_gpu = 4; + static const size_t num_elements_cpu = 4; + static const size_t num_elements_gpu = 4; static_assert(sizeof(uchar4) == num_elements_cpu * datatype_size(data_type)); }; template<> struct device_type_traits<uint> { static const DataType data_type = TYPE_UINT; - static const int num_elements_cpu = 1; - static const int num_elements_gpu = 1; + static const size_t num_elements_cpu = 1; + static const size_t num_elements_gpu = 1; static_assert(sizeof(uint) == num_elements_cpu * datatype_size(data_type)); }; template<> struct device_type_traits<uint2> { static const DataType data_type = TYPE_UINT; - static const int num_elements_cpu = 2; - static const int num_elements_gpu = 2; + static const size_t num_elements_cpu = 2; + static const size_t num_elements_gpu = 2; static_assert(sizeof(uint2) == num_elements_cpu * datatype_size(data_type)); }; template<> struct device_type_traits<uint3> { static const DataType data_type = TYPE_UINT; - static const int num_elements_cpu = 3; - static const int num_elements_gpu = 3; + static const size_t num_elements_cpu = 3; + static const size_t num_elements_gpu = 3; static_assert(sizeof(uint3) == num_elements_cpu * datatype_size(data_type)); }; template<> struct device_type_traits<uint4> { static const DataType data_type = TYPE_UINT; - static const int num_elements_cpu = 4; - static const int num_elements_gpu = 4; + static const size_t num_elements_cpu = 4; + static const size_t num_elements_gpu = 4; static_assert(sizeof(uint4) == num_elements_cpu * datatype_size(data_type)); }; template<> struct device_type_traits<int> { static const DataType data_type = TYPE_INT; - static const int num_elements_cpu = 1; - static const int num_elements_gpu = 1; + static const size_t num_elements_cpu = 1; + static const size_t num_elements_gpu = 1; static_assert(sizeof(int) == num_elements_cpu * datatype_size(data_type)); }; template<> struct device_type_traits<int2> { static const DataType data_type = TYPE_INT; - static const int num_elements_cpu = 2; - static const int num_elements_gpu = 2; + static const size_t num_elements_cpu = 2; + static const size_t num_elements_gpu = 2; static_assert(sizeof(int2) == num_elements_cpu * datatype_size(data_type)); }; template<> struct device_type_traits<int3> { static const DataType data_type = TYPE_INT; - static const int num_elements_cpu = 4; - static const int num_elements_gpu = 3; + static const size_t num_elements_cpu = 4; + static const size_t num_elements_gpu = 3; static_assert(sizeof(int3) == num_elements_cpu * datatype_size(data_type)); }; template<> struct device_type_traits<int4> { static const DataType data_type = TYPE_INT; - static const int num_elements_cpu = 4; - static const int num_elements_gpu = 4; + static const size_t num_elements_cpu = 4; + static const size_t num_elements_gpu = 4; static_assert(sizeof(int4) == num_elements_cpu * datatype_size(data_type)); }; template<> struct device_type_traits<float> { static const DataType data_type = TYPE_FLOAT; - static const int num_elements_cpu = 1; - static const int num_elements_gpu = 1; + static const size_t num_elements_cpu = 1; + static const size_t num_elements_gpu = 1; static_assert(sizeof(float) == num_elements_cpu * datatype_size(data_type)); }; template<> struct device_type_traits<float2> { static const DataType data_type = TYPE_FLOAT; - static const int num_elements_cpu = 2; - static const int num_elements_gpu = 2; + static const size_t num_elements_cpu = 2; + static const size_t num_elements_gpu = 2; static_assert(sizeof(float2) == num_elements_cpu * datatype_size(data_type)); }; template<> struct device_type_traits<float3> { static const DataType data_type = TYPE_FLOAT; - static const int num_elements_cpu = 4; - static const int num_elements_gpu = 3; + static const size_t num_elements_cpu = 4; + static const size_t num_elements_gpu = 3; static_assert(sizeof(float3) == num_elements_cpu * datatype_size(data_type)); }; template<> struct device_type_traits<float4> { static const DataType data_type = TYPE_FLOAT; - static const int num_elements_cpu = 4; - static const int num_elements_gpu = 4; + static const size_t num_elements_cpu = 4; + static const size_t num_elements_gpu = 4; static_assert(sizeof(float4) == num_elements_cpu * datatype_size(data_type)); }; template<> struct device_type_traits<half> { static const DataType data_type = TYPE_HALF; - static const int num_elements_cpu = 1; - static const int num_elements_gpu = 1; + static const size_t num_elements_cpu = 1; + static const size_t num_elements_gpu = 1; static_assert(sizeof(half) == num_elements_cpu * datatype_size(data_type)); }; template<> struct device_type_traits<ushort4> { static const DataType data_type = TYPE_UINT16; - static const int num_elements_cpu = 4; - static const int num_elements_gpu = 4; + static const size_t num_elements_cpu = 4; + static const size_t num_elements_gpu = 4; static_assert(sizeof(ushort4) == num_elements_cpu * datatype_size(data_type)); }; template<> struct device_type_traits<uint16_t> { static const DataType data_type = TYPE_UINT16; - static const int num_elements_cpu = 1; - static const int num_elements_gpu = 1; + static const size_t num_elements_cpu = 1; + static const size_t num_elements_gpu = 1; static_assert(sizeof(uint16_t) == num_elements_cpu * datatype_size(data_type)); }; template<> struct device_type_traits<half4> { static const DataType data_type = TYPE_HALF; - static const int num_elements_cpu = 4; - static const int num_elements_gpu = 4; + static const size_t num_elements_cpu = 4; + static const size_t num_elements_gpu = 4; static_assert(sizeof(half4) == num_elements_cpu * datatype_size(data_type)); }; template<> struct device_type_traits<uint64_t> { static const DataType data_type = TYPE_UINT64; - static const int num_elements_cpu = 1; - static const int num_elements_gpu = 1; + static const size_t num_elements_cpu = 1; + static const size_t num_elements_gpu = 1; static_assert(sizeof(uint64_t) == num_elements_cpu * datatype_size(data_type)); }; @@ -277,6 +277,7 @@ class device_memory { protected: friend class CUDADevice; friend class OptiXDevice; + friend class HIPDevice; /* Only create through subclasses. */ device_memory(Device *device, const char *name, MemoryType type); @@ -296,7 +297,7 @@ class device_memory { void device_alloc(); void device_free(); void device_copy_to(); - void device_copy_from(int y, int w, int h, int elem); + void device_copy_from(size_t y, size_t w, size_t h, size_t elem); void device_zero(); bool device_is_cpu(); @@ -565,7 +566,7 @@ template<typename T> class device_vector : public device_memory { device_copy_from(0, data_width, (data_height == 0) ? 1 : data_height, sizeof(T)); } - void copy_from_device(int y, int w, int h) + void copy_from_device(size_t y, size_t w, size_t h) { device_copy_from(y, w, h, sizeof(T)); } @@ -601,7 +602,7 @@ template<typename T> class device_vector : public device_memory { class device_sub_ptr { public: - device_sub_ptr(device_memory &mem, int offset, int size); + device_sub_ptr(device_memory &mem, size_t offset, size_t size); ~device_sub_ptr(); device_ptr operator*() const diff --git a/intern/cycles/device/device_queue.cpp b/intern/cycles/device/device_queue.cpp index a89ba68d62c..f2b2f3496e0 100644 --- a/intern/cycles/device/device_queue.cpp +++ b/intern/cycles/device/device_queue.cpp @@ -57,8 +57,9 @@ void DeviceQueue::debug_init_execution() { if (VLOG_IS_ON(3)) { last_sync_time_ = time_dt(); - last_kernels_enqueued_ = 0; } + + last_kernels_enqueued_ = 0; } void DeviceQueue::debug_enqueue(DeviceKernel kernel, const int work_size) @@ -66,8 +67,9 @@ void DeviceQueue::debug_enqueue(DeviceKernel kernel, const int work_size) if (VLOG_IS_ON(3)) { VLOG(4) << "GPU queue launch " << device_kernel_as_string(kernel) << ", work_size " << work_size; - last_kernels_enqueued_ |= (uint64_t(1) << (uint64_t)kernel); } + + last_kernels_enqueued_ |= (uint64_t(1) << (uint64_t)kernel); } void DeviceQueue::debug_synchronize() @@ -80,8 +82,14 @@ void DeviceQueue::debug_synchronize() stats_kernel_time_[last_kernels_enqueued_] += elapsed_time; last_sync_time_ = new_time; - last_kernels_enqueued_ = 0; } + + last_kernels_enqueued_ = 0; +} + +string DeviceQueue::debug_active_kernels() +{ + return device_kernel_mask_as_string(last_kernels_enqueued_); } CCL_NAMESPACE_END diff --git a/intern/cycles/device/device_queue.h b/intern/cycles/device/device_queue.h index edda3e61d51..e6835b787cf 100644 --- a/intern/cycles/device/device_queue.h +++ b/intern/cycles/device/device_queue.h @@ -21,6 +21,7 @@ #include "device/device_graphics_interop.h" #include "util/util_logging.h" #include "util/util_map.h" +#include "util/util_string.h" #include "util/util_unique_ptr.h" CCL_NAMESPACE_BEGIN @@ -101,6 +102,7 @@ class DeviceQueue { void debug_init_execution(); void debug_enqueue(DeviceKernel kernel, const int work_size); void debug_synchronize(); + string debug_active_kernels(); /* Combination of kernels enqueued together sync last synchronize. */ DeviceKernelMask last_kernels_enqueued_; diff --git a/intern/cycles/device/dummy/device.cpp b/intern/cycles/device/dummy/device.cpp index 678276ed025..e3cea272300 100644 --- a/intern/cycles/device/dummy/device.cpp +++ b/intern/cycles/device/dummy/device.cpp @@ -48,7 +48,7 @@ class DummyDevice : public Device { { } - virtual void mem_copy_from(device_memory &, int, int, int, int) override + virtual void mem_copy_from(device_memory &, size_t, size_t, size_t, size_t) override { } diff --git a/intern/cycles/device/hip/device.cpp b/intern/cycles/device/hip/device.cpp new file mode 100644 index 00000000000..90028ac7f10 --- /dev/null +++ b/intern/cycles/device/hip/device.cpp @@ -0,0 +1,276 @@ +/* + * Copyright 2011-2021 Blender Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "device/hip/device.h" + +#include "util/util_logging.h" + +#ifdef WITH_HIP +# include "device/device.h" +# include "device/hip/device_impl.h" + +# include "util/util_string.h" +# include "util/util_windows.h" +#endif /* WITH_HIP */ + +CCL_NAMESPACE_BEGIN + +bool device_hip_init() +{ +#if !defined(WITH_HIP) + return false; +#elif defined(WITH_HIP_DYNLOAD) + static bool initialized = false; + static bool result = false; + + if (initialized) + return result; + + initialized = true; + int hipew_result = hipewInit(HIPEW_INIT_HIP); + if (hipew_result == HIPEW_SUCCESS) { + VLOG(1) << "HIPEW initialization succeeded"; + if (HIPDevice::have_precompiled_kernels()) { + VLOG(1) << "Found precompiled kernels"; + result = true; + } + else if (hipewCompilerPath() != NULL) { + VLOG(1) << "Found HIPCC " << hipewCompilerPath(); + result = true; + } + else { + VLOG(1) << "Neither precompiled kernels nor HIPCC was found," + << " unable to use HIP"; + } + } + else { + VLOG(1) << "HIPEW initialization failed: " + << ((hipew_result == HIPEW_ERROR_ATEXIT_FAILED) ? "Error setting up atexit() handler" : + "Error opening the library"); + } + + return result; +#else /* WITH_HIP_DYNLOAD */ + return true; +#endif /* WITH_HIP_DYNLOAD */ +} + +Device *device_hip_create(const DeviceInfo &info, Stats &stats, Profiler &profiler) +{ +#ifdef WITH_HIP + return new HIPDevice(info, stats, profiler); +#else + (void)info; + (void)stats; + (void)profiler; + + LOG(FATAL) << "Request to create HIP device without compiled-in support. Should never happen."; + + return nullptr; +#endif +} + +#ifdef WITH_HIP +static hipError_t device_hip_safe_init() +{ +# ifdef _WIN32 + __try { + return hipInit(0); + } + __except (EXCEPTION_EXECUTE_HANDLER) { + /* Ignore crashes inside the HIP driver and hope we can + * survive even with corrupted HIP installs. */ + fprintf(stderr, "Cycles HIP: driver crashed, continuing without HIP.\n"); + } + + return hipErrorNoDevice; +# else + return hipInit(0); +# endif +} +#endif /* WITH_HIP */ + +void device_hip_info(vector<DeviceInfo> &devices) +{ +#ifdef WITH_HIP + hipError_t result = device_hip_safe_init(); + if (result != hipSuccess) { + if (result != hipErrorNoDevice) + fprintf(stderr, "HIP hipInit: %s\n", hipewErrorString(result)); + return; + } + + int count = 0; + result = hipGetDeviceCount(&count); + if (result != hipSuccess) { + fprintf(stderr, "HIP hipGetDeviceCount: %s\n", hipewErrorString(result)); + return; + } + + vector<DeviceInfo> display_devices; + + for (int num = 0; num < count; num++) { + char name[256]; + + result = hipDeviceGetName(name, 256, num); + if (result != hipSuccess) { + fprintf(stderr, "HIP :hipDeviceGetName: %s\n", hipewErrorString(result)); + continue; + } + + int major; + hipDeviceGetAttribute(&major, hipDeviceAttributeComputeCapabilityMajor, num); + // TODO : (Arya) What is the last major version we are supporting? + + DeviceInfo info; + + info.type = DEVICE_HIP; + info.description = string(name); + info.num = num; + + info.has_half_images = (major >= 3); + info.has_nanovdb = true; + info.denoisers = 0; + + info.has_gpu_queue = true; + /* Check if the device has P2P access to any other device in the system. */ + for (int peer_num = 0; peer_num < count && !info.has_peer_memory; peer_num++) { + if (num != peer_num) { + int can_access = 0; + hipDeviceCanAccessPeer(&can_access, num, peer_num); + info.has_peer_memory = (can_access != 0); + } + } + + int pci_location[3] = {0, 0, 0}; + hipDeviceGetAttribute(&pci_location[0], hipDeviceAttributePciDomainID, num); + hipDeviceGetAttribute(&pci_location[1], hipDeviceAttributePciBusId, num); + hipDeviceGetAttribute(&pci_location[2], hipDeviceAttributePciDeviceId, num); + info.id = string_printf("HIP_%s_%04x:%02x:%02x", + name, + (unsigned int)pci_location[0], + (unsigned int)pci_location[1], + (unsigned int)pci_location[2]); + + /* If device has a kernel timeout and no compute preemption, we assume + * it is connected to a display and will freeze the display while doing + * computations. */ + int timeout_attr = 0, preempt_attr = 0; + hipDeviceGetAttribute(&timeout_attr, hipDeviceAttributeKernelExecTimeout, num); + + if (timeout_attr && !preempt_attr) { + VLOG(1) << "Device is recognized as display."; + info.description += " (Display)"; + info.display_device = true; + display_devices.push_back(info); + } + else { + VLOG(1) << "Device has compute preemption or is not used for display."; + devices.push_back(info); + } + VLOG(1) << "Added device \"" << name << "\" with id \"" << info.id << "\"."; + } + + if (!display_devices.empty()) + devices.insert(devices.end(), display_devices.begin(), display_devices.end()); +#else /* WITH_HIP */ + (void)devices; +#endif /* WITH_HIP */ +} + +string device_hip_capabilities() +{ +#ifdef WITH_HIP + hipError_t result = device_hip_safe_init(); + if (result != hipSuccess) { + if (result != hipErrorNoDevice) { + return string("Error initializing HIP: ") + hipewErrorString(result); + } + return "No HIP device found\n"; + } + + int count; + result = hipGetDeviceCount(&count); + if (result != hipSuccess) { + return string("Error getting devices: ") + hipewErrorString(result); + } + + string capabilities = ""; + for (int num = 0; num < count; num++) { + char name[256]; + if (hipDeviceGetName(name, 256, num) != hipSuccess) { + continue; + } + capabilities += string("\t") + name + "\n"; + int value; +# define GET_ATTR(attr) \ + { \ + if (hipDeviceGetAttribute(&value, hipDeviceAttribute##attr, num) == hipSuccess) { \ + capabilities += string_printf("\t\thipDeviceAttribute" #attr "\t\t\t%d\n", value); \ + } \ + } \ + (void)0 + /* TODO(sergey): Strip all attributes which are not useful for us + * or does not depend on the driver. + */ + GET_ATTR(MaxThreadsPerBlock); + GET_ATTR(MaxBlockDimX); + GET_ATTR(MaxBlockDimY); + GET_ATTR(MaxBlockDimZ); + GET_ATTR(MaxGridDimX); + GET_ATTR(MaxGridDimY); + GET_ATTR(MaxGridDimZ); + GET_ATTR(MaxSharedMemoryPerBlock); + GET_ATTR(TotalConstantMemory); + GET_ATTR(WarpSize); + GET_ATTR(MaxPitch); + GET_ATTR(MaxRegistersPerBlock); + GET_ATTR(ClockRate); + GET_ATTR(TextureAlignment); + GET_ATTR(MultiprocessorCount); + GET_ATTR(KernelExecTimeout); + GET_ATTR(Integrated); + GET_ATTR(CanMapHostMemory); + GET_ATTR(ComputeMode); + GET_ATTR(MaxTexture1DWidth); + GET_ATTR(MaxTexture2DWidth); + GET_ATTR(MaxTexture2DHeight); + GET_ATTR(MaxTexture3DWidth); + GET_ATTR(MaxTexture3DHeight); + GET_ATTR(MaxTexture3DDepth); + GET_ATTR(ConcurrentKernels); + GET_ATTR(EccEnabled); + GET_ATTR(MemoryClockRate); + GET_ATTR(MemoryBusWidth); + GET_ATTR(L2CacheSize); + GET_ATTR(MaxThreadsPerMultiProcessor); + GET_ATTR(ComputeCapabilityMajor); + GET_ATTR(ComputeCapabilityMinor); + GET_ATTR(MaxSharedMemoryPerMultiprocessor); + GET_ATTR(ManagedMemory); + GET_ATTR(IsMultiGpuBoard); +# undef GET_ATTR + capabilities += "\n"; + } + + return capabilities; + +#else /* WITH_HIP */ + return ""; +#endif /* WITH_HIP */ +} + +CCL_NAMESPACE_END diff --git a/intern/cycles/device/hip/device.h b/intern/cycles/device/hip/device.h new file mode 100644 index 00000000000..965fd9e484b --- /dev/null +++ b/intern/cycles/device/hip/device.h @@ -0,0 +1,37 @@ +/* + * Copyright 2011-2021 Blender Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "util/util_string.h" +#include "util/util_vector.h" + +CCL_NAMESPACE_BEGIN + +class Device; +class DeviceInfo; +class Profiler; +class Stats; + +bool device_hip_init(); + +Device *device_hip_create(const DeviceInfo &info, Stats &stats, Profiler &profiler); + +void device_hip_info(vector<DeviceInfo> &devices); + +string device_hip_capabilities(); + +CCL_NAMESPACE_END diff --git a/intern/cycles/device/hip/device_impl.cpp b/intern/cycles/device/hip/device_impl.cpp new file mode 100644 index 00000000000..0e5ac6ce401 --- /dev/null +++ b/intern/cycles/device/hip/device_impl.cpp @@ -0,0 +1,1343 @@ +/* + * Copyright 2011-2021 Blender Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef WITH_HIP + +# include <climits> +# include <limits.h> +# include <stdio.h> +# include <stdlib.h> +# include <string.h> + +# include "device/hip/device_impl.h" + +# include "render/buffers.h" + +# include "util/util_debug.h" +# include "util/util_foreach.h" +# include "util/util_logging.h" +# include "util/util_map.h" +# include "util/util_md5.h" +# include "util/util_opengl.h" +# include "util/util_path.h" +# include "util/util_string.h" +# include "util/util_system.h" +# include "util/util_time.h" +# include "util/util_types.h" +# include "util/util_windows.h" + +CCL_NAMESPACE_BEGIN + +class HIPDevice; + +bool HIPDevice::have_precompiled_kernels() +{ + string fatbins_path = path_get("lib"); + return path_exists(fatbins_path); +} + +bool HIPDevice::show_samples() const +{ + /* The HIPDevice only processes one tile at a time, so showing samples is fine. */ + return true; +} + +BVHLayoutMask HIPDevice::get_bvh_layout_mask() const +{ + return BVH_LAYOUT_BVH2; +} + +void HIPDevice::set_error(const string &error) +{ + Device::set_error(error); + + if (first_error) { + fprintf(stderr, "\nRefer to the Cycles GPU rendering documentation for possible solutions:\n"); + fprintf(stderr, + "https://docs.blender.org/manual/en/latest/render/cycles/gpu_rendering.html\n\n"); + first_error = false; + } +} + +HIPDevice::HIPDevice(const DeviceInfo &info, Stats &stats, Profiler &profiler) + : Device(info, stats, profiler), texture_info(this, "__texture_info", MEM_GLOBAL) +{ + first_error = true; + + hipDevId = info.num; + hipDevice = 0; + hipContext = 0; + + hipModule = 0; + + need_texture_info = false; + + device_texture_headroom = 0; + device_working_headroom = 0; + move_texture_to_host = false; + map_host_limit = 0; + map_host_used = 0; + can_map_host = 0; + pitch_alignment = 0; + + /* Initialize HIP. */ + hipError_t result = hipInit(0); + if (result != hipSuccess) { + set_error(string_printf("Failed to initialize HIP runtime (%s)", hipewErrorString(result))); + return; + } + + /* Setup device and context. */ + result = hipGetDevice(&hipDevice, hipDevId); + if (result != hipSuccess) { + set_error(string_printf("Failed to get HIP device handle from ordinal (%s)", + hipewErrorString(result))); + return; + } + + hip_assert(hipDeviceGetAttribute(&can_map_host, hipDeviceAttributeCanMapHostMemory, hipDevice)); + + hip_assert( + hipDeviceGetAttribute(&pitch_alignment, hipDeviceAttributeTexturePitchAlignment, hipDevice)); + + unsigned int ctx_flags = hipDeviceLmemResizeToMax; + if (can_map_host) { + ctx_flags |= hipDeviceMapHost; + init_host_memory(); + } + + /* Create context. */ + result = hipCtxCreate(&hipContext, ctx_flags, hipDevice); + + if (result != hipSuccess) { + set_error(string_printf("Failed to create HIP context (%s)", hipewErrorString(result))); + return; + } + + int major, minor; + hipDeviceGetAttribute(&major, hipDeviceAttributeComputeCapabilityMajor, hipDevId); + hipDeviceGetAttribute(&minor, hipDeviceAttributeComputeCapabilityMinor, hipDevId); + hipDevArchitecture = major * 100 + minor * 10; + + /* Pop context set by hipCtxCreate. */ + hipCtxPopCurrent(NULL); +} + +HIPDevice::~HIPDevice() +{ + texture_info.free(); + + hip_assert(hipCtxDestroy(hipContext)); +} + +bool HIPDevice::support_device(const uint /*kernel_features*/) +{ + int major, minor; + hipDeviceGetAttribute(&major, hipDeviceAttributeComputeCapabilityMajor, hipDevId); + hipDeviceGetAttribute(&minor, hipDeviceAttributeComputeCapabilityMinor, hipDevId); + + // TODO : (Arya) What versions do we plan to support? + return true; +} + +bool HIPDevice::check_peer_access(Device *peer_device) +{ + if (peer_device == this) { + return false; + } + if (peer_device->info.type != DEVICE_HIP && peer_device->info.type != DEVICE_OPTIX) { + return false; + } + + HIPDevice *const peer_device_hip = static_cast<HIPDevice *>(peer_device); + + int can_access = 0; + hip_assert(hipDeviceCanAccessPeer(&can_access, hipDevice, peer_device_hip->hipDevice)); + if (can_access == 0) { + return false; + } + + // Ensure array access over the link is possible as well (for 3D textures) + hip_assert(hipDeviceGetP2PAttribute( + &can_access, hipDevP2PAttrHipArrayAccessSupported, hipDevice, peer_device_hip->hipDevice)); + if (can_access == 0) { + return false; + } + + // Enable peer access in both directions + { + const HIPContextScope scope(this); + hipError_t result = hipCtxEnablePeerAccess(peer_device_hip->hipContext, 0); + if (result != hipSuccess) { + set_error(string_printf("Failed to enable peer access on HIP context (%s)", + hipewErrorString(result))); + return false; + } + } + { + const HIPContextScope scope(peer_device_hip); + hipError_t result = hipCtxEnablePeerAccess(hipContext, 0); + if (result != hipSuccess) { + set_error(string_printf("Failed to enable peer access on HIP context (%s)", + hipewErrorString(result))); + return false; + } + } + + return true; +} + +bool HIPDevice::use_adaptive_compilation() +{ + return DebugFlags().hip.adaptive_compile; +} + +/* Common NVCC flags which stays the same regardless of shading model, + * kernel sources md5 and only depends on compiler or compilation settings. + */ +string HIPDevice::compile_kernel_get_common_cflags(const uint kernel_features) +{ + const int machine = system_cpu_bits(); + const string source_path = path_get("source"); + const string include_path = source_path; + string cflags = string_printf( + "-m%d " + "--ptxas-options=\"-v\" " + "--use_fast_math " + "-DHIPCC " + "-I\"%s\"", + machine, + include_path.c_str()); + if (use_adaptive_compilation()) { + cflags += " -D__KERNEL_FEATURES__=" + to_string(kernel_features); + } + return cflags; +} + +string HIPDevice::compile_kernel(const uint kernel_features, + const char *name, + const char *base, + bool force_ptx) +{ + /* Compute kernel name. */ + int major, minor; + hipDeviceGetAttribute(&major, hipDeviceAttributeComputeCapabilityMajor, hipDevId); + hipDeviceGetAttribute(&minor, hipDeviceAttributeComputeCapabilityMinor, hipDevId); + + /* Attempt to use kernel provided with Blender. */ + if (!use_adaptive_compilation()) { + if (!force_ptx) { + const string fatbin = path_get(string_printf("lib/%s_sm_%d%d.cubin", name, major, minor)); + VLOG(1) << "Testing for pre-compiled kernel " << fatbin << "."; + if (path_exists(fatbin)) { + VLOG(1) << "Using precompiled kernel."; + return fatbin; + } + } + + /* The driver can JIT-compile PTX generated for older generations, so find the closest one. */ + int ptx_major = major, ptx_minor = minor; + while (ptx_major >= 3) { + const string ptx = path_get( + string_printf("lib/%s_compute_%d%d.ptx", name, ptx_major, ptx_minor)); + VLOG(1) << "Testing for pre-compiled kernel " << ptx << "."; + if (path_exists(ptx)) { + VLOG(1) << "Using precompiled kernel."; + return ptx; + } + + if (ptx_minor > 0) { + ptx_minor--; + } + else { + ptx_major--; + ptx_minor = 9; + } + } + } + + /* Try to use locally compiled kernel. */ + string source_path = path_get("source"); + const string source_md5 = path_files_md5_hash(source_path); + + /* We include cflags into md5 so changing hip toolkit or changing other + * compiler command line arguments makes sure fatbin gets re-built. + */ + string common_cflags = compile_kernel_get_common_cflags(kernel_features); + const string kernel_md5 = util_md5_string(source_md5 + common_cflags); + + const char *const kernel_ext = "genco"; +# ifdef _WIN32 + const char *const options = + "save-temps -Wno-parentheses-equality -Wno-unused-value --hipcc-func-supp"; +# else + const char *const options = + "save-temps -Wno-parentheses-equality -Wno-unused-value --hipcc-func-supp -O3 -ggdb"; +# endif + const string include_path = source_path; + const char *const kernel_arch = force_ptx ? "compute" : "sm"; + const string fatbin_file = string_printf( + "cycles_%s_%s_%d%d_%s", name, kernel_arch, major, minor, kernel_md5.c_str()); + const string fatbin = path_cache_get(path_join("kernels", fatbin_file)); + VLOG(1) << "Testing for locally compiled kernel " << fatbin << "."; + if (path_exists(fatbin)) { + VLOG(1) << "Using locally compiled kernel."; + return fatbin; + } + +# ifdef _WIN32 + if (!use_adaptive_compilation() && have_precompiled_kernels()) { + if (major < 3) { + set_error( + string_printf("HIP backend requires compute capability 3.0 or up, but found %d.%d. " + "Your GPU is not supported.", + major, + minor)); + } + else { + set_error( + string_printf("HIP binary kernel for this graphics card compute " + "capability (%d.%d) not found.", + major, + minor)); + } + return string(); + } +# endif + + /* Compile. */ + const char *const hipcc = hipewCompilerPath(); + if (hipcc == NULL) { + set_error( + "HIP hipcc compiler not found. " + "Install HIP toolkit in default location."); + return string(); + } + + const int hipcc_hip_version = hipewCompilerVersion(); + VLOG(1) << "Found hipcc " << hipcc << ", HIP version " << hipcc_hip_version << "."; + if (hipcc_hip_version < 40) { + printf( + "Unsupported HIP version %d.%d detected, " + "you need HIP 4.0 or newer.\n", + hipcc_hip_version / 10, + hipcc_hip_version % 10); + return string(); + } + + double starttime = time_dt(); + + path_create_directories(fatbin); + + source_path = path_join(path_join(source_path, "kernel"), + path_join("device", path_join(base, string_printf("%s.cpp", name)))); + + string command = string_printf("%s -%s -I %s --%s %s -o \"%s\"", + hipcc, + options, + include_path.c_str(), + kernel_ext, + source_path.c_str(), + fatbin.c_str()); + + printf("Compiling HIP kernel ...\n%s\n", command.c_str()); + +# ifdef _WIN32 + command = "call " + command; +# endif + if (system(command.c_str()) != 0) { + set_error( + "Failed to execute compilation command, " + "see console for details."); + return string(); + } + + /* Verify if compilation succeeded */ + if (!path_exists(fatbin)) { + set_error( + "HIP kernel compilation failed, " + "see console for details."); + return string(); + } + + printf("Kernel compilation finished in %.2lfs.\n", time_dt() - starttime); + + return fatbin; +} + +bool HIPDevice::load_kernels(const uint kernel_features) +{ + /* TODO(sergey): Support kernels re-load for HIP devices. + * + * Currently re-loading kernel will invalidate memory pointers, + * causing problems in hipCtxSynchronize. + */ + if (hipModule) { + VLOG(1) << "Skipping kernel reload, not currently supported."; + return true; + } + + /* check if hip init succeeded */ + if (hipContext == 0) + return false; + + /* check if GPU is supported */ + if (!support_device(kernel_features)) + return false; + + /* get kernel */ + const char *kernel_name = "kernel"; + string fatbin = compile_kernel(kernel_features, kernel_name); + if (fatbin.empty()) + return false; + + /* open module */ + HIPContextScope scope(this); + + string fatbin_data; + hipError_t result; + + if (path_read_text(fatbin, fatbin_data)) + result = hipModuleLoadData(&hipModule, fatbin_data.c_str()); + else + result = hipErrorFileNotFound; + + if (result != hipSuccess) + set_error(string_printf( + "Failed to load HIP kernel from '%s' (%s)", fatbin.c_str(), hipewErrorString(result))); + + if (result == hipSuccess) { + kernels.load(this); + reserve_local_memory(kernel_features); + } + + return (result == hipSuccess); +} + +void HIPDevice::reserve_local_memory(const uint) +{ + /* Together with hipDeviceLmemResizeToMax, this reserves local memory + * needed for kernel launches, so that we can reliably figure out when + * to allocate scene data in mapped host memory. */ + size_t total = 0, free_before = 0, free_after = 0; + + { + HIPContextScope scope(this); + hipMemGetInfo(&free_before, &total); + } + + { + /* Use the biggest kernel for estimation. */ + const DeviceKernel test_kernel = DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_RAYTRACE; + + /* Launch kernel, using just 1 block appears sufficient to reserve memory for all + * multiprocessors. It would be good to do this in parallel for the multi GPU case + * still to make it faster. */ + HIPDeviceQueue queue(this); + + void *d_path_index = nullptr; + void *d_render_buffer = nullptr; + int d_work_size = 0; + void *args[] = {&d_path_index, &d_render_buffer, &d_work_size}; + + queue.init_execution(); + queue.enqueue(test_kernel, 1, args); + queue.synchronize(); + } + + { + HIPContextScope scope(this); + hipMemGetInfo(&free_after, &total); + } + + VLOG(1) << "Local memory reserved " << string_human_readable_number(free_before - free_after) + << " bytes. (" << string_human_readable_size(free_before - free_after) << ")"; + +# if 0 + /* For testing mapped host memory, fill up device memory. */ + const size_t keep_mb = 1024; + + while (free_after > keep_mb * 1024 * 1024LL) { + hipDeviceptr_t tmp; + hip_assert(hipMalloc(&tmp, 10 * 1024 * 1024LL)); + hipMemGetInfo(&free_after, &total); + } +# endif +} + +void HIPDevice::init_host_memory() +{ + /* Limit amount of host mapped memory, because allocating too much can + * cause system instability. Leave at least half or 4 GB of system + * memory free, whichever is smaller. */ + size_t default_limit = 4 * 1024 * 1024 * 1024LL; + size_t system_ram = system_physical_ram(); + + if (system_ram > 0) { + if (system_ram / 2 > default_limit) { + map_host_limit = system_ram - default_limit; + } + else { + map_host_limit = system_ram / 2; + } + } + else { + VLOG(1) << "Mapped host memory disabled, failed to get system RAM"; + map_host_limit = 0; + } + + /* Amount of device memory to keep is free after texture memory + * and working memory allocations respectively. We set the working + * memory limit headroom lower so that some space is left after all + * texture memory allocations. */ + device_working_headroom = 32 * 1024 * 1024LL; // 32MB + device_texture_headroom = 128 * 1024 * 1024LL; // 128MB + + VLOG(1) << "Mapped host memory limit set to " << string_human_readable_number(map_host_limit) + << " bytes. (" << string_human_readable_size(map_host_limit) << ")"; +} + +void HIPDevice::load_texture_info() +{ + if (need_texture_info) { + /* Unset flag before copying, so this does not loop indefinitely if the copy below calls + * into 'move_textures_to_host' (which calls 'load_texture_info' again). */ + need_texture_info = false; + texture_info.copy_to_device(); + } +} + +void HIPDevice::move_textures_to_host(size_t size, bool for_texture) +{ + /* Break out of recursive call, which can happen when moving memory on a multi device. */ + static bool any_device_moving_textures_to_host = false; + if (any_device_moving_textures_to_host) { + return; + } + + /* Signal to reallocate textures in host memory only. */ + move_texture_to_host = true; + + while (size > 0) { + /* Find suitable memory allocation to move. */ + device_memory *max_mem = NULL; + size_t max_size = 0; + bool max_is_image = false; + + thread_scoped_lock lock(hip_mem_map_mutex); + foreach (HIPMemMap::value_type &pair, hip_mem_map) { + device_memory &mem = *pair.first; + HIPMem *cmem = &pair.second; + + /* Can only move textures allocated on this device (and not those from peer devices). + * And need to ignore memory that is already on the host. */ + if (!mem.is_resident(this) || cmem->use_mapped_host) { + continue; + } + + bool is_texture = (mem.type == MEM_TEXTURE || mem.type == MEM_GLOBAL) && + (&mem != &texture_info); + bool is_image = is_texture && (mem.data_height > 1); + + /* Can't move this type of memory. */ + if (!is_texture || cmem->array) { + continue; + } + + /* For other textures, only move image textures. */ + if (for_texture && !is_image) { + continue; + } + + /* Try to move largest allocation, prefer moving images. */ + if (is_image > max_is_image || (is_image == max_is_image && mem.device_size > max_size)) { + max_is_image = is_image; + max_size = mem.device_size; + max_mem = &mem; + } + } + lock.unlock(); + + /* Move to host memory. This part is mutex protected since + * multiple HIP devices could be moving the memory. The + * first one will do it, and the rest will adopt the pointer. */ + if (max_mem) { + VLOG(1) << "Move memory from device to host: " << max_mem->name; + + static thread_mutex move_mutex; + thread_scoped_lock lock(move_mutex); + + any_device_moving_textures_to_host = true; + + /* Potentially need to call back into multi device, so pointer mapping + * and peer devices are updated. This is also necessary since the device + * pointer may just be a key here, so cannot be accessed and freed directly. + * Unfortunately it does mean that memory is reallocated on all other + * devices as well, which is potentially dangerous when still in use (since + * a thread rendering on another devices would only be caught in this mutex + * if it so happens to do an allocation at the same time as well. */ + max_mem->device_copy_to(); + size = (max_size >= size) ? 0 : size - max_size; + + any_device_moving_textures_to_host = false; + } + else { + break; + } + } + + /* Unset flag before texture info is reloaded, since it should stay in device memory. */ + move_texture_to_host = false; + + /* Update texture info array with new pointers. */ + load_texture_info(); +} + +HIPDevice::HIPMem *HIPDevice::generic_alloc(device_memory &mem, size_t pitch_padding) +{ + HIPContextScope scope(this); + + hipDeviceptr_t device_pointer = 0; + size_t size = mem.memory_size() + pitch_padding; + + hipError_t mem_alloc_result = hipErrorOutOfMemory; + const char *status = ""; + + /* First try allocating in device memory, respecting headroom. We make + * an exception for texture info. It is small and frequently accessed, + * so treat it as working memory. + * + * If there is not enough room for working memory, we will try to move + * textures to host memory, assuming the performance impact would have + * been worse for working memory. */ + bool is_texture = (mem.type == MEM_TEXTURE || mem.type == MEM_GLOBAL) && (&mem != &texture_info); + bool is_image = is_texture && (mem.data_height > 1); + + size_t headroom = (is_texture) ? device_texture_headroom : device_working_headroom; + + size_t total = 0, free = 0; + hipMemGetInfo(&free, &total); + + /* Move textures to host memory if needed. */ + if (!move_texture_to_host && !is_image && (size + headroom) >= free && can_map_host) { + move_textures_to_host(size + headroom - free, is_texture); + hipMemGetInfo(&free, &total); + } + + /* Allocate in device memory. */ + if (!move_texture_to_host && (size + headroom) < free) { + mem_alloc_result = hipMalloc(&device_pointer, size); + if (mem_alloc_result == hipSuccess) { + status = " in device memory"; + } + } + + /* Fall back to mapped host memory if needed and possible. */ + + void *shared_pointer = 0; + + if (mem_alloc_result != hipSuccess && can_map_host) { + if (mem.shared_pointer) { + /* Another device already allocated host memory. */ + mem_alloc_result = hipSuccess; + shared_pointer = mem.shared_pointer; + } + else if (map_host_used + size < map_host_limit) { + /* Allocate host memory ourselves. */ + mem_alloc_result = hipHostMalloc(&shared_pointer, size); + + assert((mem_alloc_result == hipSuccess && shared_pointer != 0) || + (mem_alloc_result != hipSuccess && shared_pointer == 0)); + } + + if (mem_alloc_result == hipSuccess) { + hip_assert(hipHostGetDevicePointer(&device_pointer, shared_pointer, 0)); + map_host_used += size; + status = " in host memory"; + } + } + + if (mem_alloc_result != hipSuccess) { + status = " failed, out of device and host memory"; + set_error("System is out of GPU and shared host memory"); + } + + if (mem.name) { + VLOG(1) << "Buffer allocate: " << mem.name << ", " + << string_human_readable_number(mem.memory_size()) << " bytes. (" + << string_human_readable_size(mem.memory_size()) << ")" << status; + } + + mem.device_pointer = (device_ptr)device_pointer; + mem.device_size = size; + stats.mem_alloc(size); + + if (!mem.device_pointer) { + return NULL; + } + + /* Insert into map of allocations. */ + thread_scoped_lock lock(hip_mem_map_mutex); + HIPMem *cmem = &hip_mem_map[&mem]; + if (shared_pointer != 0) { + /* Replace host pointer with our host allocation. Only works if + * HIP memory layout is the same and has no pitch padding. Also + * does not work if we move textures to host during a render, + * since other devices might be using the memory. */ + + if (!move_texture_to_host && pitch_padding == 0 && mem.host_pointer && + mem.host_pointer != shared_pointer) { + memcpy(shared_pointer, mem.host_pointer, size); + + /* A Call to device_memory::host_free() should be preceded by + * a call to device_memory::device_free() for host memory + * allocated by a device to be handled properly. Two exceptions + * are here and a call in OptiXDevice::generic_alloc(), where + * the current host memory can be assumed to be allocated by + * device_memory::host_alloc(), not by a device */ + + mem.host_free(); + mem.host_pointer = shared_pointer; + } + mem.shared_pointer = shared_pointer; + mem.shared_counter++; + cmem->use_mapped_host = true; + } + else { + cmem->use_mapped_host = false; + } + + return cmem; +} + +void HIPDevice::generic_copy_to(device_memory &mem) +{ + if (!mem.host_pointer || !mem.device_pointer) { + return; + } + + /* If use_mapped_host of mem is false, the current device only uses device memory allocated by + * hipMalloc regardless of mem.host_pointer and mem.shared_pointer, and should copy data from + * mem.host_pointer. */ + thread_scoped_lock lock(hip_mem_map_mutex); + if (!hip_mem_map[&mem].use_mapped_host || mem.host_pointer != mem.shared_pointer) { + const HIPContextScope scope(this); + hip_assert( + hipMemcpyHtoD((hipDeviceptr_t)mem.device_pointer, mem.host_pointer, mem.memory_size())); + } +} + +void HIPDevice::generic_free(device_memory &mem) +{ + if (mem.device_pointer) { + HIPContextScope scope(this); + thread_scoped_lock lock(hip_mem_map_mutex); + const HIPMem &cmem = hip_mem_map[&mem]; + + /* If cmem.use_mapped_host is true, reference counting is used + * to safely free a mapped host memory. */ + + if (cmem.use_mapped_host) { + assert(mem.shared_pointer); + if (mem.shared_pointer) { + assert(mem.shared_counter > 0); + if (--mem.shared_counter == 0) { + if (mem.host_pointer == mem.shared_pointer) { + mem.host_pointer = 0; + } + hipHostFree(mem.shared_pointer); + mem.shared_pointer = 0; + } + } + map_host_used -= mem.device_size; + } + else { + /* Free device memory. */ + hip_assert(hipFree(mem.device_pointer)); + } + + stats.mem_free(mem.device_size); + mem.device_pointer = 0; + mem.device_size = 0; + + hip_mem_map.erase(hip_mem_map.find(&mem)); + } +} + +void HIPDevice::mem_alloc(device_memory &mem) +{ + if (mem.type == MEM_TEXTURE) { + assert(!"mem_alloc not supported for textures."); + } + else if (mem.type == MEM_GLOBAL) { + assert(!"mem_alloc not supported for global memory."); + } + else { + generic_alloc(mem); + } +} + +void HIPDevice::mem_copy_to(device_memory &mem) +{ + if (mem.type == MEM_GLOBAL) { + global_free(mem); + global_alloc(mem); + } + else if (mem.type == MEM_TEXTURE) { + tex_free((device_texture &)mem); + tex_alloc((device_texture &)mem); + } + else { + if (!mem.device_pointer) { + generic_alloc(mem); + } + generic_copy_to(mem); + } +} + +void HIPDevice::mem_copy_from(device_memory &mem, size_t y, size_t w, size_t h, size_t elem) +{ + if (mem.type == MEM_TEXTURE || mem.type == MEM_GLOBAL) { + assert(!"mem_copy_from not supported for textures."); + } + else if (mem.host_pointer) { + const size_t size = elem * w * h; + const size_t offset = elem * y * w; + + if (mem.device_pointer) { + const HIPContextScope scope(this); + hip_assert(hipMemcpyDtoH( + (char *)mem.host_pointer + offset, (hipDeviceptr_t)mem.device_pointer + offset, size)); + } + else { + memset((char *)mem.host_pointer + offset, 0, size); + } + } +} + +void HIPDevice::mem_zero(device_memory &mem) +{ + if (!mem.device_pointer) { + mem_alloc(mem); + } + if (!mem.device_pointer) { + return; + } + + /* If use_mapped_host of mem is false, mem.device_pointer currently refers to device memory + * regardless of mem.host_pointer and mem.shared_pointer. */ + thread_scoped_lock lock(hip_mem_map_mutex); + if (!hip_mem_map[&mem].use_mapped_host || mem.host_pointer != mem.shared_pointer) { + const HIPContextScope scope(this); + hip_assert(hipMemsetD8((hipDeviceptr_t)mem.device_pointer, 0, mem.memory_size())); + } + else if (mem.host_pointer) { + memset(mem.host_pointer, 0, mem.memory_size()); + } +} + +void HIPDevice::mem_free(device_memory &mem) +{ + if (mem.type == MEM_GLOBAL) { + global_free(mem); + } + else if (mem.type == MEM_TEXTURE) { + tex_free((device_texture &)mem); + } + else { + generic_free(mem); + } +} + +device_ptr HIPDevice::mem_alloc_sub_ptr(device_memory &mem, size_t offset, size_t /*size*/) +{ + return (device_ptr)(((char *)mem.device_pointer) + mem.memory_elements_size(offset)); +} + +void HIPDevice::const_copy_to(const char *name, void *host, size_t size) +{ + HIPContextScope scope(this); + hipDeviceptr_t mem; + size_t bytes; + + hip_assert(hipModuleGetGlobal(&mem, &bytes, hipModule, name)); + assert(bytes == size); + hip_assert(hipMemcpyHtoD(mem, host, size)); +} + +void HIPDevice::global_alloc(device_memory &mem) +{ + if (mem.is_resident(this)) { + generic_alloc(mem); + generic_copy_to(mem); + } + + const_copy_to(mem.name, &mem.device_pointer, sizeof(mem.device_pointer)); +} + +void HIPDevice::global_free(device_memory &mem) +{ + if (mem.is_resident(this) && mem.device_pointer) { + generic_free(mem); + } +} + +void HIPDevice::tex_alloc(device_texture &mem) +{ + HIPContextScope scope(this); + + /* General variables for both architectures */ + string bind_name = mem.name; + size_t dsize = datatype_size(mem.data_type); + size_t size = mem.memory_size(); + + hipTextureAddressMode address_mode = hipAddressModeWrap; + switch (mem.info.extension) { + case EXTENSION_REPEAT: + address_mode = hipAddressModeWrap; + break; + case EXTENSION_EXTEND: + address_mode = hipAddressModeClamp; + break; + case EXTENSION_CLIP: + // TODO : (Arya) setting this to Mode Clamp instead of Mode Border because it's unsupported + // in hip + address_mode = hipAddressModeClamp; + break; + default: + assert(0); + break; + } + + hipTextureFilterMode filter_mode; + if (mem.info.interpolation == INTERPOLATION_CLOSEST) { + filter_mode = hipFilterModePoint; + } + else { + filter_mode = hipFilterModeLinear; + } + + /* Image Texture Storage */ + hipArray_Format format; + switch (mem.data_type) { + case TYPE_UCHAR: + format = HIP_AD_FORMAT_UNSIGNED_INT8; + break; + case TYPE_UINT16: + format = HIP_AD_FORMAT_UNSIGNED_INT16; + break; + case TYPE_UINT: + format = HIP_AD_FORMAT_UNSIGNED_INT32; + break; + case TYPE_INT: + format = HIP_AD_FORMAT_SIGNED_INT32; + break; + case TYPE_FLOAT: + format = HIP_AD_FORMAT_FLOAT; + break; + case TYPE_HALF: + format = HIP_AD_FORMAT_HALF; + break; + default: + assert(0); + return; + } + + HIPMem *cmem = NULL; + hArray array_3d = NULL; + size_t src_pitch = mem.data_width * dsize * mem.data_elements; + size_t dst_pitch = src_pitch; + + if (!mem.is_resident(this)) { + thread_scoped_lock lock(hip_mem_map_mutex); + cmem = &hip_mem_map[&mem]; + cmem->texobject = 0; + + if (mem.data_depth > 1) { + array_3d = (hArray)mem.device_pointer; + cmem->array = array_3d; + } + else if (mem.data_height > 0) { + dst_pitch = align_up(src_pitch, pitch_alignment); + } + } + else if (mem.data_depth > 1) { + /* 3D texture using array, there is no API for linear memory. */ + HIP_ARRAY3D_DESCRIPTOR desc; + + desc.Width = mem.data_width; + desc.Height = mem.data_height; + desc.Depth = mem.data_depth; + desc.Format = format; + desc.NumChannels = mem.data_elements; + desc.Flags = 0; + + VLOG(1) << "Array 3D allocate: " << mem.name << ", " + << string_human_readable_number(mem.memory_size()) << " bytes. (" + << string_human_readable_size(mem.memory_size()) << ")"; + + hip_assert(hipArray3DCreate(&array_3d, &desc)); + + if (!array_3d) { + return; + } + + HIP_MEMCPY3D param; + memset(¶m, 0, sizeof(param)); + param.dstMemoryType = hipMemoryTypeArray; + param.dstArray = &array_3d; + param.srcMemoryType = hipMemoryTypeHost; + param.srcHost = mem.host_pointer; + param.srcPitch = src_pitch; + param.WidthInBytes = param.srcPitch; + param.Height = mem.data_height; + param.Depth = mem.data_depth; + + hip_assert(hipDrvMemcpy3D(¶m)); + + mem.device_pointer = (device_ptr)array_3d; + mem.device_size = size; + stats.mem_alloc(size); + + thread_scoped_lock lock(hip_mem_map_mutex); + cmem = &hip_mem_map[&mem]; + cmem->texobject = 0; + cmem->array = array_3d; + } + else if (mem.data_height > 0) { + /* 2D texture, using pitch aligned linear memory. */ + dst_pitch = align_up(src_pitch, pitch_alignment); + size_t dst_size = dst_pitch * mem.data_height; + + cmem = generic_alloc(mem, dst_size - mem.memory_size()); + if (!cmem) { + return; + } + + hip_Memcpy2D param; + memset(¶m, 0, sizeof(param)); + param.dstMemoryType = hipMemoryTypeDevice; + param.dstDevice = mem.device_pointer; + param.dstPitch = dst_pitch; + param.srcMemoryType = hipMemoryTypeHost; + param.srcHost = mem.host_pointer; + param.srcPitch = src_pitch; + param.WidthInBytes = param.srcPitch; + param.Height = mem.data_height; + + hip_assert(hipDrvMemcpy2DUnaligned(¶m)); + } + else { + /* 1D texture, using linear memory. */ + cmem = generic_alloc(mem); + if (!cmem) { + return; + } + + hip_assert(hipMemcpyHtoD(mem.device_pointer, mem.host_pointer, size)); + } + + /* Resize once */ + const uint slot = mem.slot; + if (slot >= texture_info.size()) { + /* Allocate some slots in advance, to reduce amount + * of re-allocations. */ + texture_info.resize(slot + 128); + } + + /* Set Mapping and tag that we need to (re-)upload to device */ + texture_info[slot] = mem.info; + need_texture_info = true; + + if (mem.info.data_type != IMAGE_DATA_TYPE_NANOVDB_FLOAT && + mem.info.data_type != IMAGE_DATA_TYPE_NANOVDB_FLOAT3) { + /* Kepler+, bindless textures. */ + hipResourceDesc resDesc; + memset(&resDesc, 0, sizeof(resDesc)); + + if (array_3d) { + resDesc.resType = hipResourceTypeArray; + resDesc.res.array.h_Array = &array_3d; + resDesc.flags = 0; + } + else if (mem.data_height > 0) { + resDesc.resType = hipResourceTypePitch2D; + resDesc.res.pitch2D.devPtr = mem.device_pointer; + resDesc.res.pitch2D.format = format; + resDesc.res.pitch2D.numChannels = mem.data_elements; + resDesc.res.pitch2D.height = mem.data_height; + resDesc.res.pitch2D.width = mem.data_width; + resDesc.res.pitch2D.pitchInBytes = dst_pitch; + } + else { + resDesc.resType = hipResourceTypeLinear; + resDesc.res.linear.devPtr = mem.device_pointer; + resDesc.res.linear.format = format; + resDesc.res.linear.numChannels = mem.data_elements; + resDesc.res.linear.sizeInBytes = mem.device_size; + } + + hipTextureDesc texDesc; + memset(&texDesc, 0, sizeof(texDesc)); + texDesc.addressMode[0] = address_mode; + texDesc.addressMode[1] = address_mode; + texDesc.addressMode[2] = address_mode; + texDesc.filterMode = filter_mode; + texDesc.flags = HIP_TRSF_NORMALIZED_COORDINATES; + + thread_scoped_lock lock(hip_mem_map_mutex); + cmem = &hip_mem_map[&mem]; + + hip_assert(hipTexObjectCreate(&cmem->texobject, &resDesc, &texDesc, NULL)); + + texture_info[slot].data = (uint64_t)cmem->texobject; + } + else { + texture_info[slot].data = (uint64_t)mem.device_pointer; + } +} + +void HIPDevice::tex_free(device_texture &mem) +{ + if (mem.device_pointer) { + HIPContextScope scope(this); + thread_scoped_lock lock(hip_mem_map_mutex); + const HIPMem &cmem = hip_mem_map[&mem]; + + if (cmem.texobject) { + /* Free bindless texture. */ + hipTexObjectDestroy(cmem.texobject); + } + + if (!mem.is_resident(this)) { + /* Do not free memory here, since it was allocated on a different device. */ + hip_mem_map.erase(hip_mem_map.find(&mem)); + } + else if (cmem.array) { + /* Free array. */ + hipArrayDestroy(cmem.array); + stats.mem_free(mem.device_size); + mem.device_pointer = 0; + mem.device_size = 0; + + hip_mem_map.erase(hip_mem_map.find(&mem)); + } + else { + lock.unlock(); + generic_free(mem); + } + } +} + +# if 0 +void HIPDevice::render(DeviceTask &task, + RenderTile &rtile, + device_vector<KernelWorkTile> &work_tiles) +{ + scoped_timer timer(&rtile.buffers->render_time); + + if (have_error()) + return; + + HIPContextScope scope(this); + hipFunction_t hipRender; + + /* Get kernel function. */ + if (rtile.task == RenderTile::BAKE) { + hip_assert(hipModuleGetFunction(&hipRender, hipModule, "kernel_hip_bake")); + } + else { + hip_assert(hipModuleGetFunction(&hipRender, hipModule, "kernel_hip_path_trace")); + } + + if (have_error()) { + return; + } + + hip_assert(hipFuncSetCacheConfig(hipRender, hipFuncCachePreferL1)); + + /* Allocate work tile. */ + work_tiles.alloc(1); + + KernelWorkTile *wtile = work_tiles.data(); + wtile->x = rtile.x; + wtile->y = rtile.y; + wtile->w = rtile.w; + wtile->h = rtile.h; + wtile->offset = rtile.offset; + wtile->stride = rtile.stride; + wtile->buffer = (float *)(hipDeviceptr_t)rtile.buffer; + + /* Prepare work size. More step samples render faster, but for now we + * remain conservative for GPUs connected to a display to avoid driver + * timeouts and display freezing. */ + int min_blocks, num_threads_per_block; + hip_assert( + hipModuleOccupancyMaxPotentialBlockSize(&min_blocks, &num_threads_per_block, hipRender, NULL, 0, 0)); + if (!info.display_device) { + min_blocks *= 8; + } + + uint step_samples = divide_up(min_blocks * num_threads_per_block, wtile->w * wtile->h); + + /* Render all samples. */ + uint start_sample = rtile.start_sample; + uint end_sample = rtile.start_sample + rtile.num_samples; + + for (int sample = start_sample; sample < end_sample;) { + /* Setup and copy work tile to device. */ + wtile->start_sample = sample; + wtile->num_samples = step_samples; + if (task.adaptive_sampling.use) { + wtile->num_samples = task.adaptive_sampling.align_samples(sample, step_samples); + } + wtile->num_samples = min(wtile->num_samples, end_sample - sample); + work_tiles.copy_to_device(); + + hipDeviceptr_t d_work_tiles = (hipDeviceptr_t)work_tiles.device_pointer; + uint total_work_size = wtile->w * wtile->h * wtile->num_samples; + uint num_blocks = divide_up(total_work_size, num_threads_per_block); + + /* Launch kernel. */ + void *args[] = {&d_work_tiles, &total_work_size}; + + hip_assert( + hipModuleLaunchKernel(hipRender, num_blocks, 1, 1, num_threads_per_block, 1, 1, 0, 0, args, 0)); + + /* Run the adaptive sampling kernels at selected samples aligned to step samples. */ + uint filter_sample = sample + wtile->num_samples - 1; + if (task.adaptive_sampling.use && task.adaptive_sampling.need_filter(filter_sample)) { + adaptive_sampling_filter(filter_sample, wtile, d_work_tiles); + } + + hip_assert(hipDeviceSynchronize()); + + /* Update progress. */ + sample += wtile->num_samples; + rtile.sample = sample; + task.update_progress(&rtile, rtile.w * rtile.h * wtile->num_samples); + + if (task.get_cancel()) { + if (task.need_finish_queue == false) + break; + } + } + + /* Finalize adaptive sampling. */ + if (task.adaptive_sampling.use) { + hipDeviceptr_t d_work_tiles = (hipDeviceptr_t)work_tiles.device_pointer; + adaptive_sampling_post(rtile, wtile, d_work_tiles); + hip_assert(hipDeviceSynchronize()); + task.update_progress(&rtile, rtile.w * rtile.h * wtile->num_samples); + } +} + +void HIPDevice::thread_run(DeviceTask &task) +{ + HIPContextScope scope(this); + + if (task.type == DeviceTask::RENDER) { + device_vector<KernelWorkTile> work_tiles(this, "work_tiles", MEM_READ_ONLY); + + /* keep rendering tiles until done */ + RenderTile tile; + DenoisingTask denoising(this, task); + + while (task.acquire_tile(this, tile, task.tile_types)) { + if (tile.task == RenderTile::PATH_TRACE) { + render(task, tile, work_tiles); + } + else if (tile.task == RenderTile::BAKE) { + render(task, tile, work_tiles); + } + + task.release_tile(tile); + + if (task.get_cancel()) { + if (task.need_finish_queue == false) + break; + } + } + + work_tiles.free(); + } +} +# endif + +unique_ptr<DeviceQueue> HIPDevice::gpu_queue_create() +{ + return make_unique<HIPDeviceQueue>(this); +} + +bool HIPDevice::should_use_graphics_interop() +{ + /* Check whether this device is part of OpenGL context. + * + * Using HIP device for graphics interoperability which is not part of the OpenGL context is + * possible, but from the empiric measurements it can be considerably slower than using naive + * pixels copy. */ + + HIPContextScope scope(this); + + int num_all_devices = 0; + hip_assert(hipGetDeviceCount(&num_all_devices)); + + if (num_all_devices == 0) { + return false; + } + + vector<hipDevice_t> gl_devices(num_all_devices); + uint num_gl_devices = 0; + hipGLGetDevices(&num_gl_devices, gl_devices.data(), num_all_devices, hipGLDeviceListAll); + + for (hipDevice_t gl_device : gl_devices) { + if (gl_device == hipDevice) { + return true; + } + } + + return false; +} + +int HIPDevice::get_num_multiprocessors() +{ + return get_device_default_attribute(hipDeviceAttributeMultiprocessorCount, 0); +} + +int HIPDevice::get_max_num_threads_per_multiprocessor() +{ + return get_device_default_attribute(hipDeviceAttributeMaxThreadsPerMultiProcessor, 0); +} + +bool HIPDevice::get_device_attribute(hipDeviceAttribute_t attribute, int *value) +{ + HIPContextScope scope(this); + + return hipDeviceGetAttribute(value, attribute, hipDevice) == hipSuccess; +} + +int HIPDevice::get_device_default_attribute(hipDeviceAttribute_t attribute, int default_value) +{ + int value = 0; + if (!get_device_attribute(attribute, &value)) { + return default_value; + } + return value; +} + +CCL_NAMESPACE_END + +#endif diff --git a/intern/cycles/device/hip/device_impl.h b/intern/cycles/device/hip/device_impl.h new file mode 100644 index 00000000000..1d138ee9856 --- /dev/null +++ b/intern/cycles/device/hip/device_impl.h @@ -0,0 +1,153 @@ +/* + * Copyright 2011-2021 Blender Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef WITH_HIP + +# include "device/device.h" +# include "device/hip/kernel.h" +# include "device/hip/queue.h" +# include "device/hip/util.h" + +# include "util/util_map.h" + +# ifdef WITH_HIP_DYNLOAD +# include "hipew.h" +# else +# include "util/util_opengl.h" +# endif + +CCL_NAMESPACE_BEGIN + +class DeviceQueue; + +class HIPDevice : public Device { + + friend class HIPContextScope; + + public: + hipDevice_t hipDevice; + hipCtx_t hipContext; + hipModule_t hipModule; + size_t device_texture_headroom; + size_t device_working_headroom; + bool move_texture_to_host; + size_t map_host_used; + size_t map_host_limit; + int can_map_host; + int pitch_alignment; + int hipDevId; + int hipDevArchitecture; + bool first_error; + + struct HIPMem { + HIPMem() : texobject(0), array(0), use_mapped_host(false) + { + } + + hipTextureObject_t texobject; + hArray array; + + /* If true, a mapped host memory in shared_pointer is being used. */ + bool use_mapped_host; + }; + typedef map<device_memory *, HIPMem> HIPMemMap; + HIPMemMap hip_mem_map; + thread_mutex hip_mem_map_mutex; + + /* Bindless Textures */ + device_vector<TextureInfo> texture_info; + bool need_texture_info; + + HIPDeviceKernels kernels; + + static bool have_precompiled_kernels(); + + virtual bool show_samples() const override; + + virtual BVHLayoutMask get_bvh_layout_mask() const override; + + void set_error(const string &error) override; + + HIPDevice(const DeviceInfo &info, Stats &stats, Profiler &profiler); + + virtual ~HIPDevice(); + + bool support_device(const uint /*kernel_features*/); + + bool check_peer_access(Device *peer_device) override; + + bool use_adaptive_compilation(); + + virtual string compile_kernel_get_common_cflags(const uint kernel_features); + + string compile_kernel(const uint kernel_features, + const char *name, + const char *base = "hip", + bool force_ptx = false); + + virtual bool load_kernels(const uint kernel_features) override; + void reserve_local_memory(const uint kernel_features); + + void init_host_memory(); + + void load_texture_info(); + + void move_textures_to_host(size_t size, bool for_texture); + + HIPMem *generic_alloc(device_memory &mem, size_t pitch_padding = 0); + + void generic_copy_to(device_memory &mem); + + void generic_free(device_memory &mem); + + void mem_alloc(device_memory &mem) override; + + void mem_copy_to(device_memory &mem) override; + + void mem_copy_from(device_memory &mem, size_t y, size_t w, size_t h, size_t elem) override; + + void mem_zero(device_memory &mem) override; + + void mem_free(device_memory &mem) override; + + device_ptr mem_alloc_sub_ptr(device_memory &mem, size_t offset, size_t /*size*/) override; + + virtual void const_copy_to(const char *name, void *host, size_t size) override; + + void global_alloc(device_memory &mem); + + void global_free(device_memory &mem); + + void tex_alloc(device_texture &mem); + + void tex_free(device_texture &mem); + + /* Graphics resources interoperability. */ + virtual bool should_use_graphics_interop() override; + + virtual unique_ptr<DeviceQueue> gpu_queue_create() override; + + int get_num_multiprocessors(); + int get_max_num_threads_per_multiprocessor(); + + protected: + bool get_device_attribute(hipDeviceAttribute_t attribute, int *value); + int get_device_default_attribute(hipDeviceAttribute_t attribute, int default_value); +}; + +CCL_NAMESPACE_END + +#endif diff --git a/intern/cycles/device/hip/graphics_interop.cpp b/intern/cycles/device/hip/graphics_interop.cpp new file mode 100644 index 00000000000..0d5d71019b3 --- /dev/null +++ b/intern/cycles/device/hip/graphics_interop.cpp @@ -0,0 +1,105 @@ +/* + * Copyright 2011-2021 Blender Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef WITH_HIP + +# include "device/hip/graphics_interop.h" + +# include "device/hip/device_impl.h" +# include "device/hip/util.h" + +CCL_NAMESPACE_BEGIN + +HIPDeviceGraphicsInterop::HIPDeviceGraphicsInterop(HIPDeviceQueue *queue) + : queue_(queue), device_(static_cast<HIPDevice *>(queue->device)) +{ +} + +HIPDeviceGraphicsInterop::~HIPDeviceGraphicsInterop() +{ + HIPContextScope scope(device_); + + if (hip_graphics_resource_) { + hip_device_assert(device_, hipGraphicsUnregisterResource(hip_graphics_resource_)); + } +} + +void HIPDeviceGraphicsInterop::set_display_interop( + const DisplayDriver::GraphicsInterop &display_interop) +{ + const int64_t new_buffer_area = int64_t(display_interop.buffer_width) * + display_interop.buffer_height; + + need_clear_ = display_interop.need_clear; + + if (opengl_pbo_id_ == display_interop.opengl_pbo_id && buffer_area_ == new_buffer_area) { + return; + } + + HIPContextScope scope(device_); + + if (hip_graphics_resource_) { + hip_device_assert(device_, hipGraphicsUnregisterResource(hip_graphics_resource_)); + } + + const hipError_t result = hipGraphicsGLRegisterBuffer( + &hip_graphics_resource_, display_interop.opengl_pbo_id, hipGraphicsRegisterFlagsNone); + if (result != hipSuccess) { + LOG(ERROR) << "Error registering OpenGL buffer: " << hipewErrorString(result); + } + + opengl_pbo_id_ = display_interop.opengl_pbo_id; + buffer_area_ = new_buffer_area; +} + +device_ptr HIPDeviceGraphicsInterop::map() +{ + if (!hip_graphics_resource_) { + return 0; + } + + HIPContextScope scope(device_); + + hipDeviceptr_t hip_buffer; + size_t bytes; + + hip_device_assert(device_, + hipGraphicsMapResources(1, &hip_graphics_resource_, queue_->stream())); + hip_device_assert( + device_, hipGraphicsResourceGetMappedPointer(&hip_buffer, &bytes, hip_graphics_resource_)); + + if (need_clear_) { + hip_device_assert( + device_, + hipMemsetD8Async(static_cast<hipDeviceptr_t>(hip_buffer), 0, bytes, queue_->stream())); + + need_clear_ = false; + } + + return static_cast<device_ptr>(hip_buffer); +} + +void HIPDeviceGraphicsInterop::unmap() +{ + HIPContextScope scope(device_); + + hip_device_assert(device_, + hipGraphicsUnmapResources(1, &hip_graphics_resource_, queue_->stream())); +} + +CCL_NAMESPACE_END + +#endif diff --git a/intern/cycles/device/hip/graphics_interop.h b/intern/cycles/device/hip/graphics_interop.h new file mode 100644 index 00000000000..2b2d287ff6c --- /dev/null +++ b/intern/cycles/device/hip/graphics_interop.h @@ -0,0 +1,64 @@ +/* + * Copyright 2011-2021 Blender Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef WITH_HIP + +# include "device/device_graphics_interop.h" + +# ifdef WITH_HIP_DYNLOAD +# include "hipew.h" +# endif + +CCL_NAMESPACE_BEGIN + +class HIPDevice; +class HIPDeviceQueue; + +class HIPDeviceGraphicsInterop : public DeviceGraphicsInterop { + public: + explicit HIPDeviceGraphicsInterop(HIPDeviceQueue *queue); + + HIPDeviceGraphicsInterop(const HIPDeviceGraphicsInterop &other) = delete; + HIPDeviceGraphicsInterop(HIPDeviceGraphicsInterop &&other) noexcept = delete; + + ~HIPDeviceGraphicsInterop(); + + HIPDeviceGraphicsInterop &operator=(const HIPDeviceGraphicsInterop &other) = delete; + HIPDeviceGraphicsInterop &operator=(HIPDeviceGraphicsInterop &&other) = delete; + + virtual void set_display_interop(const DisplayDriver::GraphicsInterop &display_interop) override; + + virtual device_ptr map() override; + virtual void unmap() override; + + protected: + HIPDeviceQueue *queue_ = nullptr; + HIPDevice *device_ = nullptr; + + /* OpenGL PBO which is currently registered as the destination for the CUDA buffer. */ + uint opengl_pbo_id_ = 0; + /* Buffer area in pixels of the corresponding PBO. */ + int64_t buffer_area_ = 0; + + /* The destination was requested to be cleared. */ + bool need_clear_ = false; + + hipGraphicsResource hip_graphics_resource_ = nullptr; +}; + +CCL_NAMESPACE_END + +#endif diff --git a/intern/cycles/device/hip/kernel.cpp b/intern/cycles/device/hip/kernel.cpp new file mode 100644 index 00000000000..9ede8507a0c --- /dev/null +++ b/intern/cycles/device/hip/kernel.cpp @@ -0,0 +1,69 @@ +/* + * Copyright 2011-2021 Blender Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef WITH_HIP + +# include "device/hip/kernel.h" +# include "device/hip/device_impl.h" + +CCL_NAMESPACE_BEGIN + +void HIPDeviceKernels::load(HIPDevice *device) +{ + hipModule_t hipModule = device->hipModule; + + for (int i = 0; i < (int)DEVICE_KERNEL_NUM; i++) { + HIPDeviceKernel &kernel = kernels_[i]; + + /* No mega-kernel used for GPU. */ + if (i == DEVICE_KERNEL_INTEGRATOR_MEGAKERNEL) { + continue; + } + + const std::string function_name = std::string("kernel_gpu_") + + device_kernel_as_string((DeviceKernel)i); + hip_device_assert(device, + hipModuleGetFunction(&kernel.function, hipModule, function_name.c_str())); + + if (kernel.function) { + hip_device_assert(device, hipFuncSetCacheConfig(kernel.function, hipFuncCachePreferL1)); + + hip_device_assert( + device, + hipModuleOccupancyMaxPotentialBlockSize( + &kernel.min_blocks, &kernel.num_threads_per_block, kernel.function, 0, 0)); + } + else { + LOG(ERROR) << "Unable to load kernel " << function_name; + } + } + + loaded = true; +} + +const HIPDeviceKernel &HIPDeviceKernels::get(DeviceKernel kernel) const +{ + return kernels_[(int)kernel]; +} + +bool HIPDeviceKernels::available(DeviceKernel kernel) const +{ + return kernels_[(int)kernel].function != nullptr; +} + +CCL_NAMESPACE_END + +#endif /* WITH_HIP*/ diff --git a/intern/cycles/device/hip/kernel.h b/intern/cycles/device/hip/kernel.h new file mode 100644 index 00000000000..3301731f56e --- /dev/null +++ b/intern/cycles/device/hip/kernel.h @@ -0,0 +1,54 @@ +/* + * Copyright 2011-2021 Blender Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#ifdef WITH_HIP + +# include "device/device_kernel.h" + +# ifdef WITH_HIP_DYNLOAD +# include "hipew.h" +# endif + +CCL_NAMESPACE_BEGIN + +class HIPDevice; + +/* HIP kernel and associate occupancy information. */ +class HIPDeviceKernel { + public: + hipFunction_t function = nullptr; + + int num_threads_per_block = 0; + int min_blocks = 0; +}; + +/* Cache of HIP kernels for each DeviceKernel. */ +class HIPDeviceKernels { + public: + void load(HIPDevice *device); + const HIPDeviceKernel &get(DeviceKernel kernel) const; + bool available(DeviceKernel kernel) const; + + protected: + HIPDeviceKernel kernels_[DEVICE_KERNEL_NUM]; + bool loaded = false; +}; + +CCL_NAMESPACE_END + +#endif /* WITH_HIP */ diff --git a/intern/cycles/device/hip/queue.cpp b/intern/cycles/device/hip/queue.cpp new file mode 100644 index 00000000000..78c77e5fdae --- /dev/null +++ b/intern/cycles/device/hip/queue.cpp @@ -0,0 +1,209 @@ +/* + * Copyright 2011-2021 Blender Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef WITH_HIP + +# include "device/hip/queue.h" + +# include "device/hip/device_impl.h" +# include "device/hip/graphics_interop.h" +# include "device/hip/kernel.h" + +CCL_NAMESPACE_BEGIN + +/* HIPDeviceQueue */ + +HIPDeviceQueue::HIPDeviceQueue(HIPDevice *device) + : DeviceQueue(device), hip_device_(device), hip_stream_(nullptr) +{ + const HIPContextScope scope(hip_device_); + hip_device_assert(hip_device_, hipStreamCreateWithFlags(&hip_stream_, hipStreamNonBlocking)); +} + +HIPDeviceQueue::~HIPDeviceQueue() +{ + const HIPContextScope scope(hip_device_); + hipStreamDestroy(hip_stream_); +} + +int HIPDeviceQueue::num_concurrent_states(const size_t /*state_size*/) const +{ + /* TODO: compute automatically. */ + /* TODO: must have at least num_threads_per_block. */ + return 14416128; +} + +int HIPDeviceQueue::num_concurrent_busy_states() const +{ + const int max_num_threads = hip_device_->get_num_multiprocessors() * + hip_device_->get_max_num_threads_per_multiprocessor(); + + if (max_num_threads == 0) { + return 65536; + } + + return 4 * max_num_threads; +} + +void HIPDeviceQueue::init_execution() +{ + /* Synchronize all textures and memory copies before executing task. */ + HIPContextScope scope(hip_device_); + hip_device_->load_texture_info(); + hip_device_assert(hip_device_, hipDeviceSynchronize()); + + debug_init_execution(); +} + +bool HIPDeviceQueue::kernel_available(DeviceKernel kernel) const +{ + return hip_device_->kernels.available(kernel); +} + +bool HIPDeviceQueue::enqueue(DeviceKernel kernel, const int work_size, void *args[]) +{ + if (hip_device_->have_error()) { + return false; + } + + debug_enqueue(kernel, work_size); + + const HIPContextScope scope(hip_device_); + const HIPDeviceKernel &hip_kernel = hip_device_->kernels.get(kernel); + + /* Compute kernel launch parameters. */ + const int num_threads_per_block = hip_kernel.num_threads_per_block; + const int num_blocks = divide_up(work_size, num_threads_per_block); + + int shared_mem_bytes = 0; + + switch (kernel) { + case DEVICE_KERNEL_INTEGRATOR_QUEUED_PATHS_ARRAY: + case DEVICE_KERNEL_INTEGRATOR_QUEUED_SHADOW_PATHS_ARRAY: + case DEVICE_KERNEL_INTEGRATOR_ACTIVE_PATHS_ARRAY: + case DEVICE_KERNEL_INTEGRATOR_TERMINATED_PATHS_ARRAY: + case DEVICE_KERNEL_INTEGRATOR_SORTED_PATHS_ARRAY: + case DEVICE_KERNEL_INTEGRATOR_COMPACT_PATHS_ARRAY: + /* See parall_active_index.h for why this amount of shared memory is needed. */ + shared_mem_bytes = (num_threads_per_block + 1) * sizeof(int); + break; + default: + break; + } + + /* Launch kernel. */ + hip_device_assert(hip_device_, + hipModuleLaunchKernel(hip_kernel.function, + num_blocks, + 1, + 1, + num_threads_per_block, + 1, + 1, + shared_mem_bytes, + hip_stream_, + args, + 0)); + return !(hip_device_->have_error()); +} + +bool HIPDeviceQueue::synchronize() +{ + if (hip_device_->have_error()) { + return false; + } + + const HIPContextScope scope(hip_device_); + hip_device_assert(hip_device_, hipStreamSynchronize(hip_stream_)); + debug_synchronize(); + + return !(hip_device_->have_error()); +} + +void HIPDeviceQueue::zero_to_device(device_memory &mem) +{ + assert(mem.type != MEM_GLOBAL && mem.type != MEM_TEXTURE); + + if (mem.memory_size() == 0) { + return; + } + + /* Allocate on demand. */ + if (mem.device_pointer == 0) { + hip_device_->mem_alloc(mem); + } + + /* Zero memory on device. */ + assert(mem.device_pointer != 0); + + const HIPContextScope scope(hip_device_); + hip_device_assert( + hip_device_, + hipMemsetD8Async((hipDeviceptr_t)mem.device_pointer, 0, mem.memory_size(), hip_stream_)); +} + +void HIPDeviceQueue::copy_to_device(device_memory &mem) +{ + assert(mem.type != MEM_GLOBAL && mem.type != MEM_TEXTURE); + + if (mem.memory_size() == 0) { + return; + } + + /* Allocate on demand. */ + if (mem.device_pointer == 0) { + hip_device_->mem_alloc(mem); + } + + assert(mem.device_pointer != 0); + assert(mem.host_pointer != nullptr); + + /* Copy memory to device. */ + const HIPContextScope scope(hip_device_); + hip_device_assert( + hip_device_, + hipMemcpyHtoDAsync( + (hipDeviceptr_t)mem.device_pointer, mem.host_pointer, mem.memory_size(), hip_stream_)); +} + +void HIPDeviceQueue::copy_from_device(device_memory &mem) +{ + assert(mem.type != MEM_GLOBAL && mem.type != MEM_TEXTURE); + + if (mem.memory_size() == 0) { + return; + } + + assert(mem.device_pointer != 0); + assert(mem.host_pointer != nullptr); + + /* Copy memory from device. */ + const HIPContextScope scope(hip_device_); + hip_device_assert( + hip_device_, + hipMemcpyDtoHAsync( + mem.host_pointer, (hipDeviceptr_t)mem.device_pointer, mem.memory_size(), hip_stream_)); +} + +// TODO : (Arya) Enable this after stabilizing dev branch +unique_ptr<DeviceGraphicsInterop> HIPDeviceQueue::graphics_interop_create() +{ + return make_unique<HIPDeviceGraphicsInterop>(this); +} + +CCL_NAMESPACE_END + +#endif /* WITH_HIP */ diff --git a/intern/cycles/device/hip/queue.h b/intern/cycles/device/hip/queue.h new file mode 100644 index 00000000000..04c8a5982ce --- /dev/null +++ b/intern/cycles/device/hip/queue.h @@ -0,0 +1,68 @@ +/* + * Copyright 2011-2021 Blender Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#ifdef WITH_HIP + +# include "device/device_kernel.h" +# include "device/device_memory.h" +# include "device/device_queue.h" + +# include "device/hip/util.h" + +CCL_NAMESPACE_BEGIN + +class HIPDevice; +class device_memory; + +/* Base class for HIP queues. */ +class HIPDeviceQueue : public DeviceQueue { + public: + HIPDeviceQueue(HIPDevice *device); + ~HIPDeviceQueue(); + + virtual int num_concurrent_states(const size_t state_size) const override; + virtual int num_concurrent_busy_states() const override; + + virtual void init_execution() override; + + virtual bool kernel_available(DeviceKernel kernel) const override; + + virtual bool enqueue(DeviceKernel kernel, const int work_size, void *args[]) override; + + virtual bool synchronize() override; + + virtual void zero_to_device(device_memory &mem) override; + virtual void copy_to_device(device_memory &mem) override; + virtual void copy_from_device(device_memory &mem) override; + + virtual hipStream_t stream() + { + return hip_stream_; + } + + // TODO : (Arya) Enable this after stabilizing the dev branch + virtual unique_ptr<DeviceGraphicsInterop> graphics_interop_create() override; + + protected: + HIPDevice *hip_device_; + hipStream_t hip_stream_; +}; + +CCL_NAMESPACE_END + +#endif /* WITH_HIP */ diff --git a/intern/cycles/device/hip/util.cpp b/intern/cycles/device/hip/util.cpp new file mode 100644 index 00000000000..44f52c4e17b --- /dev/null +++ b/intern/cycles/device/hip/util.cpp @@ -0,0 +1,61 @@ +/* + * Copyright 2011-2021 Blender Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef WITH_HIP + +# include "device/hip/util.h" +# include "device/hip/device_impl.h" + +CCL_NAMESPACE_BEGIN + +HIPContextScope::HIPContextScope(HIPDevice *device) : device(device) +{ + hip_device_assert(device, hipCtxPushCurrent(device->hipContext)); +} + +HIPContextScope::~HIPContextScope() +{ + hip_device_assert(device, hipCtxPopCurrent(NULL)); +} + +# ifndef WITH_HIP_DYNLOAD +const char *hipewErrorString(hipError_t result) +{ + /* We can only give error code here without major code duplication, that + * should be enough since dynamic loading is only being disabled by folks + * who knows what they're doing anyway. + * + * NOTE: Avoid call from several threads. + */ + static string error; + error = string_printf("%d", result); + return error.c_str(); +} + +const char *hipewCompilerPath() +{ + return CYCLES_HIP_HIPCC_EXECUTABLE; +} + +int hipewCompilerVersion() +{ + return (HIP_VERSION / 100) + (HIP_VERSION % 100 / 10); +} +# endif + +CCL_NAMESPACE_END + +#endif /* WITH_HIP */ diff --git a/intern/cycles/device/hip/util.h b/intern/cycles/device/hip/util.h new file mode 100644 index 00000000000..0db5174a3db --- /dev/null +++ b/intern/cycles/device/hip/util.h @@ -0,0 +1,63 @@ +/* + * Copyright 2011-2021 Blender Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#ifdef WITH_HIP + +# ifdef WITH_HIP_DYNLOAD +# include "hipew.h" +# endif + +CCL_NAMESPACE_BEGIN + +class HIPDevice; + +/* Utility to push/pop HIP context. */ +class HIPContextScope { + public: + HIPContextScope(HIPDevice *device); + ~HIPContextScope(); + + private: + HIPDevice *device; +}; + +/* Utility for checking return values of HIP function calls. */ +# define hip_device_assert(hip_device, stmt) \ + { \ + hipError_t result = stmt; \ + if (result != hipSuccess) { \ + const char *name = hipewErrorString(result); \ + hip_device->set_error( \ + string_printf("%s in %s (%s:%d)", name, #stmt, __FILE__, __LINE__)); \ + } \ + } \ + (void)0 + +# define hip_assert(stmt) hip_device_assert(this, stmt) + +# ifndef WITH_HIP_DYNLOAD +/* Transparently implement some functions, so majority of the file does not need + * to worry about difference between dynamically loaded and linked HIP at all. */ +const char *hipewErrorString(hipError_t result); +const char *hipewCompilerPath(); +int hipewCompilerVersion(); +# endif /* WITH_HIP_DYNLOAD */ + +CCL_NAMESPACE_END + +#endif /* WITH_HIP */ diff --git a/intern/cycles/device/multi/device.cpp b/intern/cycles/device/multi/device.cpp index 6dbcce2d9a5..4f995abf2c4 100644 --- a/intern/cycles/device/multi/device.cpp +++ b/intern/cycles/device/multi/device.cpp @@ -315,14 +315,14 @@ class MultiDevice : public Device { stats.mem_alloc(mem.device_size - existing_size); } - void mem_copy_from(device_memory &mem, int y, int w, int h, int elem) override + void mem_copy_from(device_memory &mem, size_t y, size_t w, size_t h, size_t elem) override { device_ptr key = mem.device_pointer; - int i = 0, sub_h = h / devices.size(); + size_t i = 0, sub_h = h / devices.size(); foreach (SubDevice &sub, devices) { - int sy = y + i * sub_h; - int sh = (i == (int)devices.size() - 1) ? h - sub_h * i : sub_h; + size_t sy = y + i * sub_h; + size_t sh = (i == (size_t)devices.size() - 1) ? h - sub_h * i : sub_h; SubDevice *owner_sub = find_matching_mem_device(key, sub); mem.device = owner_sub->device; diff --git a/intern/cycles/device/optix/device_impl.cpp b/intern/cycles/device/optix/device_impl.cpp index b54d423a183..49d4e22143f 100644 --- a/intern/cycles/device/optix/device_impl.cpp +++ b/intern/cycles/device/optix/device_impl.cpp @@ -315,6 +315,11 @@ bool OptiXDevice::load_kernels(const uint kernel_features) group_descs[PG_HITS].kind = OPTIX_PROGRAM_GROUP_KIND_HITGROUP; group_descs[PG_HITS].hitgroup.moduleAH = optix_module; group_descs[PG_HITS].hitgroup.entryFunctionNameAH = "__anyhit__kernel_optix_shadow_all_hit"; + group_descs[PG_HITV].kind = OPTIX_PROGRAM_GROUP_KIND_HITGROUP; + group_descs[PG_HITV].hitgroup.moduleCH = optix_module; + group_descs[PG_HITV].hitgroup.entryFunctionNameCH = "__closesthit__kernel_optix_hit"; + group_descs[PG_HITV].hitgroup.moduleAH = optix_module; + group_descs[PG_HITV].hitgroup.entryFunctionNameAH = "__anyhit__kernel_optix_volume_test"; if (kernel_features & KERNEL_FEATURE_HAIR) { if (kernel_features & KERNEL_FEATURE_HAIR_THICK) { @@ -397,6 +402,7 @@ bool OptiXDevice::load_kernels(const uint kernel_features) trace_css = std::max(trace_css, stack_size[PG_HITD].cssIS + stack_size[PG_HITD].cssAH); trace_css = std::max(trace_css, stack_size[PG_HITS].cssIS + stack_size[PG_HITS].cssAH); trace_css = std::max(trace_css, stack_size[PG_HITL].cssIS + stack_size[PG_HITL].cssAH); + trace_css = std::max(trace_css, stack_size[PG_HITV].cssIS + stack_size[PG_HITV].cssAH); trace_css = std::max(trace_css, stack_size[PG_HITD_MOTION].cssIS + stack_size[PG_HITD_MOTION].cssAH); trace_css = std::max(trace_css, @@ -421,6 +427,7 @@ bool OptiXDevice::load_kernels(const uint kernel_features) pipeline_groups.push_back(groups[PG_HITD]); pipeline_groups.push_back(groups[PG_HITS]); pipeline_groups.push_back(groups[PG_HITL]); + pipeline_groups.push_back(groups[PG_HITV]); if (motion_blur) { pipeline_groups.push_back(groups[PG_HITD_MOTION]); pipeline_groups.push_back(groups[PG_HITS_MOTION]); @@ -459,6 +466,7 @@ bool OptiXDevice::load_kernels(const uint kernel_features) pipeline_groups.push_back(groups[PG_HITD]); pipeline_groups.push_back(groups[PG_HITS]); pipeline_groups.push_back(groups[PG_HITL]); + pipeline_groups.push_back(groups[PG_HITV]); if (motion_blur) { pipeline_groups.push_back(groups[PG_HITD_MOTION]); pipeline_groups.push_back(groups[PG_HITS_MOTION]); @@ -1390,25 +1398,33 @@ void OptiXDevice::build_bvh(BVH *bvh, Progress &progress, bool refit) /* Set user instance ID to object index (but leave low bit blank). */ instance.instanceId = ob->get_device_index() << 1; - /* Have to have at least one bit in the mask, or else instance would always be culled. */ - instance.visibilityMask = 1; + /* Add some of the object visibility bits to the mask. + * __prim_visibility contains the combined visibility bits of all instances, so is not + * reliable if they differ between instances. But the OptiX visibility mask can only contain + * 8 bits, so have to trade-off here and select just a few important ones. + */ + instance.visibilityMask = ob->visibility_for_tracing() & 0xFF; - if (ob->get_geometry()->has_volume) { - /* Volumes have a special bit set in the visibility mask so a trace can mask only volumes. - */ - instance.visibilityMask |= 2; + /* Have to have at least one bit in the mask, or else instance would always be culled. */ + if (0 == instance.visibilityMask) { + instance.visibilityMask = 0xFF; } - if (ob->get_geometry()->geometry_type == Geometry::HAIR) { - /* Same applies to curves (so they can be skipped in local trace calls). */ - instance.visibilityMask |= 4; - - if (motion_blur && ob->get_geometry()->has_motion_blur() && - static_cast<const Hair *>(ob->get_geometry())->curve_shape == CURVE_THICK) { + if (ob->get_geometry()->geometry_type == Geometry::HAIR && + static_cast<const Hair *>(ob->get_geometry())->curve_shape == CURVE_THICK) { + if (motion_blur && ob->get_geometry()->has_motion_blur()) { /* Select between motion blur and non-motion blur built-in intersection module. */ instance.sbtOffset = PG_HITD_MOTION - PG_HITD; } } + else { + /* Can disable __anyhit__kernel_optix_visibility_test by default (except for thick curves, + * since it needs to filter out end-caps there). + * It is enabled where necessary (visibility mask exceeds 8 bits or the other any-hit + * programs like __anyhit__kernel_optix_shadow_all_hit) via OPTIX_RAY_FLAG_ENFORCE_ANYHIT. + */ + instance.flags = OPTIX_INSTANCE_FLAG_DISABLE_ANYHIT; + } /* Insert motion traversable if object has motion. */ if (motion_blur && ob->use_motion()) { @@ -1474,7 +1490,7 @@ void OptiXDevice::build_bvh(BVH *bvh, Progress &progress, bool refit) delete[] reinterpret_cast<uint8_t *>(&motion_transform); /* Disable instance transform if object uses motion transform already. */ - instance.flags = OPTIX_INSTANCE_FLAG_DISABLE_TRANSFORM; + instance.flags |= OPTIX_INSTANCE_FLAG_DISABLE_TRANSFORM; /* Get traversable handle to motion transform. */ optixConvertPointerToTraversableHandle(context, @@ -1491,7 +1507,7 @@ void OptiXDevice::build_bvh(BVH *bvh, Progress &progress, bool refit) } else { /* Disable instance transform if geometry already has it applied to vertex data. */ - instance.flags = OPTIX_INSTANCE_FLAG_DISABLE_TRANSFORM; + instance.flags |= OPTIX_INSTANCE_FLAG_DISABLE_TRANSFORM; /* Non-instanced objects read ID from 'prim_object', so distinguish * them from instanced objects with the low bit set. */ instance.instanceId |= 1; diff --git a/intern/cycles/device/optix/device_impl.h b/intern/cycles/device/optix/device_impl.h index 91ef52e0a5a..3695ac6afc2 100644 --- a/intern/cycles/device/optix/device_impl.h +++ b/intern/cycles/device/optix/device_impl.h @@ -40,6 +40,7 @@ enum { PG_HITD, /* Default hit group. */ PG_HITS, /* __SHADOW_RECORD_ALL__ hit group. */ PG_HITL, /* __BVH_LOCAL__ hit group (only used for triangles). */ + PG_HITV, /* __VOLUME__ hit group. */ PG_HITD_MOTION, PG_HITS_MOTION, PG_CALL_SVM_AO, @@ -51,7 +52,7 @@ enum { static const int MISS_PROGRAM_GROUP_OFFSET = PG_MISS; static const int NUM_MIS_PROGRAM_GROUPS = 1; static const int HIT_PROGAM_GROUP_OFFSET = PG_HITD; -static const int NUM_HIT_PROGRAM_GROUPS = 5; +static const int NUM_HIT_PROGRAM_GROUPS = 6; static const int CALLABLE_PROGRAM_GROUPS_BASE = PG_CALL_SVM_AO; static const int NUM_CALLABLE_PROGRAM_GROUPS = 3; diff --git a/intern/cycles/integrator/CMakeLists.txt b/intern/cycles/integrator/CMakeLists.txt index bfabd35d7c3..949254606b8 100644 --- a/intern/cycles/integrator/CMakeLists.txt +++ b/intern/cycles/integrator/CMakeLists.txt @@ -27,6 +27,8 @@ set(SRC pass_accessor.cpp pass_accessor_cpu.cpp pass_accessor_gpu.cpp + path_trace_display.cpp + path_trace_tile.cpp path_trace_work.cpp path_trace_work_cpu.cpp path_trace_work_gpu.cpp @@ -47,6 +49,8 @@ set(SRC_HEADERS pass_accessor.h pass_accessor_cpu.h pass_accessor_gpu.h + path_trace_display.h + path_trace_tile.h path_trace_work.h path_trace_work_cpu.h path_trace_work_gpu.h diff --git a/intern/cycles/integrator/path_trace.cpp b/intern/cycles/integrator/path_trace.cpp index b62a06aea43..7624b244175 100644 --- a/intern/cycles/integrator/path_trace.cpp +++ b/intern/cycles/integrator/path_trace.cpp @@ -19,8 +19,9 @@ #include "device/cpu/device.h" #include "device/device.h" #include "integrator/pass_accessor.h" +#include "integrator/path_trace_display.h" +#include "integrator/path_trace_tile.h" #include "integrator/render_scheduler.h" -#include "render/gpu_display.h" #include "render/pass.h" #include "render/scene.h" #include "render/tile.h" @@ -67,11 +68,11 @@ PathTrace::PathTrace(Device *device, PathTrace::~PathTrace() { /* Destroy any GPU resource which was used for graphics interop. - * Need to have access to the GPUDisplay as it is the only source of drawing context which is - * used for interop. */ - if (gpu_display_) { + * Need to have access to the PathTraceDisplay as it is the only source of drawing context which + * is used for interop. */ + if (display_) { for (auto &&path_trace_work : path_trace_works_) { - path_trace_work->destroy_gpu_resources(gpu_display_.get()); + path_trace_work->destroy_gpu_resources(display_.get()); } } } @@ -94,7 +95,7 @@ bool PathTrace::ready_to_reset() { /* The logic here is optimized for the best feedback in the viewport, which implies having a GPU * display. Of there is no such display, the logic here will break. */ - DCHECK(gpu_display_); + DCHECK(display_); /* The logic here tries to provide behavior which feels the most interactive feel to artists. * General idea is to be able to reset as quickly as possible, while still providing interactive @@ -126,8 +127,8 @@ void PathTrace::reset(const BufferParams &full_params, const BufferParams &big_t /* NOTE: GPU display checks for buffer modification and avoids unnecessary re-allocation. * It is requires to inform about reset whenever it happens, so that the redraw state tracking is * properly updated. */ - if (gpu_display_) { - gpu_display_->reset(full_params); + if (display_) { + display_->reset(full_params); } render_state_.has_denoised_result = false; @@ -244,7 +245,7 @@ static void foreach_sliced_buffer_params(const vector<unique_ptr<PathTraceWork>> const int slice_height = max(lround(height * weight), 1); /* Disallow negative values to deal with situations when there are more compute devices than - * scanlines. */ + * scan-lines. */ const int remaining_height = max(0, height - current_y); BufferParams slide_params = buffer_params; @@ -535,25 +536,35 @@ void PathTrace::denoise(const RenderWork &render_work) render_scheduler_.report_denoise_time(render_work, time_dt() - start_time); } -void PathTrace::set_gpu_display(unique_ptr<GPUDisplay> gpu_display) +void PathTrace::set_output_driver(unique_ptr<OutputDriver> driver) { - gpu_display_ = move(gpu_display); + output_driver_ = move(driver); } -void PathTrace::clear_gpu_display() +void PathTrace::set_display_driver(unique_ptr<DisplayDriver> driver) { - if (gpu_display_) { - gpu_display_->clear(); + if (driver) { + display_ = make_unique<PathTraceDisplay>(move(driver)); + } + else { + display_ = nullptr; + } +} + +void PathTrace::clear_display() +{ + if (display_) { + display_->clear(); } } void PathTrace::draw() { - if (!gpu_display_) { + if (!display_) { return; } - did_draw_after_reset_ |= gpu_display_->draw(); + did_draw_after_reset_ |= display_->draw(); } void PathTrace::update_display(const RenderWork &render_work) @@ -562,31 +573,32 @@ void PathTrace::update_display(const RenderWork &render_work) return; } - if (!gpu_display_ && !tile_buffer_update_cb) { + if (!display_ && !output_driver_) { VLOG(3) << "Ignore display update."; return; } if (full_params_.width == 0 || full_params_.height == 0) { - VLOG(3) << "Skipping GPUDisplay update due to 0 size of the render buffer."; + VLOG(3) << "Skipping PathTraceDisplay update due to 0 size of the render buffer."; return; } const double start_time = time_dt(); - if (tile_buffer_update_cb) { + if (output_driver_) { VLOG(3) << "Invoke buffer update callback."; - tile_buffer_update_cb(); + PathTraceTile tile(*this); + output_driver_->update_render_tile(tile); } - if (gpu_display_) { + if (display_) { VLOG(3) << "Perform copy to GPUDisplay work."; const int resolution_divider = render_work.resolution_divider; const int texture_width = max(1, full_params_.width / resolution_divider); const int texture_height = max(1, full_params_.height / resolution_divider); - if (!gpu_display_->update_begin(texture_width, texture_height)) { + if (!display_->update_begin(texture_width, texture_height)) { LOG(ERROR) << "Error beginning GPUDisplay update."; return; } @@ -600,10 +612,10 @@ void PathTrace::update_display(const RenderWork &render_work) * all works in parallel. */ const int num_samples = get_num_samples_in_buffer(); for (auto &&path_trace_work : path_trace_works_) { - path_trace_work->copy_to_gpu_display(gpu_display_.get(), pass_mode, num_samples); + path_trace_work->copy_to_display(display_.get(), pass_mode, num_samples); } - gpu_display_->update_end(); + display_->update_end(); } render_scheduler_.report_display_update_time(render_work, time_dt() - start_time); @@ -753,20 +765,26 @@ bool PathTrace::is_cancel_requested() void PathTrace::tile_buffer_write() { - if (!tile_buffer_write_cb) { + if (!output_driver_) { return; } - tile_buffer_write_cb(); + PathTraceTile tile(*this); + output_driver_->write_render_tile(tile); } void PathTrace::tile_buffer_read() { - if (!tile_buffer_read_cb) { + if (!device_scene_->data.bake.use) { return; } - if (tile_buffer_read_cb()) { + if (!output_driver_) { + return; + } + + PathTraceTile tile(*this); + if (output_driver_->read_render_tile(tile)) { tbb::parallel_for_each(path_trace_works_, [](unique_ptr<PathTraceWork> &path_trace_work) { path_trace_work->copy_render_buffers_to_device(); }); @@ -801,7 +819,7 @@ void PathTrace::tile_buffer_write_to_disk() } if (!tile_manager_.write_tile(*buffers)) { - LOG(ERROR) << "Error writing tile to file."; + device_->set_error("Error writing tile to file"); } } @@ -894,7 +912,14 @@ void PathTrace::process_full_buffer_from_disk(string_view filename) DenoiseParams denoise_params; if (!tile_manager_.read_full_buffer_from_disk(filename, &full_frame_buffers, &denoise_params)) { - LOG(ERROR) << "Error reading tiles from file."; + const string error_message = "Error reading tiles from file"; + if (progress_) { + progress_->set_error(error_message); + progress_->set_cancel(error_message); + } + else { + LOG(ERROR) << error_message; + } return; } @@ -998,6 +1023,11 @@ int2 PathTrace::get_render_tile_offset() const return make_int2(tile.x, tile.y); } +int2 PathTrace::get_render_size() const +{ + return tile_manager_.get_size(); +} + const BufferParams &PathTrace::get_render_tile_params() const { if (full_frame_state_.render_buffers) { @@ -1028,6 +1058,8 @@ static const char *device_type_for_description(const DeviceType type) return "CUDA"; case DEVICE_OPTIX: return "OptiX"; + case DEVICE_HIP: + return "HIP"; case DEVICE_DUMMY: return "Dummy"; case DEVICE_MULTI: diff --git a/intern/cycles/integrator/path_trace.h b/intern/cycles/integrator/path_trace.h index fc7713e6df9..dbb22c204d9 100644 --- a/intern/cycles/integrator/path_trace.h +++ b/intern/cycles/integrator/path_trace.h @@ -31,12 +31,14 @@ CCL_NAMESPACE_BEGIN class AdaptiveSampling; class Device; class DeviceScene; +class DisplayDriver; class Film; class RenderBuffers; class RenderScheduler; class RenderWork; +class PathTraceDisplay; +class OutputDriver; class Progress; -class GPUDisplay; class TileManager; /* PathTrace class takes care of kernel graph and scheduling on a (multi)device. It takes care of @@ -98,13 +100,16 @@ class PathTrace { * Use this to configure the adaptive sampler before rendering any samples. */ void set_adaptive_sampling(const AdaptiveSampling &adaptive_sampling); - /* Set GPU display which takes care of drawing the render result. */ - void set_gpu_display(unique_ptr<GPUDisplay> gpu_display); + /* Sets output driver for render buffer output. */ + void set_output_driver(unique_ptr<OutputDriver> driver); - /* Clear the GPU display by filling it in with all zeroes. */ - void clear_gpu_display(); + /* Set display driver for interactive render buffer display. */ + void set_display_driver(unique_ptr<DisplayDriver> driver); - /* Perform drawing of the current state of the GPUDisplay. */ + /* Clear the display buffer by filling it in with all zeroes. */ + void clear_display(); + + /* Perform drawing of the current state of the DisplayDriver. */ void draw(); /* Cancel rendering process as soon as possible, without waiting for full tile to be sampled. @@ -157,6 +162,7 @@ class PathTrace { * instead. */ int2 get_render_tile_size() const; int2 get_render_tile_offset() const; + int2 get_render_size() const; /* Get buffer parameters of the current tile. * @@ -168,18 +174,6 @@ class PathTrace { * times, and so on. */ string full_report() const; - /* Callback which communicates an updates state of the render buffer of the current big tile. - * Is called during path tracing to communicate work-in-progress state of the final buffer. */ - function<void(void)> tile_buffer_update_cb; - - /* Callback which communicates final rendered buffer. Is called after path-tracing is done. */ - function<void(void)> tile_buffer_write_cb; - - /* Callback which initializes rendered buffer. Is called before path-tracing starts. - * - * This is used for baking. */ - function<bool(void)> tile_buffer_read_cb; - /* Callback which is called to report current rendering progress. * * It is supposed to be cheaper than buffer update/write, hence can be called more often. @@ -252,7 +246,11 @@ class PathTrace { RenderScheduler &render_scheduler_; TileManager &tile_manager_; - unique_ptr<GPUDisplay> gpu_display_; + /* Display driver for interactive render buffer display. */ + unique_ptr<PathTraceDisplay> display_; + + /* Output driver to write render buffer to. */ + unique_ptr<OutputDriver> output_driver_; /* Per-compute device descriptors of work which is responsible for path tracing on its configured * device. */ @@ -286,7 +284,7 @@ class PathTrace { /* Parameters of the big tile with the current resolution divider applied. */ BufferParams effective_big_tile_params; - /* Denosier was run and there are denoised versions of the passes in the render buffers. */ + /* Denoiser was run and there are denoised versions of the passes in the render buffers. */ bool has_denoised_result = false; /* Current tile has been written (to either disk or callback. diff --git a/intern/cycles/render/gpu_display.cpp b/intern/cycles/integrator/path_trace_display.cpp index a8f0cc50583..28f0a7f7745 100644 --- a/intern/cycles/render/gpu_display.cpp +++ b/intern/cycles/integrator/path_trace_display.cpp @@ -14,20 +14,25 @@ * limitations under the License. */ -#include "render/gpu_display.h" +#include "integrator/path_trace_display.h" #include "render/buffers.h" + #include "util/util_logging.h" CCL_NAMESPACE_BEGIN -void GPUDisplay::reset(const BufferParams &buffer_params) +PathTraceDisplay::PathTraceDisplay(unique_ptr<DisplayDriver> driver) : driver_(move(driver)) +{ +} + +void PathTraceDisplay::reset(const BufferParams &buffer_params) { thread_scoped_lock lock(mutex_); - const GPUDisplayParams old_params = params_; + const DisplayDriver::Params old_params = params_; - params_.offset = make_int2(buffer_params.full_x, buffer_params.full_y); + params_.full_offset = make_int2(buffer_params.full_x, buffer_params.full_y); params_.full_size = make_int2(buffer_params.full_width, buffer_params.full_height); params_.size = make_int2(buffer_params.width, buffer_params.height); @@ -44,7 +49,7 @@ void GPUDisplay::reset(const BufferParams &buffer_params) texture_state_.is_outdated = true; } -void GPUDisplay::mark_texture_updated() +void PathTraceDisplay::mark_texture_updated() { texture_state_.is_outdated = false; texture_state_.is_usable = true; @@ -54,7 +59,7 @@ void GPUDisplay::mark_texture_updated() * Update procedure. */ -bool GPUDisplay::update_begin(int texture_width, int texture_height) +bool PathTraceDisplay::update_begin(int texture_width, int texture_height) { DCHECK(!update_state_.is_active); @@ -66,15 +71,15 @@ bool GPUDisplay::update_begin(int texture_width, int texture_height) /* Get parameters within a mutex lock, to avoid reset() modifying them at the same time. * The update itself is non-blocking however, for better performance and to avoid * potential deadlocks due to locks held by the subclass. */ - GPUDisplayParams params; + DisplayDriver::Params params; { thread_scoped_lock lock(mutex_); params = params_; texture_state_.size = make_int2(texture_width, texture_height); } - if (!do_update_begin(params, texture_width, texture_height)) { - LOG(ERROR) << "GPUDisplay implementation could not begin update."; + if (!driver_->update_begin(params, texture_width, texture_height)) { + LOG(ERROR) << "PathTraceDisplay implementation could not begin update."; return false; } @@ -83,7 +88,7 @@ bool GPUDisplay::update_begin(int texture_width, int texture_height) return true; } -void GPUDisplay::update_end() +void PathTraceDisplay::update_end() { DCHECK(update_state_.is_active); @@ -92,12 +97,12 @@ void GPUDisplay::update_end() return; } - do_update_end(); + driver_->update_end(); update_state_.is_active = false; } -int2 GPUDisplay::get_texture_size() const +int2 PathTraceDisplay::get_texture_size() const { return texture_state_.size; } @@ -106,25 +111,54 @@ int2 GPUDisplay::get_texture_size() const * Texture update from CPU buffer. */ -void GPUDisplay::copy_pixels_to_texture( +void PathTraceDisplay::copy_pixels_to_texture( const half4 *rgba_pixels, int texture_x, int texture_y, int pixels_width, int pixels_height) { DCHECK(update_state_.is_active); if (!update_state_.is_active) { - LOG(ERROR) << "Attempt to copy pixels data outside of GPUDisplay update."; + LOG(ERROR) << "Attempt to copy pixels data outside of PathTraceDisplay update."; return; } mark_texture_updated(); - do_copy_pixels_to_texture(rgba_pixels, texture_x, texture_y, pixels_width, pixels_height); + + /* This call copies pixels to a mapped texture buffer which is typically much cheaper from CPU + * time point of view than to copy data directly to a texture. + * + * The possible downside of this approach is that it might require a higher peak memory when + * doing partial updates of the texture (although, in practice even partial updates might peak + * with a full-frame buffer stored on the CPU if the GPU is currently occupied). */ + half4 *mapped_rgba_pixels = map_texture_buffer(); + if (!mapped_rgba_pixels) { + return; + } + + const int texture_width = texture_state_.size.x; + const int texture_height = texture_state_.size.y; + + if (texture_x == 0 && texture_y == 0 && pixels_width == texture_width && + pixels_height == texture_height) { + const size_t size_in_bytes = sizeof(half4) * texture_width * texture_height; + memcpy(mapped_rgba_pixels, rgba_pixels, size_in_bytes); + } + else { + const half4 *rgba_row = rgba_pixels; + half4 *mapped_rgba_row = mapped_rgba_pixels + texture_y * texture_width + texture_x; + for (int y = 0; y < pixels_height; + ++y, rgba_row += pixels_width, mapped_rgba_row += texture_width) { + memcpy(mapped_rgba_row, rgba_row, sizeof(half4) * pixels_width); + } + } + + unmap_texture_buffer(); } /* -------------------------------------------------------------------- * Texture buffer mapping. */ -half4 *GPUDisplay::map_texture_buffer() +half4 *PathTraceDisplay::map_texture_buffer() { DCHECK(!texture_buffer_state_.is_mapped); DCHECK(update_state_.is_active); @@ -135,11 +169,11 @@ half4 *GPUDisplay::map_texture_buffer() } if (!update_state_.is_active) { - LOG(ERROR) << "Attempt to copy pixels data outside of GPUDisplay update."; + LOG(ERROR) << "Attempt to copy pixels data outside of PathTraceDisplay update."; return nullptr; } - half4 *mapped_rgba_pixels = do_map_texture_buffer(); + half4 *mapped_rgba_pixels = driver_->map_texture_buffer(); if (mapped_rgba_pixels) { texture_buffer_state_.is_mapped = true; @@ -148,7 +182,7 @@ half4 *GPUDisplay::map_texture_buffer() return mapped_rgba_pixels; } -void GPUDisplay::unmap_texture_buffer() +void PathTraceDisplay::unmap_texture_buffer() { DCHECK(texture_buffer_state_.is_mapped); @@ -160,14 +194,14 @@ void GPUDisplay::unmap_texture_buffer() texture_buffer_state_.is_mapped = false; mark_texture_updated(); - do_unmap_texture_buffer(); + driver_->unmap_texture_buffer(); } /* -------------------------------------------------------------------- * Graphics interoperability. */ -DeviceGraphicsInteropDestination GPUDisplay::graphics_interop_get() +DisplayDriver::GraphicsInterop PathTraceDisplay::graphics_interop_get() { DCHECK(!texture_buffer_state_.is_mapped); DCHECK(update_state_.is_active); @@ -175,38 +209,45 @@ DeviceGraphicsInteropDestination GPUDisplay::graphics_interop_get() if (texture_buffer_state_.is_mapped) { LOG(ERROR) << "Attempt to use graphics interoperability mode while the texture buffer is mapped."; - return DeviceGraphicsInteropDestination(); + return DisplayDriver::GraphicsInterop(); } if (!update_state_.is_active) { - LOG(ERROR) << "Attempt to use graphics interoperability outside of GPUDisplay update."; - return DeviceGraphicsInteropDestination(); + LOG(ERROR) << "Attempt to use graphics interoperability outside of PathTraceDisplay update."; + return DisplayDriver::GraphicsInterop(); } /* Assume that interop will write new values to the texture. */ mark_texture_updated(); - return do_graphics_interop_get(); + return driver_->graphics_interop_get(); } -void GPUDisplay::graphics_interop_activate() +void PathTraceDisplay::graphics_interop_activate() { + driver_->graphics_interop_activate(); } -void GPUDisplay::graphics_interop_deactivate() +void PathTraceDisplay::graphics_interop_deactivate() { + driver_->graphics_interop_deactivate(); } /* -------------------------------------------------------------------- * Drawing. */ -bool GPUDisplay::draw() +void PathTraceDisplay::clear() +{ + driver_->clear(); +} + +bool PathTraceDisplay::draw() { /* Get parameters within a mutex lock, to avoid reset() modifying them at the same time. * The drawing itself is non-blocking however, for better performance and to avoid * potential deadlocks due to locks held by the subclass. */ - GPUDisplayParams params; + DisplayDriver::Params params; bool is_usable; bool is_outdated; @@ -218,7 +259,7 @@ bool GPUDisplay::draw() } if (is_usable) { - do_draw(params); + driver_->draw(params); } return !is_outdated; diff --git a/intern/cycles/render/gpu_display.h b/intern/cycles/integrator/path_trace_display.h index a01348d28d5..24aaa0df6b1 100644 --- a/intern/cycles/render/gpu_display.h +++ b/intern/cycles/integrator/path_trace_display.h @@ -16,52 +16,30 @@ #pragma once -#include "device/device_graphics_interop.h" +#include "render/display_driver.h" + #include "util/util_half.h" #include "util/util_thread.h" #include "util/util_types.h" +#include "util/util_unique_ptr.h" CCL_NAMESPACE_BEGIN class BufferParams; -/* GPUDisplay class takes care of drawing render result in a viewport. The render result is stored - * in a GPU-side texture, which is updated from a path tracer and drawn by an application. +/* PathTraceDisplay is used for efficient render buffer display. * - * The base GPUDisplay does some special texture state tracking, which allows render Session to - * make decisions on whether reset for an updated state is possible or not. This state should only - * be tracked in a base class and a particular implementation should not worry about it. + * The host applications implements a DisplayDriver, storing a render pass in a GPU-side + * textures. This texture is continuously updated by the path tracer and drawn by the host + * application. * - * The subclasses should only implement the pure virtual methods, which allows them to not worry - * about parent method calls, which helps them to be as small and reliable as possible. */ - -class GPUDisplayParams { - public: - /* Offset of the display within a viewport. - * For example, set to a lower-bottom corner of border render in Blender's viewport. */ - int2 offset = make_int2(0, 0); - - /* Full viewport size. - * - * NOTE: Is not affected by the resolution divider. */ - int2 full_size = make_int2(0, 0); - - /* Effective vieport size. - * In the case of border render, size of the border rectangle. - * - * NOTE: Is not affected by the resolution divider. */ - int2 size = make_int2(0, 0); - - bool modified(const GPUDisplayParams &other) const - { - return !(offset == other.offset && full_size == other.full_size && size == other.size); - } -}; + * PathTraceDisplay is a wrapper around the DisplayDriver, adding thread safety, state tracking + * and error checking. */ -class GPUDisplay { +class PathTraceDisplay { public: - GPUDisplay() = default; - virtual ~GPUDisplay() = default; + PathTraceDisplay(unique_ptr<DisplayDriver> driver); + virtual ~PathTraceDisplay() = default; /* Reset the display for the new state of render session. Is called whenever session is reset, * which happens on changes like viewport navigation or viewport dimension change. @@ -69,11 +47,6 @@ class GPUDisplay { * This call will configure parameters for a changed buffer and reset the texture state. */ void reset(const BufferParams &buffer_params); - const GPUDisplayParams &get_params() const - { - return params_; - } - /* -------------------------------------------------------------------- * Update procedure. * @@ -94,7 +67,8 @@ class GPUDisplay { /* -------------------------------------------------------------------- * Texture update from CPU buffer. * - * NOTE: The GPUDisplay should be marked for an update being in process with `update_begin()`. + * NOTE: The PathTraceDisplay should be marked for an update being in process with + * `update_begin()`. * * Most portable implementation, which must be supported by all platforms. Might not be the most * efficient one. @@ -115,7 +89,8 @@ class GPUDisplay { * This functionality is used to update GPU-side texture content without need to maintain CPU * side buffer on the caller. * - * NOTE: The GPUDisplay should be marked for an update being in process with `update_begin()`. + * NOTE: The PathTraceDisplay should be marked for an update being in process with + * `update_begin()`. * * NOTE: Texture buffer can not be mapped while graphics interoperability is active. This means * that `map_texture_buffer()` is not allowed between `graphics_interop_begin()` and @@ -145,14 +120,14 @@ class GPUDisplay { * that `graphics_interop_get()` is not allowed between `map_texture_buffer()` and * `unmap_texture_buffer()` calls. */ - /* Get GPUDisplay graphics interoperability information which acts as a destination for the + /* Get PathTraceDisplay graphics interoperability information which acts as a destination for the * device API. */ - DeviceGraphicsInteropDestination graphics_interop_get(); + DisplayDriver::GraphicsInterop graphics_interop_get(); /* (De)activate GPU display for graphics interoperability outside of regular display update * routines. */ - virtual void graphics_interop_activate(); - virtual void graphics_interop_deactivate(); + void graphics_interop_activate(); + void graphics_interop_deactivate(); /* -------------------------------------------------------------------- * Drawing. @@ -163,47 +138,26 @@ class GPUDisplay { * This call might happen in parallel with draw, but can never happen in parallel with the * update. * - * The actual zero-ing can be deferred to a later moment. What is important is that after clear + * The actual zeroing can be deferred to a later moment. What is important is that after clear * and before pixels update the drawing texture will be fully empty, and that partial update * after clear will write new pixel values for an updating area, leaving everything else zeroed. * * If the GPU display supports graphics interoperability then the zeroing the display is to be - * delegated to the device via the `DeviceGraphicsInteropDestination`. */ - virtual void clear() = 0; + * delegated to the device via the `DisplayDriver::GraphicsInterop`. */ + void clear(); /* Draw the current state of the texture. * * Returns true if this call did draw an updated state of the texture. */ bool draw(); - protected: - /* Implementation-specific calls which subclasses are to implement. - * These `do_foo()` method corresponds to their `foo()` calls, but they are purely virtual to - * simplify their particular implementation. */ - virtual bool do_update_begin(const GPUDisplayParams ¶ms, - int texture_width, - int texture_height) = 0; - virtual void do_update_end() = 0; - - virtual void do_copy_pixels_to_texture(const half4 *rgba_pixels, - int texture_x, - int texture_y, - int pixels_width, - int pixels_height) = 0; - - virtual half4 *do_map_texture_buffer() = 0; - virtual void do_unmap_texture_buffer() = 0; - - /* Note that this might be called in parallel to do_update_begin() and do_update_end(), - * the subclass is responsible for appropriate mutex locks to avoid multiple threads - * editing and drawing the texture at the same time. */ - virtual void do_draw(const GPUDisplayParams ¶ms) = 0; - - virtual DeviceGraphicsInteropDestination do_graphics_interop_get() = 0; - private: + /* Display driver implemented by the host application. */ + unique_ptr<DisplayDriver> driver_; + + /* Current display parameters */ thread_mutex mutex_; - GPUDisplayParams params_; + DisplayDriver::Params params_; /* Mark texture as its content has been updated. * Used from places which knows that the texture content has been brought up-to-date, so that the diff --git a/intern/cycles/integrator/path_trace_tile.cpp b/intern/cycles/integrator/path_trace_tile.cpp new file mode 100644 index 00000000000..540f4aa5f68 --- /dev/null +++ b/intern/cycles/integrator/path_trace_tile.cpp @@ -0,0 +1,107 @@ +/* + * Copyright 2021 Blender Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "integrator/path_trace_tile.h" +#include "integrator/pass_accessor_cpu.h" +#include "integrator/path_trace.h" + +#include "render/buffers.h" +#include "render/film.h" +#include "render/pass.h" +#include "render/scene.h" + +CCL_NAMESPACE_BEGIN + +PathTraceTile::PathTraceTile(PathTrace &path_trace) + : OutputDriver::Tile(path_trace.get_render_tile_offset(), + path_trace.get_render_tile_size(), + path_trace.get_render_size(), + path_trace.get_render_tile_params().layer, + path_trace.get_render_tile_params().view), + path_trace_(path_trace), + copied_from_device_(false) +{ +} + +bool PathTraceTile::get_pass_pixels(const string_view pass_name, + const int num_channels, + float *pixels) const +{ + /* NOTE: The code relies on a fact that session is fully update and no scene/buffer modification + * is happening while this function runs. */ + + if (!copied_from_device_) { + /* Copy from device on demand. */ + path_trace_.copy_render_tile_from_device(); + const_cast<PathTraceTile *>(this)->copied_from_device_ = true; + } + + const BufferParams &buffer_params = path_trace_.get_render_tile_params(); + + const BufferPass *pass = buffer_params.find_pass(pass_name); + if (pass == nullptr) { + return false; + } + + const bool has_denoised_result = path_trace_.has_denoised_result(); + if (pass->mode == PassMode::DENOISED && !has_denoised_result) { + pass = buffer_params.find_pass(pass->type); + if (pass == nullptr) { + /* Happens when denoised result pass is requested but is never written by the kernel. */ + return false; + } + } + + pass = buffer_params.get_actual_display_pass(pass); + + const float exposure = buffer_params.exposure; + const int num_samples = path_trace_.get_num_render_tile_samples(); + + PassAccessor::PassAccessInfo pass_access_info(*pass); + pass_access_info.use_approximate_shadow_catcher = buffer_params.use_approximate_shadow_catcher; + pass_access_info.use_approximate_shadow_catcher_background = + pass_access_info.use_approximate_shadow_catcher && !buffer_params.use_transparent_background; + + const PassAccessorCPU pass_accessor(pass_access_info, exposure, num_samples); + const PassAccessor::Destination destination(pixels, num_channels); + + return path_trace_.get_render_tile_pixels(pass_accessor, destination); +} + +bool PathTraceTile::set_pass_pixels(const string_view pass_name, + const int num_channels, + const float *pixels) const +{ + /* NOTE: The code relies on a fact that session is fully update and no scene/buffer modification + * is happening while this function runs. */ + + const BufferParams &buffer_params = path_trace_.get_render_tile_params(); + const BufferPass *pass = buffer_params.find_pass(pass_name); + if (!pass) { + return false; + } + + const float exposure = buffer_params.exposure; + const int num_samples = 1; + + const PassAccessor::PassAccessInfo pass_access_info(*pass); + PassAccessorCPU pass_accessor(pass_access_info, exposure, num_samples); + PassAccessor::Source source(pixels, num_channels); + + return path_trace_.set_render_tile_pixels(pass_accessor, source); +} + +CCL_NAMESPACE_END diff --git a/intern/cycles/integrator/path_trace_tile.h b/intern/cycles/integrator/path_trace_tile.h new file mode 100644 index 00000000000..fd3e2969f6c --- /dev/null +++ b/intern/cycles/integrator/path_trace_tile.h @@ -0,0 +1,43 @@ +/* + * Copyright 2021 Blender Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "render/output_driver.h" + +CCL_NAMESPACE_BEGIN + +/* PathTraceTile + * + * Implementation of OutputDriver::Tile interface for path tracer. */ + +class PathTrace; + +class PathTraceTile : public OutputDriver::Tile { + public: + PathTraceTile(PathTrace &path_trace); + + bool get_pass_pixels(const string_view pass_name, const int num_channels, float *pixels) const; + bool set_pass_pixels(const string_view pass_name, + const int num_channels, + const float *pixels) const; + + private: + PathTrace &path_trace_; + bool copied_from_device_; +}; + +CCL_NAMESPACE_END diff --git a/intern/cycles/integrator/path_trace_work.cpp b/intern/cycles/integrator/path_trace_work.cpp index d9634acac10..c29177907c9 100644 --- a/intern/cycles/integrator/path_trace_work.cpp +++ b/intern/cycles/integrator/path_trace_work.cpp @@ -16,12 +16,12 @@ #include "device/device.h" +#include "integrator/path_trace_display.h" #include "integrator/path_trace_work.h" #include "integrator/path_trace_work_cpu.h" #include "integrator/path_trace_work_gpu.h" #include "render/buffers.h" #include "render/film.h" -#include "render/gpu_display.h" #include "render/scene.h" #include "kernel/kernel_types.h" @@ -185,12 +185,12 @@ PassAccessor::PassAccessInfo PathTraceWork::get_display_pass_access_info(PassMod return pass_access_info; } -PassAccessor::Destination PathTraceWork::get_gpu_display_destination_template( - const GPUDisplay *gpu_display) const +PassAccessor::Destination PathTraceWork::get_display_destination_template( + const PathTraceDisplay *display) const { PassAccessor::Destination destination(film_->get_display_pass()); - const int2 display_texture_size = gpu_display->get_texture_size(); + const int2 display_texture_size = display->get_texture_size(); const int texture_x = effective_buffer_params_.full_x - effective_full_params_.full_x; const int texture_y = effective_buffer_params_.full_y - effective_full_params_.full_y; diff --git a/intern/cycles/integrator/path_trace_work.h b/intern/cycles/integrator/path_trace_work.h index 8c9c8811199..404165b7c55 100644 --- a/intern/cycles/integrator/path_trace_work.h +++ b/intern/cycles/integrator/path_trace_work.h @@ -28,7 +28,7 @@ class BufferParams; class Device; class DeviceScene; class Film; -class GPUDisplay; +class PathTraceDisplay; class RenderBuffers; class PathTraceWork { @@ -83,11 +83,9 @@ class PathTraceWork { * noisy pass mode will be passed here when it is known that the buffer does not have denoised * passes yet (because denoiser did not run). If the denoised pass is requested and denoiser is * not used then this function will fall-back to the noisy pass instead. */ - virtual void copy_to_gpu_display(GPUDisplay *gpu_display, - PassMode pass_mode, - int num_samples) = 0; + virtual void copy_to_display(PathTraceDisplay *display, PassMode pass_mode, int num_samples) = 0; - virtual void destroy_gpu_resources(GPUDisplay *gpu_display) = 0; + virtual void destroy_gpu_resources(PathTraceDisplay *display) = 0; /* Copy data from/to given render buffers. * Will copy pixels from a corresponding place (from multi-device point of view) of the render @@ -104,7 +102,7 @@ class PathTraceWork { * - Copies work's render buffer to its device. */ void copy_from_render_buffers(const RenderBuffers *render_buffers); - /* Special version of the `copy_from_render_buffers()` which only copies denosied passes from the + /* Special version of the `copy_from_render_buffers()` which only copies denoised passes from the * given render buffers, leaving rest of the passes. * * Same notes about device copying applies to this call as well. */ @@ -162,8 +160,8 @@ class PathTraceWork { /* Get destination which offset and stride are configured so that writing to it will write to a * proper location of GPU display texture, taking current tile and device slice into account. */ - PassAccessor::Destination get_gpu_display_destination_template( - const GPUDisplay *gpu_display) const; + PassAccessor::Destination get_display_destination_template( + const PathTraceDisplay *display) const; /* Device which will be used for path tracing. * Note that it is an actual render device (and never is a multi-device). */ diff --git a/intern/cycles/integrator/path_trace_work_cpu.cpp b/intern/cycles/integrator/path_trace_work_cpu.cpp index b9a33b64051..18a5365453d 100644 --- a/intern/cycles/integrator/path_trace_work_cpu.cpp +++ b/intern/cycles/integrator/path_trace_work_cpu.cpp @@ -19,10 +19,12 @@ #include "device/cpu/kernel.h" #include "device/device.h" +#include "kernel/kernel_path_state.h" + #include "integrator/pass_accessor_cpu.h" +#include "integrator/path_trace_display.h" #include "render/buffers.h" -#include "render/gpu_display.h" #include "render/scene.h" #include "util/util_atomic.h" @@ -116,13 +118,17 @@ void PathTraceWorkCPU::render_samples_full_pipeline(KernelGlobals *kernel_global const KernelWorkTile &work_tile, const int samples_num) { - const bool has_shadow_catcher = device_scene_->data.integrator.has_shadow_catcher; const bool has_bake = device_scene_->data.bake.use; - IntegratorStateCPU integrator_states[2] = {}; + IntegratorStateCPU integrator_states[2]; IntegratorStateCPU *state = &integrator_states[0]; - IntegratorStateCPU *shadow_catcher_state = &integrator_states[1]; + IntegratorStateCPU *shadow_catcher_state = nullptr; + + if (device_scene_->data.integrator.has_shadow_catcher) { + shadow_catcher_state = &integrator_states[1]; + path_state_init_queues(kernel_globals, shadow_catcher_state); + } KernelWorkTile sample_work_tile = work_tile; float *render_buffer = buffers_->buffer.data(); @@ -147,7 +153,7 @@ void PathTraceWorkCPU::render_samples_full_pipeline(KernelGlobals *kernel_global kernels_.integrator_megakernel(kernel_globals, state, render_buffer); - if (has_shadow_catcher) { + if (shadow_catcher_state) { kernels_.integrator_megakernel(kernel_globals, shadow_catcher_state, render_buffer); } @@ -155,14 +161,14 @@ void PathTraceWorkCPU::render_samples_full_pipeline(KernelGlobals *kernel_global } } -void PathTraceWorkCPU::copy_to_gpu_display(GPUDisplay *gpu_display, - PassMode pass_mode, - int num_samples) +void PathTraceWorkCPU::copy_to_display(PathTraceDisplay *display, + PassMode pass_mode, + int num_samples) { - half4 *rgba_half = gpu_display->map_texture_buffer(); + half4 *rgba_half = display->map_texture_buffer(); if (!rgba_half) { - /* TODO(sergey): Look into using copy_to_gpu_display() if mapping failed. Might be needed for - * some implementations of GPUDisplay which can not map memory? */ + /* TODO(sergey): Look into using copy_to_display() if mapping failed. Might be needed for + * some implementations of PathTraceDisplay which can not map memory? */ return; } @@ -172,7 +178,7 @@ void PathTraceWorkCPU::copy_to_gpu_display(GPUDisplay *gpu_display, const PassAccessorCPU pass_accessor(pass_access_info, kfilm.exposure, num_samples); - PassAccessor::Destination destination = get_gpu_display_destination_template(gpu_display); + PassAccessor::Destination destination = get_display_destination_template(display); destination.pixels_half_rgba = rgba_half; tbb::task_arena local_arena = local_tbb_arena_create(device_); @@ -180,10 +186,10 @@ void PathTraceWorkCPU::copy_to_gpu_display(GPUDisplay *gpu_display, pass_accessor.get_render_tile_pixels(buffers_.get(), effective_buffer_params_, destination); }); - gpu_display->unmap_texture_buffer(); + display->unmap_texture_buffer(); } -void PathTraceWorkCPU::destroy_gpu_resources(GPUDisplay * /*gpu_display*/) +void PathTraceWorkCPU::destroy_gpu_resources(PathTraceDisplay * /*display*/) { } diff --git a/intern/cycles/integrator/path_trace_work_cpu.h b/intern/cycles/integrator/path_trace_work_cpu.h index ab729bbf879..d011e8d05bd 100644 --- a/intern/cycles/integrator/path_trace_work_cpu.h +++ b/intern/cycles/integrator/path_trace_work_cpu.h @@ -50,10 +50,10 @@ class PathTraceWorkCPU : public PathTraceWork { int start_sample, int samples_num) override; - virtual void copy_to_gpu_display(GPUDisplay *gpu_display, - PassMode pass_mode, - int num_samples) override; - virtual void destroy_gpu_resources(GPUDisplay *gpu_display) override; + virtual void copy_to_display(PathTraceDisplay *display, + PassMode pass_mode, + int num_samples) override; + virtual void destroy_gpu_resources(PathTraceDisplay *display) override; virtual bool copy_render_buffers_from_device() override; virtual bool copy_render_buffers_to_device() override; diff --git a/intern/cycles/integrator/path_trace_work_gpu.cpp b/intern/cycles/integrator/path_trace_work_gpu.cpp index 135466becc6..17c49f244d2 100644 --- a/intern/cycles/integrator/path_trace_work_gpu.cpp +++ b/intern/cycles/integrator/path_trace_work_gpu.cpp @@ -15,12 +15,12 @@ */ #include "integrator/path_trace_work_gpu.h" +#include "integrator/path_trace_display.h" #include "device/device.h" #include "integrator/pass_accessor_gpu.h" #include "render/buffers.h" -#include "render/gpu_display.h" #include "render/scene.h" #include "util/util_logging.h" #include "util/util_tbb.h" @@ -46,7 +46,7 @@ PathTraceWorkGPU::PathTraceWorkGPU(Device *device, queued_paths_(device, "queued_paths", MEM_READ_WRITE), num_queued_paths_(device, "num_queued_paths", MEM_READ_WRITE), work_tiles_(device, "work_tiles", MEM_READ_WRITE), - gpu_display_rgba_half_(device, "display buffer half", MEM_READ_WRITE), + display_rgba_half_(device, "display buffer half", MEM_READ_WRITE), max_num_paths_(queue_->num_concurrent_states(sizeof(IntegratorStateCPU))), min_num_active_paths_(queue_->num_concurrent_busy_states()), max_active_path_index_(0) @@ -95,8 +95,8 @@ void PathTraceWorkGPU::alloc_integrator_soa() #define KERNEL_STRUCT_END(name) \ break; \ } -#define KERNEL_STRUCT_END_ARRAY(name, array_size) \ - if (array_index == array_size - 1) { \ +#define KERNEL_STRUCT_END_ARRAY(name, cpu_array_size, gpu_array_size) \ + if (array_index == gpu_array_size - 1) { \ break; \ } \ } @@ -652,7 +652,7 @@ int PathTraceWorkGPU::get_num_active_paths() bool PathTraceWorkGPU::should_use_graphics_interop() { /* There are few aspects with the graphics interop when using multiple devices caused by the fact - * that the GPUDisplay has a single texture: + * that the PathTraceDisplay has a single texture: * * CUDA will return `CUDA_ERROR_NOT_SUPPORTED` from `cuGraphicsGLRegisterBuffer()` when * attempting to register OpenGL PBO which has been mapped. Which makes sense, because @@ -678,9 +678,9 @@ bool PathTraceWorkGPU::should_use_graphics_interop() return interop_use_; } -void PathTraceWorkGPU::copy_to_gpu_display(GPUDisplay *gpu_display, - PassMode pass_mode, - int num_samples) +void PathTraceWorkGPU::copy_to_display(PathTraceDisplay *display, + PassMode pass_mode, + int num_samples) { if (device_->have_error()) { /* Don't attempt to update GPU display if the device has errors: the error state will make @@ -694,7 +694,7 @@ void PathTraceWorkGPU::copy_to_gpu_display(GPUDisplay *gpu_display, } if (should_use_graphics_interop()) { - if (copy_to_gpu_display_interop(gpu_display, pass_mode, num_samples)) { + if (copy_to_display_interop(display, pass_mode, num_samples)) { return; } @@ -703,12 +703,12 @@ void PathTraceWorkGPU::copy_to_gpu_display(GPUDisplay *gpu_display, interop_use_ = false; } - copy_to_gpu_display_naive(gpu_display, pass_mode, num_samples); + copy_to_display_naive(display, pass_mode, num_samples); } -void PathTraceWorkGPU::copy_to_gpu_display_naive(GPUDisplay *gpu_display, - PassMode pass_mode, - int num_samples) +void PathTraceWorkGPU::copy_to_display_naive(PathTraceDisplay *display, + PassMode pass_mode, + int num_samples) { const int full_x = effective_buffer_params_.full_x; const int full_y = effective_buffer_params_.full_y; @@ -725,43 +725,42 @@ void PathTraceWorkGPU::copy_to_gpu_display_naive(GPUDisplay *gpu_display, * NOTE: allocation happens to the final resolution so that no re-allocation happens on every * change of the resolution divider. However, if the display becomes smaller, shrink the * allocated memory as well. */ - if (gpu_display_rgba_half_.data_width != final_width || - gpu_display_rgba_half_.data_height != final_height) { - gpu_display_rgba_half_.alloc(final_width, final_height); + if (display_rgba_half_.data_width != final_width || + display_rgba_half_.data_height != final_height) { + display_rgba_half_.alloc(final_width, final_height); /* TODO(sergey): There should be a way to make sure device-side memory is allocated without * transferring zeroes to the device. */ - queue_->zero_to_device(gpu_display_rgba_half_); + queue_->zero_to_device(display_rgba_half_); } PassAccessor::Destination destination(film_->get_display_pass()); - destination.d_pixels_half_rgba = gpu_display_rgba_half_.device_pointer; + destination.d_pixels_half_rgba = display_rgba_half_.device_pointer; get_render_tile_film_pixels(destination, pass_mode, num_samples); - gpu_display_rgba_half_.copy_from_device(); + queue_->copy_from_device(display_rgba_half_); + queue_->synchronize(); - gpu_display->copy_pixels_to_texture( - gpu_display_rgba_half_.data(), texture_x, texture_y, width, height); + display->copy_pixels_to_texture(display_rgba_half_.data(), texture_x, texture_y, width, height); } -bool PathTraceWorkGPU::copy_to_gpu_display_interop(GPUDisplay *gpu_display, - PassMode pass_mode, - int num_samples) +bool PathTraceWorkGPU::copy_to_display_interop(PathTraceDisplay *display, + PassMode pass_mode, + int num_samples) { if (!device_graphics_interop_) { device_graphics_interop_ = queue_->graphics_interop_create(); } - const DeviceGraphicsInteropDestination graphics_interop_dst = - gpu_display->graphics_interop_get(); - device_graphics_interop_->set_destination(graphics_interop_dst); + const DisplayDriver::GraphicsInterop graphics_interop_dst = display->graphics_interop_get(); + device_graphics_interop_->set_display_interop(graphics_interop_dst); const device_ptr d_rgba_half = device_graphics_interop_->map(); if (!d_rgba_half) { return false; } - PassAccessor::Destination destination = get_gpu_display_destination_template(gpu_display); + PassAccessor::Destination destination = get_display_destination_template(display); destination.d_pixels_half_rgba = d_rgba_half; get_render_tile_film_pixels(destination, pass_mode, num_samples); @@ -771,14 +770,14 @@ bool PathTraceWorkGPU::copy_to_gpu_display_interop(GPUDisplay *gpu_display, return true; } -void PathTraceWorkGPU::destroy_gpu_resources(GPUDisplay *gpu_display) +void PathTraceWorkGPU::destroy_gpu_resources(PathTraceDisplay *display) { if (!device_graphics_interop_) { return; } - gpu_display->graphics_interop_activate(); + display->graphics_interop_activate(); device_graphics_interop_ = nullptr; - gpu_display->graphics_interop_deactivate(); + display->graphics_interop_deactivate(); } void PathTraceWorkGPU::get_render_tile_film_pixels(const PassAccessor::Destination &destination, diff --git a/intern/cycles/integrator/path_trace_work_gpu.h b/intern/cycles/integrator/path_trace_work_gpu.h index 38788122b0d..9212537d2fd 100644 --- a/intern/cycles/integrator/path_trace_work_gpu.h +++ b/intern/cycles/integrator/path_trace_work_gpu.h @@ -48,10 +48,10 @@ class PathTraceWorkGPU : public PathTraceWork { int start_sample, int samples_num) override; - virtual void copy_to_gpu_display(GPUDisplay *gpu_display, - PassMode pass_mode, - int num_samples) override; - virtual void destroy_gpu_resources(GPUDisplay *gpu_display) override; + virtual void copy_to_display(PathTraceDisplay *display, + PassMode pass_mode, + int num_samples) override; + virtual void destroy_gpu_resources(PathTraceDisplay *display) override; virtual bool copy_render_buffers_from_device() override; virtual bool copy_render_buffers_to_device() override; @@ -88,16 +88,16 @@ class PathTraceWorkGPU : public PathTraceWork { int get_num_active_paths(); - /* Check whether graphics interop can be used for the GPUDisplay update. */ + /* Check whether graphics interop can be used for the PathTraceDisplay update. */ bool should_use_graphics_interop(); - /* Naive implementation of the `copy_to_gpu_display()` which performs film conversion on the - * device, then copies pixels to the host and pushes them to the `gpu_display`. */ - void copy_to_gpu_display_naive(GPUDisplay *gpu_display, PassMode pass_mode, int num_samples); + /* Naive implementation of the `copy_to_display()` which performs film conversion on the + * device, then copies pixels to the host and pushes them to the `display`. */ + void copy_to_display_naive(PathTraceDisplay *display, PassMode pass_mode, int num_samples); - /* Implementation of `copy_to_gpu_display()` which uses driver's OpenGL/GPU interoperability + /* Implementation of `copy_to_display()` which uses driver's OpenGL/GPU interoperability * functionality, avoiding copy of pixels to the host. */ - bool copy_to_gpu_display_interop(GPUDisplay *gpu_display, PassMode pass_mode, int num_samples); + bool copy_to_display_interop(PathTraceDisplay *display, PassMode pass_mode, int num_samples); /* Synchronously run film conversion kernel and store display result in the given destination. */ void get_render_tile_film_pixels(const PassAccessor::Destination &destination, @@ -139,9 +139,9 @@ class PathTraceWorkGPU : public PathTraceWork { /* Temporary buffer for passing work tiles to kernel. */ device_vector<KernelWorkTile> work_tiles_; - /* Temporary buffer used by the copy_to_gpu_display() whenever graphics interoperability is not + /* Temporary buffer used by the copy_to_display() whenever graphics interoperability is not * available. Is allocated on-demand. */ - device_vector<half4> gpu_display_rgba_half_; + device_vector<half4> display_rgba_half_; unique_ptr<DeviceGraphicsInterop> device_graphics_interop_; diff --git a/intern/cycles/integrator/render_scheduler.cpp b/intern/cycles/integrator/render_scheduler.cpp index 3e5b3417a6a..322d3d5f94c 100644 --- a/intern/cycles/integrator/render_scheduler.cpp +++ b/intern/cycles/integrator/render_scheduler.cpp @@ -384,7 +384,7 @@ bool RenderScheduler::set_postprocess_render_work(RenderWork *render_work) } if (denoiser_params_.use && !state_.last_work_tile_was_denoised) { - render_work->tile.denoise = true; + render_work->tile.denoise = !tile_manager_.has_multiple_tiles(); any_scheduled = true; } @@ -903,6 +903,12 @@ bool RenderScheduler::work_need_denoise(bool &delayed, bool &ready_to_display) return false; } + /* When multiple tiles are used the full frame will be denoised. + * Avoid per-tile denoising to save up render time. */ + if (tile_manager_.has_multiple_tiles()) { + return false; + } + if (done()) { /* Always denoise at the last sample. */ return true; diff --git a/intern/cycles/integrator/render_scheduler.h b/intern/cycles/integrator/render_scheduler.h index b7b598fb10c..c4ab15e54ba 100644 --- a/intern/cycles/integrator/render_scheduler.h +++ b/intern/cycles/integrator/render_scheduler.h @@ -31,7 +31,7 @@ class RenderWork { int resolution_divider = 1; /* Initialize render buffers. - * Includes steps like zero-ing the buffer on the device, and optional reading of pixels from the + * Includes steps like zeroing the buffer on the device, and optional reading of pixels from the * baking target. */ bool init_render_buffers = false; @@ -344,7 +344,7 @@ class RenderScheduler { /* Number of rendered samples on top of the start sample. */ int num_rendered_samples = 0; - /* Point in time the latest GPUDisplay work has been scheduled. */ + /* Point in time the latest PathTraceDisplay work has been scheduled. */ double last_display_update_time = 0.0; /* Value of -1 means display was never updated. */ int last_display_update_sample = -1; diff --git a/intern/cycles/integrator/shader_eval.cpp b/intern/cycles/integrator/shader_eval.cpp index d35ff4cd03f..a14e41ec5be 100644 --- a/intern/cycles/integrator/shader_eval.cpp +++ b/intern/cycles/integrator/shader_eval.cpp @@ -149,14 +149,14 @@ bool ShaderEval::eval_gpu(Device *device, /* Execute work on GPU in chunk, so we can cancel. * TODO : query appropriate size from device.*/ - const int chunk_size = 65536; + const int64_t chunk_size = 65536; - const int work_size = output.size(); + const int64_t work_size = output.size(); void *d_input = (void *)input.device_pointer; void *d_output = (void *)output.device_pointer; - for (int d_offset = 0; d_offset < work_size; d_offset += chunk_size) { - int d_work_size = min(chunk_size, work_size - d_offset); + for (int64_t d_offset = 0; d_offset < work_size; d_offset += chunk_size) { + int64_t d_work_size = std::min(chunk_size, work_size - d_offset); void *args[] = {&d_input, &d_output, &d_offset, &d_work_size}; queue->enqueue(kernel, d_work_size, args); diff --git a/intern/cycles/kernel/CMakeLists.txt b/intern/cycles/kernel/CMakeLists.txt index 4196539a9b1..7b56216e887 100644 --- a/intern/cycles/kernel/CMakeLists.txt +++ b/intern/cycles/kernel/CMakeLists.txt @@ -35,6 +35,10 @@ set(SRC_DEVICE_CUDA device/cuda/kernel.cu ) +set(SRC_DEVICE_HIP + device/hip/kernel.cpp +) + set(SRC_DEVICE_OPTIX device/optix/kernel.cu device/optix/kernel_shader_raytrace.cu @@ -106,6 +110,12 @@ set(SRC_DEVICE_CUDA_HEADERS device/cuda/globals.h ) +set(SRC_DEVICE_HIP_HEADERS + device/hip/compat.h + device/hip/config.h + device/hip/globals.h +) + set(SRC_DEVICE_OPTIX_HEADERS device/optix/compat.h device/optix/globals.h @@ -458,6 +468,104 @@ if(WITH_CYCLES_CUDA_BINARIES) cycles_set_solution_folder(cycles_kernel_cuda) endif() +####################################################### START + +# HIP module + +if(WITH_CYCLES_HIP_BINARIES) + # 64 bit only + set(HIP_BITS 64) + + # HIP version + execute_process(COMMAND ${HIP_HIPCC_EXECUTABLE} "--version" OUTPUT_VARIABLE HIPCC_OUT) + string(REGEX REPLACE ".*release ([0-9]+)\\.([0-9]+).*" "\\1" HIP_VERSION_MAJOR "${HIPCC_OUT}") + string(REGEX REPLACE ".*release ([0-9]+)\\.([0-9]+).*" "\\2" HIP_VERSION_MINOR "${HIPCC_OUT}") + set(HIP_VERSION "${HIP_VERSION_MAJOR}${HIP_VERSION_MINOR}") + + + message(WARNING + "HIP version ${HIP_VERSION_MAJOR}.${HIP_VERSION_MINOR} detected") + + # build for each arch + set(hip_sources device/hip/kernel.cpp + ${SRC_HEADERS} + ${SRC_DEVICE_HIP_HEADERS} + ${SRC_BVH_HEADERS} + ${SRC_SVM_HEADERS} + ${SRC_GEOM_HEADERS} + ${SRC_INTEGRATOR_HEADERS} + ${SRC_CLOSURE_HEADERS} + ${SRC_UTIL_HEADERS} + ) + set(hip_fatbins) + + macro(CYCLES_HIP_KERNEL_ADD arch prev_arch name flags sources experimental) + if(${arch} MATCHES "compute_.*") + set(format "ptx") + else() + set(format "fatbin") + endif() + set(hip_file ${name}_${arch}.${format}) + + set(kernel_sources ${sources}) + if(NOT ${prev_arch} STREQUAL "none") + if(${prev_arch} MATCHES "compute_.*") + set(kernel_sources ${kernel_sources} ${name}_${prev_arch}.ptx) + else() + set(kernel_sources ${kernel_sources} ${name}_${prev_arch}.fatbin) + endif() + endif() + + set(hip_kernel_src "/device/hip/${name}.cpp") + + set(hip_flags ${flags} + -D CCL_NAMESPACE_BEGIN= + -D CCL_NAMESPACE_END= + -D HIPCC + -m ${HIP_BITS} + -I ${CMAKE_CURRENT_SOURCE_DIR}/.. + -I ${CMAKE_CURRENT_SOURCE_DIR}/device/hip + --use_fast_math + -o ${CMAKE_CURRENT_BINARY_DIR}/${hip_file}) + + if(${experimental}) + set(hip_flags ${hip_flags} -D __KERNEL_EXPERIMENTAL__) + set(name ${name}_experimental) + endif() + + if(WITH_CYCLES_DEBUG) + set(hip_flags ${hip_flags} -D __KERNEL_DEBUG__) + endif() + + if(WITH_NANOVDB) + set(hip_flags ${hip_flags} + -D WITH_NANOVDB + -I "${NANOVDB_INCLUDE_DIR}") + endif() + endmacro() + + set(prev_arch "none") + foreach(arch ${CYCLES_HIP_BINARIES_ARCH}) + set(hip_hipcc_executable ${HIP_HIPCC_EXECUTABLE}) + set(hip_toolkit_root_dir ${HIP_TOOLKIT_ROOT_DIR}) + if(DEFINED hip_hipcc_executable AND DEFINED hip_toolkit_root_dir) + # Compile regular kernel + CYCLES_HIP_KERNEL_ADD(${arch} ${prev_arch} kernel "" "${hip_sources}" FALSE) + + if(WITH_CYCLES_HIP_BUILD_SERIAL) + set(prev_arch ${arch}) + endif() + + unset(hip_hipcc_executable) + unset(hip_toolkit_root_dir) + endif() + endforeach() + + add_custom_target(cycles_kernel_hip ALL DEPENDS ${hip_fatbins}) + cycles_set_solution_folder(cycles_kernel_hip) +endif() + +####################################################### END # OptiX PTX modules if(WITH_CYCLES_DEVICE_OPTIX AND WITH_CYCLES_CUDA_BINARIES) @@ -602,11 +710,13 @@ endif() cycles_add_library(cycles_kernel "${LIB}" ${SRC_DEVICE_CPU} ${SRC_DEVICE_CUDA} + ${SRC_DEVICE_HIP} ${SRC_DEVICE_OPTIX} ${SRC_HEADERS} ${SRC_DEVICE_CPU_HEADERS} ${SRC_DEVICE_GPU_HEADERS} ${SRC_DEVICE_CUDA_HEADERS} + ${SRC_DEVICE_HIP_HEADERS} ${SRC_DEVICE_OPTIX_HEADERS} ${SRC_BVH_HEADERS} ${SRC_CLOSURE_HEADERS} @@ -621,6 +731,7 @@ source_group("geom" FILES ${SRC_GEOM_HEADERS}) source_group("integrator" FILES ${SRC_INTEGRATOR_HEADERS}) source_group("kernel" FILES ${SRC_HEADERS}) source_group("device\\cpu" FILES ${SRC_DEVICE_CPU} ${SRC_DEVICE_CPU_HEADERS}) +source_group("device\\hip" FILES ${SRC_DEVICE_HIP} ${SRC_DEVICE_HIP_HEADERS}) source_group("device\\gpu" FILES ${SRC_DEVICE_GPU_HEADERS}) source_group("device\\cuda" FILES ${SRC_DEVICE_CUDA} ${SRC_DEVICE_CUDA_HEADERS}) source_group("device\\optix" FILES ${SRC_DEVICE_OPTIX} ${SRC_DEVICE_OPTIX_HEADERS}) @@ -632,14 +743,19 @@ endif() if(WITH_CYCLES_DEVICE_OPTIX AND WITH_CYCLES_CUDA_BINARIES) add_dependencies(cycles_kernel cycles_kernel_optix) endif() +if(WITH_CYCLES_HIP) + add_dependencies(cycles_kernel cycles_kernel_hip) +endif() # Install kernel source for runtime compilation delayed_install(${CMAKE_CURRENT_SOURCE_DIR} "${SRC_DEVICE_CUDA}" ${CYCLES_INSTALL_PATH}/source/kernel/device/cuda) +delayed_install(${CMAKE_CURRENT_SOURCE_DIR} "${SRC_DEVICE_HIP}" ${CYCLES_INSTALL_PATH}/source/kernel/device/hip) delayed_install(${CMAKE_CURRENT_SOURCE_DIR} "${SRC_DEVICE_OPTIX}" ${CYCLES_INSTALL_PATH}/source/kernel/device/optix) delayed_install(${CMAKE_CURRENT_SOURCE_DIR} "${SRC_HEADERS}" ${CYCLES_INSTALL_PATH}/source/kernel) delayed_install(${CMAKE_CURRENT_SOURCE_DIR} "${SRC_DEVICE_GPU_HEADERS}" ${CYCLES_INSTALL_PATH}/source/kernel/device/gpu) delayed_install(${CMAKE_CURRENT_SOURCE_DIR} "${SRC_DEVICE_CUDA_HEADERS}" ${CYCLES_INSTALL_PATH}/source/kernel/device/cuda) +delayed_install(${CMAKE_CURRENT_SOURCE_DIR} "${SRC_DEVICE_HIP_HEADERS}" ${CYCLES_INSTALL_PATH}/source/kernel/device/hip) delayed_install(${CMAKE_CURRENT_SOURCE_DIR} "${SRC_DEVICE_OPTIX_HEADERS}" ${CYCLES_INSTALL_PATH}/source/kernel/device/optix) delayed_install(${CMAKE_CURRENT_SOURCE_DIR} "${SRC_BVH_HEADERS}" ${CYCLES_INSTALL_PATH}/source/kernel/bvh) delayed_install(${CMAKE_CURRENT_SOURCE_DIR} "${SRC_CLOSURE_HEADERS}" ${CYCLES_INSTALL_PATH}/source/kernel/closure) diff --git a/intern/cycles/kernel/bvh/bvh.h b/intern/cycles/kernel/bvh/bvh.h index 539e9fd05fb..0b44cc5db34 100644 --- a/intern/cycles/kernel/bvh/bvh.h +++ b/intern/cycles/kernel/bvh/bvh.h @@ -167,15 +167,25 @@ ccl_device_intersect bool scene_intersect(const KernelGlobals *kg, uint p4 = visibility; uint p5 = PRIMITIVE_NONE; + uint ray_mask = visibility & 0xFF; + uint ray_flags = OPTIX_RAY_FLAG_NONE; + if (0 == ray_mask && (visibility & ~0xFF) != 0) { + ray_mask = 0xFF; + ray_flags = OPTIX_RAY_FLAG_ENFORCE_ANYHIT; + } + else if (visibility & PATH_RAY_SHADOW_OPAQUE) { + ray_flags = OPTIX_RAY_FLAG_TERMINATE_ON_FIRST_HIT; + } + optixTrace(scene_intersect_valid(ray) ? kernel_data.bvh.scene : 0, ray->P, ray->D, 0.0f, ray->t, ray->time, - 0xF, - OPTIX_RAY_FLAG_NONE, - 0, // SBT offset for PG_HITD + ray_mask, + ray_flags, + 0, /* SBT offset for PG_HITD */ 0, 0, p0, @@ -251,11 +261,11 @@ ccl_device_intersect bool scene_intersect_local(const KernelGlobals *kg, uint p2 = ((uint64_t)local_isect) & 0xFFFFFFFF; uint p3 = (((uint64_t)local_isect) >> 32) & 0xFFFFFFFF; uint p4 = local_object; - // Is set to zero on miss or if ray is aborted, so can be used as return value + /* Is set to zero on miss or if ray is aborted, so can be used as return value. */ uint p5 = max_hits; if (local_isect) { - local_isect->num_hits = 0; // Initialize hit count to zero + local_isect->num_hits = 0; /* Initialize hit count to zero. */ } optixTrace(scene_intersect_valid(ray) ? kernel_data.bvh.scene : 0, ray->P, @@ -263,11 +273,10 @@ ccl_device_intersect bool scene_intersect_local(const KernelGlobals *kg, 0.0f, ray->t, ray->time, - // Skip curves - 0x3, - // Need to always call into __anyhit__kernel_optix_local_hit + 0xFF, + /* Need to always call into __anyhit__kernel_optix_local_hit. */ OPTIX_RAY_FLAG_ENFORCE_ANYHIT, - 2, // SBT offset for PG_HITL + 2, /* SBT offset for PG_HITL */ 0, 0, p0, @@ -365,17 +374,22 @@ ccl_device_intersect bool scene_intersect_shadow_all(const KernelGlobals *kg, uint p4 = visibility; uint p5 = false; - *num_hits = 0; // Initialize hit count to zero + uint ray_mask = visibility & 0xFF; + if (0 == ray_mask && (visibility & ~0xFF) != 0) { + ray_mask = 0xFF; + } + + *num_hits = 0; /* Initialize hit count to zero. */ optixTrace(scene_intersect_valid(ray) ? kernel_data.bvh.scene : 0, ray->P, ray->D, 0.0f, ray->t, ray->time, - 0xF, - // Need to always call into __anyhit__kernel_optix_shadow_all_hit + ray_mask, + /* Need to always call into __anyhit__kernel_optix_shadow_all_hit. */ OPTIX_RAY_FLAG_ENFORCE_ANYHIT, - 1, // SBT offset for PG_HITS + 1, /* SBT offset for PG_HITS */ 0, 0, p0, @@ -444,16 +458,21 @@ ccl_device_intersect bool scene_intersect_volume(const KernelGlobals *kg, uint p4 = visibility; uint p5 = PRIMITIVE_NONE; + uint ray_mask = visibility & 0xFF; + if (0 == ray_mask && (visibility & ~0xFF) != 0) { + ray_mask = 0xFF; + } + optixTrace(scene_intersect_valid(ray) ? kernel_data.bvh.scene : 0, ray->P, ray->D, 0.0f, ray->t, ray->time, - // Skip everything but volumes - 0x2, - OPTIX_RAY_FLAG_NONE, - 0, // SBT offset for PG_HITD + ray_mask, + /* Need to always call into __anyhit__kernel_optix_volume_test. */ + OPTIX_RAY_FLAG_ENFORCE_ANYHIT, + 3, /* SBT offset for PG_HITV */ 0, 0, p0, diff --git a/intern/cycles/kernel/device/gpu/parallel_active_index.h b/intern/cycles/kernel/device/gpu/parallel_active_index.h index 85500bf4d07..db4a4bf71e0 100644 --- a/intern/cycles/kernel/device/gpu/parallel_active_index.h +++ b/intern/cycles/kernel/device/gpu/parallel_active_index.h @@ -21,11 +21,15 @@ CCL_NAMESPACE_BEGIN /* Given an array of states, build an array of indices for which the states * are active. * - * Shared memory requirement is sizeof(int) * (number_of_warps + 1) */ + * Shared memory requirement is `sizeof(int) * (number_of_warps + 1)`. */ #include "util/util_atomic.h" -#define GPU_PARALLEL_ACTIVE_INDEX_DEFAULT_BLOCK_SIZE 512 +#ifdef __HIP__ +# define GPU_PARALLEL_ACTIVE_INDEX_DEFAULT_BLOCK_SIZE 1024 +#else +# define GPU_PARALLEL_ACTIVE_INDEX_DEFAULT_BLOCK_SIZE 512 +#endif template<uint blocksize, typename IsActiveOp> __device__ void gpu_parallel_active_index_array(const uint num_states, diff --git a/intern/cycles/kernel/device/gpu/parallel_prefix_sum.h b/intern/cycles/kernel/device/gpu/parallel_prefix_sum.h index f609520b8b4..a1349e82efb 100644 --- a/intern/cycles/kernel/device/gpu/parallel_prefix_sum.h +++ b/intern/cycles/kernel/device/gpu/parallel_prefix_sum.h @@ -27,7 +27,11 @@ CCL_NAMESPACE_BEGIN #include "util/util_atomic.h" -#define GPU_PARALLEL_PREFIX_SUM_DEFAULT_BLOCK_SIZE 512 +#ifdef __HIP__ +# define GPU_PARALLEL_PREFIX_SUM_DEFAULT_BLOCK_SIZE 1024 +#else +# define GPU_PARALLEL_PREFIX_SUM_DEFAULT_BLOCK_SIZE 512 +#endif template<uint blocksize> __device__ void gpu_parallel_prefix_sum(int *values, const int num_values) { diff --git a/intern/cycles/kernel/device/gpu/parallel_reduce.h b/intern/cycles/kernel/device/gpu/parallel_reduce.h index 65b1990dbb8..b60dceb2ed0 100644 --- a/intern/cycles/kernel/device/gpu/parallel_reduce.h +++ b/intern/cycles/kernel/device/gpu/parallel_reduce.h @@ -26,7 +26,11 @@ CCL_NAMESPACE_BEGIN * the overall cost of the algorithm while keeping the work complexity O(n) and * the step complexity O(log n). (Brent's Theorem optimization) */ -#define GPU_PARALLEL_SUM_DEFAULT_BLOCK_SIZE 512 +#ifdef __HIP__ +# define GPU_PARALLEL_SUM_DEFAULT_BLOCK_SIZE 1024 +#else +# define GPU_PARALLEL_SUM_DEFAULT_BLOCK_SIZE 512 +#endif template<uint blocksize, typename InputT, typename OutputT, typename ConvertOp> __device__ void gpu_parallel_sum( diff --git a/intern/cycles/kernel/device/gpu/parallel_sorted_index.h b/intern/cycles/kernel/device/gpu/parallel_sorted_index.h index 99b35468517..9bca1fad22f 100644 --- a/intern/cycles/kernel/device/gpu/parallel_sorted_index.h +++ b/intern/cycles/kernel/device/gpu/parallel_sorted_index.h @@ -26,7 +26,11 @@ CCL_NAMESPACE_BEGIN #include "util/util_atomic.h" -#define GPU_PARALLEL_SORTED_INDEX_DEFAULT_BLOCK_SIZE 512 +#ifdef __HIP__ +# define GPU_PARALLEL_SORTED_INDEX_DEFAULT_BLOCK_SIZE 1024 +#else +# define GPU_PARALLEL_SORTED_INDEX_DEFAULT_BLOCK_SIZE 512 +#endif #define GPU_PARALLEL_SORTED_INDEX_INACTIVE_KEY (~0) template<uint blocksize, typename GetKeyOp> diff --git a/intern/cycles/kernel/device/hip/compat.h b/intern/cycles/kernel/device/hip/compat.h new file mode 100644 index 00000000000..95338fe7d6e --- /dev/null +++ b/intern/cycles/kernel/device/hip/compat.h @@ -0,0 +1,121 @@ +/* + * Copyright 2011-2021 Blender Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#define __KERNEL_GPU__ +#define __KERNEL_HIP__ +#define CCL_NAMESPACE_BEGIN +#define CCL_NAMESPACE_END + +#ifndef ATTR_FALLTHROUGH +# define ATTR_FALLTHROUGH +#endif + +#ifdef __HIPCC_RTC__ +typedef unsigned int uint32_t; +typedef unsigned long long uint64_t; +#else +# include <stdint.h> +#endif + +#ifdef CYCLES_HIPBIN_CC +# define FLT_MIN 1.175494350822287507969e-38f +# define FLT_MAX 340282346638528859811704183484516925440.0f +# define FLT_EPSILON 1.192092896e-07F +#endif + +/* Qualifiers */ + +#define ccl_device __device__ __inline__ +#define ccl_device_inline __device__ __inline__ +#define ccl_device_forceinline __device__ __forceinline__ +#define ccl_device_noinline __device__ __noinline__ +#define ccl_device_noinline_cpu ccl_device +#define ccl_global +#define ccl_static_constant __constant__ +#define ccl_device_constant __constant__ __device__ +#define ccl_constant const +#define ccl_gpu_shared __shared__ +#define ccl_private +#define ccl_may_alias +#define ccl_addr_space +#define ccl_restrict __restrict__ +#define ccl_loop_no_unroll +#define ccl_align(n) __align__(n) +#define ccl_optional_struct_init + +#define kernel_assert(cond) + +/* Types */ +#ifdef __HIP__ +# include "hip/hip_fp16.h" +# include "hip/hip_runtime.h" +#endif + +#ifdef _MSC_VER +# include <immintrin.h> +#endif + +#define ccl_gpu_thread_idx_x (threadIdx.x) +#define ccl_gpu_block_dim_x (blockDim.x) +#define ccl_gpu_block_idx_x (blockIdx.x) +#define ccl_gpu_grid_dim_x (gridDim.x) +#define ccl_gpu_warp_size (warpSize) + +#define ccl_gpu_global_id_x() (ccl_gpu_block_idx_x * ccl_gpu_block_dim_x + ccl_gpu_thread_idx_x) +#define ccl_gpu_global_size_x() (ccl_gpu_grid_dim_x * ccl_gpu_block_dim_x) + +/* GPU warp synchronization */ + +#define ccl_gpu_syncthreads() __syncthreads() +#define ccl_gpu_ballot(predicate) __ballot(predicate) +#define ccl_gpu_shfl_down_sync(mask, var, detla) __shfl_down(var, detla) +#define ccl_gpu_popc(x) __popc(x) + +/* GPU texture objects */ +typedef hipTextureObject_t ccl_gpu_tex_object; + +template<typename T> +ccl_device_forceinline T ccl_gpu_tex_object_read_2D(const ccl_gpu_tex_object texobj, + const float x, + const float y) +{ + return tex2D<T>(texobj, x, y); +} + +template<typename T> +ccl_device_forceinline T ccl_gpu_tex_object_read_3D(const ccl_gpu_tex_object texobj, + const float x, + const float y, + const float z) +{ + return tex3D<T>(texobj, x, y, z); +} + +/* Use fast math functions */ + +#define cosf(x) __cosf(((float)(x))) +#define sinf(x) __sinf(((float)(x))) +#define powf(x, y) __powf(((float)(x)), ((float)(y))) +#define tanf(x) __tanf(((float)(x))) +#define logf(x) __logf(((float)(x))) +#define expf(x) __expf(((float)(x))) + +/* Types */ + +#include "util/util_half.h" +#include "util/util_types.h" diff --git a/intern/cycles/kernel/device/hip/config.h b/intern/cycles/kernel/device/hip/config.h new file mode 100644 index 00000000000..2fde0d46015 --- /dev/null +++ b/intern/cycles/kernel/device/hip/config.h @@ -0,0 +1,57 @@ +/* + * Copyright 2011-2021 Blender Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* Device data taken from HIP occupancy calculator. + * + * Terminology + * - HIP GPUs have multiple streaming multiprocessors + * - Each multiprocessor executes multiple thread blocks + * - Each thread block contains a number of threads, also known as the block size + * - Multiprocessors have a fixed number of registers, and the amount of registers + * used by each threads limits the number of threads per block. + */ + +/* Launch Bound Definitions */ +#define GPU_MULTIPRESSOR_MAX_REGISTERS 65536 +#define GPU_MULTIPROCESSOR_MAX_BLOCKS 64 +#define GPU_BLOCK_MAX_THREADS 1024 +#define GPU_THREAD_MAX_REGISTERS 255 + +#define GPU_KERNEL_BLOCK_NUM_THREADS 1024 +#define GPU_KERNEL_MAX_REGISTERS 64 + +/* Compute number of threads per block and minimum blocks per multiprocessor + * given the maximum number of registers per thread. */ + +#define ccl_gpu_kernel(block_num_threads, thread_num_registers) \ + extern "C" __global__ void __launch_bounds__(block_num_threads, \ + GPU_MULTIPRESSOR_MAX_REGISTERS / \ + (block_num_threads * thread_num_registers)) + +/* sanity checks */ + +#if GPU_KERNEL_BLOCK_NUM_THREADS > GPU_BLOCK_MAX_THREADS +# error "Maximum number of threads per block exceeded" +#endif + +#if GPU_MULTIPRESSOR_MAX_REGISTERS / (GPU_KERNEL_BLOCK_NUM_THREADS * GPU_KERNEL_MAX_REGISTERS) > \ + GPU_MULTIPROCESSOR_MAX_BLOCKS +# error "Maximum number of blocks per multiprocessor exceeded" +#endif + +#if GPU_KERNEL_MAX_REGISTERS > GPU_THREAD_MAX_REGISTERS +# error "Maximum number of registers per thread exceeded" +#endif diff --git a/intern/cycles/kernel/device/hip/globals.h b/intern/cycles/kernel/device/hip/globals.h new file mode 100644 index 00000000000..39978ae7899 --- /dev/null +++ b/intern/cycles/kernel/device/hip/globals.h @@ -0,0 +1,49 @@ +/* + * Copyright 2011-2021 Blender Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* Constant Globals */ + +#pragma once + +#include "kernel/kernel_profiling.h" +#include "kernel/kernel_types.h" + +#include "kernel/integrator/integrator_state.h" + +CCL_NAMESPACE_BEGIN + +/* Not actually used, just a NULL pointer that gets passed everywhere, which we + * hope gets optimized out by the compiler. */ +struct KernelGlobals { + /* NOTE: Keep the size in sync with SHADOW_STACK_MAX_HITS. */ + int unused[1]; +}; + +/* Global scene data and textures */ +__constant__ KernelData __data; +#define KERNEL_TEX(type, name) __attribute__((used)) const __constant__ __device__ type *name; +#include "kernel/kernel_textures.h" + +/* Integrator state */ +__constant__ IntegratorStateGPU __integrator_state; + +/* Abstraction macros */ +#define kernel_data __data +#define kernel_tex_fetch(t, index) t[(index)] +#define kernel_tex_array(t) (t) +#define kernel_integrator_state __integrator_state + +CCL_NAMESPACE_END diff --git a/intern/cycles/kernel/device/hip/kernel.cpp b/intern/cycles/kernel/device/hip/kernel.cpp new file mode 100644 index 00000000000..c801320a2e1 --- /dev/null +++ b/intern/cycles/kernel/device/hip/kernel.cpp @@ -0,0 +1,28 @@ +/* + * Copyright 2011-2021 Blender Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* HIP kernel entry points */ + +#ifdef __HIP_DEVICE_COMPILE__ + +# include "kernel/device/hip/compat.h" +# include "kernel/device/hip/config.h" +# include "kernel/device/hip/globals.h" + +# include "kernel/device/gpu/image.h" +# include "kernel/device/gpu/kernel.h" + +#endif diff --git a/intern/cycles/kernel/device/optix/kernel.cu b/intern/cycles/kernel/device/optix/kernel.cu index c1e36febfc0..7a79e0c4823 100644 --- a/intern/cycles/kernel/device/optix/kernel.cu +++ b/intern/cycles/kernel/device/optix/kernel.cu @@ -19,7 +19,7 @@ #include "kernel/device/optix/compat.h" #include "kernel/device/optix/globals.h" -#include "kernel/device/gpu/image.h" // Texture lookup uses normal CUDA intrinsics +#include "kernel/device/gpu/image.h" /* Texture lookup uses normal CUDA intrinsics. */ #include "kernel/integrator/integrator_state.h" #include "kernel/integrator/integrator_state_flow.h" @@ -44,18 +44,18 @@ template<typename T> ccl_device_forceinline T *get_payload_ptr_2() template<bool always = false> ccl_device_forceinline uint get_object_id() { #ifdef __OBJECT_MOTION__ - // Always get the the instance ID from the TLAS - // There might be a motion transform node between TLAS and BLAS which does not have one + /* Always get the the instance ID from the TLAS. + * There might be a motion transform node between TLAS and BLAS which does not have one. */ uint object = optixGetInstanceIdFromHandle(optixGetTransformListHandle(0)); #else uint object = optixGetInstanceId(); #endif - // Choose between always returning object ID or only for instances + /* Choose between always returning object ID or only for instances. */ if (always || (object & 1) == 0) - // Can just remove the low bit since instance always contains object ID + /* Can just remove the low bit since instance always contains object ID. */ return object >> 1; else - // Set to OBJECT_NONE if this is not an instanced object + /* Set to OBJECT_NONE if this is not an instanced object. */ return OBJECT_NONE; } @@ -93,23 +93,30 @@ extern "C" __global__ void __raygen__kernel_optix_integrator_intersect_volume_st extern "C" __global__ void __miss__kernel_optix_miss() { - // 'kernel_path_lamp_emission' checks intersection distance, so need to set it even on a miss + /* 'kernel_path_lamp_emission' checks intersection distance, so need to set it even on a miss. */ optixSetPayload_0(__float_as_uint(optixGetRayTmax())); optixSetPayload_5(PRIMITIVE_NONE); } extern "C" __global__ void __anyhit__kernel_optix_local_hit() { +#ifdef __HAIR__ + if (!optixIsTriangleHit()) { + /* Ignore curves. */ + return optixIgnoreIntersection(); + } +#endif + #ifdef __BVH_LOCAL__ const uint object = get_object_id<true>(); if (object != optixGetPayload_4() /* local_object */) { - // Only intersect with matching object + /* Only intersect with matching object. */ return optixIgnoreIntersection(); } const uint max_hits = optixGetPayload_5(); if (max_hits == 0) { - // Special case for when no hit information is requested, just report that something was hit + /* Special case for when no hit information is requested, just report that something was hit */ optixSetPayload_5(true); return optixTerminateRay(); } @@ -136,8 +143,9 @@ extern "C" __global__ void __anyhit__kernel_optix_local_hit() } else { if (local_isect->num_hits && optixGetRayTmax() > local_isect->hits[0].t) { - // Record closest intersection only - // Do not terminate ray here, since there is no guarantee about distance ordering in any-hit + /* Record closest intersection only. + * Do not terminate ray here, since there is no guarantee about distance ordering in any-hit. + */ return optixIgnoreIntersection(); } @@ -154,14 +162,14 @@ extern "C" __global__ void __anyhit__kernel_optix_local_hit() isect->u = 1.0f - barycentrics.y - barycentrics.x; isect->v = barycentrics.x; - // Record geometric normal + /* Record geometric normal. */ const uint tri_vindex = kernel_tex_fetch(__prim_tri_index, isect->prim); const float3 tri_a = float4_to_float3(kernel_tex_fetch(__prim_tri_verts, tri_vindex + 0)); const float3 tri_b = float4_to_float3(kernel_tex_fetch(__prim_tri_verts, tri_vindex + 1)); const float3 tri_c = float4_to_float3(kernel_tex_fetch(__prim_tri_verts, tri_vindex + 2)); local_isect->Ng[hit] = normalize(cross(tri_b - tri_a, tri_c - tri_a)); - // Continue tracing (without this the trace call would return after the first hit) + /* Continue tracing (without this the trace call would return after the first hit). */ optixIgnoreIntersection(); #endif } @@ -190,7 +198,7 @@ extern "C" __global__ void __anyhit__kernel_optix_shadow_all_hit() u = __uint_as_float(optixGetAttribute_0()); v = __uint_as_float(optixGetAttribute_1()); - // Filter out curve endcaps + /* Filter out curve endcaps. */ if (u == 0.0f || u == 1.0f) { ignore_intersection = true; } @@ -241,10 +249,10 @@ extern "C" __global__ void __anyhit__kernel_optix_shadow_all_hit() isect->type = kernel_tex_fetch(__prim_type, prim); # ifdef __TRANSPARENT_SHADOWS__ - // Detect if this surface has a shader with transparent shadows + /* Detect if this surface has a shader with transparent shadows. */ if (!shader_transparent_shadow(NULL, isect) || max_hits == 0) { # endif - // If no transparent shadows, all light is blocked and we can stop immediately + /* If no transparent shadows, all light is blocked and we can stop immediately. */ optixSetPayload_5(true); return optixTerminateRay(); # ifdef __TRANSPARENT_SHADOWS__ @@ -252,24 +260,39 @@ extern "C" __global__ void __anyhit__kernel_optix_shadow_all_hit() # endif } - // Continue tracing + /* Continue tracing. */ optixIgnoreIntersection(); #endif } -extern "C" __global__ void __anyhit__kernel_optix_visibility_test() +extern "C" __global__ void __anyhit__kernel_optix_volume_test() { - uint visibility = optixGetPayload_4(); +#ifdef __HAIR__ + if (!optixIsTriangleHit()) { + /* Ignore curves. */ + return optixIgnoreIntersection(); + } +#endif + #ifdef __VISIBILITY_FLAG__ const uint prim = optixGetPrimitiveIndex(); + const uint visibility = optixGetPayload_4(); if ((kernel_tex_fetch(__prim_visibility, prim) & visibility) == 0) { return optixIgnoreIntersection(); } #endif + const uint object = get_object_id<true>(); + if ((kernel_tex_fetch(__object_flag, object) & SD_OBJECT_HAS_VOLUME) == 0) { + return optixIgnoreIntersection(); + } +} + +extern "C" __global__ void __anyhit__kernel_optix_visibility_test() +{ #ifdef __HAIR__ if (!optixIsTriangleHit()) { - // Filter out curve endcaps + /* Filter out curve endcaps. */ const float u = __uint_as_float(optixGetAttribute_0()); if (u == 0.0f || u == 1.0f) { return optixIgnoreIntersection(); @@ -277,18 +300,26 @@ extern "C" __global__ void __anyhit__kernel_optix_visibility_test() } #endif - // Shadow ray early termination +#ifdef __VISIBILITY_FLAG__ + const uint prim = optixGetPrimitiveIndex(); + const uint visibility = optixGetPayload_4(); + if ((kernel_tex_fetch(__prim_visibility, prim) & visibility) == 0) { + return optixIgnoreIntersection(); + } + + /* Shadow ray early termination. */ if (visibility & PATH_RAY_SHADOW_OPAQUE) { return optixTerminateRay(); } +#endif } extern "C" __global__ void __closesthit__kernel_optix_hit() { - optixSetPayload_0(__float_as_uint(optixGetRayTmax())); // Intersection distance + optixSetPayload_0(__float_as_uint(optixGetRayTmax())); /* Intersection distance */ optixSetPayload_3(optixGetPrimitiveIndex()); optixSetPayload_4(get_object_id()); - // Can be PRIMITIVE_TRIANGLE and PRIMITIVE_MOTION_TRIANGLE or curve type and segment index + /* Can be PRIMITIVE_TRIANGLE and PRIMITIVE_MOTION_TRIANGLE or curve type and segment index. */ optixSetPayload_5(kernel_tex_fetch(__prim_type, optixGetPrimitiveIndex())); if (optixIsTriangleHit()) { @@ -297,7 +328,7 @@ extern "C" __global__ void __closesthit__kernel_optix_hit() optixSetPayload_2(__float_as_uint(barycentrics.x)); } else { - optixSetPayload_1(optixGetAttribute_0()); // Same as 'optixGetCurveParameter()' + optixSetPayload_1(optixGetAttribute_0()); /* Same as 'optixGetCurveParameter()' */ optixSetPayload_2(optixGetAttribute_1()); } } @@ -311,7 +342,7 @@ ccl_device_inline void optix_intersection_curve(const uint prim, const uint type float3 P = optixGetObjectRayOrigin(); float3 dir = optixGetObjectRayDirection(); - // The direction is not normalized by default, but the curve intersection routine expects that + /* The direction is not normalized by default, but the curve intersection routine expects that */ float len; dir = normalize_len(dir, &len); @@ -323,15 +354,15 @@ ccl_device_inline void optix_intersection_curve(const uint prim, const uint type Intersection isect; isect.t = optixGetRayTmax(); - // Transform maximum distance into object space + /* Transform maximum distance into object space. */ if (isect.t != FLT_MAX) isect.t *= len; if (curve_intersect(NULL, &isect, P, dir, isect.t, visibility, object, prim, time, type)) { optixReportIntersection(isect.t / len, type & PRIMITIVE_ALL, - __float_as_int(isect.u), // Attribute_0 - __float_as_int(isect.v)); // Attribute_1 + __float_as_int(isect.u), /* Attribute_0 */ + __float_as_int(isect.v)); /* Attribute_1 */ } } diff --git a/intern/cycles/kernel/geom/geom_curve_intersect.h b/intern/cycles/kernel/geom/geom_curve_intersect.h index 213f3e62ee0..a068e93790a 100644 --- a/intern/cycles/kernel/geom/geom_curve_intersect.h +++ b/intern/cycles/kernel/geom/geom_curve_intersect.h @@ -713,7 +713,7 @@ ccl_device_inline void curve_shader_setup(const KernelGlobals *kg, P = transform_point(&tfm, P); D = transform_direction(&tfm, D * t); - D = normalize_len(D, &t); + D = safe_normalize_len(D, &t); } int prim = kernel_tex_fetch(__prim_index, isect_prim); @@ -764,8 +764,10 @@ ccl_device_inline void curve_shader_setup(const KernelGlobals *kg, /* Thick curves, compute normal using direction from inside the curve. * This could be optimized by recording the normal in the intersection, * however for Optix this would go beyond the size of the payload. */ + /* NOTE: It is possible that P will be the same as P_inside (precision issues, or very small + * radius). In this case use the view direction to approximate the normal. */ const float3 P_inside = float4_to_float3(catmull_rom_basis_eval(P_curve, sd->u)); - const float3 Ng = normalize(P - P_inside); + const float3 Ng = (!isequal_float3(P, P_inside)) ? normalize(P - P_inside) : -sd->I; sd->N = Ng; sd->Ng = Ng; diff --git a/intern/cycles/kernel/geom/geom_motion_triangle.h b/intern/cycles/kernel/geom/geom_motion_triangle.h index eb4a39e062b..239bd0a37b2 100644 --- a/intern/cycles/kernel/geom/geom_motion_triangle.h +++ b/intern/cycles/kernel/geom/geom_motion_triangle.h @@ -41,7 +41,18 @@ ccl_device_inline int find_attribute_motion(const KernelGlobals *kg, uint4 attr_map = kernel_tex_fetch(__attributes_map, attr_offset); while (attr_map.x != id) { - attr_offset += ATTR_PRIM_TYPES; + if (UNLIKELY(attr_map.x == ATTR_STD_NONE)) { + if (UNLIKELY(attr_map.y == 0)) { + return (int)ATTR_STD_NOT_FOUND; + } + else { + /* Chain jump to a different part of the table. */ + attr_offset = attr_map.z; + } + } + else { + attr_offset += ATTR_PRIM_TYPES; + } attr_map = kernel_tex_fetch(__attributes_map, attr_offset); } diff --git a/intern/cycles/kernel/integrator/integrator_shade_surface.h b/intern/cycles/kernel/integrator/integrator_shade_surface.h index 73b7cad32be..a24473addcc 100644 --- a/intern/cycles/kernel/integrator/integrator_shade_surface.h +++ b/intern/cycles/kernel/integrator/integrator_shade_surface.h @@ -365,19 +365,16 @@ ccl_device bool integrate_surface(INTEGRATOR_STATE_ARGS, #ifdef __VOLUME__ if (!(sd.flag & SD_HAS_ONLY_VOLUME)) { #endif + const int path_flag = INTEGRATOR_STATE(path, flag); - { - const int path_flag = INTEGRATOR_STATE(path, flag); #ifdef __SUBSURFACE__ - /* Can skip shader evaluation for BSSRDF exit point without bump mapping. */ - if (!(path_flag & PATH_RAY_SUBSURFACE) || ((sd.flag & SD_HAS_BSSRDF_BUMP))) + /* Can skip shader evaluation for BSSRDF exit point without bump mapping. */ + if (!(path_flag & PATH_RAY_SUBSURFACE) || ((sd.flag & SD_HAS_BSSRDF_BUMP))) #endif - { - /* Evaluate shader. */ - PROFILING_EVENT(PROFILING_SHADE_SURFACE_EVAL); - shader_eval_surface<node_feature_mask>( - INTEGRATOR_STATE_PASS, &sd, render_buffer, path_flag); - } + { + /* Evaluate shader. */ + PROFILING_EVENT(PROFILING_SHADE_SURFACE_EVAL); + shader_eval_surface<node_feature_mask>(INTEGRATOR_STATE_PASS, &sd, render_buffer, path_flag); } #ifdef __SUBSURFACE__ @@ -417,17 +414,20 @@ ccl_device bool integrate_surface(INTEGRATOR_STATE_ARGS, /* Perform path termination. Most paths have already been terminated in * the intersect_closest kernel, this is just for emission and for dividing - * throughput by the probability at the right moment. */ - const int path_flag = INTEGRATOR_STATE(path, flag); - const float probability = (path_flag & PATH_RAY_TERMINATE_ON_NEXT_SURFACE) ? - 0.0f : - path_state_continuation_probability(INTEGRATOR_STATE_PASS, - path_flag); - if (probability == 0.0f) { - return false; - } - else if (probability != 1.0f) { - INTEGRATOR_STATE_WRITE(path, throughput) /= probability; + * throughput by the probability at the right moment. + * + * Also ensure we don't do it twice for SSS at both the entry and exit point. */ + if (!(path_flag & PATH_RAY_SUBSURFACE)) { + const float probability = (path_flag & PATH_RAY_TERMINATE_ON_NEXT_SURFACE) ? + 0.0f : + path_state_continuation_probability(INTEGRATOR_STATE_PASS, + path_flag); + if (probability == 0.0f) { + return false; + } + else if (probability != 1.0f) { + INTEGRATOR_STATE_WRITE(path, throughput) /= probability; + } } #ifdef __DENOISING_FEATURES__ diff --git a/intern/cycles/kernel/integrator/integrator_shade_volume.h b/intern/cycles/kernel/integrator/integrator_shade_volume.h index 095a28ac505..dac3efb3996 100644 --- a/intern/cycles/kernel/integrator/integrator_shade_volume.h +++ b/intern/cycles/kernel/integrator/integrator_shade_volume.h @@ -74,7 +74,7 @@ ccl_device_inline bool shadow_volume_shader_sample(INTEGRATOR_STATE_ARGS, ShaderData *ccl_restrict sd, float3 *ccl_restrict extinction) { - shader_eval_volume(INTEGRATOR_STATE_PASS, sd, PATH_RAY_SHADOW, [=](const int i) { + shader_eval_volume<true>(INTEGRATOR_STATE_PASS, sd, PATH_RAY_SHADOW, [=](const int i) { return integrator_state_read_shadow_volume_stack(INTEGRATOR_STATE_PASS, i); }); @@ -93,7 +93,7 @@ ccl_device_inline bool volume_shader_sample(INTEGRATOR_STATE_ARGS, VolumeShaderCoefficients *coeff) { const int path_flag = INTEGRATOR_STATE(path, flag); - shader_eval_volume(INTEGRATOR_STATE_PASS, sd, path_flag, [=](const int i) { + shader_eval_volume<false>(INTEGRATOR_STATE_PASS, sd, path_flag, [=](const int i) { return integrator_state_read_volume_stack(INTEGRATOR_STATE_PASS, i); }); diff --git a/intern/cycles/kernel/integrator/integrator_state.h b/intern/cycles/kernel/integrator/integrator_state.h index 094446be02c..f745ad3f4b9 100644 --- a/intern/cycles/kernel/integrator/integrator_state.h +++ b/intern/cycles/kernel/integrator/integrator_state.h @@ -60,7 +60,15 @@ CCL_NAMESPACE_BEGIN * TODO: these could be made dynamic depending on the features used in the scene. */ #define INTEGRATOR_VOLUME_STACK_SIZE VOLUME_STACK_SIZE -#define INTEGRATOR_SHADOW_ISECT_SIZE 4 + +#define INTEGRATOR_SHADOW_ISECT_SIZE_CPU 1024 +#define INTEGRATOR_SHADOW_ISECT_SIZE_GPU 4 + +#ifdef __KERNEL_CPU__ +# define INTEGRATOR_SHADOW_ISECT_SIZE INTEGRATOR_SHADOW_ISECT_SIZE_CPU +#else +# define INTEGRATOR_SHADOW_ISECT_SIZE INTEGRATOR_SHADOW_ISECT_SIZE_GPU +#endif /* Data structures */ @@ -74,9 +82,9 @@ typedef struct IntegratorStateCPU { #define KERNEL_STRUCT_END(name) \ } \ name; -#define KERNEL_STRUCT_END_ARRAY(name, size) \ +#define KERNEL_STRUCT_END_ARRAY(name, cpu_size, gpu_size) \ } \ - name[size]; + name[cpu_size]; #include "kernel/integrator/integrator_state_template.h" #undef KERNEL_STRUCT_BEGIN #undef KERNEL_STRUCT_MEMBER @@ -103,9 +111,9 @@ typedef struct IntegratorStateGPU { #define KERNEL_STRUCT_END(name) \ } \ name; -#define KERNEL_STRUCT_END_ARRAY(name, size) \ +#define KERNEL_STRUCT_END_ARRAY(name, cpu_size, gpu_size) \ } \ - name[size]; + name[gpu_size]; #include "kernel/integrator/integrator_state_template.h" #undef KERNEL_STRUCT_BEGIN #undef KERNEL_STRUCT_MEMBER diff --git a/intern/cycles/kernel/integrator/integrator_state_template.h b/intern/cycles/kernel/integrator/integrator_state_template.h index 41dd1bfcdbf..0d8126c64aa 100644 --- a/intern/cycles/kernel/integrator/integrator_state_template.h +++ b/intern/cycles/kernel/integrator/integrator_state_template.h @@ -107,7 +107,7 @@ KERNEL_STRUCT_END(subsurface) KERNEL_STRUCT_BEGIN(volume_stack) KERNEL_STRUCT_ARRAY_MEMBER(volume_stack, int, object, KERNEL_FEATURE_VOLUME) KERNEL_STRUCT_ARRAY_MEMBER(volume_stack, int, shader, KERNEL_FEATURE_VOLUME) -KERNEL_STRUCT_END_ARRAY(volume_stack, INTEGRATOR_VOLUME_STACK_SIZE) +KERNEL_STRUCT_END_ARRAY(volume_stack, INTEGRATOR_VOLUME_STACK_SIZE, INTEGRATOR_VOLUME_STACK_SIZE) /********************************* Shadow Path State **************************/ @@ -153,11 +153,15 @@ KERNEL_STRUCT_ARRAY_MEMBER(shadow_isect, int, object, KERNEL_FEATURE_PATH_TRACIN KERNEL_STRUCT_ARRAY_MEMBER(shadow_isect, int, type, KERNEL_FEATURE_PATH_TRACING) /* TODO: exclude for GPU. */ KERNEL_STRUCT_ARRAY_MEMBER(shadow_isect, float3, Ng, KERNEL_FEATURE_PATH_TRACING) -KERNEL_STRUCT_END_ARRAY(shadow_isect, INTEGRATOR_SHADOW_ISECT_SIZE) +KERNEL_STRUCT_END_ARRAY(shadow_isect, + INTEGRATOR_SHADOW_ISECT_SIZE_CPU, + INTEGRATOR_SHADOW_ISECT_SIZE_GPU) /**************************** Shadow Volume Stack *****************************/ KERNEL_STRUCT_BEGIN(shadow_volume_stack) KERNEL_STRUCT_ARRAY_MEMBER(shadow_volume_stack, int, object, KERNEL_FEATURE_VOLUME) KERNEL_STRUCT_ARRAY_MEMBER(shadow_volume_stack, int, shader, KERNEL_FEATURE_VOLUME) -KERNEL_STRUCT_END_ARRAY(shadow_volume_stack, INTEGRATOR_VOLUME_STACK_SIZE) +KERNEL_STRUCT_END_ARRAY(shadow_volume_stack, + INTEGRATOR_VOLUME_STACK_SIZE, + INTEGRATOR_VOLUME_STACK_SIZE) diff --git a/intern/cycles/kernel/integrator/integrator_state_util.h b/intern/cycles/kernel/integrator/integrator_state_util.h index cdf412fe22f..08d6cb00114 100644 --- a/intern/cycles/kernel/integrator/integrator_state_util.h +++ b/intern/cycles/kernel/integrator/integrator_state_util.h @@ -217,10 +217,10 @@ ccl_device_inline void integrator_state_copy_only(const IntegratorState to_state while (false) \ ; -# define KERNEL_STRUCT_END_ARRAY(name, array_size) \ +# define KERNEL_STRUCT_END_ARRAY(name, cpu_array_size, gpu_array_size) \ ++index; \ } \ - while (index < array_size) \ + while (index < gpu_array_size) \ ; # include "kernel/integrator/integrator_state_template.h" @@ -264,7 +264,12 @@ ccl_device_inline void integrator_state_shadow_catcher_split(INTEGRATOR_STATE_AR IntegratorStateCPU *ccl_restrict split_state = state + 1; - *split_state = *state; + /* Only copy the required subset, since shadow intersections are big and irrelevant here. */ + split_state->path = state->path; + split_state->ray = state->ray; + split_state->isect = state->isect; + memcpy(split_state->volume_stack, state->volume_stack, sizeof(state->volume_stack)); + split_state->shadow_path = state->shadow_path; split_state->path.flag |= PATH_RAY_SHADOW_CATCHER_PASS; #endif diff --git a/intern/cycles/kernel/kernel_accumulate.h b/intern/cycles/kernel/kernel_accumulate.h index 9e12d24dcf4..f4d00e4c20c 100644 --- a/intern/cycles/kernel/kernel_accumulate.h +++ b/intern/cycles/kernel/kernel_accumulate.h @@ -386,7 +386,7 @@ ccl_device_inline void kernel_accum_light(INTEGRATOR_STATE_CONST_ARGS, { /* The throughput for shadow paths already contains the light shader evaluation. */ float3 contribution = INTEGRATOR_STATE(shadow_path, throughput); - kernel_accum_clamp(kg, &contribution, INTEGRATOR_STATE(shadow_path, bounce) - 1); + kernel_accum_clamp(kg, &contribution, INTEGRATOR_STATE(shadow_path, bounce)); ccl_global float *buffer = kernel_accum_pixel_render_buffer(INTEGRATOR_STATE_PASS, render_buffer); diff --git a/intern/cycles/kernel/kernel_bake.h b/intern/cycles/kernel/kernel_bake.h index e025bcd6674..abb1ba455e6 100644 --- a/intern/cycles/kernel/kernel_bake.h +++ b/intern/cycles/kernel/kernel_bake.h @@ -42,6 +42,16 @@ ccl_device void kernel_displace_evaluate(const KernelGlobals *kg, object_inverse_dir_transform(kg, &sd, &D); +#ifdef __KERNEL_DEBUG_NAN__ + if (!isfinite3_safe(D)) { + kernel_assert(!"Cycles displacement with non-finite value detected"); + } +#endif + + /* Ensure finite displacement, preventing BVH from becoming degenerate and avoiding possible + * traversal issues caused by non-finite math. */ + D = ensure_finite3(D); + /* Write output. */ output[offset] += make_float4(D.x, D.y, D.z, 0.0f); } @@ -66,7 +76,16 @@ ccl_device void kernel_background_evaluate(const KernelGlobals *kg, const int path_flag = PATH_RAY_EMISSION; shader_eval_surface<KERNEL_FEATURE_NODE_MASK_SURFACE_LIGHT>( INTEGRATOR_STATE_PASS_NULL, &sd, NULL, path_flag); - const float3 color = shader_background_eval(&sd); + float3 color = shader_background_eval(&sd); + +#ifdef __KERNEL_DEBUG_NAN__ + if (!isfinite3_safe(color)) { + kernel_assert(!"Cycles background with non-finite value detected"); + } +#endif + + /* Ensure finite color, avoiding possible numerical instabilities in the path tracing kernels. */ + color = ensure_finite3(color); /* Write output. */ output[offset] += make_float4(color.x, color.y, color.z, 0.0f); diff --git a/intern/cycles/kernel/kernel_film.h b/intern/cycles/kernel/kernel_film.h index 715d764fb31..e8f4a21878e 100644 --- a/intern/cycles/kernel/kernel_film.h +++ b/intern/cycles/kernel/kernel_film.h @@ -394,7 +394,7 @@ film_calculate_shadow_catcher(const KernelFilmConvert *ccl_restrict kfilm_conver /* NOTE: It is possible that the Shadow Catcher pass is requested as an output without actual * shadow catcher objects in the scene. In this case there will be no auxiliary passes required - * for the devision (to save up memory). So delay the asserts to this point so that the number of + * for the decision (to save up memory). So delay the asserts to this point so that the number of * samples check handles such configuration. */ kernel_assert(kfilm_convert->pass_offset != PASS_UNUSED); kernel_assert(kfilm_convert->pass_combined != PASS_UNUSED); diff --git a/intern/cycles/kernel/kernel_jitter.h b/intern/cycles/kernel/kernel_jitter.h index 354e8115538..1beaf3cc2b2 100644 --- a/intern/cycles/kernel/kernel_jitter.h +++ b/intern/cycles/kernel/kernel_jitter.h @@ -74,10 +74,6 @@ ccl_device_inline float cmj_randfloat_simple(uint i, uint p) ccl_device float pmj_sample_1D(const KernelGlobals *kg, uint sample, uint rng_hash, uint dimension) { - /* The PMJ sample sets contain a sample with (x,y) with NUM_PMJ_SAMPLES so for 1D - * the x part is used as the sample (TODO(@leesonw): Add using both x and y parts - * independently). */ - /* Perform Owen shuffle of the sample number to reorder the samples. */ #ifdef _SIMPLE_HASH_ const uint rv = cmj_hash_simple(dimension, rng_hash); @@ -95,7 +91,10 @@ ccl_device float pmj_sample_1D(const KernelGlobals *kg, uint sample, uint rng_ha const uint sample_set = s / NUM_PMJ_SAMPLES; const uint d = (dimension + sample_set); const uint dim = d % NUM_PMJ_PATTERNS; - int index = 2 * (dim * NUM_PMJ_SAMPLES + (s % NUM_PMJ_SAMPLES)); + + /* The PMJ sample sets contain a sample with (x,y) with NUM_PMJ_SAMPLES so for 1D + * the x part is used for even dims and the y for odd. */ + int index = 2 * ((dim >> 1) * NUM_PMJ_SAMPLES + (s % NUM_PMJ_SAMPLES)) + (dim & 1); float fx = kernel_tex_fetch(__sample_pattern_lut, index); @@ -104,12 +103,11 @@ ccl_device float pmj_sample_1D(const KernelGlobals *kg, uint sample, uint rng_ha # ifdef _SIMPLE_HASH_ float dx = cmj_randfloat_simple(d, rng_hash); # else - /* Only jitter within the grid interval. */ float dx = cmj_randfloat(d, rng_hash); # endif - fx = fx + dx * (1.0f / NUM_PMJ_SAMPLES); + /* Jitter sample locations and map back into [0 1]. */ + fx = fx + dx; fx = fx - floorf(fx); - #else # warning "Not using Cranley-Patterson Rotation." #endif @@ -136,7 +134,7 @@ ccl_device void pmj_sample_2D( /* Based on the sample number a sample pattern is selected and offset by the dimension. */ const uint sample_set = s / NUM_PMJ_SAMPLES; const uint d = (dimension + sample_set); - const uint dim = d % NUM_PMJ_PATTERNS; + uint dim = d % NUM_PMJ_PATTERNS; int index = 2 * (dim * NUM_PMJ_SAMPLES + (s % NUM_PMJ_SAMPLES)); float fx = kernel_tex_fetch(__sample_pattern_lut, index); @@ -151,17 +149,17 @@ ccl_device void pmj_sample_2D( float dx = cmj_randfloat(d, rng_hash); float dy = cmj_randfloat(d + 1, rng_hash); # endif - /* Only jitter within the grid cells. */ - fx = fx + dx * (1.0f / NUM_PMJ_DIVISIONS); - fy = fy + dy * (1.0f / NUM_PMJ_DIVISIONS); - fx = fx - floorf(fx); - fy = fy - floorf(fy); + /* Jitter sample locations and map back to the unit square [0 1]x[0 1]. */ + float sx = fx + dx; + float sy = fy + dy; + sx = sx - floorf(sx); + sy = sy - floorf(sy); #else # warning "Not using Cranley Patterson Rotation." #endif - (*x) = fx; - (*y) = fy; + (*x) = sx; + (*y) = sy; } CCL_NAMESPACE_END diff --git a/intern/cycles/kernel/kernel_shader.h b/intern/cycles/kernel/kernel_shader.h index 3052bb53040..e7133724c85 100644 --- a/intern/cycles/kernel/kernel_shader.h +++ b/intern/cycles/kernel/kernel_shader.h @@ -186,8 +186,8 @@ ccl_device_inline float _shader_bsdf_multi_eval(const KernelGlobals *kg, float sum_sample_weight, const uint light_shader_flags) { - /* 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 (int i = 0; i < sd->num_closure; i++) { const ShaderClosure *sc = &sd->closure[i]; @@ -750,7 +750,7 @@ ccl_device int shader_phase_sample_closure(const KernelGlobals *kg, /* Volume Evaluation */ -template<typename StackReadOp> +template<const bool shadow, typename StackReadOp> ccl_device_inline void shader_eval_volume(INTEGRATOR_STATE_CONST_ARGS, ShaderData *ccl_restrict sd, const int path_flag, @@ -815,8 +815,11 @@ ccl_device_inline void shader_eval_volume(INTEGRATOR_STATE_CONST_ARGS, # endif /* Merge closures to avoid exceeding number of closures limit. */ - if (i > 0) - shader_merge_volume_closures(sd); + if (!shadow) { + if (i > 0) { + shader_merge_volume_closures(sd); + } + } } } diff --git a/intern/cycles/kernel/kernel_types.h b/intern/cycles/kernel/kernel_types.h index 66b7310ab65..3cc42bf7a85 100644 --- a/intern/cycles/kernel/kernel_types.h +++ b/intern/cycles/kernel/kernel_types.h @@ -572,6 +572,7 @@ typedef enum AttributeStandard { ATTR_STD_MOTION_VERTEX_NORMAL, ATTR_STD_PARTICLE, ATTR_STD_CURVE_INTERCEPT, + ATTR_STD_CURVE_LENGTH, ATTR_STD_CURVE_RANDOM, ATTR_STD_PTEX_FACE_ID, ATTR_STD_PTEX_UV, diff --git a/intern/cycles/kernel/osl/osl_services.cpp b/intern/cycles/kernel/osl/osl_services.cpp index 396f42080e4..4fc46a255a8 100644 --- a/intern/cycles/kernel/osl/osl_services.cpp +++ b/intern/cycles/kernel/osl/osl_services.cpp @@ -107,6 +107,7 @@ ustring OSLRenderServices::u_geom_undisplaced("geom:undisplaced"); ustring OSLRenderServices::u_is_smooth("geom:is_smooth"); ustring OSLRenderServices::u_is_curve("geom:is_curve"); ustring OSLRenderServices::u_curve_thickness("geom:curve_thickness"); +ustring OSLRenderServices::u_curve_length("geom:curve_length"); ustring OSLRenderServices::u_curve_tangent_normal("geom:curve_tangent_normal"); ustring OSLRenderServices::u_curve_random("geom:curve_random"); ustring OSLRenderServices::u_path_ray_length("path:ray_length"); diff --git a/intern/cycles/kernel/osl/osl_services.h b/intern/cycles/kernel/osl/osl_services.h index 58accb46e7d..2a5400282b3 100644 --- a/intern/cycles/kernel/osl/osl_services.h +++ b/intern/cycles/kernel/osl/osl_services.h @@ -294,6 +294,7 @@ class OSLRenderServices : public OSL::RendererServices { static ustring u_is_smooth; static ustring u_is_curve; static ustring u_curve_thickness; + static ustring u_curve_length; static ustring u_curve_tangent_normal; static ustring u_curve_random; static ustring u_path_ray_length; diff --git a/intern/cycles/kernel/shaders/CMakeLists.txt b/intern/cycles/kernel/shaders/CMakeLists.txt index 02be7813369..6b62e7bb52f 100644 --- a/intern/cycles/kernel/shaders/CMakeLists.txt +++ b/intern/cycles/kernel/shaders/CMakeLists.txt @@ -41,6 +41,7 @@ set(SRC_OSL node_vector_displacement.osl node_emission.osl node_environment_texture.osl + node_float_curve.osl node_fresnel.osl node_gamma.osl node_geometry.osl diff --git a/intern/cycles/kernel/shaders/node_float_curve.osl b/intern/cycles/kernel/shaders/node_float_curve.osl new file mode 100644 index 00000000000..f1f05fd88a9 --- /dev/null +++ b/intern/cycles/kernel/shaders/node_float_curve.osl @@ -0,0 +1,32 @@ +/* + * Copyright 2011-2021 Blender Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "node_ramp_util.h" +#include "stdcycles.h" + +shader node_float_curve(float ramp[] = {0.0}, + float min_x = 0.0, + float max_x = 1.0, + float ValueIn = 0.0, + float Factor = 0.0, + output float ValueOut = 0.0) +{ + float c = (ValueIn - min_x) / (max_x - min_x); + + ValueOut = rgb_ramp_lookup(ramp, c, 1, 1); + + ValueOut = mix(ValueIn, ValueOut, Factor); +} diff --git a/intern/cycles/kernel/shaders/node_hair_info.osl b/intern/cycles/kernel/shaders/node_hair_info.osl index ee08ea57e68..ddc2e28b83a 100644 --- a/intern/cycles/kernel/shaders/node_hair_info.osl +++ b/intern/cycles/kernel/shaders/node_hair_info.osl @@ -18,12 +18,14 @@ shader node_hair_info(output float IsStrand = 0.0, output float Intercept = 0.0, + output float Length = 0.0, output float Thickness = 0.0, output normal TangentNormal = N, output float Random = 0) { getattribute("geom:is_curve", IsStrand); getattribute("geom:curve_intercept", Intercept); + getattribute("geom:curve_length", Length); getattribute("geom:curve_thickness", Thickness); getattribute("geom:curve_tangent_normal", TangentNormal); getattribute("geom:curve_random", Random); diff --git a/intern/cycles/kernel/svm/svm.h b/intern/cycles/kernel/svm/svm.h index 4aee1ef11b3..ad609b15f86 100644 --- a/intern/cycles/kernel/svm/svm.h +++ b/intern/cycles/kernel/svm/svm.h @@ -493,11 +493,13 @@ ccl_device void svm_eval_nodes(INTEGRATOR_STATE_CONST_ARGS, case NODE_IES: svm_node_ies(kg, sd, stack, node); break; - case NODE_RGB_CURVES: case NODE_VECTOR_CURVES: offset = svm_node_curves(kg, sd, stack, node, offset); break; + case NODE_FLOAT_CURVE: + offset = svm_node_curve(kg, sd, stack, node, offset); + break; case NODE_TANGENT: svm_node_tangent(kg, sd, stack, node); break; diff --git a/intern/cycles/kernel/svm/svm_geometry.h b/intern/cycles/kernel/svm/svm_geometry.h index 10e9f291d0e..432529eb061 100644 --- a/intern/cycles/kernel/svm/svm_geometry.h +++ b/intern/cycles/kernel/svm/svm_geometry.h @@ -213,6 +213,8 @@ ccl_device_noinline void svm_node_hair_info( } case NODE_INFO_CURVE_INTERCEPT: break; /* handled as attribute */ + case NODE_INFO_CURVE_LENGTH: + break; /* handled as attribute */ case NODE_INFO_CURVE_RANDOM: break; /* handled as attribute */ case NODE_INFO_CURVE_THICKNESS: { diff --git a/intern/cycles/kernel/svm/svm_ramp.h b/intern/cycles/kernel/svm/svm_ramp.h index e92df3c093c..563e5bcb5e4 100644 --- a/intern/cycles/kernel/svm/svm_ramp.h +++ b/intern/cycles/kernel/svm/svm_ramp.h @@ -21,6 +21,48 @@ CCL_NAMESPACE_BEGIN /* NOTE: svm_ramp.h, svm_ramp_util.h and node_ramp_util.h must stay consistent */ +ccl_device_inline float fetch_float(const KernelGlobals *kg, int offset) +{ + uint4 node = kernel_tex_fetch(__svm_nodes, offset); + return __uint_as_float(node.x); +} + +ccl_device_inline float float_ramp_lookup(const KernelGlobals *kg, + int offset, + float f, + bool interpolate, + bool extrapolate, + int table_size) +{ + if ((f < 0.0f || f > 1.0f) && extrapolate) { + float t0, dy; + if (f < 0.0f) { + t0 = fetch_float(kg, offset); + dy = t0 - fetch_float(kg, offset + 1); + f = -f; + } + else { + t0 = fetch_float(kg, offset + table_size - 1); + dy = t0 - fetch_float(kg, offset + table_size - 2); + f = f - 1.0f; + } + return t0 + dy * f * (table_size - 1); + } + + f = saturate(f) * (table_size - 1); + + /* clamp int as well in case of NaN */ + int i = clamp(float_to_int(f), 0, table_size - 1); + float t = f - (float)i; + + float a = fetch_float(kg, offset + i); + + if (interpolate && t > 0.0f) + a = (1.0f - t) * a + t * fetch_float(kg, offset + i + 1); + + return a; +} + ccl_device_inline float4 rgb_ramp_lookup(const KernelGlobals *kg, int offset, float f, @@ -105,6 +147,30 @@ ccl_device_noinline int svm_node_curves( return offset; } +ccl_device_noinline int svm_node_curve( + const KernelGlobals *kg, ShaderData *sd, float *stack, uint4 node, int offset) +{ + uint fac_offset, value_in_offset, out_offset; + svm_unpack_node_uchar3(node.y, &fac_offset, &value_in_offset, &out_offset); + + uint table_size = read_node(kg, &offset).x; + + float fac = stack_load_float(stack, fac_offset); + float in = stack_load_float(stack, value_in_offset); + + const float min = __int_as_float(node.z), max = __int_as_float(node.w); + const float range = max - min; + const float relpos = (in - min) / range; + + float v = float_ramp_lookup(kg, offset, relpos, true, true, table_size); + + in = (1.0f - fac) * in + fac * v; + stack_store_float(stack, out_offset, in); + + offset += table_size; + return offset; +} + CCL_NAMESPACE_END #endif /* __SVM_RAMP_H__ */ diff --git a/intern/cycles/kernel/svm/svm_types.h b/intern/cycles/kernel/svm/svm_types.h index c053be96c51..59a0e33acbc 100644 --- a/intern/cycles/kernel/svm/svm_types.h +++ b/intern/cycles/kernel/svm/svm_types.h @@ -122,6 +122,7 @@ typedef enum ShaderNodeType { NODE_AOV_START, NODE_AOV_COLOR, NODE_AOV_VALUE, + NODE_FLOAT_CURVE, /* NOTE: for best OpenCL performance, item definition in the enum must * match the switch case order in svm.h. */ } ShaderNodeType; @@ -173,6 +174,7 @@ typedef enum NodeParticleInfo { typedef enum NodeHairInfo { NODE_INFO_CURVE_IS_STRAND, NODE_INFO_CURVE_INTERCEPT, + NODE_INFO_CURVE_LENGTH, NODE_INFO_CURVE_THICKNESS, /* Fade for minimum hair width transiency. */ // NODE_INFO_CURVE_FADE, diff --git a/intern/cycles/render/CMakeLists.txt b/intern/cycles/render/CMakeLists.txt index 6edb5261b32..323222b8c85 100644 --- a/intern/cycles/render/CMakeLists.txt +++ b/intern/cycles/render/CMakeLists.txt @@ -35,7 +35,6 @@ set(SRC denoising.cpp film.cpp geometry.cpp - gpu_display.cpp graph.cpp hair.cpp image.cpp @@ -78,9 +77,10 @@ set(SRC_HEADERS colorspace.h constant_fold.h denoising.h + display_driver.h + output_driver.h film.h geometry.h - gpu_display.h graph.h hair.h image.h diff --git a/intern/cycles/render/attribute.cpp b/intern/cycles/render/attribute.cpp index ea5a5f50f2d..aaf21ad9fd2 100644 --- a/intern/cycles/render/attribute.cpp +++ b/intern/cycles/render/attribute.cpp @@ -342,6 +342,8 @@ const char *Attribute::standard_name(AttributeStandard std) return "particle"; case ATTR_STD_CURVE_INTERCEPT: return "curve_intercept"; + case ATTR_STD_CURVE_LENGTH: + return "curve_length"; case ATTR_STD_CURVE_RANDOM: return "curve_random"; case ATTR_STD_PTEX_FACE_ID: @@ -586,6 +588,9 @@ Attribute *AttributeSet::add(AttributeStandard std, ustring name) case ATTR_STD_CURVE_INTERCEPT: attr = add(name, TypeDesc::TypeFloat, ATTR_ELEMENT_CURVE_KEY); break; + case ATTR_STD_CURVE_LENGTH: + attr = add(name, TypeDesc::TypeFloat, ATTR_ELEMENT_CURVE); + break; case ATTR_STD_CURVE_RANDOM: attr = add(name, TypeDesc::TypeFloat, ATTR_ELEMENT_CURVE); break; diff --git a/intern/cycles/render/buffers.cpp b/intern/cycles/render/buffers.cpp index 1882510cd70..3682b55049a 100644 --- a/intern/cycles/render/buffers.cpp +++ b/intern/cycles/render/buffers.cpp @@ -22,7 +22,6 @@ #include "util/util_foreach.h" #include "util/util_hash.h" #include "util/util_math.h" -#include "util/util_opengl.h" #include "util/util_time.h" #include "util/util_types.h" diff --git a/intern/cycles/render/display_driver.h b/intern/cycles/render/display_driver.h new file mode 100644 index 00000000000..85f305034d7 --- /dev/null +++ b/intern/cycles/render/display_driver.h @@ -0,0 +1,131 @@ +/* + * Copyright 2021 Blender Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "util/util_half.h" +#include "util/util_types.h" + +CCL_NAMESPACE_BEGIN + +/* Display driver for efficient interactive display of renders. + * + * Host applications implement this interface for viewport rendering. For best performance, we + * recommend: + * - Allocating a texture on the GPU to be interactively updated + * - Using the graphics interop mechanism to avoid CPU-GPU copying overhead + * - Using a dedicated or thread-safe graphics API context for updates, to avoid + * blocking the host application. + */ +class DisplayDriver { + public: + DisplayDriver() = default; + virtual ~DisplayDriver() = default; + + /* Render buffer parameters. */ + struct Params { + public: + /* Render resolution, ignoring progressive resolution changes. + * The texture buffer should be allocated with this size. */ + int2 size = make_int2(0, 0); + + /* For border rendering, the full resolution of the render, and the offset within that larger + * render. */ + int2 full_size = make_int2(0, 0); + int2 full_offset = make_int2(0, 0); + + bool modified(const Params &other) const + { + return !(full_offset == other.full_offset && full_size == other.full_size && + size == other.size); + } + }; + + /* Update the render from the rendering thread. + * + * Cycles periodically updates the render to be displayed. For multithreaded updates with + * potentially multiple rendering devices, it will call these methods as follows. + * + * if (driver.update_begin(params, width, height)) { + * parallel_for_each(rendering_device) { + * buffer = driver.map_texture_buffer(); + * if (buffer) { + * fill(buffer); + * driver.unmap_texture_buffer(); + * } + * } + * driver.update_end(); + * } + * + * The parameters may dynamically change due to camera changes in the scene, and resources should + * be re-allocated accordingly. + * + * The width and height passed to update_begin() are the effective render resolution taking into + * account progressive resolution changes, which may be equal to or smaller than the params.size. + * For efficiency, changes in this resolution should be handled without re-allocating resources, + * but rather by using a subset of the full resolution buffer. */ + virtual bool update_begin(const Params ¶ms, int width, int height) = 0; + virtual void update_end() = 0; + + virtual half4 *map_texture_buffer() = 0; + virtual void unmap_texture_buffer() = 0; + + /* Optionally return a handle to a native graphics API texture buffer. If supported, + * the rendering device may write directly to this buffer instead of calling + * map_texture_buffer() and unmap_texture_buffer(). */ + class GraphicsInterop { + public: + /* Dimensions of the buffer, in pixels. */ + int buffer_width = 0; + int buffer_height = 0; + + /* OpenGL pixel buffer object. */ + int opengl_pbo_id = 0; + + /* Clear the entire buffer before doing partial write to it. */ + bool need_clear = false; + }; + + virtual GraphicsInterop graphics_interop_get() + { + return GraphicsInterop(); + } + + /* (De)activate graphics context required for editing or deleting the graphics interop + * object. + * + * For example, destruction of the CUDA object associated with an OpenGL requires the + * OpenGL context to be active. */ + virtual void graphics_interop_activate(){}; + virtual void graphics_interop_deactivate(){}; + + /* Clear the display buffer by filling it with zeros. */ + virtual void clear() = 0; + + /* Draw the render using the native graphics API. + * + * Note that this may be called in parallel to updates. The implementation is responsible for + * mutex locking or other mechanisms to avoid conflicts. + * + * The parameters may have changed since the last update. The implementation is responsible for + * deciding to skip or adjust render display for such changes. + * + * Host application drawing the render buffer should use Session.draw(), which will + * call this method. */ + virtual void draw(const Params ¶ms) = 0; +}; + +CCL_NAMESPACE_END diff --git a/intern/cycles/render/film.cpp b/intern/cycles/render/film.cpp index 8e14b338bd3..ad3336ca089 100644 --- a/intern/cycles/render/film.cpp +++ b/intern/cycles/render/film.cpp @@ -434,7 +434,8 @@ void Film::update_passes(Scene *scene, bool add_sample_count_pass) const ObjectManager *object_manager = scene->object_manager; Integrator *integrator = scene->integrator; - if (!is_modified() && !object_manager->need_update() && !integrator->is_modified()) { + if (!is_modified() && !object_manager->need_update() && !integrator->is_modified() && + !background->is_modified()) { return; } diff --git a/intern/cycles/render/integrator.h b/intern/cycles/render/integrator.h index 32e108d62ca..5ad419e02ca 100644 --- a/intern/cycles/render/integrator.h +++ b/intern/cycles/render/integrator.h @@ -19,7 +19,7 @@ #include "kernel/kernel_types.h" -#include "device/device_denoise.h" /* For the paramaters and type enum. */ +#include "device/device_denoise.h" /* For the parameters and type enum. */ #include "graph/node.h" #include "integrator/adaptive_sampling.h" diff --git a/intern/cycles/render/nodes.cpp b/intern/cycles/render/nodes.cpp index 03b79d7de3e..1629895ff6e 100644 --- a/intern/cycles/render/nodes.cpp +++ b/intern/cycles/render/nodes.cpp @@ -4368,6 +4368,7 @@ NODE_DEFINE(HairInfoNode) SOCKET_OUT_FLOAT(is_strand, "Is Strand"); SOCKET_OUT_FLOAT(intercept, "Intercept"); + SOCKET_OUT_FLOAT(size, "Length"); SOCKET_OUT_FLOAT(thickness, "Thickness"); SOCKET_OUT_NORMAL(tangent_normal, "Tangent Normal"); #if 0 /* Output for minimum hair width transparency - deactivated. */ @@ -4390,6 +4391,9 @@ void HairInfoNode::attributes(Shader *shader, AttributeRequestSet *attributes) if (!intercept_out->links.empty()) attributes->add(ATTR_STD_CURVE_INTERCEPT); + if (!output("Length")->links.empty()) + attributes->add(ATTR_STD_CURVE_LENGTH); + if (!output("Random")->links.empty()) attributes->add(ATTR_STD_CURVE_RANDOM); } @@ -4412,6 +4416,12 @@ void HairInfoNode::compile(SVMCompiler &compiler) compiler.add_node(NODE_ATTR, attr, compiler.stack_assign(out), NODE_ATTR_OUTPUT_FLOAT); } + out = output("Length"); + if (!out->links.empty()) { + int attr = compiler.attribute(ATTR_STD_CURVE_LENGTH); + compiler.add_node(NODE_ATTR, attr, compiler.stack_assign(out), NODE_ATTR_OUTPUT_FLOAT); + } + out = output("Thickness"); if (!out->links.empty()) { compiler.add_node(NODE_HAIR_INFO, NODE_INFO_CURVE_THICKNESS, compiler.stack_assign(out)); @@ -6372,7 +6382,7 @@ void BumpNode::constant_fold(const ConstantFolder &folder) /* TODO(sergey): Ignore bump with zero strength. */ } -/* Curve node */ +/* Curves node */ CurvesNode::CurvesNode(const NodeType *node_type) : ShaderNode(node_type) { @@ -6521,6 +6531,83 @@ void VectorCurvesNode::compile(OSLCompiler &compiler) CurvesNode::compile(compiler, "node_vector_curves"); } +/* FloatCurveNode */ + +NODE_DEFINE(FloatCurveNode) +{ + NodeType *type = NodeType::add("float_curve", create, NodeType::SHADER); + + SOCKET_FLOAT_ARRAY(curve, "Curve", array<float>()); + SOCKET_FLOAT(min_x, "Min X", 0.0f); + SOCKET_FLOAT(max_x, "Max X", 1.0f); + + SOCKET_IN_FLOAT(fac, "Factor", 0.0f); + SOCKET_IN_FLOAT(value, "Value", 0.0f); + + SOCKET_OUT_FLOAT(value, "Value"); + + return type; +} + +FloatCurveNode::FloatCurveNode() : ShaderNode(get_node_type()) +{ +} + +void FloatCurveNode::constant_fold(const ConstantFolder &folder) +{ + ShaderInput *value_in = input("Value"); + ShaderInput *fac_in = input("Factor"); + + /* evaluate fully constant node */ + if (folder.all_inputs_constant()) { + if (curve.size() == 0) { + return; + } + + float pos = (value - min_x) / (max_x - min_x); + float result = float_ramp_lookup(curve.data(), pos, true, true, curve.size()); + + folder.make_constant(value + fac * (result - value)); + } + /* remove no-op node */ + else if (!fac_in->link && fac == 0.0f) { + /* link is not null because otherwise all inputs are constant */ + folder.bypass(value_in->link); + } +} + +void FloatCurveNode::compile(SVMCompiler &compiler) +{ + if (curve.size() == 0) + return; + + ShaderInput *value_in = input("Value"); + ShaderInput *fac_in = input("Factor"); + ShaderOutput *value_out = output("Value"); + + compiler.add_node(NODE_FLOAT_CURVE, + compiler.encode_uchar4(compiler.stack_assign(fac_in), + compiler.stack_assign(value_in), + compiler.stack_assign(value_out)), + __float_as_int(min_x), + __float_as_int(max_x)); + + compiler.add_node(curve.size()); + for (int i = 0; i < curve.size(); i++) + compiler.add_node(make_float4(curve[i])); +} + +void FloatCurveNode::compile(OSLCompiler &compiler) +{ + if (curve.size() == 0) + return; + + compiler.parameter_array("ramp", curve.data(), curve.size()); + compiler.parameter(this, "min_x"); + compiler.parameter(this, "max_x"); + compiler.add(this, "node_float_curve"); +} + /* RGBRampNode */ NODE_DEFINE(RGBRampNode) diff --git a/intern/cycles/render/nodes.h b/intern/cycles/render/nodes.h index 22bdb06b059..5ac72835ac5 100644 --- a/intern/cycles/render/nodes.h +++ b/intern/cycles/render/nodes.h @@ -1398,6 +1398,18 @@ class VectorCurvesNode : public CurvesNode { void constant_fold(const ConstantFolder &folder); }; +class FloatCurveNode : public ShaderNode { + public: + SHADER_NODE_CLASS(FloatCurveNode) + void constant_fold(const ConstantFolder &folder); + + NODE_SOCKET_API_ARRAY(array<float>, curve) + NODE_SOCKET_API(float, min_x) + NODE_SOCKET_API(float, max_x) + NODE_SOCKET_API(float, fac) + NODE_SOCKET_API(float, value) +}; + class RGBRampNode : public ShaderNode { public: SHADER_NODE_CLASS(RGBRampNode) diff --git a/intern/cycles/render/osl.cpp b/intern/cycles/render/osl.cpp index d28b222c10e..5a43b641872 100644 --- a/intern/cycles/render/osl.cpp +++ b/intern/cycles/render/osl.cpp @@ -727,8 +727,8 @@ void OSLCompiler::add(ShaderNode *node, const char *name, bool isfilepath) } } - /* create shader of the appropriate type. OSL only distinguishes between "surface" - * and "displacement" atm */ + /* Create shader of the appropriate type. OSL only distinguishes between "surface" + * and "displacement" at the moment. */ if (current_type == SHADER_TYPE_SURFACE) ss->Shader("surface", name, id(node).c_str()); else if (current_type == SHADER_TYPE_VOLUME) diff --git a/intern/cycles/render/output_driver.h b/intern/cycles/render/output_driver.h new file mode 100644 index 00000000000..b7e980d71d4 --- /dev/null +++ b/intern/cycles/render/output_driver.h @@ -0,0 +1,82 @@ +/* + * Copyright 2021 Blender Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "util/util_math.h" +#include "util/util_string.h" +#include "util/util_types.h" + +CCL_NAMESPACE_BEGIN + +/* Output driver for reading render buffers. + * + * Host applications implement this interface for outputting render buffers for offline rendering. + * Drivers can be used to copy the buffers into the host application or write them directly to + * disk. This interface may also be used for interactive display, however the DisplayDriver is more + * efficient for that purpose. + */ +class OutputDriver { + public: + OutputDriver() = default; + virtual ~OutputDriver() = default; + + class Tile { + public: + Tile(const int2 offset, + const int2 size, + const int2 full_size, + const string_view layer, + const string_view view) + : offset(offset), size(size), full_size(full_size), layer(layer), view(view) + { + } + virtual ~Tile() = default; + + const int2 offset; + const int2 size; + const int2 full_size; + const string layer; + const string view; + + virtual bool get_pass_pixels(const string_view pass_name, + const int num_channels, + float *pixels) const = 0; + virtual bool set_pass_pixels(const string_view pass_name, + const int num_channels, + const float *pixels) const = 0; + }; + + /* Write tile once it has finished rendering. */ + virtual void write_render_tile(const Tile &tile) = 0; + + /* Update tile while rendering is in progress. Return true if any update + * was performed. */ + virtual bool update_render_tile(const Tile & /* tile */) + { + return false; + } + + /* For baking, read render pass PASS_BAKE_PRIMITIVE and PASS_BAKE_DIFFERENTIAL + * to determine which shading points to use for baking at each pixel. Return + * true if any data was read. */ + virtual bool read_render_tile(const Tile & /* tile */) + { + return false; + } +}; + +CCL_NAMESPACE_END diff --git a/intern/cycles/render/session.cpp b/intern/cycles/render/session.cpp index 823c34ed519..550188b196a 100644 --- a/intern/cycles/render/session.cpp +++ b/intern/cycles/render/session.cpp @@ -25,12 +25,13 @@ #include "render/bake.h" #include "render/buffers.h" #include "render/camera.h" -#include "render/gpu_display.h" +#include "render/display_driver.h" #include "render/graph.h" #include "render/integrator.h" #include "render/light.h" #include "render/mesh.h" #include "render/object.h" +#include "render/output_driver.h" #include "render/scene.h" #include "render/session.h" @@ -38,7 +39,6 @@ #include "util/util_function.h" #include "util/util_logging.h" #include "util/util_math.h" -#include "util/util_opengl.h" #include "util/util_task.h" #include "util/util_time.h" @@ -65,25 +65,6 @@ Session::Session(const SessionParams ¶ms_, const SceneParams &scene_params) path_trace_ = make_unique<PathTrace>( device, scene->film, &scene->dscene, render_scheduler_, tile_manager_); path_trace_->set_progress(&progress); - path_trace_->tile_buffer_update_cb = [&]() { - if (!update_render_tile_cb) { - return; - } - update_render_tile_cb(); - }; - path_trace_->tile_buffer_write_cb = [&]() { - if (!write_render_tile_cb) { - return; - } - write_render_tile_cb(); - }; - path_trace_->tile_buffer_read_cb = [&]() -> bool { - if (!read_render_tile_cb) { - return false; - } - read_render_tile_cb(); - return true; - }; path_trace_->progress_update_cb = [&]() { update_status_time(); }; tile_manager_.full_buffer_written_cb = [&](string_view filename) { @@ -98,24 +79,6 @@ Session::~Session() { cancel(); - /* TODO(sergey): Bring the passes in viewport back. - * It is unclear why there is such an exception needed though. */ -#if 0 - if (buffers && params.write_render_cb) { - /* Copy to display buffer and write out image if requested */ - delete display; - - display = new DisplayBuffer(device, false); - display->reset(buffers->params); - copy_to_display_buffer(params.samples); - - int w = display->draw_width; - int h = display->draw_height; - uchar4 *pixels = display->rgba_byte.copy_from_device(0, w, h); - params.write_render_cb((uchar *)pixels, w, h, 4); - } -#endif - /* Make sure path tracer is destroyed before the device. This is needed because destruction might * need to access device for device memory free. */ /* TODO(sergey): Convert device to be unique_ptr, and rely on C++ to destruct objects in the @@ -163,7 +126,7 @@ bool Session::ready_to_reset() void Session::run_main_render_loop() { - path_trace_->clear_gpu_display(); + path_trace_->clear_display(); while (true) { RenderWork render_work = run_update_for_next_iteration(); @@ -397,8 +360,8 @@ int2 Session::get_effective_tile_size() const /* TODO(sergey): Take available memory into account, and if there is enough memory do not tile * and prefer optimal performance. */ - - return make_int2(params.tile_size, params.tile_size); + const int tile_size = tile_manager_.compute_render_tile_size(params.tile_size); + return make_int2(tile_size, tile_size); } void Session::do_delayed_reset() @@ -515,9 +478,33 @@ void Session::set_pause(bool pause) } } -void Session::set_gpu_display(unique_ptr<GPUDisplay> gpu_display) +void Session::set_output_driver(unique_ptr<OutputDriver> driver) { - path_trace_->set_gpu_display(move(gpu_display)); + path_trace_->set_output_driver(move(driver)); +} + +void Session::set_display_driver(unique_ptr<DisplayDriver> driver) +{ + path_trace_->set_display_driver(move(driver)); +} + +double Session::get_estimated_remaining_time() const +{ + const float completed = progress.get_progress(); + if (completed == 0.0f) { + return 0.0; + } + + double total_time, render_time; + progress.get_time(total_time, render_time); + double remaining = (1.0 - (double)completed) * (render_time / (double)completed); + + const double time_limit = render_scheduler_.get_time_limit(); + if (time_limit != 0.0) { + remaining = min(remaining, max(time_limit - render_time, 0.0)); + } + + return remaining; } void Session::wait() @@ -619,101 +606,6 @@ void Session::collect_statistics(RenderStats *render_stats) } /* -------------------------------------------------------------------- - * Tile and tile pixels access. - */ - -bool Session::has_multiple_render_tiles() const -{ - return tile_manager_.has_multiple_tiles(); -} - -int2 Session::get_render_tile_size() const -{ - return path_trace_->get_render_tile_size(); -} - -int2 Session::get_render_tile_offset() const -{ - return path_trace_->get_render_tile_offset(); -} - -string_view Session::get_render_tile_layer() const -{ - const BufferParams &buffer_params = path_trace_->get_render_tile_params(); - return buffer_params.layer; -} - -string_view Session::get_render_tile_view() const -{ - const BufferParams &buffer_params = path_trace_->get_render_tile_params(); - return buffer_params.view; -} - -bool Session::copy_render_tile_from_device() -{ - return path_trace_->copy_render_tile_from_device(); -} - -bool Session::get_render_tile_pixels(const string &pass_name, int num_components, float *pixels) -{ - /* NOTE: The code relies on a fact that session is fully update and no scene/buffer modification - * is happening while this function runs. */ - - const BufferParams &buffer_params = path_trace_->get_render_tile_params(); - - const BufferPass *pass = buffer_params.find_pass(pass_name); - if (pass == nullptr) { - return false; - } - - const bool has_denoised_result = path_trace_->has_denoised_result(); - if (pass->mode == PassMode::DENOISED && !has_denoised_result) { - pass = buffer_params.find_pass(pass->type); - if (pass == nullptr) { - /* Happens when denoised result pass is requested but is never written by the kernel. */ - return false; - } - } - - pass = buffer_params.get_actual_display_pass(pass); - - const float exposure = buffer_params.exposure; - const int num_samples = path_trace_->get_num_render_tile_samples(); - - PassAccessor::PassAccessInfo pass_access_info(*pass); - pass_access_info.use_approximate_shadow_catcher = buffer_params.use_approximate_shadow_catcher; - pass_access_info.use_approximate_shadow_catcher_background = - pass_access_info.use_approximate_shadow_catcher && !buffer_params.use_transparent_background; - - const PassAccessorCPU pass_accessor(pass_access_info, exposure, num_samples); - const PassAccessor::Destination destination(pixels, num_components); - - return path_trace_->get_render_tile_pixels(pass_accessor, destination); -} - -bool Session::set_render_tile_pixels(const string &pass_name, - int num_components, - const float *pixels) -{ - /* NOTE: The code relies on a fact that session is fully update and no scene/buffer modification - * is happening while this function runs. */ - - const BufferPass *pass = buffer_params_.find_pass(pass_name); - if (!pass) { - return false; - } - - const float exposure = scene->film->get_exposure(); - const int num_samples = render_scheduler_.get_num_rendered_samples(); - - const PassAccessor::PassAccessInfo pass_access_info(*pass); - PassAccessorCPU pass_accessor(pass_access_info, exposure, num_samples); - PassAccessor::Source source(pixels, num_components); - - return path_trace_->set_render_tile_pixels(pass_accessor, source); -} - -/* -------------------------------------------------------------------- * Full-frame on-disk storage. */ diff --git a/intern/cycles/render/session.h b/intern/cycles/render/session.h index 5623604bfe8..46c964bc98c 100644 --- a/intern/cycles/render/session.h +++ b/intern/cycles/render/session.h @@ -35,9 +35,10 @@ CCL_NAMESPACE_BEGIN class BufferParams; class Device; class DeviceScene; +class DisplayDriver; +class OutputDriver; class PathTrace; class Progress; -class GPUDisplay; class RenderBuffers; class Scene; class SceneParams; @@ -67,8 +68,6 @@ class SessionParams { ShadingSystem shadingsystem; - function<bool(const uchar *pixels, int width, int height, int channels)> write_render_cb; - SessionParams() { headless = false; @@ -114,10 +113,6 @@ class Session { Stats stats; Profiler profiler; - function<void(void)> write_render_tile_cb; - function<void(void)> update_render_tile_cb; - function<void(void)> read_render_tile_cb; - /* Callback is invoked by tile manager whenever on-dist tiles storage file is closed after * writing. Allows an engine integration to keep track of those files without worry about * transferring the information when it needs to re-create session during rendering. */ @@ -143,7 +138,10 @@ class Session { void set_samples(int samples); void set_time_limit(double time_limit); - void set_gpu_display(unique_ptr<GPUDisplay> gpu_display); + void set_output_driver(unique_ptr<OutputDriver> driver); + void set_display_driver(unique_ptr<DisplayDriver> driver); + + double get_estimated_remaining_time() const; void device_free(); @@ -154,24 +152,6 @@ class Session { void collect_statistics(RenderStats *stats); /* -------------------------------------------------------------------- - * Tile and tile pixels access. - */ - - bool has_multiple_render_tiles() const; - - /* Get size and offset (relative to the buffer's full x/y) of the currently rendering tile. */ - int2 get_render_tile_size() const; - int2 get_render_tile_offset() const; - - string_view get_render_tile_layer() const; - string_view get_render_tile_view() const; - - bool copy_render_tile_from_device(); - - bool get_render_tile_pixels(const string &pass_name, int num_components, float *pixels); - bool set_render_tile_pixels(const string &pass_name, int num_components, const float *pixels); - - /* -------------------------------------------------------------------- * Full-frame on-disk storage. */ diff --git a/intern/cycles/render/tile.cpp b/intern/cycles/render/tile.cpp index 28910bffa7b..7e53a9d0911 100644 --- a/intern/cycles/render/tile.cpp +++ b/intern/cycles/render/tile.cpp @@ -307,8 +307,8 @@ static bool configure_image_spec_from_buffer(ImageSpec *image_spec, DCHECK_GT(tile_size.x, 0); DCHECK_GT(tile_size.y, 0); - image_spec->tile_width = tile_size.x; - image_spec->tile_height = tile_size.y; + image_spec->tile_width = min(TileManager::IMAGE_TILE_SIZE, tile_size.x); + image_spec->tile_height = min(TileManager::IMAGE_TILE_SIZE, tile_size.y); } return true; @@ -335,6 +335,15 @@ TileManager::~TileManager() { } +int TileManager::compute_render_tile_size(const int suggested_tile_size) const +{ + /* Must be a multiple of IMAGE_TILE_SIZE so that we can write render tiles into the image file + * aligned on image tile boundaries. We can't set IMAGE_TILE_SIZE equal to the render tile size + * because too big tile size leads to integer overflow inside OpenEXR. */ + return (suggested_tile_size <= IMAGE_TILE_SIZE) ? suggested_tile_size : + align_up(suggested_tile_size, IMAGE_TILE_SIZE); +} + void TileManager::reset_scheduling(const BufferParams ¶ms, int2 tile_size) { VLOG(3) << "Using tile size of " << tile_size; @@ -411,6 +420,11 @@ const Tile &TileManager::get_current_tile() const return tile_state_.current_tile; } +const int2 TileManager::get_size() const +{ + return make_int2(buffer_params_.width, buffer_params_.height); +} + bool TileManager::open_tile_output() { write_state_.filename = path_temp_get("cycles-tile-buffer-" + tile_file_unique_part_ + "-" + @@ -427,7 +441,12 @@ bool TileManager::open_tile_output() return false; } - write_state_.tile_out->open(write_state_.filename, write_state_.image_spec); + if (!write_state_.tile_out->open(write_state_.filename, write_state_.image_spec)) { + LOG(ERROR) << "Error opening tile file: " << write_state_.tile_out->geterror(); + write_state_.tile_out = nullptr; + return false; + } + write_state_.num_tiles_written = 0; VLOG(3) << "Opened tile file " << write_state_.filename; @@ -466,33 +485,29 @@ bool TileManager::write_tile(const RenderBuffers &tile_buffers) const BufferParams &tile_params = tile_buffers.params; - vector<float> pixel_storage; const float *pixels = tile_buffers.buffer.data(); - - /* Tiled writing expects pixels to contain data for an entire tile. Pad the render buffers with - * empty pixels for tiles which are on the image boundary. */ - if (tile_params.width != tile_size_.x || tile_params.height != tile_size_.y) { - const int64_t pass_stride = tile_params.pass_stride; - const int64_t src_row_stride = tile_params.width * pass_stride; - - const int64_t dst_row_stride = tile_size_.x * pass_stride; - pixel_storage.resize(dst_row_stride * tile_size_.y); - - const float *src = tile_buffers.buffer.data(); - float *dst = pixel_storage.data(); - pixels = dst; - - for (int y = 0; y < tile_params.height; ++y, src += src_row_stride, dst += dst_row_stride) { - memcpy(dst, src, src_row_stride * sizeof(float)); - } - } - const int tile_x = tile_params.full_x - buffer_params_.full_x; const int tile_y = tile_params.full_y - buffer_params_.full_y; VLOG(3) << "Write tile at " << tile_x << ", " << tile_y; - if (!write_state_.tile_out->write_tile(tile_x, tile_y, 0, TypeDesc::FLOAT, pixels)) { + + /* The image tile sizes in the OpenEXR file are different from the size of our big tiles. The + * write_tiles() method expects a contiguous image region that will be split into tiles + * internally. OpenEXR expects the size of this region to be a multiple of the tile size, + * however OpenImageIO automatically adds the required padding. + * + * The only thing we have to ensure is that the tile_x and tile_y are a multiple of the + * image tile size, which happens in compute_render_tile_size. */ + if (!write_state_.tile_out->write_tiles(tile_x, + tile_x + tile_params.width, + tile_y, + tile_y + tile_params.height, + 0, + 1, + TypeDesc::FLOAT, + pixels)) { LOG(ERROR) << "Error writing tile " << write_state_.tile_out->geterror(); + return false; } ++write_state_.num_tiles_written; @@ -518,7 +533,14 @@ void TileManager::finish_write_tiles() VLOG(3) << "Write dummy tile at " << tile.x << ", " << tile.y; - write_state_.tile_out->write_tile(tile.x, tile.y, 0, TypeDesc::FLOAT, pixel_storage.data()); + write_state_.tile_out->write_tiles(tile.x, + tile.x + tile.width, + tile.y, + tile.y + tile.height, + 0, + 1, + TypeDesc::FLOAT, + pixel_storage.data()); } } diff --git a/intern/cycles/render/tile.h b/intern/cycles/render/tile.h index 71b9e966278..08eaa4034f0 100644 --- a/intern/cycles/render/tile.h +++ b/intern/cycles/render/tile.h @@ -82,6 +82,7 @@ class TileManager { bool done(); const Tile &get_current_tile() const; + const int2 get_size() const; /* Write render buffer of a tile to a file on disk. * @@ -107,6 +108,12 @@ class TileManager { RenderBuffers *buffers, DenoiseParams *denoise_params); + /* Compute valid tile size compatible with image saving. */ + int compute_render_tile_size(const int suggested_tile_size) const; + + /* Tile size in the image file. */ + static const int IMAGE_TILE_SIZE = 128; + protected: /* Get tile configuration for its index. * The tile index must be within [0, state_.tile_state_). */ diff --git a/intern/cycles/util/util_atomic.h b/intern/cycles/util/util_atomic.h index de17efafcf2..faba411c769 100644 --- a/intern/cycles/util/util_atomic.h +++ b/intern/cycles/util/util_atomic.h @@ -34,7 +34,7 @@ #else /* __KERNEL_GPU__ */ -# ifdef __KERNEL_CUDA__ +# if defined(__KERNEL_CUDA__) || defined(__KERNEL_HIP__) # define atomic_add_and_fetch_float(p, x) (atomicAdd((float *)(p), (float)(x)) + (float)(x)) diff --git a/intern/cycles/util/util_debug.cpp b/intern/cycles/util/util_debug.cpp index 1d598725c84..2245668d02f 100644 --- a/intern/cycles/util/util_debug.cpp +++ b/intern/cycles/util/util_debug.cpp @@ -59,12 +59,23 @@ DebugFlags::CUDA::CUDA() : adaptive_compile(false) reset(); } +DebugFlags::HIP::HIP() : adaptive_compile(false) +{ + reset(); +} + void DebugFlags::CUDA::reset() { if (getenv("CYCLES_CUDA_ADAPTIVE_COMPILE") != NULL) adaptive_compile = true; } +void DebugFlags::HIP::reset() +{ + if (getenv("CYCLES_HIP_ADAPTIVE_COMPILE") != NULL) + adaptive_compile = true; +} + DebugFlags::OptiX::OptiX() { reset(); @@ -103,6 +114,10 @@ std::ostream &operator<<(std::ostream &os, DebugFlagsConstRef debug_flags) os << "OptiX flags:\n" << " Debug : " << string_from_bool(debug_flags.optix.use_debug) << "\n"; + + os << "HIP flags:\n" + << " HIP streams : " << string_from_bool(debug_flags.hip.adaptive_compile) << "\n"; + return os; } diff --git a/intern/cycles/util/util_debug.h b/intern/cycles/util/util_debug.h index 99e2723180c..81677201790 100644 --- a/intern/cycles/util/util_debug.h +++ b/intern/cycles/util/util_debug.h @@ -89,7 +89,18 @@ class DebugFlags { void reset(); /* Whether adaptive feature based runtime compile is enabled or not. - * Requires the CUDA Toolkit and only works on Linux atm. */ + * Requires the CUDA Toolkit and only works on Linux at the moment. */ + bool adaptive_compile; + }; + + /* Descriptor of HIP feature-set to be used. */ + struct HIP { + HIP(); + + /* Reset flags to their defaults. */ + void reset(); + + /* Whether adaptive feature based runtime compile is enabled or not.*/ bool adaptive_compile; }; @@ -124,6 +135,9 @@ class DebugFlags { /* Requested OptiX flags. */ OptiX optix; + /* Requested HIP flags. */ + HIP hip; + private: DebugFlags(); diff --git a/intern/cycles/util/util_half.h b/intern/cycles/util/util_half.h index d9edfec5da3..f36a492a1b0 100644 --- a/intern/cycles/util/util_half.h +++ b/intern/cycles/util/util_half.h @@ -29,7 +29,7 @@ CCL_NAMESPACE_BEGIN /* Half Floats */ /* CUDA has its own half data type, no need to define then */ -#ifndef __KERNEL_CUDA__ +#if !defined(__KERNEL_CUDA__) && !defined(__KERNEL_HIP__) /* Implementing this as a class rather than a typedef so that the compiler can tell it apart from * unsigned shorts. */ class half { @@ -59,7 +59,7 @@ struct half4 { half x, y, z, w; }; -#ifdef __KERNEL_CUDA__ +#if defined(__KERNEL_CUDA__) || defined(__KERNEL_HIP__) ccl_device_inline void float4_store_half(half *h, float4 f) { @@ -73,6 +73,7 @@ ccl_device_inline void float4_store_half(half *h, float4 f) ccl_device_inline void float4_store_half(half *h, float4 f) { + # ifndef __KERNEL_SSE2__ for (int i = 0; i < 4; i++) { /* optimized float to half for pixels: @@ -109,6 +110,8 @@ ccl_device_inline void float4_store_half(half *h, float4 f) # endif } +# ifndef __KERNEL_HIP__ + ccl_device_inline float half_to_float(half h) { float f; @@ -117,6 +120,23 @@ ccl_device_inline float half_to_float(half h) return f; } +# else + +ccl_device_inline float half_to_float(std::uint32_t a) noexcept +{ + + std::uint32_t u = ((a << 13) + 0x70000000U) & 0x8fffe000U; + + std::uint32_t v = __float_as_uint(__uint_as_float(u) * + __uint_as_float(0x77800000U) /*0x1.0p+112f*/) + + 0x38000000U; + + u = (a & 0x7fff) != 0 ? v : u; + + return __uint_as_float(u) * __uint_as_float(0x07800000U) /*0x1.0p-112f*/; +} + +# endif /* __KERNEL_HIP__ */ ccl_device_inline float4 half4_to_float4(half4 h) { diff --git a/intern/cycles/util/util_math.h b/intern/cycles/util/util_math.h index 6d728dde679..cb1e94c838c 100644 --- a/intern/cycles/util/util_math.h +++ b/intern/cycles/util/util_math.h @@ -26,6 +26,10 @@ # include <cmath> #endif +#ifdef __HIP__ +# include <hip/hip_vector_types.h> +#endif + #include <float.h> #include <math.h> #include <stdio.h> @@ -83,7 +87,8 @@ CCL_NAMESPACE_BEGIN /* Scalar */ -#ifdef _WIN32 +#ifndef __HIP__ +# ifdef _WIN32 ccl_device_inline float fmaxf(float a, float b) { return (a > b) ? a : b; @@ -93,7 +98,9 @@ ccl_device_inline float fminf(float a, float b) { return (a < b) ? a : b; } -#endif /* _WIN32 */ + +# endif /* _WIN32 */ +#endif /* __HIP__ */ #ifndef __KERNEL_GPU__ using std::isfinite; @@ -199,6 +206,7 @@ ccl_device_inline uint as_uint(float f) return u.i; } +#ifndef __HIP__ ccl_device_inline int __float_as_int(float f) { union { @@ -238,6 +246,7 @@ ccl_device_inline float __uint_as_float(uint i) u.i = i; return u.f; } +#endif ccl_device_inline int4 __float4_as_int4(float4 f) { @@ -669,7 +678,7 @@ ccl_device float bits_to_01(uint bits) ccl_device_inline uint count_leading_zeros(uint x) { -#if defined(__KERNEL_CUDA__) || defined(__KERNEL_OPTIX__) +#if defined(__KERNEL_CUDA__) || defined(__KERNEL_OPTIX__) || defined(__KERNEL_HIP__) return __clz(x); #else assert(x != 0); @@ -685,7 +694,7 @@ ccl_device_inline uint count_leading_zeros(uint x) ccl_device_inline uint count_trailing_zeros(uint x) { -#if defined(__KERNEL_CUDA__) || defined(__KERNEL_OPTIX__) +#if defined(__KERNEL_CUDA__) || defined(__KERNEL_OPTIX__) || defined(__KERNEL_HIP__) return (__ffs(x) - 1); #else assert(x != 0); @@ -701,7 +710,7 @@ ccl_device_inline uint count_trailing_zeros(uint x) ccl_device_inline uint find_first_set(uint x) { -#if defined(__KERNEL_CUDA__) || defined(__KERNEL_OPTIX__) +#if defined(__KERNEL_CUDA__) || defined(__KERNEL_OPTIX__) || defined(__KERNEL_HIP__) return __ffs(x); #else # ifdef _MSC_VER diff --git a/intern/cycles/util/util_math_intersect.h b/intern/cycles/util/util_math_intersect.h index fa3a541eea9..fd0c9124345 100644 --- a/intern/cycles/util/util_math_intersect.h +++ b/intern/cycles/util/util_math_intersect.h @@ -40,7 +40,7 @@ ccl_device bool ray_sphere_intersect(float3 ray_P, /* Ray points away from sphere. */ return false; } - const float dsq = tsq - tp * tp; /* pythagoras */ + const float dsq = tsq - tp * tp; /* Pythagoras. */ if (dsq > radiussq) { /* Closest point on ray outside sphere. */ return false; diff --git a/intern/cycles/util/util_progress.h b/intern/cycles/util/util_progress.h index dca8d3d0ab5..176ee11e1e9 100644 --- a/intern/cycles/util/util_progress.h +++ b/intern/cycles/util/util_progress.h @@ -100,7 +100,7 @@ class Progress { cancel = true; } - bool get_cancel() + bool get_cancel() const { if (!cancel && cancel_cb) cancel_cb(); @@ -108,7 +108,7 @@ class Progress { return cancel; } - string get_cancel_message() + string get_cancel_message() const { thread_scoped_lock lock(progress_mutex); return cancel_message; @@ -130,12 +130,12 @@ class Progress { cancel = true; } - bool get_error() + bool get_error() const { return error; } - string get_error_message() + string get_error_message() const { thread_scoped_lock lock(progress_mutex); return error_message; @@ -168,7 +168,7 @@ class Progress { } } - void get_time(double &total_time_, double &render_time_) + void get_time(double &total_time_, double &render_time_) const { thread_scoped_lock lock(progress_mutex); @@ -200,7 +200,7 @@ class Progress { total_pixel_samples = total_pixel_samples_; } - float get_progress() + float get_progress() const { thread_scoped_lock lock(progress_mutex); @@ -236,7 +236,7 @@ class Progress { } } - int get_current_sample() + int get_current_sample() const { thread_scoped_lock lock(progress_mutex); /* Note that the value here always belongs to the last tile that updated, @@ -244,13 +244,13 @@ class Progress { return current_tile_sample; } - int get_rendered_tiles() + int get_rendered_tiles() const { thread_scoped_lock lock(progress_mutex); return rendered_tiles; } - int get_denoised_tiles() + int get_denoised_tiles() const { thread_scoped_lock lock(progress_mutex); return denoised_tiles; @@ -300,7 +300,7 @@ class Progress { set_update(); } - void get_status(string &status_, string &substatus_) + void get_status(string &status_, string &substatus_) const { thread_scoped_lock lock(progress_mutex); @@ -330,8 +330,8 @@ class Progress { } protected: - thread_mutex progress_mutex; - thread_mutex update_mutex; + mutable thread_mutex progress_mutex; + mutable thread_mutex update_mutex; function<void()> update_cb; function<void()> cancel_cb; diff --git a/intern/ghost/GHOST_IWindow.h b/intern/ghost/GHOST_IWindow.h index 5f9bd808c8c..91f576ca304 100644 --- a/intern/ghost/GHOST_IWindow.h +++ b/intern/ghost/GHOST_IWindow.h @@ -40,7 +40,7 @@ * There are two coordinate systems: * * - The screen coordinate system. The origin of the screen is located in the - * upper left corner of the screen.</li> + * upper left corner of the screen. * - The client rectangle coordinate system. The client rectangle of a window * is the area that is drawable by the application (excluding title bars etc.). */ diff --git a/intern/ghost/intern/GHOST_DisplayManagerSDL.cpp b/intern/ghost/intern/GHOST_DisplayManagerSDL.cpp index 5b026eb1632..09b2e4dfe2b 100644 --- a/intern/ghost/intern/GHOST_DisplayManagerSDL.cpp +++ b/intern/ghost/intern/GHOST_DisplayManagerSDL.cpp @@ -101,8 +101,7 @@ GHOST_TSuccess GHOST_DisplayManagerSDL::setCurrentDisplaySetting( uint8_t display, const GHOST_DisplaySetting &setting) { /* - * Mode switching code ported from Quake 2 version 3.21 and bzflag version - * 2.4.0: + * Mode switching code ported from Quake 2 version 3.21 and BZFLAG version 2.4.0: * ftp://ftp.idsoftware.com/idstuff/source/q2source-3.21.zip * See linux/gl_glx.c:GLimp_SetMode * http://wiki.bzflag.org/BZFlag_Source diff --git a/intern/ghost/intern/GHOST_SystemSDL.cpp b/intern/ghost/intern/GHOST_SystemSDL.cpp index 35c7a7ef463..5370d4df857 100644 --- a/intern/ghost/intern/GHOST_SystemSDL.cpp +++ b/intern/ghost/intern/GHOST_SystemSDL.cpp @@ -374,8 +374,8 @@ void GHOST_SystemSDL::processEvent(SDL_Event *sdl_event) if (window->getCursorGrabBounds(bounds) == GHOST_kFailure) window->getClientBounds(bounds); - /* Could also clamp to screen bounds wrap with a window outside the view will fail atm. - * Use offset of 8 in case the window is at screen bounds. */ + /* Could also clamp to screen bounds wrap with a window outside the view will + * fail at the moment. Use offset of 8 in case the window is at screen bounds. */ bounds.wrapPoint(x_new, y_new, 8, window->getCursorGrabAxis()); window->getCursorGrabAccum(x_accum, y_accum); diff --git a/intern/ghost/intern/GHOST_SystemWin32.cpp b/intern/ghost/intern/GHOST_SystemWin32.cpp index f44107ee000..482f20f5cd1 100644 --- a/intern/ghost/intern/GHOST_SystemWin32.cpp +++ b/intern/ghost/intern/GHOST_SystemWin32.cpp @@ -1100,8 +1100,8 @@ GHOST_EventCursor *GHOST_SystemWin32::processCursorEvent(GHOST_WindowWin32 *wind window->getClientBounds(bounds); } - /* Could also clamp to screen bounds wrap with a window outside the view will fail atm. - * Use inset in case the window is at screen bounds. */ + /* Could also clamp to screen bounds wrap with a window outside the view will + * fail at the moment. Use inset in case the window is at screen bounds. */ bounds.wrapPoint(x_new, y_new, 2, window->getCursorGrabAxis()); window->getCursorGrabAccum(x_accum, y_accum); diff --git a/intern/ghost/intern/GHOST_SystemX11.cpp b/intern/ghost/intern/GHOST_SystemX11.cpp index 10ccb00cc15..86b4245ca67 100644 --- a/intern/ghost/intern/GHOST_SystemX11.cpp +++ b/intern/ghost/intern/GHOST_SystemX11.cpp @@ -973,8 +973,8 @@ void GHOST_SystemX11::processEvent(XEvent *xe) if (window->getCursorGrabBounds(bounds) == GHOST_kFailure) window->getClientBounds(bounds); - /* Could also clamp to screen bounds wrap with a window outside the view will fail atm. - * Use offset of 8 in case the window is at screen bounds. */ + /* Could also clamp to screen bounds wrap with a window outside the view will + * fail at the moment. Use offset of 8 in case the window is at screen bounds. */ bounds.wrapPoint(x_new, y_new, 8, window->getCursorGrabAxis()); window->getCursorGrabAccum(x_accum, y_accum); @@ -1528,13 +1528,13 @@ void GHOST_SystemX11::processEvent(XEvent *xe) window->GetTabletData().Pressure = axis_value / ((float)xtablet.PressureLevels); } - /* the (short) cast and the & 0xffff is bizarre and unexplained anywhere, - * but I got garbage data without it. Found it in the xidump.c source --matt + /* NOTE(@broken): the (short) cast and the & 0xffff is bizarre and unexplained anywhere, + * but I got garbage data without it. Found it in the `xidump.c` source. * - * The '& 0xffff' just truncates the value to its two lowest bytes, this probably means - * some drivers do not properly set the whole int value? Since we convert to float - * afterward, I don't think we need to cast to short here, but do not have a device to - * check this. --mont29 + * NOTE(@mont29): The '& 0xffff' just truncates the value to its two lowest bytes, + * this probably means some drivers do not properly set the whole int value? + * Since we convert to float afterward, + * I don't think we need to cast to short here, but do not have a device to check this. */ if (AXIS_VALUE_GET(3, axis_value)) { window->GetTabletData().Xtilt = (short)(axis_value & 0xffff) / diff --git a/intern/ghost/intern/GHOST_WindowX11.cpp b/intern/ghost/intern/GHOST_WindowX11.cpp index de389951613..8b44403c598 100644 --- a/intern/ghost/intern/GHOST_WindowX11.cpp +++ b/intern/ghost/intern/GHOST_WindowX11.cpp @@ -1092,9 +1092,9 @@ GHOST_TSuccess GHOST_WindowX11::setOrder(GHOST_TWindowOrder order) XWindowAttributes attr; Atom atom; - /* We use both XRaiseWindow and _NET_ACTIVE_WINDOW, since some - * window managers ignore the former (e.g. kwin from kde) and others - * don't implement the latter (e.g. fluxbox pre 0.9.9) */ + /* We use both #XRaiseWindow and #_NET_ACTIVE_WINDOW, since some + * window managers ignore the former (e.g. KWIN from KDE) and others + * don't implement the latter (e.g. FLUXBOX before 0.9.9). */ XRaiseWindow(m_display, m_window); diff --git a/intern/guardedalloc/intern/mallocn_guarded_impl.c b/intern/guardedalloc/intern/mallocn_guarded_impl.c index 98a8553a3eb..bba72c907eb 100644 --- a/intern/guardedalloc/intern/mallocn_guarded_impl.c +++ b/intern/guardedalloc/intern/mallocn_guarded_impl.c @@ -89,7 +89,7 @@ typedef struct localListBase { void *first, *last; } localListBase; -/* note: keep this struct aligned (e.g., irix/gcc) - Hos */ +/* NOTE(@hos): keep this struct aligned (e.g., IRIX/GCC). */ typedef struct MemHead { int tag1; size_t len; @@ -98,9 +98,8 @@ typedef struct MemHead { const char *nextname; int tag2; short pad1; - short alignment; /* if non-zero aligned alloc was used - * and alignment is stored here. - */ + /* if non-zero aligned allocation was used and alignment is stored here. */ + short alignment; #ifdef DEBUG_MEMCOUNTER int _count; #endif diff --git a/intern/opensubdiv/internal/evaluator/evaluator_impl.cc b/intern/opensubdiv/internal/evaluator/evaluator_impl.cc index b3fc021e1ee..4f4f332ff15 100644 --- a/intern/opensubdiv/internal/evaluator/evaluator_impl.cc +++ b/intern/opensubdiv/internal/evaluator/evaluator_impl.cc @@ -553,7 +553,7 @@ void convertPatchCoordsToArray(const OpenSubdiv_PatchCoord *patch_coords, } // namespace -// Note: Define as a class instead of typedcef to make it possible +// Note: Define as a class instead of typedef to make it possible // to have anonymous class in opensubdiv_evaluator_internal.h class CpuEvalOutput : public VolatileEvalOutput<CpuVertexBuffer, CpuVertexBuffer, diff --git a/intern/opensubdiv/opensubdiv_capi_type.h b/intern/opensubdiv/opensubdiv_capi_type.h index e759c5f43b0..e78842036be 100644 --- a/intern/opensubdiv/opensubdiv_capi_type.h +++ b/intern/opensubdiv/opensubdiv_capi_type.h @@ -23,7 +23,7 @@ extern "C" { #endif -// Keep this a bitmask os it's possible to pass available +// Keep this a bitmask so it's possible to pass available // evaluators to Blender. typedef enum eOpenSubdivEvaluator { OPENSUBDIV_EVALUATOR_CPU = (1 << 0), diff --git a/release/datafiles/locale b/release/datafiles/locale -Subproject 94c39b5832b9ef3b56ed94ce4011412e3d776eb +Subproject 4833954c0ac85cc407e1d5a153aa11b1d1823ec diff --git a/release/datafiles/userdef/userdef_default_theme.c b/release/datafiles/userdef/userdef_default_theme.c index 85532d01d5c..6753dc8563e 100644 --- a/release/datafiles/userdef/userdef_default_theme.c +++ b/release/datafiles/userdef/userdef_default_theme.c @@ -817,7 +817,7 @@ const bTheme U_theme_default = { }, .shade2 = RGBA(0x7f707064), .grid = RGBA(0x23232300), - .wire = RGBA(0x808080ff), + .wire = RGBA(0x232323ff), .select = RGBA(0xed5700ff), .active = RGBA(0xffffffff), .edge_select = RGBA(0xffffffff), @@ -1175,6 +1175,35 @@ const bTheme U_theme_default = { .color = RGBA(0x7a5441ff), }, }, + .strip_color = { + { + .color = RGBA(0xe2605bff), + }, + { + .color = RGBA(0xf1a355ff), + }, + { + .color = RGBA(0xf1dc55ff), + }, + { + .color = RGBA(0x7bcc7bff), + }, + { + .color = RGBA(0x5db6eaff), + }, + { + .color = RGBA(0x8d59daff), + }, + { + .color = RGBA(0xc673b8ff), + }, + { + .color = RGBA(0x7a5441ff), + }, + { + .color = RGBA(0x5f5f5fff), + }, + }, }; /* clang-format on */ diff --git a/release/scripts/addons b/release/scripts/addons -Subproject a85360cbdfbbee2bb46bcb93900f597a989bd33 +Subproject c64726810ba781d980921947ba819b1364689e5 diff --git a/release/scripts/addons_contrib b/release/scripts/addons_contrib -Subproject 42da56aa73726710107031787af5eea18679798 +Subproject 5a82baad9f986722104280e8354a4427d8e9eab diff --git a/release/scripts/modules/bl_i18n_utils/utils.py b/release/scripts/modules/bl_i18n_utils/utils.py index 95184853f73..13fb87d386a 100644 --- a/release/scripts/modules/bl_i18n_utils/utils.py +++ b/release/scripts/modules/bl_i18n_utils/utils.py @@ -219,7 +219,7 @@ def enable_addons(addons=None, support=None, disable=False, check_only=False): try: import bpy except ModuleNotFoundError: - print("Could not import bpy, enable_addons must be run from whithin Blender.") + print("Could not import bpy, enable_addons must be run from within Blender.") return if addons is None: diff --git a/release/scripts/modules/bpy_extras/asset_utils.py b/release/scripts/modules/bpy_extras/asset_utils.py index 2cd5dddefbc..c7ebdc1d5e1 100644 --- a/release/scripts/modules/bpy_extras/asset_utils.py +++ b/release/scripts/modules/bpy_extras/asset_utils.py @@ -52,19 +52,12 @@ class AssetBrowserPanel: bl_space_type = 'FILE_BROWSER' @classmethod - def poll(cls, context): + def asset_browser_panel_poll(cls, context): return SpaceAssetInfo.is_asset_browser_poll(context) - -class AssetBrowserSpecificCategoryPanel(AssetBrowserPanel): - asset_categories = set() # Set of strings like 'ANIMATIONS', see `asset_category_items` in rna_space.c - @classmethod def poll(cls, context): - return ( - SpaceAssetInfo.is_asset_browser_poll(context) - and context.space_data.params.asset_category in cls.asset_categories - ) + return cls.asset_browser_panel_poll(context) class AssetMetaDataPanel: diff --git a/release/scripts/modules/rna_manual_reference.py b/release/scripts/modules/rna_manual_reference.py index 40f59307bec..797eb2627b3 100644 --- a/release/scripts/modules/rna_manual_reference.py +++ b/release/scripts/modules/rna_manual_reference.py @@ -39,8 +39,8 @@ if LANG is not None: url_manual_prefix = url_manual_prefix.replace("manual/en", "manual/" + LANG) url_manual_mapping = ( - ("bpy.types.cyclesworldsettings.sample_mbpy.types.cyclesworldsettings.sample_map_resolutionbpy.types.cyclesworldsettings.sample_map_resolutionap_resolution*", "render/cycles/world_settings.html#bpy-types-cyclesworldsettings-sample-mbpy-types-cyclesworldsettings-sample-map-resolutionbpy-types-cyclesworldsettings-sample-map-resolutionap-resolution"), ("bpy.types.movietrackingsettings.refine_intrinsics_tangential_distortion*", "movie_clip/tracking/clip/toolbar/solve.html#bpy-types-movietrackingsettings-refine-intrinsics-tangential-distortion"), + ("bpy.types.spacesequesequencertimelineoverlaynceeditor.show_strip_offset*", "editors/video_sequencer/sequencer/display.html#bpy-types-spacesequesequencertimelineoverlaynceeditor-show-strip-offset"), ("bpy.types.movietrackingsettings.refine_intrinsics_radial_distortion*", "movie_clip/tracking/clip/toolbar/solve.html#bpy-types-movietrackingsettings-refine-intrinsics-radial-distortion"), ("bpy.types.fluiddomainsettings.sndparticle_potential_max_trappedair*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-potential-max-trappedair"), ("bpy.types.fluiddomainsettings.sndparticle_potential_min_trappedair*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-potential-min-trappedair"), @@ -60,11 +60,13 @@ url_manual_mapping = ( ("bpy.types.fluiddomainsettings.sndparticle_sampling_wavecrest*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-sampling-wavecrest"), ("bpy.types.rigidbodyconstraint.use_override_solver_iterations*", "physics/rigid_body/constraints/introduction.html#bpy-types-rigidbodyconstraint-use-override-solver-iterations"), ("bpy.types.toolsettings.use_transform_correct_face_attributes*", "modeling/meshes/tools/tool_settings.html#bpy-types-toolsettings-use-transform-correct-face-attributes"), + ("bpy.types.cyclesrendersettings.preview_adaptive_min_samples*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-preview-adaptive-min-samples"), ("bpy.types.rendersettings.use_sequencer_override_scene_strip*", "video_editing/preview/sidebar.html#bpy-types-rendersettings-use-sequencer-override-scene-strip"), ("bpy.types.toolsettings.use_transform_correct_keep_connected*", "modeling/meshes/tools/tool_settings.html#bpy-types-toolsettings-use-transform-correct-keep-connected"), + ("bpy.types.cyclesrendersettings.preview_denoising_prefilter*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-preview-denoising-prefilter"), ("bpy.types.fluiddomainsettings.sndparticle_potential_radius*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-potential-radius"), ("bpy.types.cyclesrendersettings.film_transparent_roughness*", "render/cycles/render_settings/film.html#bpy-types-cyclesrendersettings-film-transparent-roughness"), - ("bpy.types.cyclesrendersettings.sample_all_lights_indirect*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-sample-all-lights-indirect"), + ("bpy.types.cyclesrendersettings.preview_adaptive_threshold*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-preview-adaptive-threshold"), ("bpy.types.fluiddomainsettings.openvdb_cache_compress_type*", "physics/fluid/type/domain/cache.html#bpy-types-fluiddomainsettings-openvdb-cache-compress-type"), ("bpy.types.fluiddomainsettings.sndparticle_bubble_buoyancy*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-bubble-buoyancy"), ("bpy.types.fluiddomainsettings.sndparticle_combined_export*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-combined-export"), @@ -76,14 +78,13 @@ url_manual_mapping = ( ("bpy.types.toolsettings.annotation_stroke_placement_view3d*", "interface/annotate_tool.html#bpy-types-toolsettings-annotation-stroke-placement-view3d"), ("bpy.types.fluiddomainsettings.use_collision_border_front*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-use-collision-border-front"), ("bpy.types.fluiddomainsettings.use_collision_border_right*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-use-collision-border-right"), + ("bpy.types.sequencertimelineoverlay.waveform_display_type*", "editors/video_sequencer/sequencer/display.html#bpy-types-sequencertimelineoverlay-waveform-display-type"), ("bpy.types.cyclesmaterialsettings.use_transparent_shadow*", "render/cycles/material_settings.html#bpy-types-cyclesmaterialsettings-use-transparent-shadow"), ("bpy.types.cyclesobjectsettings.shadow_terminator_offset*", "render/cycles/object_settings/object_data.html#bpy-types-cyclesobjectsettings-shadow-terminator-offset"), ("bpy.types.cyclesobjectsettings.use_adaptive_subdivision*", "render/cycles/object_settings/adaptive_subdiv.html#bpy-types-cyclesobjectsettings-use-adaptive-subdivision"), ("bpy.types.cyclesrendersettings.debug_use_spatial_splits*", "render/cycles/render_settings/performance.html#bpy-types-cyclesrendersettings-debug-use-spatial-splits"), ("bpy.types.cyclesrendersettings.light_sampling_threshold*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-light-sampling-threshold"), - ("bpy.types.cyclesrendersettings.preview_start_resolution*", "render/cycles/render_settings/performance.html#bpy-types-cyclesrendersettings-preview-start-resolution"), ("bpy.types.cyclesrendersettings.rolling_shutter_duration*", "render/cycles/render_settings/motion_blur.html#bpy-types-cyclesrendersettings-rolling-shutter-duration"), - ("bpy.types.cyclesrendersettings.sample_all_lights_direct*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-sample-all-lights-direct"), ("bpy.types.cyclesrendersettings.volume_preview_step_rate*", "render/cycles/render_settings/volumes.html#bpy-types-cyclesrendersettings-volume-preview-step-rate"), ("bpy.types.fluiddomainsettings.sndparticle_update_radius*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-update-radius"), ("bpy.types.fluiddomainsettings.use_collision_border_back*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-use-collision-border-back"), @@ -97,14 +98,15 @@ url_manual_mapping = ( ("bpy.types.gpencilsculptsettings.use_multiframe_falloff*", "grease_pencil/multiframe.html#bpy-types-gpencilsculptsettings-use-multiframe-falloff"), ("bpy.types.movietrackingsettings.use_keyframe_selection*", "movie_clip/tracking/clip/toolbar/solve.html#bpy-types-movietrackingsettings-use-keyframe-selection"), ("bpy.types.rendersettings.simplify_gpencil_antialiasing*", "render/cycles/render_settings/simplify.html#bpy-types-rendersettings-simplify-gpencil-antialiasing"), + ("bpy.types.sequencertimelineoverlay.show_strip_duration*", "editors/video_sequencer/sequencer/display.html#bpy-types-sequencertimelineoverlay-show-strip-duration"), ("bpy.types.spaceoutliner.use_filter_lib_override_system*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-lib-override-system"), ("bpy.types.toolsettings.use_transform_pivot_point_align*", "scene_layout/object/tools/tool_settings.html#bpy-types-toolsettings-use-transform-pivot-point-align"), ("bpy.types.brush.show_multiplane_scrape_planes_preview*", "sculpt_paint/sculpting/tools/multiplane_scrape.html#bpy-types-brush-show-multiplane-scrape-planes-preview"), ("bpy.types.cyclesmaterialsettings.volume_interpolation*", "render/cycles/material_settings.html#bpy-types-cyclesmaterialsettings-volume-interpolation"), ("bpy.types.cyclesrendersettings.debug_optix_curves_api*", "render/cycles/render_settings/debug.html#bpy-types-cyclesrendersettings-debug-optix-curves-api"), + ("bpy.types.cyclesrendersettings.denoising_input_passes*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-denoising-input-passes"), ("bpy.types.cyclesrendersettings.film_transparent_glass*", "render/cycles/render_settings/film.html#bpy-types-cyclesrendersettings-film-transparent-glass"), ("bpy.types.cyclesrendersettings.offscreen_dicing_scale*", "render/cycles/render_settings/subdivision.html#bpy-types-cyclesrendersettings-offscreen-dicing-scale"), - ("bpy.types.cyclesrendersettings.use_progressive_refine*", "render/cycles/render_settings/performance.html#bpy-types-cyclesrendersettings-use-progressive-refine"), ("bpy.types.fluiddomainsettings.sndparticle_bubble_drag*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-bubble-drag"), ("bpy.types.linestylegeometrymodifier_backbonestretcher*", "render/freestyle/view_layer/line_style/modifiers/geometry/backbone_stretcher.html#bpy-types-linestylegeometrymodifier-backbonestretcher"), ("bpy.types.linestylegeometrymodifier_sinusdisplacement*", "render/freestyle/view_layer/line_style/modifiers/geometry/sinus_displacement.html#bpy-types-linestylegeometrymodifier-sinusdisplacement"), @@ -123,6 +125,7 @@ url_manual_mapping = ( ("bpy.types.gpencillayer.use_annotation_onion_skinning*", "interface/annotate_tool.html#bpy-types-gpencillayer-use-annotation-onion-skinning"), ("bpy.types.greasepencil.use_adaptive_curve_resolution*", "grease_pencil/modes/edit/curve_editing.html#bpy-types-greasepencil-use-adaptive-curve-resolution"), ("bpy.types.linestylegeometrymodifier_polygonalization*", "render/freestyle/view_layer/line_style/modifiers/geometry/polygonization.html#bpy-types-linestylegeometrymodifier-polygonalization"), + ("bpy.types.sequencertimelineoverlay.show_strip_source*", "editors/video_sequencer/sequencer/display.html#bpy-types-sequencertimelineoverlay-show-strip-source"), ("bpy.types.toolsettings.use_gpencil_automerge_strokes*", "grease_pencil/modes/draw/introduction.html#bpy-types-toolsettings-use-gpencil-automerge-strokes"), ("bpy.types.toolsettings.use_proportional_edit_objects*", "editors/3dview/controls/proportional_editing.html#bpy-types-toolsettings-use-proportional-edit-objects"), ("bpy.ops.view3d.edit_mesh_extrude_move_shrink_fatten*", "modeling/meshes/editing/face/extrude_faces_normal.html#bpy-ops-view3d-edit-mesh-extrude-move-shrink-fatten"), @@ -133,7 +136,7 @@ url_manual_mapping = ( ("bpy.types.cyclesrendersettings.motion_blur_position*", "render/cycles/render_settings/motion_blur.html#bpy-types-cyclesrendersettings-motion-blur-position"), ("bpy.types.cyclesrendersettings.rolling_shutter_type*", "render/cycles/render_settings/motion_blur.html#bpy-types-cyclesrendersettings-rolling-shutter-type"), ("bpy.types.cyclesrendersettings.transmission_bounces*", "render/cycles/render_settings/light_paths.html#bpy-types-cyclesrendersettings-transmission-bounces"), - ("bpy.types.cyclesrendersettings.transmission_samples*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-transmission-samples"), + ("bpy.types.cyclesworldsettings.sample_map_resolution*", "render/cycles/world_settings.html#bpy-types-cyclesworldsettings-sample-map-resolution"), ("bpy.types.fluiddomainsettings.display_interpolation*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-display-interpolation"), ("bpy.types.fluiddomainsettings.gridlines_cell_filter*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-gridlines-cell-filter"), ("bpy.types.fluiddomainsettings.gridlines_color_field*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-gridlines-color-field"), @@ -147,13 +150,13 @@ url_manual_mapping = ( ("bpy.types.rendersettings_simplify_gpencil_shader_fx*", "render/cycles/render_settings/simplify.html#bpy-types-rendersettings-simplify-gpencil-shader-fx"), ("bpy.types.rendersettings_simplify_gpencil_view_fill*", "render/cycles/render_settings/simplify.html#bpy-types-rendersettings-simplify-gpencil-view-fill"), ("bpy.types.sequencertoolsettings.snap_to_hold_offset*", "video_editing/sequencer/editing.html#bpy-types-sequencertoolsettings-snap-to-hold-offset"), - ("bpy.types.spacesequenceeditor.waveform_display_type*", "editors/video_sequencer/sequencer/display.html#bpy-types-spacesequenceeditor-waveform-display-type"), ("bpy.types.toolsettings.use_mesh_automerge_and_split*", "modeling/meshes/tools/tool_settings.html#bpy-types-toolsettings-use-mesh-automerge-and-split"), ("bpy.types.brush.cloth_constraint_softbody_strength*", "sculpt_paint/sculpting/tools/cloth.html#bpy-types-brush-cloth-constraint-softbody-strength"), ("bpy.types.brush.elastic_deform_volume_preservation*", "sculpt_paint/sculpting/tools/elastic_deform.html#bpy-types-brush-elastic-deform-volume-preservation"), ("bpy.types.brushgpencilsettings.fill_simplify_level*", "grease_pencil/modes/draw/tools/fill.html#bpy-types-brushgpencilsettings-fill-simplify-level"), ("bpy.types.brushgpencilsettings.use_jitter_pressure*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-use-jitter-pressure"), ("bpy.types.brushgpencilsettings.use_settings_random*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-use-settings-random"), + ("bpy.types.cyclesrendersettings.denoising_prefilter*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-denoising-prefilter"), ("bpy.types.cyclesrendersettings.preview_dicing_rate*", "render/cycles/render_settings/subdivision.html#bpy-types-cyclesrendersettings-preview-dicing-rate"), ("bpy.types.cyclesrendersettings.sample_clamp_direct*", "render/cycles/render_settings/light_paths.html#bpy-types-cyclesrendersettings-sample-clamp-direct"), ("bpy.types.cyclesworldsettings.volume_interpolation*", "render/cycles/world_settings.html#bpy-types-cyclesworldsettings-volume-interpolation"), @@ -166,6 +169,7 @@ url_manual_mapping = ( ("bpy.types.freestylelineset.select_external_contour*", "render/freestyle/view_layer/line_set.html#bpy-types-freestylelineset-select-external-contour"), ("bpy.types.linestylegeometrymodifier_simplification*", "render/freestyle/view_layer/line_style/modifiers/geometry/simplification.html#bpy-types-linestylegeometrymodifier-simplification"), ("bpy.types.materialgpencilstyle.use_overlap_strokes*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-use-overlap-strokes"), + ("bpy.types.sequencertimelineoverlay.show_strip_name*", "editors/video_sequencer/sequencer/display.html#bpy-types-sequencertimelineoverlay-show-strip-name"), ("bpy.types.spacespreadsheet.geometry_component_type*", "editors/spreadsheet.html#bpy-types-spacespreadsheet-geometry-component-type"), ("bpy.types.toolsettings.use_gpencil_weight_data_add*", "grease_pencil/modes/draw/introduction.html#bpy-types-toolsettings-use-gpencil-weight-data-add"), ("bpy.types.view3doverlay.texture_paint_mode_opacity*", "editors/3dview/display/overlays.html#bpy-types-view3doverlay-texture-paint-mode-opacity"), @@ -180,10 +184,6 @@ url_manual_mapping = ( ("bpy.types.cyclesrendersettings.adaptive_threshold*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-adaptive-threshold"), ("bpy.types.cyclesrendersettings.camera_cull_margin*", "render/cycles/render_settings/simplify.html#bpy-types-cyclesrendersettings-camera-cull-margin"), ("bpy.types.cyclesrendersettings.debug_use_hair_bvh*", "render/cycles/render_settings/performance.html#bpy-types-cyclesrendersettings-debug-use-hair-bvh"), - ("bpy.types.cyclesrendersettings.mesh_light_samples*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-mesh-light-samples"), - ("bpy.types.cyclesrendersettings.preview_aa_samples*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-preview-aa-samples"), - ("bpy.types.cyclesrendersettings.subsurface_samples*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-subsurface-samples"), - ("bpy.types.cyclesrendersettings.use_square_samples*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-use-square-samples"), ("bpy.types.fluiddomainsettings.export_manta_script*", "physics/fluid/type/domain/cache.html#bpy-types-fluiddomainsettings-export-manta-script"), ("bpy.types.fluiddomainsettings.fractions_threshold*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-fractions-threshold"), ("bpy.types.fluiddomainsettings.particle_band_width*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-particle-band-width"), @@ -201,11 +201,12 @@ url_manual_mapping = ( ("bpy.types.movietrackingsettings.use_tripod_solver*", "movie_clip/tracking/clip/toolbar/solve.html#bpy-types-movietrackingsettings-use-tripod-solver"), ("bpy.types.rendersettings.simplify_child_particles*", "render/cycles/render_settings/simplify.html#bpy-types-rendersettings-simplify-child-particles"), ("bpy.types.rendersettings.use_high_quality_normals*", "render/eevee/render_settings/performance.html#bpy-types-rendersettings-use-high-quality-normals"), + ("bpy.types.sequencerpreviewoverlay.show_annotation*", "video_editing/preview/introduction.html#bpy-types-sequencerpreviewoverlay-show-annotation"), + ("bpy.types.sequencerpreviewoverlay.show_safe_areas*", "video_editing/preview/introduction.html#bpy-types-sequencerpreviewoverlay-show-safe-areas"), ("bpy.types.sequencertoolsettings.snap_ignore_muted*", "video_editing/sequencer/editing.html#bpy-types-sequencertoolsettings-snap-ignore-muted"), ("bpy.types.sequencertoolsettings.snap_ignore_sound*", "video_editing/sequencer/editing.html#bpy-types-sequencertoolsettings-snap-ignore-sound"), ("bpy.types.spaceoutliner.use_filter_case_sensitive*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-case-sensitive"), ("bpy.types.spaceoutliner.use_filter_object_content*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-object-content"), - ("bpy.types.spacesequenceeditor.show_strip_duration*", "editors/video_sequencer/sequencer/display.html#bpy-types-spacesequenceeditor-show-strip-duration"), ("bpy.types.toolsettings.use_proportional_connected*", "editors/3dview/controls/proportional_editing.html#bpy-types-toolsettings-use-proportional-connected"), ("bpy.types.toolsettings.use_proportional_projected*", "editors/3dview/controls/proportional_editing.html#bpy-types-toolsettings-use-proportional-projected"), ("bpy.types.view3doverlay.vertex_paint_mode_opacity*", "editors/3dview/display/overlays.html#bpy-types-view3doverlay-vertex-paint-mode-opacity"), @@ -284,13 +285,13 @@ url_manual_mapping = ( ("bpy.types.materialgpencilstyle.use_fill_holdout*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-use-fill-holdout"), ("bpy.types.particlesettings.use_parent_particles*", "physics/particles/emitter/render.html#bpy-types-particlesettings-use-parent-particles"), ("bpy.types.rigidbodyconstraint.solver_iterations*", "physics/rigid_body/constraints/introduction.html#bpy-types-rigidbodyconstraint-solver-iterations"), + ("bpy.types.sequencerpreviewoverlay.show_metadata*", "video_editing/preview/introduction.html#bpy-types-sequencerpreviewoverlay-show-metadata"), + ("bpy.types.sequencertimelineoverlay.show_fcurves*", "editors/video_sequencer/sequencer/display.html#bpy-types-sequencertimelineoverlay-show-fcurves"), ("bpy.types.spaceclipeditor.use_grayscale_preview*", "editors/clip/display/clip_display.html#bpy-types-spaceclipeditor-use-grayscale-preview"), ("bpy.types.spaceoutliner.use_filter_lib_override*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-lib-override"), ("bpy.types.spaceoutliner.use_filter_object_empty*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-object-empty"), ("bpy.types.spaceoutliner.use_filter_object_light*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-object-light"), ("bpy.types.spacesequenceeditor.proxy_render_size*", "video_editing/preview/sidebar.html#bpy-types-spacesequenceeditor-proxy-render-size"), - ("bpy.types.spacesequenceeditor.show_strip_offset*", "editors/video_sequencer/sequencer/display.html#bpy-types-spacesequenceeditor-show-strip-offset"), - ("bpy.types.spacesequenceeditor.show_strip_source*", "editors/video_sequencer/sequencer/display.html#bpy-types-spacesequenceeditor-show-strip-source"), ("bpy.types.spacespreadsheetrowfilter.column_name*", "editors/spreadsheet.html#bpy-types-spacespreadsheetrowfilter-column-name"), ("bpy.types.toolsettings.gpencil_stroke_placement*", "grease_pencil/modes/draw/stroke_placement.html#bpy-types-toolsettings-gpencil-stroke-placement"), ("bpy.types.toolsettings.use_keyframe_cycle_aware*", "editors/timeline.html#bpy-types-toolsettings-use-keyframe-cycle-aware"), @@ -301,7 +302,6 @@ url_manual_mapping = ( ("bpy.types.cyclesobjectsettings.use_camera_cull*", "render/cycles/object_settings/object_data.html#bpy-types-cyclesobjectsettings-use-camera-cull"), ("bpy.types.cyclesobjectsettings.use_motion_blur*", "render/cycles/object_settings/object_data.html#bpy-types-cyclesobjectsettings-use-motion-blur"), ("bpy.types.cyclesrendersettings.diffuse_bounces*", "render/cycles/render_settings/light_paths.html#bpy-types-cyclesrendersettings-diffuse-bounces"), - ("bpy.types.cyclesrendersettings.diffuse_samples*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-diffuse-samples"), ("bpy.types.cyclesrendersettings.preview_samples*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-preview-samples"), ("bpy.types.cyclesrendersettings.use_camera_cull*", "render/cycles/render_settings/simplify.html#bpy-types-cyclesrendersettings-use-camera-cull"), ("bpy.types.cyclesworldsettings.volume_step_size*", "render/cycles/world_settings.html#bpy-types-cyclesworldsettings-volume-step-size"), @@ -340,9 +340,7 @@ url_manual_mapping = ( ("bpy.types.cyclesmaterialsettings.displacement*", "render/cycles/material_settings.html#bpy-types-cyclesmaterialsettings-displacement"), ("bpy.types.cyclesrendersettings.debug_bvh_type*", "render/cycles/render_settings/debug.html#bpy-types-cyclesrendersettings-debug-bvh-type"), ("bpy.types.cyclesrendersettings.glossy_bounces*", "render/cycles/render_settings/light_paths.html#bpy-types-cyclesrendersettings-glossy-bounces"), - ("bpy.types.cyclesrendersettings.glossy_samples*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-glossy-samples"), ("bpy.types.cyclesrendersettings.volume_bounces*", "render/cycles/render_settings/light_paths.html#bpy-types-cyclesrendersettings-volume-bounces"), - ("bpy.types.cyclesrendersettings.volume_samples*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-volume-samples"), ("bpy.types.cyclesworldsettings.sampling_method*", "render/cycles/world_settings.html#bpy-types-cyclesworldsettings-sampling-method"), ("bpy.types.cyclesworldsettings.volume_sampling*", "render/cycles/world_settings.html#bpy-types-cyclesworldsettings-volume-sampling"), ("bpy.types.editbone.bbone_handle_use_scale_end*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-handle-use-scale-end"), @@ -370,16 +368,12 @@ url_manual_mapping = ( ("bpy.types.spacegrapheditor.show_extrapolation*", "editors/graph_editor/introduction.html#bpy-types-spacegrapheditor-show-extrapolation"), ("bpy.types.spaceoutliner.use_filter_collection*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-collection"), ("bpy.types.spacesequenceeditor.display_channel*", "video_editing/preview/sidebar.html#bpy-types-spacesequenceeditor-display-channel"), - ("bpy.types.spacesequenceeditor.show_annotation*", "video_editing/preview/introduction.html#bpy-types-spacesequenceeditor-show-annotation"), ("bpy.types.spacesequenceeditor.show_region_hud*", "video_editing/sequencer/navigating.html#bpy-types-spacesequenceeditor-show-region-hud"), - ("bpy.types.spacesequenceeditor.show_safe_areas*", "video_editing/preview/introduction.html#bpy-types-spacesequenceeditor-show-safe-areas"), - ("bpy.types.spacesequenceeditor.show_strip_name*", "editors/video_sequencer/sequencer/display.html#bpy-types-spacesequenceeditor-show-strip-name"), ("bpy.types.spacespreadsheet.show_only_selected*", "editors/spreadsheet.html#bpy-types-spacespreadsheet-show-only-selected"), ("bpy.types.spacespreadsheetrowfilter.operation*", "editors/spreadsheet.html#bpy-types-spacespreadsheetrowfilter-operation"), ("bpy.types.spacespreadsheetrowfilter.threshold*", "editors/spreadsheet.html#bpy-types-spacespreadsheetrowfilter-threshold"), ("bpy.types.toolsettings.use_snap_grid_absolute*", "editors/3dview/controls/snapping.html#bpy-types-toolsettings-use-snap-grid-absolute"), ("bpy.types.view3doverlay.show_face_orientation*", "editors/3dview/display/overlays.html#bpy-types-view3doverlay-show-face-orientation"), - ("bpy.types.worldlighting.use_ambient_occlusion*", "render/cycles/world_settings.html#bpy-types-worldlighting-use-ambient-occlusion"), ("bpy.ops.object.blenderkit_material_thumbnail*", "addons/3d_view/blenderkit.html#bpy-ops-object-blenderkit-material-thumbnail"), ("bpy.ops.object.multires_higher_levels_delete*", "modeling/modifiers/generate/multiresolution.html#bpy-ops-object-multires-higher-levels-delete"), ("bpy.ops.object.vertex_group_copy_to_selected*", "modeling/meshes/properties/vertex_groups/vertex_groups.html#bpy-ops-object-vertex-group-copy-to-selected"), @@ -460,9 +454,9 @@ url_manual_mapping = ( ("bpy.types.sculpt.constant_detail_resolution*", "sculpt_paint/sculpting/tool_settings/dyntopo.html#bpy-types-sculpt-constant-detail-resolution"), ("bpy.types.spaceclipeditor.annotation_source*", "movie_clip/tracking/clip/sidebar/view.html#bpy-types-spaceclipeditor-annotation-source"), ("bpy.types.spaceclipeditor.show_blue_channel*", "editors/clip/display/clip_display.html#bpy-types-spaceclipeditor-show-blue-channel"), + ("bpy.types.spacefilebrowser.system_bookmarks*", "editors/file_browser.html#bpy-types-spacefilebrowser-system-bookmarks"), ("bpy.types.spaceoutliner.use_filter_children*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-children"), ("bpy.types.spaceoutliner.use_filter_complete*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-complete"), - ("bpy.types.spacesequenceeditor.show_metadata*", "video_editing/preview/introduction.html#bpy-types-spacesequenceeditor-show-metadata"), ("bpy.types.spacespreadsheet.attribute_domain*", "editors/spreadsheet.html#bpy-types-spacespreadsheet-attribute-domain"), ("bpy.types.spacespreadsheetrowfilter.enabled*", "editors/spreadsheet.html#bpy-types-spacespreadsheetrowfilter-enabled"), ("bpy.types.spaceview3d.transform_orientation*", "editors/3dview/controls/orientation.html#bpy-types-spaceview3d-transform-orientation"), @@ -484,10 +478,10 @@ url_manual_mapping = ( ("bpy.types.cyclesrendersettings.blur_glossy*", "render/cycles/render_settings/light_paths.html#bpy-types-cyclesrendersettings-blur-glossy"), ("bpy.types.cyclesrendersettings.dicing_rate*", "render/cycles/render_settings/subdivision.html#bpy-types-cyclesrendersettings-dicing-rate"), ("bpy.types.cyclesrendersettings.max_bounces*", "render/cycles/render_settings/light_paths.html#bpy-types-cyclesrendersettings-max-bounces"), - ("bpy.types.cyclesrendersettings.progressive*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-progressive"), ("bpy.types.cyclesrendersettings.use_fast_gi*", "render/cycles/render_settings/light_paths.html#bpy-types-cyclesrendersettings-use-fast-gi"), ("bpy.types.editbone.bbone_custom_handle_end*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-custom-handle-end"), ("bpy.types.editbone.bbone_handle_type_start*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-handle-type-start"), + ("bpy.types.fileselectparams.recursion_level*", "editors/file_browser.html#bpy-types-fileselectparams-recursion-level"), ("bpy.types.fluiddomainsettings.adapt_margin*", "physics/fluid/type/domain/gas/adaptive_domain.html#bpy-types-fluiddomainsettings-adapt-margin"), ("bpy.types.fluiddomainsettings.burning_rate*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-burning-rate"), ("bpy.types.fluiddomainsettings.guide_parent*", "physics/fluid/type/domain/guides.html#bpy-types-fluiddomainsettings-guide-parent"), @@ -516,7 +510,6 @@ url_manual_mapping = ( ("bpy.types.spaceclipeditor.show_red_channel*", "editors/clip/display/clip_display.html#bpy-types-spaceclipeditor-show-red-channel"), ("bpy.types.spaceclipeditor.use_mute_footage*", "editors/clip/display/clip_display.html#bpy-types-spaceclipeditor-use-mute-footage"), ("bpy.types.spacesequenceeditor.overlay_type*", "video_editing/preview/sidebar.html#bpy-types-spacesequenceeditor-overlay-type"), - ("bpy.types.spacesequenceeditor.show_fcurves*", "editors/video_sequencer/sequencer/display.html#bpy-types-spacesequenceeditor-show-fcurves"), ("bpy.types.spaceuveditor.sticky_select_mode*", "editors/uv/selecting.html#bpy-types-spaceuveditor-sticky-select-mode"), ("bpy.types.spaceview3d.show_object_viewport*", "editors/3dview/display/visibility.html#bpy-types-spaceview3d-show-object-viewport"), ("bpy.types.view3doverlay.show_fade_inactive*", "editors/3dview/display/overlays.html#bpy-types-view3doverlay-show-fade-inactive"), @@ -536,10 +529,8 @@ url_manual_mapping = ( ("bpy.types.brush.surface_smooth_iterations*", "sculpt_paint/sculpting/tools/smooth.html#bpy-types-brush-surface-smooth-iterations"), ("bpy.types.brushgpencilsettings.pen_jitter*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-pen-jitter"), ("bpy.types.cyclescurverendersettings.shape*", "render/cycles/render_settings/hair.html#bpy-types-cyclescurverendersettings-shape"), - ("bpy.types.cyclesrendersettings.aa_samples*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-aa-samples"), ("bpy.types.cyclesrendersettings.ao_bounces*", "render/cycles/render_settings/light_paths.html#bpy-types-cyclesrendersettings-ao-bounces"), - ("bpy.types.cyclesrendersettings.ao_samples*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-ao-samples"), - ("bpy.types.cyclesrendersettings.tile_order*", "render/cycles/render_settings/performance.html#bpy-types-cyclesrendersettings-tile-order"), + ("bpy.types.cyclesrendersettings.time_limit*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-time-limit"), ("bpy.types.cyclesvisibilitysettings.camera*", "render/cycles/world_settings.html#bpy-types-cyclesvisibilitysettings-camera"), ("bpy.types.cyclesworldsettings.max_bounces*", "render/cycles/world_settings.html#bpy-types-cyclesworldsettings-max-bounces"), ("bpy.types.fluiddomainsettings.domain_type*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-domain-type"), @@ -575,6 +566,8 @@ url_manual_mapping = ( ("bpy.types.rendersettings.use_single_layer*", "render/layers/view_layer.html#bpy-types-rendersettings-use-single-layer"), ("bpy.types.sceneeevee.use_taa_reprojection*", "render/eevee/render_settings/sampling.html#bpy-types-sceneeevee-use-taa-reprojection"), ("bpy.types.sequenceeditor.use_overlay_lock*", "video_editing/preview/sidebar.html#bpy-types-sequenceeditor-use-overlay-lock"), + ("bpy.types.spacefilebrowser.recent_folders*", "editors/file_browser.html#bpy-types-spacefilebrowser-recent-folders"), + ("bpy.types.spacefilebrowser.system_folders*", "editors/file_browser.html#bpy-types-spacefilebrowser-system-folders"), ("bpy.types.spaceoutliner.use_filter_object*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-object"), ("bpy.types.spacesequenceeditor.use_proxies*", "video_editing/preview/sidebar.html#bpy-types-spacesequenceeditor-use-proxies"), ("bpy.types.spaceuveditor.show_pixel_coords*", "editors/uv/sidebar.html#bpy-types-spaceuveditor-show-pixel-coords"), @@ -606,6 +599,7 @@ url_manual_mapping = ( ("bpy.types.cyclescamerasettings.longitude*", "render/cycles/object_settings/cameras.html#bpy-types-cyclescamerasettings-longitude"), ("bpy.types.editbone.bbone_handle_type_end*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-handle-type-end"), ("bpy.types.editbone.use_endroll_as_inroll*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-use-endroll-as-inroll"), + ("bpy.types.fileselectparams.filter_search*", "editors/file_browser.html#bpy-types-fileselectparams-filter-search"), ("bpy.types.fluiddomainsettings.cache_type*", "physics/fluid/type/domain/cache.html#bpy-types-fluiddomainsettings-cache-type"), ("bpy.types.fluiddomainsettings.flip_ratio*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-flip-ratio"), ("bpy.types.fluiddomainsettings.guide_beta*", "physics/fluid/type/domain/guides.html#bpy-types-fluiddomainsettings-guide-beta"), @@ -674,6 +668,8 @@ url_manual_mapping = ( ("bpy.types.cyclesrendersettings.caustics*", "render/cycles/render_settings/light_paths.html#bpy-types-cyclesrendersettings-caustics"), ("bpy.types.cyclesrendersettings.denoiser*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-denoiser"), ("bpy.types.editbone.use_inherit_rotation*", "animation/armatures/bones/properties/relations.html#bpy-types-editbone-use-inherit-rotation"), + ("bpy.types.fileselectparams.display_size*", "editors/file_browser.html#bpy-types-fileselectparams-display-size"), + ("bpy.types.fileselectparams.display_type*", "editors/file_browser.html#bpy-types-fileselectparams-display-type"), ("bpy.types.fluiddomainsettings.use_guide*", "physics/fluid/type/domain/guides.html#bpy-types-fluiddomainsettings-use-guide"), ("bpy.types.fluiddomainsettings.use_noise*", "physics/fluid/type/domain/gas/noise.html#bpy-types-fluiddomainsettings-use-noise"), ("bpy.types.fluiddomainsettings.use_slice*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-use-slice"), @@ -736,6 +732,8 @@ url_manual_mapping = ( ("bpy.types.compositornodebrightcontrast*", "compositing/types/color/bright_contrast.html#bpy-types-compositornodebrightcontrast"), ("bpy.types.compositornodedoubleedgemask*", "compositing/types/matte/double_edge_mask.html#bpy-types-compositornodedoubleedgemask"), ("bpy.types.cyclesrendersettings.samples*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-samples"), + ("bpy.types.fileselectparams.show_hidden*", "editors/file_browser.html#bpy-types-fileselectparams-show-hidden"), + ("bpy.types.fileselectparams.sort_method*", "editors/file_browser.html#bpy-types-fileselectparams-sort-method"), ("bpy.types.fluiddomainsettings.clipping*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-clipping"), ("bpy.types.fluiddomainsettings.use_mesh*", "physics/fluid/type/domain/liquid/mesh.html#bpy-types-fluiddomainsettings-use-mesh"), ("bpy.types.freestylelinestyle.angle_max*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-angle-max"), @@ -752,6 +750,7 @@ url_manual_mapping = ( ("bpy.types.object.use_empty_image_alpha*", "modeling/empties.html#bpy-types-object-use-empty-image-alpha"), ("bpy.types.rendersettings.frame_map_new*", "render/output/properties/frame_range.html#bpy-types-rendersettings-frame-map-new"), ("bpy.types.rendersettings.frame_map_old*", "render/output/properties/frame_range.html#bpy-types-rendersettings-frame-map-old"), + ("bpy.types.rendersettings.use_auto_tile*", "render/cycles/render_settings/performance.html#bpy-types-rendersettings-use-auto-tile"), ("bpy.types.rendersettings.use_overwrite*", "render/output/properties/output.html#bpy-types-rendersettings-use-overwrite"), ("bpy.types.rendersettings.use_sequencer*", "render/output/properties/post_processing.html#bpy-types-rendersettings-use-sequencer"), ("bpy.types.sceneeevee.volumetric_shadow*", "render/eevee/render_settings/volumetrics.html#bpy-types-sceneeevee-volumetric-shadow"), @@ -797,6 +796,7 @@ url_manual_mapping = ( ("bpy.types.compositornodesetalpha.mode*", "compositing/types/converter/set_alpha.html#bpy-types-compositornodesetalpha-mode"), ("bpy.types.dopesheet.use_filter_invert*", "editors/graph_editor/channels.html#bpy-types-dopesheet-use-filter-invert"), ("bpy.types.editbone.use_local_location*", "animation/armatures/bones/properties/relations.html#bpy-types-editbone-use-local-location"), + ("bpy.types.fileselectparams.use_filter*", "editors/file_browser.html#bpy-types-fileselectparams-use-filter"), ("bpy.types.fluiddomainsettings.gravity*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-gravity"), ("bpy.types.fluidflowsettings.flow_type*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-flow-type"), ("bpy.types.fluidflowsettings.subframes*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-subframes"), @@ -868,6 +868,7 @@ url_manual_mapping = ( ("bpy.types.compositornodecolorbalance*", "compositing/types/color/color_balance.html#bpy-types-compositornodecolorbalance"), ("bpy.types.compositornodekeyingscreen*", "compositing/types/matte/keying_screen.html#bpy-types-compositornodekeyingscreen"), ("bpy.types.dynamicpaintcanvassettings*", "physics/dynamic_paint/canvas.html#bpy-types-dynamicpaintcanvassettings"), + ("bpy.types.fileselectparams.directory*", "editors/file_browser.html#bpy-types-fileselectparams-directory"), ("bpy.types.fluidflowsettings.use_flow*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-use-flow"), ("bpy.types.fmodifierfunctiongenerator*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifierfunctiongenerator"), ("bpy.types.geometrynodeattributeclamp*", "modeling/geometry_nodes/attribute/attribute_clamp.html#bpy-types-geometrynodeattributeclamp"), @@ -891,6 +892,7 @@ url_manual_mapping = ( ("bpy.types.shadernodeambientocclusion*", "render/shader_nodes/input/ao.html#bpy-types-shadernodeambientocclusion"), ("bpy.types.shadernodevolumeabsorption*", "render/shader_nodes/shader/volume_absorption.html#bpy-types-shadernodevolumeabsorption"), ("bpy.types.shadernodevolumeprincipled*", "render/shader_nodes/shader/volume_principled.html#bpy-types-shadernodevolumeprincipled"), + ("bpy.types.spacefilebrowser.bookmarks*", "editors/file_browser.html#bpy-types-spacefilebrowser-bookmarks"), ("bpy.types.spaceoutliner.display_mode*", "editors/outliner/interface.html#bpy-types-spaceoutliner-display-mode"), ("bpy.types.spaceoutliner.filter_state*", "editors/outliner/interface.html#bpy-types-spaceoutliner-filter-state"), ("bpy.types.toolsettings.keyframe_type*", "editors/timeline.html#bpy-types-toolsettings-keyframe-type"), @@ -940,6 +942,7 @@ url_manual_mapping = ( ("bpy.types.cyclesrendersettings.seed*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-seed"), ("bpy.types.dynamicpaintbrushsettings*", "physics/dynamic_paint/brush.html#bpy-types-dynamicpaintbrushsettings"), ("bpy.types.editbone.use_scale_easing*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-use-scale-easing"), + ("bpy.types.fileselectparams.filename*", "editors/file_browser.html#bpy-types-fileselectparams-filename"), ("bpy.types.fluiddomainsettings.alpha*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-alpha"), ("bpy.types.fluidflowsettings.density*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-density"), ("bpy.types.freestylelineset.qi_start*", "render/freestyle/view_layer/line_set.html#bpy-types-freestylelineset-qi-start"), @@ -1048,6 +1051,8 @@ url_manual_mapping = ( ("bpy.types.nodesocketinterface.name*", "interface/controls/nodes/groups.html#bpy-types-nodesocketinterface-name"), ("bpy.types.object.is_shadow_catcher*", "render/cycles/object_settings/object_data.html#bpy-types-object-is-shadow-catcher"), ("bpy.types.particleinstancemodifier*", "modeling/modifiers/physics/particle_instance.html#bpy-types-particleinstancemodifier"), + ("bpy.types.rendersettings.tile_size*", "render/cycles/render_settings/performance.html#bpy-types-rendersettings-tile-size"), + ("bpy.types.sequencertimelineoverlay*", "editors/video_sequencer/sequencer/display.html#bpy-types-sequencertimelineoverlay"), ("bpy.types.sequencetransform.offset*", "video_editing/sequencer/sidebar/strip.html#bpy-types-sequencetransform-offset"), ("bpy.types.shadernodebrightcontrast*", "render/shader_nodes/color/bright_contrast.html#bpy-types-shadernodebrightcontrast"), ("bpy.types.shadernodebsdfprincipled*", "render/shader_nodes/shader/principled.html#bpy-types-shadernodebsdfprincipled"), @@ -1143,6 +1148,7 @@ url_manual_mapping = ( ("bpy.types.rendersettings.fps_base*", "render/output/properties/format.html#bpy-types-rendersettings-fps-base"), ("bpy.types.rigidbodyobject.enabled*", "physics/rigid_body/properties/settings.html#bpy-types-rigidbodyobject-enabled"), ("bpy.types.sceneeevee.use_overscan*", "render/eevee/render_settings/film.html#bpy-types-sceneeevee-use-overscan"), + ("bpy.types.sequencerpreviewoverlay*", "video_editing/preview/introduction.html#bpy-types-sequencerpreviewoverlay"), ("bpy.types.sequencetransform.scale*", "video_editing/sequencer/sidebar/strip.html#bpy-types-sequencetransform-scale"), ("bpy.types.shadernodeeeveespecular*", "render/shader_nodes/shader/specular_bsdf.html#bpy-types-shadernodeeeveespecular"), ("bpy.types.shadernodehuesaturation*", "render/shader_nodes/color/hue_saturation.html#bpy-types-shadernodehuesaturation"), @@ -1153,7 +1159,7 @@ url_manual_mapping = ( ("bpy.types.vertexweightmixmodifier*", "modeling/modifiers/modify/weight_mix.html#bpy-types-vertexweightmixmodifier"), ("bpy.types.viewlayer.use_freestyle*", "render/freestyle/view_layer/freestyle.html#bpy-types-viewlayer-use-freestyle"), ("bpy.types.volumedisplay.use_slice*", "modeling/volumes/properties.html#bpy-types-volumedisplay-use-slice"), - ("bpy.types.worldlighting.ao_factor*", "render/cycles/world_settings.html#bpy-types-worldlighting-ao-factor"), + ("bpy.types.worldlighting.ao_factor*", "render/cycles/render_settings/light_paths.html#bpy-types-worldlighting-ao-factor"), ("bpy.types.worldmistsettings.depth*", "render/cycles/world_settings.html#bpy-types-worldmistsettings-depth"), ("bpy.types.worldmistsettings.start*", "render/cycles/world_settings.html#bpy-types-worldmistsettings-start"), ("bpy.ops.armature.armature_layers*", "animation/armatures/bones/editing/change_layers.html#bpy-ops-armature-armature-layers"), @@ -1255,11 +1261,11 @@ url_manual_mapping = ( ("bpy.types.shadernodevectorrotate*", "render/shader_nodes/vector/vector_rotate.html#bpy-types-shadernodevectorrotate"), ("bpy.types.sound.use_memory_cache*", "video_editing/sequencer/sidebar/strip.html#bpy-types-sound-use-memory-cache"), ("bpy.types.spaceview3d.show_gizmo*", "editors/3dview/display/gizmo.html#bpy-types-spaceview3d-show-gizmo"), - ("bpy.types.texturegpencilmodifier*", "grease_pencil/modifiers/color/texture_mapping.html#bpy-types-texturegpencilmodifier"), + ("bpy.types.texturegpencilmodifier*", "grease_pencil/modifiers/modify/texture_mapping.html#bpy-types-texturegpencilmodifier"), ("bpy.types.volumedisplacemodifier*", "modeling/modifiers/deform/volume_displace.html#bpy-types-volumedisplacemodifier"), ("bpy.types.volumerender.step_size*", "modeling/volumes/properties.html#bpy-types-volumerender-step-size"), ("bpy.types.weightednormalmodifier*", "modeling/modifiers/modify/weighted_normal.html#bpy-types-weightednormalmodifier"), - ("bpy.types.worldlighting.distance*", "render/cycles/world_settings.html#bpy-types-worldlighting-distance"), + ("bpy.types.worldlighting.distance*", "render/cycles/render_settings/light_paths.html#bpy-types-worldlighting-distance"), ("bpy.ops.armature.autoside_names*", "animation/armatures/bones/editing/naming.html#bpy-ops-armature-autoside-names"), ("bpy.ops.armature.calculate_roll*", "animation/armatures/bones/editing/bone_roll.html#bpy-ops-armature-calculate-roll"), ("bpy.ops.armature.duplicate_move*", "animation/armatures/bones/editing/duplicate.html#bpy-ops-armature-duplicate-move"), @@ -1360,8 +1366,6 @@ url_manual_mapping = ( ("bpy.types.object.visible_shadow*", "render/cycles/object_settings/object_data.html#bpy-types-object-visible-shadow"), ("bpy.types.offsetgpencilmodifier*", "grease_pencil/modifiers/deform/offset.html#bpy-types-offsetgpencilmodifier"), ("bpy.types.posebone.custom_shape*", "animation/armatures/bones/properties/display.html#bpy-types-posebone-custom-shape"), - ("bpy.types.rendersettings.tile_x*", "render/cycles/render_settings/performance.html#bpy-types-rendersettings-tile-x"), - ("bpy.types.rendersettings.tile_y*", "render/cycles/render_settings/performance.html#bpy-types-rendersettings-tile-y"), ("bpy.types.rigifyselectioncolors*", "addons/rigging/rigify/metarigs.html#bpy-types-rigifyselectioncolors"), ("bpy.types.sceneeevee.volumetric*", "render/eevee/render_settings/volumetrics.html#bpy-types-sceneeevee-volumetric"), ("bpy.types.screen.show_statusbar*", "interface/window_system/topbar.html#bpy-types-screen-show-statusbar"), @@ -1573,7 +1577,7 @@ url_manual_mapping = ( ("bpy.types.stretchtoconstraint*", "animation/constraints/tracking/stretch_to.html#bpy-types-stretchtoconstraint"), ("bpy.types.texturenodecurvergb*", "editors/texture_node/types/color/rgb_curves.html#bpy-types-texturenodecurvergb"), ("bpy.types.texturenodevaltorgb*", "editors/texture_node/types/converter/rgb_to_bw.html#bpy-types-texturenodevaltorgb"), - ("bpy.types.timegpencilmodifier*", "grease_pencil/modifiers/deform/time_offset.html#bpy-types-timegpencilmodifier"), + ("bpy.types.timegpencilmodifier*", "grease_pencil/modifiers/modify/time_offset.html#bpy-types-timegpencilmodifier"), ("bpy.types.tintgpencilmodifier*", "grease_pencil/modifiers/color/tint.html#bpy-types-tintgpencilmodifier"), ("bpy.types.transformconstraint*", "animation/constraints/transform/transformation.html#bpy-types-transformconstraint"), ("bpy.types.triangulatemodifier*", "modeling/modifiers/generate/triangulate.html#bpy-types-triangulatemodifier"), @@ -1652,6 +1656,7 @@ url_manual_mapping = ( ("bpy.types.curve.twist_smooth*", "modeling/curves/properties/shape.html#bpy-types-curve-twist-smooth"), ("bpy.types.curvepaintsettings*", "modeling/curves/tools/draw.html#bpy-types-curvepaintsettings"), ("bpy.types.fcurve.array_index*", "editors/graph_editor/fcurves/properties.html#bpy-types-fcurve-array-index"), + ("bpy.types.fileselectidfilter*", "editors/file_browser.html#bpy-types-fileselectidfilter"), ("bpy.types.fmodifiergenerator*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifiergenerator"), ("bpy.types.freestylelinestyle*", "render/freestyle/view_layer/line_style/index.html#bpy-types-freestylelinestyle"), ("bpy.types.gammacrosssequence*", "video_editing/sequencer/strips/transitions/gamma_cross.html#bpy-types-gammacrosssequence"), @@ -1820,6 +1825,7 @@ url_manual_mapping = ( ("bpy.ops.clip.track_markers*", "movie_clip/tracking/clip/editing/track.html#bpy-ops-clip-track-markers"), ("bpy.ops.curve.extrude_move*", "modeling/curves/editing/control_points.html#bpy-ops-curve-extrude-move"), ("bpy.ops.curve.make_segment*", "modeling/curves/editing/control_points.html#bpy-ops-curve-make-segment"), + ("bpy.ops.file.directory_new*", "editors/file_browser.html#bpy-ops-file-directory-new"), ("bpy.ops.graph.euler_filter*", "editors/graph_editor/fcurves/editing.html#bpy-ops-graph-euler-filter"), ("bpy.ops.marker.camera_bind*", "animation/markers.html#bpy-ops-marker-camera-bind"), ("bpy.ops.mask.select_circle*", "movie_clip/masking/selecting.html#bpy-ops-mask-select-circle"), @@ -1874,6 +1880,7 @@ url_manual_mapping = ( ("bpy.types.editbone.bbone_x*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-x"), ("bpy.types.editbone.bbone_z*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-z"), ("bpy.types.fcurve.data_path*", "editors/graph_editor/fcurves/properties.html#bpy-types-fcurve-data-path"), + ("bpy.types.fileselectparams*", "editors/file_browser.html#bpy-types-fileselectparams"), ("bpy.types.fmodifierstepped*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifierstepped"), ("bpy.types.freestylelineset*", "render/freestyle/view_layer/line_set.html#bpy-types-freestylelineset"), ("bpy.types.mask.frame_start*", "movie_clip/masking/sidebar.html#bpy-types-mask-frame-start"), @@ -1904,6 +1911,7 @@ url_manual_mapping = ( ("bpy.types.softbodymodifier*", "physics/soft_body/index.html#bpy-types-softbodymodifier"), ("bpy.types.softbodysettings*", "physics/soft_body/settings/index.html#bpy-types-softbodysettings"), ("bpy.types.solidifymodifier*", "modeling/modifiers/generate/solidify.html#bpy-types-solidifymodifier"), + ("bpy.types.spacefilebrowser*", "editors/file_browser.html#bpy-types-spacefilebrowser"), ("bpy.types.spacegrapheditor*", "editors/graph_editor/index.html#bpy-types-spacegrapheditor"), ("bpy.types.spacepreferences*", "editors/preferences/index.html#bpy-types-spacepreferences"), ("bpy.types.spacespreadsheet*", "editors/spreadsheet.html#bpy-types-spacespreadsheet"), @@ -1923,6 +1931,7 @@ url_manual_mapping = ( ("bpy.ops.clip.solve_camera*", "movie_clip/tracking/clip/editing/track.html#bpy-ops-clip-solve-camera"), ("bpy.ops.constraint.delete*", "animation/constraints/interface/header.html#bpy-ops-constraint-delete"), ("bpy.ops.curve.smooth_tilt*", "modeling/curves/editing/control_points.html#bpy-ops-curve-smooth-tilt"), + ("bpy.ops.file.reset_recent*", "editors/file_browser.html#bpy-ops-file-reset-recent"), ("bpy.ops.fluid.bake_guides*", "physics/fluid/type/domain/guides.html#bpy-ops-fluid-bake-guides"), ("bpy.ops.fluid.free_guides*", "physics/fluid/type/domain/guides.html#bpy-ops-fluid-free-guides"), ("bpy.ops.font.style_toggle*", "modeling/texts/editing.html#bpy-ops-font-style-toggle"), @@ -2252,6 +2261,7 @@ url_manual_mapping = ( ("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.file.previous*", "editors/file_browser.html#bpy-ops-file-previous"), ("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.image.replace*", "editors/image/editing.html#bpy-ops-image-replace"), @@ -2300,6 +2310,8 @@ url_manual_mapping = ( ("bpy.ops.curve.delete*", "modeling/curves/editing/curve.html#bpy-ops-curve-delete"), ("bpy.ops.curve.reveal*", "modeling/curves/editing/curve.html#bpy-ops-curve-reveal"), ("bpy.ops.curve.smooth*", "modeling/curves/editing/control_points.html#bpy-ops-curve-smooth"), + ("bpy.ops.file.execute*", "editors/file_browser.html#bpy-ops-file-execute"), + ("bpy.ops.file.refresh*", "editors/file_browser.html#bpy-ops-file-refresh"), ("bpy.ops.fluid.preset*", "physics/fluid/type/domain/liquid/diffusion.html#bpy-ops-fluid-preset"), ("bpy.ops.gpencil.copy*", "grease_pencil/modes/edit/grease_pencil_menu.html#bpy-ops-gpencil-copy"), ("bpy.ops.graph.delete*", "editors/graph_editor/fcurves/editing.html#bpy-ops-graph-delete"), @@ -2341,6 +2353,8 @@ url_manual_mapping = ( ("bpy.types.vectorfont*", "modeling/texts/index.html#bpy-types-vectorfont"), ("bpy.ops.clip.reload*", "movie_clip/tracking/clip/editing/clip.html#bpy-ops-clip-reload"), ("bpy.ops.curve.split*", "modeling/curves/editing/curve.html#bpy-ops-curve-split"), + ("bpy.ops.file.cancel*", "editors/file_browser.html#bpy-ops-file-cancel"), + ("bpy.ops.file.parent*", "editors/file_browser.html#bpy-ops-file-parent"), ("bpy.ops.graph.clean*", "editors/graph_editor/fcurves/editing.html#bpy-ops-graph-clean"), ("bpy.ops.graph.paste*", "editors/graph_editor/fcurves/editing.html#bpy-ops-graph-paste"), ("bpy.ops.mesh.bisect*", "modeling/meshes/editing/mesh/bisect.html#bpy-ops-mesh-bisect"), @@ -2420,6 +2434,7 @@ url_manual_mapping = ( ("bpy.types.spacenla*", "editors/nla/index.html#bpy-types-spacenla"), ("bpy.types.sunlight*", "render/lights/light_object.html#bpy-types-sunlight"), ("bpy.ops.clip.open*", "movie_clip/tracking/clip/editing/clip.html#bpy-ops-clip-open"), + ("bpy.ops.file.next*", "editors/file_browser.html#bpy-ops-file-next"), ("bpy.ops.image.new*", "editors/image/editing.html#bpy-ops-image-new"), ("bpy.ops.mesh.fill*", "modeling/meshes/editing/face/fill.html#bpy-ops-mesh-fill"), ("bpy.ops.mesh.poke*", "modeling/meshes/editing/face/poke_faces.html#bpy-ops-mesh-poke"), @@ -2530,6 +2545,7 @@ url_manual_mapping = ( ("bpy.ops.anim*", "animation/index.html#bpy-ops-anim"), ("bpy.ops.boid*", "physics/particles/emitter/physics/boids.html#bpy-ops-boid"), ("bpy.ops.clip*", "movie_clip/index.html#bpy-ops-clip"), + ("bpy.ops.file*", "editors/file_browser.html#bpy-ops-file"), ("bpy.ops.font*", "modeling/texts/index.html#bpy-ops-font"), ("bpy.ops.mask*", "movie_clip/masking/index.html#bpy-ops-mask"), ("bpy.ops.mesh*", "modeling/meshes/index.html#bpy-ops-mesh"), diff --git a/release/scripts/modules/rna_prop_ui.py b/release/scripts/modules/rna_prop_ui.py index 26a2f9ad89b..6d92c94a85c 100644 --- a/release/scripts/modules/rna_prop_ui.py +++ b/release/scripts/modules/rna_prop_ui.py @@ -21,9 +21,10 @@ import bpy from mathutils import Vector +from bpy.types import bpy_prop_array from idprop.types import IDPropertyArray, IDPropertyGroup -ARRAY_TYPES = (list, tuple, IDPropertyArray, Vector) +ARRAY_TYPES = (list, tuple, IDPropertyArray, Vector, bpy_prop_array) # Maximum length of an array property for which a multi-line # edit field will be displayed in the Custom Properties panel. @@ -136,7 +137,7 @@ def draw(layout, context, context_member, property_type, *, use_edit=True): def assign_props(prop, val, key): prop.data_path = context_member - prop.property = key + prop.property_name = key try: prop.value = str(val) diff --git a/release/scripts/presets/keyconfig/Blender.py b/release/scripts/presets/keyconfig/Blender.py index 15cc6097979..1852e150589 100644 --- a/release/scripts/presets/keyconfig/Blender.py +++ b/release/scripts/presets/keyconfig/Blender.py @@ -8,6 +8,7 @@ from bpy.props import ( DIRNAME, FILENAME = os.path.split(__file__) IDNAME = os.path.splitext(FILENAME)[0] + def update_fn(_self, _context): load() @@ -54,12 +55,18 @@ class Prefs(bpy.types.KeyConfigPreferences): default='PLAY', update=update_fn, ) - use_key_activate_tools: BoolProperty( - name="Keys Activate Tools", + tool_key_mode: EnumProperty( + name="Tool Keys:", description=( - "Key shortcuts such as G, R, and S activate the tool instead of running it immediately" + "The method of keys to activate tools such as move, rotate & scale (G, R, S)" ), - default=False, + items=( + ('IMMEDIATE', "Immediate", + "Activate actions immediately"), + ('TOOL', "Active Tool", + "Activate the tool for editors that support tools"), + ), + default='IMMEDIATE', update=update_fn, ) @@ -85,14 +92,28 @@ class Prefs(bpy.types.KeyConfigPreferences): default=False, update=update_fn, ) + # NOTE: expose `use_alt_tool` and `use_alt_cursor` as two options in the UI + # as the tool-tips and titles are different enough depending on RMB/LMB select. use_alt_tool: BoolProperty( name="Alt Tool Access", description=( - "Hold Alt to use the active tool when the gizmo would normally be required" + "Hold Alt to use the active tool when the gizmo would normally be required\n" + "Incompatible with the input preference \"Emulate 3 Button Mouse\" when the \"Alt\" key is used" + ), + default=False, + update=update_fn, + ) + use_alt_cursor: BoolProperty( + name="Alt Cursor Access", + description=( + "Hold Alt-LMB to place the Cursor (instead of LMB), allows tools to activate on press instead of drag.\n" + "Incompatible with the input preference \"Emulate 3 Button Mouse\" when the \"Alt\" key is used" ), default=False, update=update_fn, ) + # end note. + use_select_all_toggle: BoolProperty( name="Select All Toggles", description=( @@ -196,39 +217,63 @@ class Prefs(bpy.types.KeyConfigPreferences): update=update_fn, ) + use_file_single_click: BoolProperty( + name="Open Folders on Single Click", + description=( + "Navigate into folders by clicking on them once instead of twice" + ), + default=False, + update=update_fn, + ) + def draw(self, layout): + from bpy import context + layout.use_property_split = True layout.use_property_decorate = False + prefs = context.preferences + is_select_left = (self.select_mouse == 'LEFT') + use_mouse_emulate_3_button = ( + prefs.inputs.use_mouse_emulate_3_button and + prefs.inputs.mouse_emulate_3_button_modifier == 'ALT' + ) # General settings. col = layout.column() - col.row().prop(self, "select_mouse", text="Select with Mouse Button", expand=True) - col.row().prop(self, "spacebar_action", text="Spacebar Action", expand=True) + col.row().prop(self, "select_mouse", text="Select with Mouse Button:", expand=True) + col.row().prop(self, "spacebar_action", text="Spacebar Action:", expand=True) if is_select_left: - col.row().prop(self, "gizmo_action", text="Activate Gizmo Event", expand=True) + col.row().prop(self, "gizmo_action", text="Activate Gizmo Event:", expand=True) else: - col.row().prop(self, "rmb_action", text="Right Mouse Select Action", expand=True) + col.row().prop(self, "rmb_action", text="Right Mouse Select Action:", expand=True) - # Checkboxes sub-layout. + col.row().prop(self, "tool_key_mode", expand=True) + + # Check-box sub-layout. col = layout.column() sub = col.column(align=True) row = sub.row() row.prop(self, "use_alt_click_leader") + + rowsub = row.row() if is_select_left: - row.prop(self, "use_alt_tool") + rowsub.prop(self, "use_alt_tool") + else: + rowsub.prop(self, "use_alt_cursor") + rowsub.active = not use_mouse_emulate_3_button + row = sub.row() row.prop(self, "use_select_all_toggle") - row.prop(self, "use_key_activate_tools", text="Key Activates Tools") # 3DView settings. col = layout.column() col.label(text="3D View") - col.row().prop(self, "v3d_tilde_action", text="Grave Accent / Tilde Action", expand=True) - col.row().prop(self, "v3d_mmb_action", text="Middle Mouse Action", expand=True) - col.row().prop(self, "v3d_alt_mmb_drag_action", text="Alt Middle Mouse Drag Action", expand=True) + col.row().prop(self, "v3d_tilde_action", text="Grave Accent / Tilde Action:", expand=True) + col.row().prop(self, "v3d_mmb_action", text="Middle Mouse Action:", expand=True) + col.row().prop(self, "v3d_alt_mmb_drag_action", text="Alt Middle Mouse Drag Action:", expand=True) # Checkboxes sub-layout. col = layout.column() @@ -237,6 +282,10 @@ class Prefs(bpy.types.KeyConfigPreferences): sub.prop(self, "use_pie_click_drag") sub.prop(self, "use_v3d_shade_ex_pie") + # File Browser settings. + col = layout.column() + col.label(text="File Browser") + col.row().prop(self, "use_file_single_click") blender_default = bpy.utils.execfile(os.path.join(DIRNAME, "keymap_data", "blender_default.py")) @@ -250,29 +299,33 @@ def load(): kc = context.window_manager.keyconfigs.new(IDNAME) kc_prefs = kc.preferences + is_select_left = (kc_prefs.select_mouse == 'LEFT') + use_mouse_emulate_3_button = ( + prefs.inputs.use_mouse_emulate_3_button and + prefs.inputs.mouse_emulate_3_button_modifier == 'ALT' + ) + keyconfig_data = blender_default.generate_keymaps( blender_default.Params( select_mouse=kc_prefs.select_mouse, - use_mouse_emulate_3_button=( - prefs.inputs.use_mouse_emulate_3_button and - prefs.inputs.mouse_emulate_3_button_modifier == 'ALT' - ), + use_mouse_emulate_3_button=use_mouse_emulate_3_button, spacebar_action=kc_prefs.spacebar_action, - use_key_activate_tools=kc_prefs.use_key_activate_tools, + use_key_activate_tools=(kc_prefs.tool_key_mode == 'TOOL'), v3d_tilde_action=kc_prefs.v3d_tilde_action, use_v3d_mmb_pan=(kc_prefs.v3d_mmb_action == 'PAN'), v3d_alt_mmb_drag_action=kc_prefs.v3d_alt_mmb_drag_action, use_select_all_toggle=kc_prefs.use_select_all_toggle, use_v3d_tab_menu=kc_prefs.use_v3d_tab_menu, use_v3d_shade_ex_pie=kc_prefs.use_v3d_shade_ex_pie, - use_gizmo_drag=( - kc_prefs.select_mouse == 'LEFT' and - kc_prefs.gizmo_action == 'DRAG' + use_gizmo_drag=(is_select_left and kc_prefs.gizmo_action == 'DRAG'), + use_fallback_tool=(True if is_select_left else (kc_prefs.rmb_action == 'FALLBACK_TOOL')), + use_alt_tool_or_cursor=( + (not use_mouse_emulate_3_button) and + (kc_prefs.use_alt_tool if is_select_left else kc_prefs.use_alt_cursor) ), - use_fallback_tool=(True if (kc_prefs.select_mouse == 'LEFT') else (kc_prefs.rmb_action == 'FALLBACK_TOOL')), - use_alt_tool=(kc_prefs.use_alt_tool and kc_prefs.select_mouse == 'LEFT'), use_alt_click_leader=kc_prefs.use_alt_click_leader, use_pie_click_drag=kc_prefs.use_pie_click_drag, + use_file_single_click=kc_prefs.use_file_single_click, ), ) diff --git a/release/scripts/presets/keyconfig/keymap_data/blender_default.py b/release/scripts/presets/keyconfig/keymap_data/blender_default.py index 5ecbe7715e3..35eb6490265 100644 --- a/release/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/release/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -31,14 +31,14 @@ class Params: "action_tweak", "tool_mouse", "tool_tweak", + "tool_maybe_tweak", + "tool_maybe_tweak_value", "context_menu_event", "cursor_set_event", "cursor_tweak_event", "use_mouse_emulate_3_button", - # Experimental option. - "pie_value", - # User preferences. + # User preferences: # # Swap 'Space/Shift-Space'. "spacebar_action", @@ -66,14 +66,26 @@ class Params: # Alt-MMB axis switching 'RELATIVE' or 'ABSOLUTE' axis switching. "v3d_alt_mmb_drag_action", + "use_file_single_click", # Convenience variables: # (derived from other settings). # # This case needs to be checked often, - # convenience for: `params.use_fallback_tool if params.select_mouse == 'RIGHT' else False`. + # Shorthand for: `(params.use_fallback_tool if params.select_mouse == 'RIGHT' else False)`. "use_fallback_tool_rmb", - # Convenience for: `'CLICK' if params.use_fallback_tool_rmb else params.select_mouse_value`. + # Shorthand for: `('CLICK' if params.use_fallback_tool_rmb else params.select_mouse_value)`. "select_mouse_value_fallback", + # Shorthand for: `('CLICK_DRAG' if params.use_pie_click_drag else 'PRESS')` + "pie_value", + # Shorthand for: `{"type": params.tool_tweak, "value": 'ANY'}`. + "tool_tweak_event", + # Shorthand for: `{"type": params.tool_maybe_tweak, "value": params.tool_maybe_tweak_value}`. + # + # NOTE: This is typically used for active tool key-map items however it should never + # be used for selection tools (the default box-select tool for example). + # Since this means with RMB select enabled in edit-mode for e.g. + # `Ctrl-LMB` would be caught by box-select instead of add/extrude. + "tool_maybe_tweak_event", ) def __init__( @@ -92,9 +104,10 @@ class Params: use_v3d_tab_menu=False, use_v3d_shade_ex_pie=False, use_v3d_mmb_pan=False, - use_alt_tool=False, + use_alt_tool_or_cursor=False, use_alt_click_leader=False, use_pie_click_drag=False, + use_file_single_click=False, v3d_tilde_action='VIEW', v3d_alt_mmb_drag_action='RELATIVE', ): @@ -102,6 +115,9 @@ class Params: self.apple = (platform == 'darwin') self.legacy = legacy + if use_mouse_emulate_3_button: + assert(use_alt_tool_or_cursor is False) + if select_mouse == 'RIGHT': # Right mouse select. self.select_mouse = 'RIGHTMOUSE' @@ -111,12 +127,23 @@ class Params: self.action_tweak = 'EVT_TWEAK_L' self.tool_mouse = 'LEFTMOUSE' self.tool_tweak = 'EVT_TWEAK_L' + if use_alt_tool_or_cursor: + self.tool_maybe_tweak = 'LEFTMOUSE' + self.tool_maybe_tweak_value = 'PRESS' + else: + self.tool_maybe_tweak = 'EVT_TWEAK_L' + self.tool_maybe_tweak_value = 'ANY' + self.context_menu_event = {"type": 'W', "value": 'PRESS'} - self.cursor_set_event = {"type": 'LEFTMOUSE', "value": 'CLICK'} + + # Use the "cursor" functionality for RMB select. + if use_alt_tool_or_cursor: + self.cursor_set_event = {"type": 'LEFTMOUSE', "value": 'PRESS', "alt": True} + else: + self.cursor_set_event = {"type": 'LEFTMOUSE', "value": 'CLICK'} + self.cursor_tweak_event = None self.use_fallback_tool = use_fallback_tool - self.use_fallback_tool_rmb = use_fallback_tool - self.select_mouse_value_fallback = 'CLICK' if self.use_fallback_tool_rmb else self.select_mouse_value self.tool_modifier = {} else: # Left mouse select uses Click event for selection. This is a little @@ -129,6 +156,8 @@ class Params: self.action_tweak = 'EVT_TWEAK_R' self.tool_mouse = 'LEFTMOUSE' self.tool_tweak = 'EVT_TWEAK_L' + self.tool_maybe_tweak = 'EVT_TWEAK_L' + self.tool_maybe_tweak_value = 'ANY' if self.legacy: self.context_menu_event = {"type": 'W', "value": 'PRESS'} @@ -138,10 +167,9 @@ class Params: self.cursor_set_event = {"type": 'RIGHTMOUSE', "value": 'PRESS', "shift": True} self.cursor_tweak_event = {"type": 'EVT_TWEAK_R', "value": 'ANY', "shift": True} self.use_fallback_tool = True - self.use_fallback_tool_rmb = False - self.select_mouse_value_fallback = self.select_mouse_value - if use_alt_tool: + # Use the "tool" functionality for LMB select. + if use_alt_tool_or_cursor: # Allow `Alt` to be pressed or not. self.tool_modifier = {"alt": -1} else: @@ -149,7 +177,7 @@ class Params: self.use_mouse_emulate_3_button = use_mouse_emulate_3_button - # User preferences + # User preferences: self.spacebar_action = spacebar_action self.use_key_activate_tools = use_key_activate_tools @@ -163,10 +191,15 @@ class Params: self.use_alt_click_leader = use_alt_click_leader self.use_pie_click_drag = use_pie_click_drag - if not use_pie_click_drag: - self.pie_value = 'PRESS' - else: - self.pie_value = 'CLICK_DRAG' + + self.use_file_single_click = use_file_single_click + + # Convenience variables: + self.use_fallback_tool_rmb = self.use_fallback_tool if self.select_mouse == 'RIGHT' else False + self.select_mouse_value_fallback = 'CLICK' if self.use_fallback_tool_rmb else self.select_mouse_value + self.pie_value = 'CLICK_DRAG' if use_pie_click_drag else 'PRESS' + self.tool_tweak_event = {"type": self.tool_tweak, "value": 'ANY'} + self.tool_maybe_tweak_event = {"type": self.tool_maybe_tweak, "value": self.tool_maybe_tweak_value} # ------------------------------------------------------------------------------ @@ -188,6 +221,13 @@ def _fallback_id(text, fallback): return text +def any_except(*args): + mod = {"ctrl": -1, "alt": -1, "shift": -1, "oskey": -1} + for arg in args: + del mod[arg] + return mod + + # ------------------------------------------------------------------------------ # Keymap Item Wrappers @@ -300,20 +340,23 @@ def _template_items_object_subdivision_set(): def _template_items_gizmo_tweak_value(): return [ - ("gizmogroup.gizmo_tweak", {"type": 'LEFTMOUSE', "value": 'PRESS', "any": True}, None), + ("gizmogroup.gizmo_tweak", + {"type": 'LEFTMOUSE', "value": 'PRESS', **any_except("alt")}, None), ] def _template_items_gizmo_tweak_value_click_drag(): return [ - ("gizmogroup.gizmo_tweak", {"type": 'LEFTMOUSE', "value": 'CLICK', "any": True}, None), - ("gizmogroup.gizmo_tweak", {"type": 'EVT_TWEAK_L', "value": 'ANY', "any": True}, None), + ("gizmogroup.gizmo_tweak", + {"type": 'LEFTMOUSE', "value": 'CLICK', **any_except("alt")}, None), + ("gizmogroup.gizmo_tweak", + {"type": 'EVT_TWEAK_L', "value": 'ANY', **any_except("alt")}, None), ] def _template_items_gizmo_tweak_value_drag(): return [ - ("gizmogroup.gizmo_tweak", {"type": 'EVT_TWEAK_L', "value": 'ANY', "any": True}, None), + ("gizmogroup.gizmo_tweak", {"type": 'EVT_TWEAK_L', "value": 'ANY', **any_except("alt")}, None), ] @@ -2112,16 +2155,20 @@ def km_file_browser_main(params): {"items": items}, ) + if not params.use_file_single_click: + items.extend([ + ("file.select", {"type": 'LEFTMOUSE', "value": 'DOUBLE_CLICK'}, + {"properties": [("open", True), ("deselect_all", not params.legacy)]}), + ]) + items.extend([ ("file.mouse_execute", {"type": 'LEFTMOUSE', "value": 'DOUBLE_CLICK'}, None), # Both .execute and .select are needed here. The former only works if # there's a file operator (i.e. not in regular editor mode) but is # needed to load files. The latter makes selection work if there's no # operator (i.e. in regular editor mode). - ("file.select", {"type": 'LEFTMOUSE', "value": 'DOUBLE_CLICK'}, - {"properties": [("open", True), ("deselect_all", not params.legacy)]}), ("file.select", {"type": 'LEFTMOUSE', "value": 'PRESS'}, - {"properties": [("open", False), ("deselect_all", not params.legacy)]}), + {"properties": [("open", params.use_file_single_click), ("deselect_all", not params.legacy)]}), ("file.select", {"type": 'LEFTMOUSE', "value": 'CLICK', "ctrl": True}, {"properties": [("extend", True), ("open", False)]}), ("file.select", {"type": 'LEFTMOUSE', "value": 'CLICK', "shift": True}, @@ -4365,7 +4412,6 @@ def km_object_mode(params): ("object.duplicates_make_real", {"type": 'A', "value": 'PRESS', "shift": True, "ctrl": True}, None), op_menu("VIEW3D_MT_make_single_user", {"type": 'U', "value": 'PRESS'}), ("object.convert", {"type": 'C', "value": 'PRESS', "alt": True}, None), - ("object.proxy_make", {"type": 'P', "value": 'PRESS', "ctrl": True, "alt": True}, None), ("object.make_local", {"type": 'L', "value": 'PRESS'}, None), ("object.data_transfer", {"type": 'T', "value": 'PRESS', "shift": True, "ctrl": True}, None), ]) @@ -5639,6 +5685,9 @@ def km_knife_tool_modal_map(_params): ("IGNORE_SNAP_OFF", {"type": 'LEFT_CTRL', "value": 'RELEASE', "any": True}, None), ("IGNORE_SNAP_ON", {"type": 'RIGHT_CTRL', "value": 'PRESS', "any": True}, None), ("IGNORE_SNAP_OFF", {"type": 'RIGHT_CTRL', "value": 'RELEASE', "any": True}, None), + ("X_AXIS", {"type": 'X', "value": 'PRESS'}, None), + ("Y_AXIS", {"type": 'Y', "value": 'PRESS'}, None), + ("Z_AXIS", {"type": 'Z', "value": 'PRESS'}, None), ("ANGLE_SNAP_TOGGLE", {"type": 'A', "value": 'PRESS'}, None), ("CYCLE_ANGLE_SNAP_EDGE", {"type": 'R', "value": 'PRESS'}, None), ("CUT_THROUGH_TOGGLE", {"type": 'C', "value": 'PRESS'}, None), @@ -6046,7 +6095,7 @@ def km_generic_tool_annotate_line(params): "Generic Tool: Annotate Line", {"space_type": 'EMPTY', "region_type": 'WINDOW'}, {"items": [ - ("gpencil.annotate", {"type": params.tool_tweak, "value": 'ANY'}, + ("gpencil.annotate", params.tool_maybe_tweak_event, {"properties": [("mode", 'DRAW_STRAIGHT'), ("wait_for_input", False)]}), ("gpencil.annotate", {"type": params.tool_mouse, "value": 'PRESS', "ctrl": True}, {"properties": [("mode", 'ERASER'), ("wait_for_input", False)]}), @@ -6096,7 +6145,8 @@ def km_image_editor_tool_uv_cursor(params): {"space_type": 'IMAGE_EDITOR', "region_type": 'WINDOW'}, {"items": [ ("uv.cursor_set", {"type": params.tool_mouse, "value": 'PRESS'}, None), - ("transform.translate", {"type": params.tool_tweak, "value": 'ANY'}, + # Don't use `tool_maybe_tweak_event` since it conflicts with `PRESS` that places the cursor. + ("transform.translate", params.tool_tweak_event, {"properties": [("release_confirm", True), ("cursor_transform", True)]}), ]}, ) @@ -6121,8 +6171,8 @@ def km_image_editor_tool_uv_select_box(params, *, fallback): {"items": [ *([] if (fallback and not params.use_fallback_tool) else _template_items_tool_select_actions_simple( "uv.select_box", - type=params.select_tweak if fallback else params.tool_tweak, - value='ANY')), + # Don't use `tool_maybe_tweak_event`, see comment for this slot. + **({"type": params.select_tweak, "value": 'ANY'} if fallback else params.tool_tweak_event))), *_template_uv_select_for_fallback(params, fallback), ]}, ) @@ -6151,9 +6201,7 @@ def km_image_editor_tool_uv_select_lasso(params, *, fallback): {"items": [ *([] if (fallback and not params.use_fallback_tool) else _template_items_tool_select_actions_simple( "uv.select_lasso", - type=params.select_tweak if fallback else params.tool_tweak, - value='ANY') - ), + **({"type": params.select_tweak, "value": 'ANY'} if fallback else params.tool_tweak_event))), *_template_uv_select_for_fallback(params, fallback), ]}, ) @@ -6164,7 +6212,7 @@ def km_image_editor_tool_uv_rip_region(params): "Image Editor Tool: Uv, Rip Region", {"space_type": 'IMAGE_EDITOR', "region_type": 'WINDOW'}, {"items": [ - ("uv.rip_move", {"type": params.tool_tweak, "value": 'ANY', **params.tool_modifier}, + ("uv.rip_move", {**params.tool_maybe_tweak_event, **params.tool_modifier}, {"properties": [("TRANSFORM_OT_translate", [("release_confirm", True)])]}), ]}, ) @@ -6194,7 +6242,7 @@ def km_image_editor_tool_uv_move(params): "Image Editor Tool: Uv, Move", {"space_type": 'IMAGE_EDITOR', "region_type": 'WINDOW'}, {"items": [ - ("transform.translate", {"type": params.tool_tweak, "value": 'ANY', **params.tool_modifier}, + ("transform.translate", {**params.tool_maybe_tweak_event, **params.tool_modifier}, {"properties": [("release_confirm", True)]}), ]}, ) @@ -6205,7 +6253,7 @@ def km_image_editor_tool_uv_rotate(params): "Image Editor Tool: Uv, Rotate", {"space_type": 'IMAGE_EDITOR', "region_type": 'WINDOW'}, {"items": [ - ("transform.rotate", {"type": params.tool_tweak, "value": 'ANY', **params.tool_modifier}, + ("transform.rotate", {**params.tool_maybe_tweak_event, **params.tool_modifier}, {"properties": [("release_confirm", True)]}), ]}, ) @@ -6216,7 +6264,7 @@ def km_image_editor_tool_uv_scale(params): "Image Editor Tool: Uv, Scale", {"space_type": 'IMAGE_EDITOR', "region_type": 'WINDOW'}, {"items": [ - ("transform.resize", {"type": params.tool_tweak, "value": 'ANY', **params.tool_modifier}, + ("transform.resize", {**params.tool_maybe_tweak_event, **params.tool_modifier}, {"properties": [("release_confirm", True)]}), ]}, ) @@ -6241,7 +6289,8 @@ def km_node_editor_tool_select_box(params, *, fallback): {"space_type": 'NODE_EDITOR', "region_type": 'WINDOW'}, {"items": [ *([] if (fallback and not params.use_fallback_tool) else _template_items_tool_select_actions_simple( - "node.select_box", type=params.tool_tweak, value='ANY', + "node.select_box", + type=params.tool_maybe_tweak, value=params.tool_maybe_tweak_value, properties=[("tweak", True)], )), ]}, @@ -6288,7 +6337,8 @@ def km_3d_view_tool_cursor(params): {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ ("view3d.cursor3d", {"type": params.tool_mouse, "value": 'PRESS'}, None), - ("transform.translate", {"type": params.tool_tweak, "value": 'ANY'}, + # Don't use `tool_maybe_tweak_event` since it conflicts with `PRESS` that places the cursor. + ("transform.translate", params.tool_tweak_event, {"properties": [("release_confirm", True), ("cursor_transform", True)]}), ]}, ) @@ -6314,8 +6364,8 @@ def km_3d_view_tool_select_box(params, *, fallback): {"items": [ *([] if (fallback and not params.use_fallback_tool) else _template_items_tool_select_actions( "view3d.select_box", - type=params.select_tweak if fallback else params.tool_tweak, - value='ANY')), + # Don't use `tool_maybe_tweak_event`, see comment for this slot. + **({"type": params.select_tweak, "value": 'ANY'} if fallback else params.tool_tweak_event))), *_template_view3d_select_for_fallback(params, fallback), ]}, ) @@ -6345,8 +6395,7 @@ def km_3d_view_tool_select_lasso(params, *, fallback): {"items": [ *([] if (fallback and not params.use_fallback_tool) else _template_items_tool_select_actions( "view3d.select_lasso", - type=params.select_tweak if fallback else params.tool_tweak, - value='ANY')), + **({"type": params.select_tweak, "value": 'ANY'} if fallback else params.tool_tweak_event))), *_template_view3d_select_for_fallback(params, fallback), ]} ) @@ -6357,8 +6406,7 @@ def km_3d_view_tool_transform(params): "3D View Tool: Transform", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("transform.from_gizmo", - {"type": params.tool_tweak, "value": 'ANY', **params.tool_modifier}, None), + ("transform.from_gizmo", {**params.tool_maybe_tweak_event, **params.tool_modifier}, None), ]}, ) @@ -6368,8 +6416,7 @@ def km_3d_view_tool_move(params): "3D View Tool: Move", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("transform.translate", - {"type": params.tool_tweak, "value": 'ANY', **params.tool_modifier}, + ("transform.translate", {**params.tool_maybe_tweak_event, **params.tool_modifier}, {"properties": [("release_confirm", True)]}), ]}, ) @@ -6380,8 +6427,7 @@ def km_3d_view_tool_rotate(params): "3D View Tool: Rotate", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("transform.rotate", - {"type": params.tool_tweak, "value": 'ANY', **params.tool_modifier}, + ("transform.rotate", {**params.tool_maybe_tweak_event, **params.tool_modifier}, {"properties": [("release_confirm", True)]}), ]}, ) @@ -6392,14 +6438,14 @@ def km_3d_view_tool_scale(params): "3D View Tool: Scale", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("transform.resize", - {"type": params.tool_tweak, "value": 'ANY', **params.tool_modifier}, + ("transform.resize", {**params.tool_maybe_tweak_event, **params.tool_modifier}, {"properties": [("release_confirm", True)]}), ]}, ) def km_3d_view_tool_shear(params): + # Don't use 'tool_maybe_tweak_value' since we would loose tweak direction support. return ( "3D View Tool: Shear", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, @@ -6424,7 +6470,7 @@ def km_3d_view_tool_measure(params): "3D View Tool: Measure", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("view3d.ruler_add", {"type": params.tool_tweak, "value": 'ANY'}, None), + ("view3d.ruler_add", params.tool_maybe_tweak_event, None), ("view3d.ruler_remove", {"type": 'X', "value": 'PRESS'}, None), ("view3d.ruler_remove", {"type": 'DEL', "value": 'PRESS'}, None), ]}, @@ -6436,7 +6482,7 @@ def km_3d_view_tool_pose_breakdowner(params): "3D View Tool: Pose, Breakdowner", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("pose.breakdown", {"type": params.tool_tweak, "value": 'ANY', **params.tool_modifier}, None), + ("pose.breakdown", {**params.tool_maybe_tweak_event, **params.tool_modifier}, None), ]}, ) @@ -6446,8 +6492,7 @@ def km_3d_view_tool_pose_push(params): "3D View Tool: Pose, Push", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("pose.push", - {"type": params.tool_tweak, "value": 'ANY', **params.tool_modifier}, None), + ("pose.push", {**params.tool_maybe_tweak_event, **params.tool_modifier}, None), ]}, ) @@ -6457,8 +6502,7 @@ def km_3d_view_tool_pose_relax(params): "3D View Tool: Pose, Relax", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("pose.relax", - {"type": params.tool_tweak, "value": 'ANY', **params.tool_modifier}, None), + ("pose.relax", {**params.tool_maybe_tweak_event, **params.tool_modifier}, None), ]}, ) @@ -6468,8 +6512,7 @@ def km_3d_view_tool_edit_armature_roll(params): "3D View Tool: Edit Armature, Roll", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("transform.transform", - {"type": params.tool_tweak, "value": 'ANY', **params.tool_modifier}, + ("transform.transform", {**params.tool_maybe_tweak_event, **params.tool_modifier}, {"properties": [("release_confirm", True), ("mode", 'BONE_ROLL')]}), ]}, ) @@ -6480,7 +6523,7 @@ def km_3d_view_tool_edit_armature_bone_size(params): "3D View Tool: Edit Armature, Bone Size", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("transform.transform", {"type": params.tool_tweak, "value": 'ANY', **params.tool_modifier}, + ("transform.transform", {**params.tool_maybe_tweak_event, **params.tool_modifier}, {"properties": [("release_confirm", True), ("mode", 'BONE_ENVELOPE')]}), ]}, ) @@ -6492,7 +6535,7 @@ def km_3d_view_tool_edit_armature_bone_envelope(params): {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("transform.bbone_resize", {"type": params.tool_tweak, "value": 'ANY', **params.tool_modifier}, + ("transform.bbone_resize", {**params.tool_maybe_tweak_event, **params.tool_modifier}, {"properties": [("release_confirm", True)]}), ]}, ) @@ -6503,7 +6546,7 @@ def km_3d_view_tool_edit_armature_extrude(params): "3D View Tool: Edit Armature, Extrude", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("armature.extrude_move", {"type": params.tool_tweak, "value": 'ANY', **params.tool_modifier}, + ("armature.extrude_move", {**params.tool_maybe_tweak_event, **params.tool_modifier}, {"properties": [("TRANSFORM_OT_translate", [("release_confirm", True)])]}), ]}, ) @@ -6524,7 +6567,12 @@ def km_3d_view_tool_interactive_add(params): "3D View Tool: Object, Add Primitive", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("view3d.interactive_add", {"type": params.tool_tweak, "value": 'ANY', "any": True}, + ("view3d.interactive_add", + {**params.tool_maybe_tweak_event, + # While "Alt" isn't an important shortcut to support, + # when the preferences to activate tools when "Alt" is held is used, + # it's illogical not to support holding "Alt", even though it is not required. + **({"any": True} if "alt" in params.tool_modifier else any_except("alt"))}, {"properties": [("wait_for_input", False)]}), ]}, ) @@ -6535,7 +6583,7 @@ def km_3d_view_tool_edit_mesh_extrude_region(params): "3D View Tool: Edit Mesh, Extrude Region", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("mesh.extrude_context_move", {"type": params.tool_tweak, "value": 'ANY', **params.tool_modifier}, + ("mesh.extrude_context_move", {**params.tool_maybe_tweak_event, **params.tool_modifier}, {"properties": [("TRANSFORM_OT_translate", [("release_confirm", True)])]}), ]}, ) @@ -6546,7 +6594,7 @@ def km_3d_view_tool_edit_mesh_extrude_manifold(params): "3D View Tool: Edit Mesh, Extrude Manifold", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("mesh.extrude_manifold", {"type": params.tool_tweak, "value": 'ANY', **params.tool_modifier}, + ("mesh.extrude_manifold", {**params.tool_maybe_tweak_event, **params.tool_modifier}, {"properties": [ ("MESH_OT_extrude_region", [("use_dissolve_ortho_edges", True)]), ("TRANSFORM_OT_translate", [ @@ -6565,7 +6613,7 @@ def km_3d_view_tool_edit_mesh_extrude_along_normals(params): "3D View Tool: Edit Mesh, Extrude Along Normals", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("mesh.extrude_region_shrink_fatten", {"type": params.tool_tweak, "value": 'ANY', **params.tool_modifier}, + ("mesh.extrude_region_shrink_fatten", {**params.tool_maybe_tweak_event, **params.tool_modifier}, {"properties": [("TRANSFORM_OT_shrink_fatten", [("release_confirm", True)])]}), ]}, ) @@ -6576,7 +6624,7 @@ def km_3d_view_tool_edit_mesh_extrude_individual(params): "3D View Tool: Edit Mesh, Extrude Individual", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("mesh.extrude_faces_move", {"type": params.tool_tweak, "value": 'ANY', **params.tool_modifier}, + ("mesh.extrude_faces_move", {**params.tool_maybe_tweak_event, **params.tool_modifier}, {"properties": [("TRANSFORM_OT_shrink_fatten", [("release_confirm", True)])]}), ]}, ) @@ -6598,7 +6646,7 @@ def km_3d_view_tool_edit_mesh_inset_faces(params): "3D View Tool: Edit Mesh, Inset Faces", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("mesh.inset", {"type": params.tool_tweak, "value": 'ANY', **params.tool_modifier}, + ("mesh.inset", {**params.tool_maybe_tweak_event, **params.tool_modifier}, {"properties": [("release_confirm", True)]}), ]}, ) @@ -6609,7 +6657,7 @@ def km_3d_view_tool_edit_mesh_bevel(params): "3D View Tool: Edit Mesh, Bevel", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("mesh.bevel", {"type": params.tool_tweak, "value": 'ANY', **params.tool_modifier}, + ("mesh.bevel", {**params.tool_maybe_tweak_event, **params.tool_modifier}, {"properties": [("release_confirm", True)]}), ]}, ) @@ -6656,7 +6704,7 @@ def km_3d_view_tool_edit_mesh_bisect(params): {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ # No need for `tool_modifier` since this takes all input. - ("mesh.bisect", {"type": params.tool_tweak, "value": 'ANY'}, None), + ("mesh.bisect", params.tool_maybe_tweak_event, None), ]}, ) @@ -6681,7 +6729,7 @@ def km_3d_view_tool_edit_mesh_spin(params): "3D View Tool: Edit Mesh, Spin", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("mesh.spin", {"type": params.tool_tweak, "value": 'ANY', **params.tool_modifier}, None), + ("mesh.spin", {**params.tool_maybe_tweak_event, **params.tool_modifier}, None), ]}, ) @@ -6691,7 +6739,7 @@ def km_3d_view_tool_edit_mesh_spin_duplicate(params): "3D View Tool: Edit Mesh, Spin Duplicates", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("mesh.spin", {"type": params.tool_tweak, "value": 'ANY', **params.tool_modifier}, + ("mesh.spin", {**params.tool_maybe_tweak_event, **params.tool_modifier}, {"properties": [("dupli", True)]}), ]}, ) @@ -6702,7 +6750,7 @@ def km_3d_view_tool_edit_mesh_smooth(params): "3D View Tool: Edit Mesh, Smooth", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("mesh.vertices_smooth", {"type": params.tool_tweak, "value": 'ANY', **params.tool_modifier}, + ("mesh.vertices_smooth", {**params.tool_maybe_tweak_event, **params.tool_modifier}, {"properties": [("wait_for_input", False)]}), ]}, ) @@ -6713,7 +6761,7 @@ def km_3d_view_tool_edit_mesh_randomize(params): "3D View Tool: Edit Mesh, Randomize", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("transform.vertex_random", {"type": params.tool_tweak, "value": 'ANY', **params.tool_modifier}, + ("transform.vertex_random", {**params.tool_maybe_tweak_event, **params.tool_modifier}, {"properties": [("wait_for_input", False)]}), ]}, ) @@ -6724,7 +6772,7 @@ def km_3d_view_tool_edit_mesh_edge_slide(params): "3D View Tool: Edit Mesh, Edge Slide", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("transform.edge_slide", {"type": params.tool_tweak, "value": 'ANY', **params.tool_modifier}, + ("transform.edge_slide", {**params.tool_maybe_tweak_event, **params.tool_modifier}, {"properties": [("release_confirm", True)]}), ]}, ) @@ -6735,7 +6783,7 @@ def km_3d_view_tool_edit_mesh_vertex_slide(params): "3D View Tool: Edit Mesh, Vertex Slide", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("transform.vert_slide", {"type": params.tool_tweak, "value": 'ANY', **params.tool_modifier}, + ("transform.vert_slide", {**params.tool_maybe_tweak_event, **params.tool_modifier}, {"properties": [("release_confirm", True)]}), ]}, ) @@ -6746,7 +6794,7 @@ def km_3d_view_tool_edit_mesh_shrink_fatten(params): "3D View Tool: Edit Mesh, Shrink/Fatten", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("transform.shrink_fatten", {"type": params.tool_tweak, "value": 'ANY', **params.tool_modifier}, + ("transform.shrink_fatten", {**params.tool_maybe_tweak_event, **params.tool_modifier}, {"properties": [("release_confirm", True)]}), ]}, ) @@ -6757,7 +6805,7 @@ def km_3d_view_tool_edit_mesh_push_pull(params): "3D View Tool: Edit Mesh, Push/Pull", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("transform.push_pull", {"type": params.tool_tweak, "value": 'ANY', **params.tool_modifier}, + ("transform.push_pull", {**params.tool_maybe_tweak_event, **params.tool_modifier}, {"properties": [("release_confirm", True)]}), ]}, ) @@ -6768,7 +6816,7 @@ def km_3d_view_tool_edit_mesh_to_sphere(params): "3D View Tool: Edit Mesh, To Sphere", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("transform.tosphere", {"type": params.tool_tweak, "value": 'ANY', **params.tool_modifier}, + ("transform.tosphere", {**params.tool_maybe_tweak_event, **params.tool_modifier}, {"properties": [("release_confirm", True)]}), ]}, ) @@ -6779,7 +6827,7 @@ def km_3d_view_tool_edit_mesh_rip_region(params): "3D View Tool: Edit Mesh, Rip Region", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("mesh.rip_move", {"type": params.tool_tweak, "value": 'ANY', **params.tool_modifier}, + ("mesh.rip_move", {**params.tool_maybe_tweak_event, **params.tool_modifier}, {"properties": [("TRANSFORM_OT_translate", [("release_confirm", True)])]}), ]}, ) @@ -6790,7 +6838,7 @@ def km_3d_view_tool_edit_mesh_rip_edge(params): "3D View Tool: Edit Mesh, Rip Edge", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("mesh.rip_edge_move", {"type": params.tool_tweak, "value": 'ANY', **params.tool_modifier}, + ("mesh.rip_edge_move", {**params.tool_maybe_tweak_event, **params.tool_modifier}, {"properties": [("TRANSFORM_OT_translate", [("release_confirm", True)])]}), ]}, ) @@ -6813,7 +6861,7 @@ def km_3d_view_tool_edit_curve_tilt(params): "3D View Tool: Edit Curve, Tilt", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("transform.tilt", {"type": params.tool_tweak, "value": 'ANY', **params.tool_modifier}, + ("transform.tilt", {**params.tool_maybe_tweak_event, **params.tool_modifier}, {"properties": [("release_confirm", True)]}), ]}, ) @@ -6824,7 +6872,7 @@ def km_3d_view_tool_edit_curve_radius(params): "3D View Tool: Edit Curve, Radius", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("transform.transform", {"type": params.tool_tweak, "value": 'ANY', **params.tool_modifier}, + ("transform.transform", {**params.tool_maybe_tweak_event, **params.tool_modifier}, {"properties": [("mode", 'CURVE_SHRINKFATTEN'), ("release_confirm", True)]}), ]}, ) @@ -6835,7 +6883,7 @@ def km_3d_view_tool_edit_curve_randomize(params): "3D View Tool: Edit Curve, Randomize", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("transform.vertex_random", {"type": params.tool_tweak, "value": 'ANY', **params.tool_modifier}, + ("transform.vertex_random", {**params.tool_maybe_tweak_event, **params.tool_modifier}, {"properties": [("wait_for_input", False)]}), ]}, ) @@ -6846,7 +6894,7 @@ def km_3d_view_tool_edit_curve_extrude(params): "3D View Tool: Edit Curve, Extrude", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("curve.extrude_move", {"type": params.tool_tweak, "value": 'ANY', **params.tool_modifier}, + ("curve.extrude_move", {**params.tool_maybe_tweak_event, **params.tool_modifier}, {"properties": [("TRANSFORM_OT_translate", [("release_confirm", True)])]}), ]}, ) @@ -6868,9 +6916,9 @@ def km_3d_view_tool_sculpt_box_hide(params): "3D View Tool: Sculpt, Box Hide", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("paint.hide_show", {"type": params.tool_tweak, "value": 'ANY'}, + ("paint.hide_show", params.tool_maybe_tweak_event, {"properties": [("action", 'HIDE')]}), - ("paint.hide_show", {"type": params.tool_tweak, "value": 'ANY', "ctrl": True}, + ("paint.hide_show", {**params.tool_maybe_tweak_event, "ctrl": True}, {"properties": [("action", 'SHOW')]}), ("paint.hide_show", {"type": params.select_mouse, "value": params.select_mouse_value}, {"properties": [("action", 'SHOW'), ("area", 'ALL')]}), @@ -6883,9 +6931,9 @@ def km_3d_view_tool_sculpt_box_mask(params): "3D View Tool: Sculpt, Box Mask", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("paint.mask_box_gesture", {"type": params.tool_tweak, "value": 'ANY'}, + ("paint.mask_box_gesture", params.tool_maybe_tweak_event, {"properties": [("value", 1.0)]}), - ("paint.mask_box_gesture", {"type": params.tool_tweak, "value": 'ANY', "ctrl": True}, + ("paint.mask_box_gesture", {**params.tool_maybe_tweak_event, "ctrl": True}, {"properties": [("value", 0.0)]}), ]}, ) @@ -6896,9 +6944,9 @@ def km_3d_view_tool_sculpt_lasso_mask(params): "3D View Tool: Sculpt, Lasso Mask", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("paint.mask_lasso_gesture", {"type": params.tool_tweak, "value": 'ANY'}, + ("paint.mask_lasso_gesture", params.tool_maybe_tweak_event, {"properties": [("value", 1.0)]}), - ("paint.mask_lasso_gesture", {"type": params.tool_tweak, "value": 'ANY', "ctrl": True}, + ("paint.mask_lasso_gesture", {**params.tool_maybe_tweak_event, "ctrl": True}, {"properties": [("value", 0.0)]}), ]}, ) @@ -6909,8 +6957,7 @@ def km_3d_view_tool_sculpt_box_face_set(params): "3D View Tool: Sculpt, Box Face Set", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("sculpt.face_set_box_gesture", {"type": params.tool_tweak, "value": 'ANY'}, - None), + ("sculpt.face_set_box_gesture", params.tool_maybe_tweak_event, None), ]}, ) @@ -6920,8 +6967,7 @@ def km_3d_view_tool_sculpt_lasso_face_set(params): "3D View Tool: Sculpt, Lasso Face Set", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("sculpt.face_set_lasso_gesture", {"type": params.tool_tweak, "value": 'ANY'}, - None), + ("sculpt.face_set_lasso_gesture", params.tool_maybe_tweak_event, None), ]}, ) @@ -6931,8 +6977,7 @@ def km_3d_view_tool_sculpt_box_trim(params): "3D View Tool: Sculpt, Box Trim", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("sculpt.trim_box_gesture", {"type": params.tool_tweak, "value": 'ANY'}, - None), + ("sculpt.trim_box_gesture", params.tool_maybe_tweak_event, None), ]}, ) @@ -6942,8 +6987,7 @@ def km_3d_view_tool_sculpt_lasso_trim(params): "3D View Tool: Sculpt, Lasso Trim", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("sculpt.trim_lasso_gesture", {"type": params.tool_tweak, "value": 'ANY'}, - None), + ("sculpt.trim_lasso_gesture", params.tool_maybe_tweak_event, None), ]}, ) @@ -6953,9 +6997,9 @@ def km_3d_view_tool_sculpt_line_mask(params): "3D View Tool: Sculpt, Line Mask", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("paint.mask_line_gesture", {"type": params.tool_tweak, "value": 'ANY'}, + ("paint.mask_line_gesture", params.tool_maybe_tweak_event, {"properties": [("value", 1.0)]}), - ("paint.mask_line_gesture", {"type": params.tool_tweak, "value": 'ANY', "ctrl": True}, + ("paint.mask_line_gesture", {**params.tool_maybe_tweak_event, "ctrl": True}, {"properties": [("value", 0.0)]}), ]}, ) @@ -6966,8 +7010,7 @@ def km_3d_view_tool_sculpt_line_project(params): "3D View Tool: Sculpt, Line Project", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("sculpt.project_line_gesture", {"type": params.tool_tweak, "value": 'ANY'}, - None), + ("sculpt.project_line_gesture", params.tool_maybe_tweak_event, None), ]}, ) @@ -6977,8 +7020,7 @@ def km_3d_view_tool_sculpt_mesh_filter(params): "3D View Tool: Sculpt, Mesh Filter", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("sculpt.mesh_filter", {"type": params.tool_tweak, "value": 'ANY'}, - None) + ("sculpt.mesh_filter", params.tool_maybe_tweak_event, None) ]}, ) @@ -6988,8 +7030,7 @@ def km_3d_view_tool_sculpt_cloth_filter(params): "3D View Tool: Sculpt, Cloth Filter", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("sculpt.cloth_filter", {"type": params.tool_tweak, "value": 'ANY'}, - None) + ("sculpt.cloth_filter", params.tool_maybe_tweak_event, None) ]}, ) @@ -6999,8 +7040,7 @@ def km_3d_view_tool_sculpt_color_filter(params): "3D View Tool: Sculpt, Color Filter", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("sculpt.color_filter", {"type": params.tool_tweak, "value": 'ANY'}, - None) + ("sculpt.color_filter", params.tool_maybe_tweak_event, None) ]}, ) @@ -7010,10 +7050,8 @@ def km_3d_view_tool_sculpt_mask_by_color(params): "3D View Tool: Sculpt, Mask by Color", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("sculpt.mask_by_color", {"type": params.tool_mouse, "value": 'ANY'}, - None), - ("sculpt.mask_by_color", {"type": params.tool_tweak, "value": 'ANY'}, - None), + ("sculpt.mask_by_color", {"type": params.tool_mouse, "value": 'ANY'}, None), + ("sculpt.mask_by_color", params.tool_tweak_event, None), ]}, ) @@ -7023,8 +7061,7 @@ def km_3d_view_tool_sculpt_face_set_edit(params): "3D View Tool: Sculpt, Face Set Edit", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("sculpt.face_set_edit", {"type": params.tool_mouse, "value": 'PRESS'}, - None), + ("sculpt.face_set_edit", {"type": params.tool_mouse, "value": 'PRESS'}, None), ]}, ) @@ -7054,7 +7091,7 @@ def km_3d_view_tool_paint_weight_gradient(params): "3D View Tool: Paint Weight, Gradient", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("paint.weight_gradient", {"type": params.tool_tweak, "value": 'ANY'}, None), + ("paint.weight_gradient", params.tool_maybe_tweak_event, None), ]}, ) @@ -7064,7 +7101,7 @@ def km_3d_view_tool_paint_gpencil_line(params): "3D View Tool: Paint Gpencil, Line", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("gpencil.primitive_line", {"type": params.tool_tweak, "value": 'ANY'}, + ("gpencil.primitive_line", params.tool_maybe_tweak_event, {"properties": [("wait_for_input", False)]}), ("gpencil.primitive_line", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True}, {"properties": [("wait_for_input", False)]}), @@ -7081,7 +7118,7 @@ def km_3d_view_tool_paint_gpencil_polyline(params): "3D View Tool: Paint Gpencil, Polyline", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("gpencil.primitive_polyline", {"type": params.tool_tweak, "value": 'ANY'}, + ("gpencil.primitive_polyline", params.tool_maybe_tweak_event, {"properties": [("wait_for_input", False)]}), ("gpencil.primitive_polyline", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True}, {"properties": [("wait_for_input", False)]}), @@ -7096,7 +7133,7 @@ def km_3d_view_tool_paint_gpencil_box(params): "3D View Tool: Paint Gpencil, Box", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("gpencil.primitive_box", {"type": params.tool_tweak, "value": 'ANY'}, + ("gpencil.primitive_box", params.tool_maybe_tweak_event, {"properties": [("wait_for_input", False)]}), ("gpencil.primitive_box", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True}, {"properties": [("wait_for_input", False)]}), @@ -7113,7 +7150,7 @@ def km_3d_view_tool_paint_gpencil_circle(params): "3D View Tool: Paint Gpencil, Circle", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("gpencil.primitive_circle", {"type": params.tool_tweak, "value": 'ANY'}, + ("gpencil.primitive_circle", params.tool_maybe_tweak_event, {"properties": [("wait_for_input", False)]}), ("gpencil.primitive_circle", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True}, {"properties": [("wait_for_input", False)]}), @@ -7130,7 +7167,7 @@ def km_3d_view_tool_paint_gpencil_arc(params): "3D View Tool: Paint Gpencil, Arc", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("gpencil.primitive_curve", {"type": params.tool_tweak, "value": 'ANY'}, + ("gpencil.primitive_curve", params.tool_maybe_tweak_event, {"properties": [("type", 'ARC'), ("wait_for_input", False)]}), ("gpencil.primitive_curve", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True}, {"properties": [("type", 'ARC'), ("wait_for_input", False)]}), @@ -7147,7 +7184,7 @@ def km_3d_view_tool_paint_gpencil_curve(params): "3D View Tool: Paint Gpencil, Curve", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("gpencil.primitive_curve", {"type": params.tool_tweak, "value": 'ANY'}, + ("gpencil.primitive_curve", params.tool_maybe_tweak_event, {"properties": [("type", 'CURVE'), ("wait_for_input", False)]}), # Lasso select ("gpencil.select_lasso", {"type": params.action_tweak, "value": 'ANY', "ctrl": True, "alt": True}, None), @@ -7187,7 +7224,7 @@ def km_3d_view_tool_paint_gpencil_interpolate(params): "3D View Tool: Paint Gpencil, Interpolate", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("gpencil.interpolate", {"type": params.tool_tweak, "value": 'ANY'}, + ("gpencil.interpolate", params.tool_maybe_tweak_event, {"properties": [("release_confirm", True)]}), ]}, ) @@ -7212,7 +7249,9 @@ def km_3d_view_tool_edit_gpencil_select_box(params, *, fallback): {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ *([] if (fallback and not params.use_fallback_tool) else _template_items_tool_select_actions( - "gpencil.select_box", type=params.select_tweak if fallback else params.tool_tweak, value='ANY')), + "gpencil.select_box", + # Don't use `tool_maybe_tweak_event`, see comment for this slot. + **({"type": params.select_tweak, "value": 'ANY'} if fallback else params.tool_tweak_event))), *_template_view3d_gpencil_select_for_fallback(params, fallback), ]}, ) @@ -7242,8 +7281,7 @@ def km_3d_view_tool_edit_gpencil_select_lasso(params, *, fallback): {"items": [ *([] if (fallback and not params.use_fallback_tool) else _template_items_tool_select_actions( "gpencil.select_lasso", - type=params.select_tweak if fallback else params.tool_tweak, - value='ANY')), + **({"type": params.select_tweak, "value": 'ANY'} if fallback else params.tool_tweak_event))), *_template_view3d_gpencil_select_for_fallback(params, fallback), ]} ) @@ -7254,7 +7292,7 @@ def km_3d_view_tool_edit_gpencil_extrude(params): "3D View Tool: Edit Gpencil, Extrude", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("gpencil.extrude_move", {"type": params.tool_tweak, "value": 'ANY', **params.tool_modifier}, None), + ("gpencil.extrude_move", {**params.tool_maybe_tweak_event, **params.tool_modifier}, None), ]}, ) @@ -7265,7 +7303,7 @@ def km_3d_view_tool_edit_gpencil_radius(params): {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ # No need for `tool_modifier` since this takes all input. - ("transform.transform", {"type": params.tool_tweak, "value": 'ANY'}, + ("transform.transform", params.tool_maybe_tweak_event, {"properties": [("mode", 'GPENCIL_SHRINKFATTEN'), ("release_confirm", True)]}), ]}, ) @@ -7277,7 +7315,7 @@ def km_3d_view_tool_edit_gpencil_bend(params): {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ # No need for `tool_modifier` since this takes all input. - ("transform.bend", {"type": params.tool_tweak, "value": 'ANY'}, + ("transform.bend", params.tool_maybe_tweak_event, {"properties": [("release_confirm", True)]}), ]}, ) @@ -7289,7 +7327,7 @@ def km_3d_view_tool_edit_gpencil_shear(params): {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ # No need for `tool_modifier` since this takes all input. - ("transform.shear", {"type": params.tool_tweak, "value": 'ANY'}, + ("transform.shear", params.tool_maybe_tweak_event, {"properties": [("release_confirm", True)]}), ]}, ) @@ -7301,7 +7339,7 @@ def km_3d_view_tool_edit_gpencil_to_sphere(params): {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ # No need for `tool_modifier` since this takes all input. - ("transform.tosphere", {"type": params.tool_tweak, "value": 'ANY'}, + ("transform.tosphere", params.tool_maybe_tweak_event, {"properties": [("release_confirm", True)]}), ]}, ) @@ -7313,7 +7351,7 @@ def km_3d_view_tool_edit_gpencil_transform_fill(params): {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ # No need for `tool_modifier` since this takes all input. - ("gpencil.transform_fill", {"type": params.tool_tweak, "value": 'ANY'}, + ("gpencil.transform_fill", params.tool_maybe_tweak_event, {"properties": [("release_confirm", True)]}), ]}, ) @@ -7324,7 +7362,7 @@ def km_3d_view_tool_edit_gpencil_interpolate(params): "3D View Tool: Edit Gpencil, Interpolate", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, {"items": [ - ("gpencil.interpolate", {"type": params.tool_tweak, "value": 'ANY'}, + ("gpencil.interpolate", params.tool_maybe_tweak_event, {"properties": [("release_confirm", True)]}), ]}, ) @@ -7342,7 +7380,7 @@ def km_3d_view_tool_sculpt_gpencil_select_box(params): return ( "3D View Tool: Sculpt Gpencil, Select Box", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, - {"items": _template_items_tool_select_actions("gpencil.select_box", type=params.tool_tweak, value='ANY')}, + {"items": _template_items_tool_select_actions("gpencil.select_box", **params.tool_tweak_event)}, ) @@ -7361,7 +7399,7 @@ def km_3d_view_tool_sculpt_gpencil_select_lasso(params): return ( "3D View Tool: Sculpt Gpencil, Select Lasso", {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, - {"items": _template_items_tool_select_actions("gpencil.select_lasso", type=params.tool_tweak, value='ANY')}, + {"items": _template_items_tool_select_actions("gpencil.select_lasso", **params.tool_tweak_event)}, ) @@ -7383,8 +7421,9 @@ def km_sequencer_editor_tool_select_box(params, *, fallback): _fallback_id("Sequencer Tool: Select Box", fallback), {"space_type": 'SEQUENCE_EDITOR', "region_type": 'WINDOW'}, {"items": [ + # Don't use `tool_maybe_tweak_event`, see comment for this slot. *_template_items_tool_select_actions_simple( - "sequencer.select_box", type=params.tool_tweak, value='ANY', + "sequencer.select_box", **params.tool_tweak_event, properties=[("tweak", params.select_mouse == 'LEFTMOUSE')], ), # RMB select can already set the frame, match the tweak tool. @@ -7425,7 +7464,7 @@ def km_sequencer_editor_tool_move(params): "Sequencer Tool: Move", {"space_type": 'SEQUENCE_EDITOR', "region_type": 'WINDOW'}, {"items": [ - ("transform.translate", {"type": params.tool_tweak, "value": 'ANY'}, + ("transform.translate", params.tool_maybe_tweak_event, {"properties": [("release_confirm", True)]}), ]}, ) @@ -7436,7 +7475,7 @@ def km_sequencer_editor_tool_rotate(params): "Sequencer Tool: Rotate", {"space_type": 'SEQUENCE_EDITOR', "region_type": 'WINDOW'}, {"items": [ - ("transform.rotate", {"type": params.tool_tweak, "value": 'ANY'}, + ("transform.rotate", params.tool_maybe_tweak_event, {"properties": [("release_confirm", True)]}), ]}, ) @@ -7447,7 +7486,7 @@ def km_sequencer_editor_tool_scale(params): "Sequencer Tool: Scale", {"space_type": 'SEQUENCE_EDITOR', "region_type": 'WINDOW'}, {"items": [ - ("transform.resize", {"type": params.tool_tweak, "value": 'ANY'}, + ("transform.resize", params.tool_maybe_tweak_event, {"properties": [("release_confirm", True)]}), ]}, ) 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 6a24f072ed0..886abae3602 100644 --- a/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py +++ b/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py @@ -3876,6 +3876,9 @@ def km_knife_tool_modal_map(_params): ("IGNORE_SNAP_OFF", {"type": 'LEFT_SHIFT', "value": 'RELEASE', "any": True}, None), ("IGNORE_SNAP_ON", {"type": 'RIGHT_SHIFT', "value": 'PRESS', "any": True}, None), ("IGNORE_SNAP_OFF", {"type": 'RIGHT_SHIFT', "value": 'RELEASE', "any": True}, None), + ("X_AXIS", {"type": 'X', "value": 'PRESS'}, None), + ("Y_AXIS", {"type": 'Y', "value": 'PRESS'}, None), + ("Z_AXIS", {"type": 'Z', "value": 'PRESS'}, None), ("ANGLE_SNAP_TOGGLE", {"type": 'A', "value": 'PRESS'}, None), ("CYCLE_ANGLE_SNAP_EDGE", {"type": 'R', "value": 'PRESS'}, None), ("CUT_THROUGH_TOGGLE", {"type": 'C', "value": 'PRESS'}, None), diff --git a/release/scripts/startup/bl_app_templates_system/2D_Animation/__init__.py b/release/scripts/startup/bl_app_templates_system/2D_Animation/__init__.py index c8328f5ee42..f8b504b2e34 100644 --- a/release/scripts/startup/bl_app_templates_system/2D_Animation/__init__.py +++ b/release/scripts/startup/bl_app_templates_system/2D_Animation/__init__.py @@ -54,11 +54,26 @@ def update_factory_startup_grease_pencils(): gpd.onion_keyframe_type = 'ALL' +def update_factory_startup_theme(): + # To prevent saving over the current theme Preferences, + # store the current state of use_preferences_save to use later. + preferences = bpy.context.preferences + save_preferences_state = preferences.use_preferences_save + + # Turn use_preferences_save off and set header background alpha. + preferences.use_preferences_save = False + preferences.themes['Default'].view_3d.space.header[3] = 0.8 + + # Restore the original use_preferences_save status. + preferences.use_preferences_save = save_preferences_state + + @persistent def load_handler(_): update_factory_startup_screens() update_factory_startup_scenes() update_factory_startup_grease_pencils() + update_factory_startup_theme() def register(): diff --git a/release/scripts/startup/bl_operators/assets.py b/release/scripts/startup/bl_operators/assets.py index b241e537c38..de46e5c85fb 100644 --- a/release/scripts/startup/bl_operators/assets.py +++ b/release/scripts/startup/bl_operators/assets.py @@ -27,17 +27,28 @@ from bpy_extras.asset_utils import ( ) -class ASSET_OT_tag_add(Operator): +class AssetBrowserMetadataOperator: + @classmethod + def poll(cls, context): + if not SpaceAssetInfo.is_asset_browser_poll(context) or not context.asset_file_handle: + return False + + if not context.asset_file_handle.local_id: + Operator.poll_message_set( + "Asset metadata from external asset libraries can't be " + "edited, only assets stored in the current file can" + ) + return False + return True + + +class ASSET_OT_tag_add(AssetBrowserMetadataOperator, Operator): """Add a new keyword tag to the active asset""" bl_idname = "asset.tag_add" bl_label = "Add Asset Tag" bl_options = {'REGISTER', 'UNDO'} - @classmethod - def poll(cls, context): - return SpaceAssetInfo.is_asset_browser_poll(context) and SpaceAssetInfo.get_active_asset(context) - def execute(self, context): active_asset = SpaceAssetInfo.get_active_asset(context) active_asset.tags.new("Unnamed Tag") @@ -45,7 +56,7 @@ class ASSET_OT_tag_add(Operator): return {'FINISHED'} -class ASSET_OT_tag_remove(Operator): +class ASSET_OT_tag_remove(AssetBrowserMetadataOperator, Operator): """Remove an existing keyword tag from the active asset""" bl_idname = "asset.tag_remove" @@ -54,21 +65,20 @@ class ASSET_OT_tag_remove(Operator): @classmethod def poll(cls, context): - if not SpaceAssetInfo.is_asset_browser_poll(context): + if not super().poll(context): return False - active_asset = SpaceAssetInfo.get_active_asset(context) - if not active_asset: - return False - - return active_asset.active_tag in range(len(active_asset.tags)) + active_asset_file = context.asset_file_handle + asset_metadata = active_asset_file.asset_data + return asset_metadata.active_tag in range(len(asset_metadata.tags)) def execute(self, context): - active_asset = SpaceAssetInfo.get_active_asset(context) - tag = active_asset.tags[active_asset.active_tag] + active_asset_file = context.asset_file_handle + asset_metadata = active_asset_file.asset_data + tag = asset_metadata.tags[asset_metadata.active_tag] - active_asset.tags.remove(tag) - active_asset.active_tag -= 1 + asset_metadata.tags.remove(tag) + asset_metadata.active_tag -= 1 return {'FINISHED'} diff --git a/release/scripts/startup/bl_operators/wm.py b/release/scripts/startup/bl_operators/wm.py index ebf80ca9ee4..6bf45cc5a15 100644 --- a/release/scripts/startup/bl_operators/wm.py +++ b/release/scripts/startup/bl_operators/wm.py @@ -23,6 +23,7 @@ import bpy from bpy.types import ( Menu, Operator, + bpy_prop_array, ) from bpy.props import ( BoolProperty, @@ -31,6 +32,8 @@ from bpy.props import ( FloatProperty, IntProperty, StringProperty, + IntVectorProperty, + FloatVectorProperty, ) from bpy.app.translations import pgettext_iface as iface_ @@ -1266,48 +1269,20 @@ rna_path = StringProperty( options={'HIDDEN'}, ) -rna_value = StringProperty( - name="Property Value", - description="Property value edit", - maxlen=1024, -) - -rna_default = StringProperty( - name="Default Value", - description="Default value of the property. Important for NLA mixing", - maxlen=1024, -) - -rna_custom_property = StringProperty( +rna_custom_property_name = StringProperty( name="Property Name", description="Property name edit", # Match `MAX_IDPROP_NAME - 1` in Blender's source. maxlen=63, ) -rna_min = FloatProperty( - name="Min", - description="Minimum value of the property", - default=-10000.0, - precision=3, -) - -rna_max = FloatProperty( - name="Max", - description="Maximum value of the property", - default=10000.0, - precision=3, -) - -rna_use_soft_limits = BoolProperty( - name="Use Soft Limits", - description="Limits the Property Value slider to a range, values outside the range must be inputted numerically", -) - -rna_is_overridable_library = BoolProperty( - name="Is Library Overridable", - description="Allow the property to be overridden when the data-block is linked", - default=False, +rna_custom_property_type_items = ( + ('FLOAT', "Float", "A single floating-point value"), + ('FLOAT_ARRAY', "Float Array", "An array of floating-point values"), + ('INT', "Integer", "A single integer"), + ('INT_ARRAY', "Integer Array", "An array of integers"), + ('STRING', "String", "A string value"), + ('PYTHON', "Python", "Edit a python value directly, for unsupported property types"), ) # Most useful entries of rna_enum_property_subtype_items for number arrays: @@ -1319,160 +1294,333 @@ rna_vector_subtype_items = ( ('QUATERNION', "Quaternion Rotation", "Quaternion rotation (affects NLA blending)"), ) - class WM_OT_properties_edit(Operator): - """Edit the attributes of the property""" + """Change a custom property's type, or adjust how it is displayed in the interface""" bl_idname = "wm.properties_edit" bl_label = "Edit Property" # register only because invoke_props_popup requires. bl_options = {'REGISTER', 'INTERNAL'} + # Common settings used for all property types. Generally, separate properties are used for each + # type to improve the experience when choosing UI data values. + data_path: rna_path - property: rna_custom_property - value: rna_value - default: rna_default - min: rna_min - max: rna_max - use_soft_limits: rna_use_soft_limits - is_overridable_library: rna_is_overridable_library - soft_min: rna_min - soft_max: rna_max + property_name: rna_custom_property_name + property_type: EnumProperty( + name="Type", + items=lambda self, _context: WM_OT_properties_edit.type_items, + ) + is_overridable_library: BoolProperty( + name="Is Library Overridable", + description="Allow the property to be overridden when the data-block is linked", + default=False, + ) description: StringProperty( - name="Tooltip", + name="Description", + ) + + # Shared for integer and string properties. + + use_soft_limits: BoolProperty( + name="Use Soft Limits", + description="Limits the Property Value slider to a range, values outside the range must be inputted numerically", + ) + array_length: IntProperty( + name="Array Length", + default=3, + min=1, + max=32, # 32 is the maximum size for RNA array properties. + ) + + # Integer properties. + + # This property stores values for both array and non-array properties. + default_int: IntVectorProperty( + name="Default Value", + size=32, + ) + min_int: IntProperty( + name="Min", + default=-10000, + ) + max_int: IntProperty( + name="Max", + default=10000, + ) + soft_min_int: IntProperty( + name="Soft Min", + default=-10000, + ) + soft_max_int: IntProperty( + name="Soft Max", + default=10000, + ) + step_int: IntProperty( + name="Step", + min=1, + default=1, + ) + + # Float properties. + + # This property stores values for both array and non-array properties. + default_float: FloatVectorProperty( + name="Default Value", + size=32, + ) + min_float: FloatProperty( + name="Min", + default=-10000.0, + ) + max_float: FloatProperty( + name="Max", + default=-10000.0, + ) + soft_min_float: FloatProperty( + name="Soft Min", + default=-10000.0, + ) + soft_max_float: FloatProperty( + name="Soft Max", + default=-10000.0, + ) + precision: IntProperty( + name="Precision", + default=3, + min=0, + max=8, + ) + step_float: FloatProperty( + name="Step", + default=0.1, + min=0.001, ) subtype: EnumProperty( name="Subtype", items=lambda self, _context: WM_OT_properties_edit.subtype_items, ) - subtype_items = rna_vector_subtype_items - - def _init_subtype(self, prop_type, is_array, subtype): - subtype = subtype or 'NONE' - subtype_items = rna_vector_subtype_items + # String properties. - # Add a temporary enum entry to preserve unknown subtypes - if not any(subtype == item[0] for item in subtype_items): - subtype_items += ((subtype, subtype, ""),) + default_string: StringProperty( + name="Default Value", + maxlen=1024, + ) - WM_OT_properties_edit.subtype_items = subtype_items - self.subtype = subtype + # Store the value converted to a string as a fallback for otherwise unsupported types. + eval_string: StringProperty( + name="Value", + description="Python value for unsupported custom property types" + ) - def _cmp_props_get(self): - # Changing these properties will refresh the UI - return { - "use_soft_limits": self.use_soft_limits, - "soft_range": (self.soft_min, self.soft_max), - "hard_range": (self.min, self.max), - } + type_items = rna_custom_property_type_items + subtype_items = rna_vector_subtype_items - def get_value_eval(self): - failed = False - try: - value_eval = eval(self.value) - # assert else None -> None, not "None", see T33431. - assert(type(value_eval) in {str, float, int, bool, tuple, list}) - except: - failed = True - value_eval = self.value + # Helper method to avoid repetative code to retrieve a single value from sequences and non-sequences. + @staticmethod + def _convert_new_value_single(old_value, new_type): + if hasattr(old_value, "__len__"): + return new_type(old_value[0]) + return new_type(old_value) - return value_eval, failed + # Helper method to create a list of a given value and type, using a sequence or non-sequence old value. + @staticmethod + def _convert_new_value_array(old_value, new_type, new_len): + if hasattr(old_value, "__len__"): + new_array = [new_type()] * new_len + for i in range(min(len(old_value), new_len)): + new_array[i] = new_type(old_value[i]) + return new_array + return [new_type(old_value)] * new_len + + # Convert an old property for a string, avoiding unhelpful string representations for custom list types. + @staticmethod + def _convert_old_property_to_string(item, name): + # The IDProperty group view API currently doesn't have a "lookup" method. + for key, value in item.items(): + if key == name: + old_value = value + break - def get_default_eval(self): - failed = False - try: - default_eval = eval(self.default) - # assert else None -> None, not "None", see T33431. - assert(type(default_eval) in {str, float, int, bool, tuple, list}) - except: - failed = True - default_eval = self.default + # In order to get a better string conversion, convert the property to a builtin sequence type first. + to_dict = getattr(old_value, "to_dict", None) + to_list = getattr(old_value, "to_list", None) + if to_dict: + old_value = to_dict() + elif to_list: + old_value = to_list() - return default_eval, failed + return str(old_value) - def execute(self, context): + # Retrieve the current type of the custom property on the RNA struct. Some properties like group properties + # can be created in the UI, but editing their meta-data isn't supported. In that case, return 'PYTHON'. + def _get_property_type(self, item, property_name): from rna_prop_ui import ( - rna_idprop_ui_prop_update, rna_idprop_value_item_type, ) - data_path = self.data_path - prop = self.property - prop_escape = bpy.utils.escape_identifier(prop) - - prop_old = getattr(self, "_last_prop", [None])[0] + prop_value = item[property_name] - if prop_old is None: - self.report({'ERROR'}, "Direct execution not supported") - return {'CANCELLED'} - - value_eval, value_failed = self.get_value_eval() - default_eval, default_failed = self.get_default_eval() - - # First remove - item = eval("context.%s" % data_path) - - if (item.id_data and item.id_data.override_library and item.id_data.override_library.reference): - self.report({'ERROR'}, "Cannot edit properties from override data") - return {'CANCELLED'} - - prop_type_old = type(item[prop_old]) + prop_type, is_array = rna_idprop_value_item_type(prop_value) + if prop_type == int: + if is_array: + return 'INT_ARRAY' + return 'INT' + elif prop_type == float: + if is_array: + return 'FLOAT_ARRAY' + return 'FLOAT' + elif prop_type == str: + if is_array: + return 'PYTHON' + return 'STRING' - # Deleting the property will also remove the UI data. - del item[prop_old] + return 'PYTHON' - # Reassign - item[prop] = value_eval - item.property_overridable_library_set('["%s"]' % prop_escape, self.is_overridable_library) - rna_idprop_ui_prop_update(item, prop) + def _init_subtype(self, subtype): + subtype = subtype or 'NONE' + subtype_items = rna_vector_subtype_items - self._last_prop[:] = [prop] + # Add a temporary enum entry to preserve unknown subtypes + if not any(subtype == item[0] for item in subtype_items): + subtype_items += ((subtype, subtype, ""),) - prop_value = item[prop] - prop_type_new = type(prop_value) - prop_type, is_array = rna_idprop_value_item_type(prop_value) + WM_OT_properties_edit.subtype_items = subtype_items + self.subtype = subtype - if prop_type == int: - ui_data = item.id_properties_ui(prop) - if type(default_eval) == str: - self.report({'WARNING'}, "Could not evaluate number from default value") - default_eval = None - elif hasattr(default_eval, "__len__"): - default_eval = [int(round(value)) for value in default_eval] + # Fill the operator's properties with the UI data properties from the existing custom property. + # Note that if the UI data doesn't exist yet, the access will create it and use those default values. + def _fill_old_ui_data(self, item, name): + ui_data = item.id_properties_ui(name) + rna_data = ui_data.as_dict() + + if self.property_type in {'FLOAT', 'FLOAT_ARRAY'}: + self.min_float = rna_data["min"] + self.max_float = rna_data["max"] + self.soft_min_float = rna_data["soft_min"] + self.soft_max_float = rna_data["soft_max"] + self.precision = rna_data["precision"] + self.step_float = rna_data["step"] + self.subtype = rna_data["subtype"] + self.use_soft_limits = ( + self.min_float != self.soft_min_float or + self.max_float != self.soft_max_float + ) + default = self._convert_new_value_array(rna_data["default"], float, 32) + self.default_float = default if isinstance(default, list) else [default] * 32 + elif self.property_type in {'INT', 'INT_ARRAY'}: + self.min_int = rna_data["min"] + self.max_int = rna_data["max"] + self.soft_min_int = rna_data["soft_min"] + self.soft_max_int = rna_data["soft_max"] + self.step_int = rna_data["step"] + self.use_soft_limits = ( + self.min_int != self.soft_min_int or + self.max_int != self.soft_max_int + ) + self.default_int = self._convert_new_value_array(rna_data["default"], int, 32) + elif self.property_type == 'STRING': + self.default_string = rna_data["default"] + + if self.property_type in { 'FLOAT_ARRAY', 'INT_ARRAY'}: + self.array_length = len(item[name]) + + # The dictionary does not contain the description if it was empty. + self.description = rna_data.get("description", "") + + self._init_subtype(self.subtype) + escaped_name = bpy.utils.escape_identifier(name) + self.is_overridable_library = bool(item.is_property_overridable_library('["%s"]' % escaped_name)) + + # When the operator chooses a different type than the original property, + # attempt to convert the old value to the new type for continuity and speed. + def _get_converted_value(self, item, name_old, prop_type_new): + if prop_type_new == 'INT': + return self._convert_new_value_single(item[name_old], int) + + if prop_type_new == 'FLOAT': + return self._convert_new_value_single(item[name_old], float) + + if prop_type_new == 'INT_ARRAY': + prop_type_old = self._get_property_type(item, name_old) + if prop_type_old in {'INT', 'FLOAT', 'INT_ARRAY', 'FLOAT_ARRAY'}: + return self._convert_new_value_array(item[name_old], int, self.array_length) + + if prop_type_new == 'FLOAT_ARRAY': + prop_type_old = self._get_property_type(item, name_old) + if prop_type_old in {'INT', 'FLOAT', 'FLOAT_ARRAY', 'INT_ARRAY'}: + return self._convert_new_value_array(item[name_old], float, self.array_length) + + if prop_type_new == 'STRING': + return self._convert_old_property_to_string(item, name_old) + + # If all else fails, create an empty string property. That should avoid errors later on anyway. + return "" + + # Any time the target type is changed in the dialog, it's helpful to convert the UI data values + # to the new type as well, when possible, currently this only applies for floats and ints. + def _convert_old_ui_data_to_new_type(self, prop_type_old, prop_type_new): + if prop_type_new in {'INT', 'INT_ARRAY'} and prop_type_old in {'FLOAT', 'FLOAT_ARRAY'}: + self.min_int = int(self.min_float) + self.max_int = int(self.max_float) + self.soft_min_int = int(self.soft_min_float) + self.soft_max_int = int(self.soft_max_float) + self.default_int = self._convert_new_value_array(self.default_float, int, 32) + elif prop_type_new in {'FLOAT', 'FLOAT_ARRAY'} and prop_type_old in {'INT', 'INT_ARRAY'}: + self.min_float = float(self.min_int) + self.max_float = float(self.max_int) + self.soft_min_float = float(self.soft_min_int) + self.soft_max_float = float(self.soft_max_int) + self.default_float = self._convert_new_value_array(self.default_int, float, 32) + # Don't convert between string and float/int defaults here, it's not expected like the other conversions. + + # Fill the property's UI data with the values chosen in the operator. + def _create_ui_data_for_new_prop(self, item, name, prop_type_new): + if prop_type_new in {'INT', 'INT_ARRAY'}: + ui_data = item.id_properties_ui(name) ui_data.update( - min=int(round(self.min)), - max=int(round(self.max)), - soft_min=int(round(self.soft_min)), - soft_max=int(round(self.soft_max)), - default=default_eval, - subtype=self.subtype, - description=self.description + min=self.min_int, + max=self.max_int, + soft_min=self.soft_min_int if self.use_soft_limits else self.min_int, + soft_max=self.soft_max_int if self.use_soft_limits else self.min_int, + step=self.step_int, + default=self.default_int[0] if prop_type_new == 'INT' else self.default_int[:self.array_length], + description=self.description, ) - elif prop_type == float: - ui_data = item.id_properties_ui(prop) - if type(default_eval) == str: - self.report({'WARNING'}, "Could not evaluate number from default value") - default_eval = None + elif prop_type_new in {'FLOAT', 'FLOAT_ARRAY'}: + ui_data = item.id_properties_ui(name) ui_data.update( - min=self.min, - max=self.max, - soft_min=self.soft_min, - soft_max=self.soft_max, - default=default_eval, + min=self.min_float, + max=self.max_float, + soft_min=self.soft_min_float if self.use_soft_limits else self.min_float, + soft_max=self.soft_max_float if self.use_soft_limits else self.max_float, + step=self.step_float, + precision=self.precision, + default=self.default_float[0] if prop_type_new == 'FLOAT' else self.default_float[:self.array_length], + description=self.description, subtype=self.subtype, - description=self.description ) - elif prop_type == str and not is_array and not default_failed: # String arrays do not support UI data. - ui_data = item.id_properties_ui(prop) + elif prop_type_new == 'STRING': + ui_data = item.id_properties_ui(name) ui_data.update( - default=self.default, - subtype=self.subtype, - description=self.description + default=self.default_string, + description=self.description, ) + escaped_name = bpy.utils.escape_identifier(name) + item.property_overridable_library_set('["%s"]' % escaped_name, self.is_overridable_library) + + def _update_blender_for_prop_change(self, context, item, name, prop_type_old, prop_type_new): + from rna_prop_ui import ( + rna_idprop_ui_prop_update, + ) + + rna_idprop_ui_prop_update(item, name) + # If we have changed the type of the property, update its potential anim curves! if prop_type_old != prop_type_new: - data_path = '["%s"]' % prop_escape + escaped_name = bpy.utils.escape_identifier(name) + data_path = '["%s"]' % escaped_name done = set() def _update(fcurves): @@ -1498,149 +1646,196 @@ class WM_OT_properties_edit(Operator): for nt in adt.nla_tracks: _update_strips(nt.strips) - # Otherwise existing buttons which reference freed - # memory may crash Blender T26510. - # context.area.tag_redraw() + # Otherwise existing buttons which reference freed memory may crash Blender (T26510). for win in context.window_manager.windows: for area in win.screen.areas: area.tag_redraw() - return {'FINISHED'} + def execute(self, context): + name_old = getattr(self, "_old_prop_name", [None])[0] + if name_old is None: + self.report({'ERROR'}, "Direct execution not supported") + return {'CANCELLED'} - def invoke(self, context, _event): - from rna_prop_ui import ( - rna_idprop_value_to_python, - rna_idprop_value_item_type - ) + data_path = self.data_path + name = self.property_name - prop = self.property - prop_escape = bpy.utils.escape_identifier(prop) + item = eval("context.%s" % data_path) + if (item.id_data and item.id_data.override_library and item.id_data.override_library.reference): + self.report({'ERROR'}, "Cannot edit properties from override data") + return {'CANCELLED'} - data_path = self.data_path + prop_type_old = self._get_property_type(item, name_old) + prop_type_new = self.property_type + self._old_prop_name[:] = [name] + + if prop_type_new == 'PYTHON': + try: + new_value = eval(self.eval_string) + except Exception as ex: + self.report({'WARNING'}, "Python evaluation failed: " + str(ex)) + return {'CANCELLED'} + try: + item[name] = new_value + except Exception as ex: + self.report({'ERROR'}, "Failed to assign value: " + str(ex)) + return {'CANCELLED'} + if name_old != name: + del item[name_old] + else: + new_value = self._get_converted_value(item, name_old, prop_type_new) + del item[name_old] + item[name] = new_value + + self._create_ui_data_for_new_prop(item, name, prop_type_new) + self._update_blender_for_prop_change(context, item, name, prop_type_old, prop_type_new) + + return {'FINISHED'} + + def invoke(self, context, _event): + data_path = self.data_path if not data_path: self.report({'ERROR'}, "Data path not set") return {'CANCELLED'} - self._last_prop = [prop] + name = self.property_name - item = eval("context.%s" % data_path) + self._old_prop_name = [name] + self.last_property_type = self.property_type + item = eval("context.%s" % data_path) if (item.id_data and item.id_data.override_library and item.id_data.override_library.reference): - self.report({'ERROR'}, "Cannot edit properties from override data") + self.report({'ERROR'}, "Properties from override data can not be edited") return {'CANCELLED'} - # retrieve overridable static - is_overridable = item.is_property_overridable_library('["%s"]' % prop_escape) - self.is_overridable_library = bool(is_overridable) - - # default default value - value, value_failed = self.get_value_eval() - prop_type, is_array = rna_idprop_value_item_type(value) - if prop_type in {int, float}: - self.default = str(prop_type(0)) - else: - self.default = "" - - # setup defaults - if prop_type in {int, float}: - ui_data = item.id_properties_ui(prop) - rna_data = ui_data.as_dict() - self.subtype = rna_data["subtype"] - self.min = rna_data["min"] - self.max = rna_data["max"] - self.soft_min = rna_data["soft_min"] - self.soft_max = rna_data["soft_max"] - self.use_soft_limits = ( - self.min != self.soft_min or - self.max != self.soft_max - ) - self.default = str(rna_data["default"]) - self.description = rna_data.get("description", "") - elif prop_type == str and not is_array and not value_failed: # String arrays do not support UI data. - ui_data = item.id_properties_ui(prop) - rna_data = ui_data.as_dict() - self.subtype = rna_data["subtype"] - self.default = str(rna_data["default"]) - self.description = rna_data.get("description", "") - else: - self.min = self.soft_min = 0 - self.max = self.soft_max = 1 - self.use_soft_limits = False - self.description = "" + # Set operator's property type with the type of the existing property, to display the right settings. + old_type = self._get_property_type(item, name) + self.property_type = old_type - self._init_subtype(prop_type, is_array, self.subtype) + # So that the operator can do something for unsupported properties, change the property into + # a string, just for editing in the dialog. When the operator executes, it will be converted back + # into a python value. Always do this conversion, in case the Python property edit type is selected. + self.eval_string = self._convert_old_property_to_string(item, name) - # store for comparison - self._cmp_props = self._cmp_props_get() + if old_type != 'PYTHON': + self._fill_old_ui_data(item, name) wm = context.window_manager return wm.invoke_props_dialog(self) - def check(self, _context): - cmp_props = self._cmp_props_get() + def check(self, context): changed = False - if self._cmp_props != cmp_props: - if cmp_props["use_soft_limits"]: - if cmp_props["soft_range"] != self._cmp_props["soft_range"]: - self.min = min(self.min, self.soft_min) - self.max = max(self.max, self.soft_max) + + # In order to convert UI data between types for type changes before the operator has actually executed, + # compare against the type the last time the check method was called (the last time a value was edited). + if self.property_type != self.last_property_type: + self._convert_old_ui_data_to_new_type(self.last_property_type, self.property_type) + changed = True + + # Make sure that min is less than max, soft range is inside hard range, etc. + if self.property_type in {'FLOAT', 'FLOAT_ARRAY'}: + if self.min_float > self.max_float: + self.min_float, self.max_float = self.max_float, self.min_float + changed = True + if self.soft_min_float > self.soft_max_float: + self.soft_min_float, self.soft_max_float = self.soft_max_float, self.soft_min_float + changed = True + if self.use_soft_limits: + if self.soft_max_float > self.max_float: + self.soft_max_float = self.max_float changed = True - if cmp_props["hard_range"] != self._cmp_props["hard_range"]: - self.soft_min = max(self.min, self.soft_min) - self.soft_max = min(self.max, self.soft_max) + if self.soft_min_float < self.min_float: + self.soft_min_float = self.min_float changed = True - else: - if cmp_props["soft_range"] != cmp_props["hard_range"]: - self.soft_min = self.min - self.soft_max = self.max + elif self.property_type in {'INT', 'INT_ARRAY'}: + if self.min_int > self.max_int: + self.min_int, self.max_int = self.max_int, self.min_int + changed = True + if self.soft_min_int > self.soft_max_int: + self.soft_min_int, self.soft_max_int = self.soft_max_int, self.soft_min_int + changed = True + if self.use_soft_limits: + if self.soft_max_int > self.max_int: + self.soft_max_int = self.max_int + changed = True + if self.soft_min_int < self.min_int: + self.soft_min_int = self.min_int changed = True - changed |= (cmp_props["use_soft_limits"] != self._cmp_props["use_soft_limits"]) - - if changed: - cmp_props = self._cmp_props_get() - - self._cmp_props = cmp_props + self.last_property_type = self.property_type return changed def draw(self, _context): - from rna_prop_ui import ( - rna_idprop_value_item_type, - ) - layout = self.layout layout.use_property_split = True layout.use_property_decorate = False - layout.prop(self, "property") - layout.prop(self, "value") + layout.prop(self, "property_type") + layout.prop(self, "property_name") - value, value_failed = self.get_value_eval() - proptype, is_array = rna_idprop_value_item_type(value) + if self.property_type in {'FLOAT', 'FLOAT_ARRAY'}: + if self.property_type == 'FLOAT_ARRAY': + layout.prop(self, "array_length") + col = layout.column(align=True) + col.prop(self, "default_float", index=0, text="Default") + for i in range(1, self.array_length): + col.prop(self, "default_float", index=i, text=" ") + else: + layout.prop(self, "default_float", index=0) + + col = layout.column(align=True) + col.prop(self, "min_float") + col.prop(self, "max_float") + + col = layout.column() + col.prop(self, "is_overridable_library") + col.prop(self, "use_soft_limits") + + col = layout.column(align=True) + col.enabled = self.use_soft_limits + col.prop(self, "soft_min_float", text="Soft Min") + col.prop(self, "soft_max_float", text="Max") + + layout.prop(self, "step_float") + layout.prop(self, "precision") + + # Subtype is only supported for float properties currently. + if self.property_type != 'FLOAT': + layout.prop(self, "subtype") + elif self.property_type in {'INT', 'INT_ARRAY'}: + if self.property_type == 'INT_ARRAY': + layout.prop(self, "array_length") + col = layout.column(align=True) + col.prop(self, "default_int", index=0, text="Default") + for i in range(1, self.array_length): + col.prop(self, "default_int", index=i, text=" ") + else: + layout.prop(self, "default_int", index=0) - row = layout.row() - row.enabled = proptype in {int, float, str} - row.prop(self, "default") + col = layout.column(align=True) + col.prop(self, "min_int") + col.prop(self, "max_int") - col = layout.column(align=True) - col.prop(self, "min") - col.prop(self, "max") + col = layout.column() + col.prop(self, "is_overridable_library") + col.prop(self, "use_soft_limits") - col = layout.column() - col.prop(self, "is_overridable_library") - col.prop(self, "use_soft_limits") + col = layout.column(align=True) + col.enabled = self.use_soft_limits + col.prop(self, "soft_min_int", text="Soft Min") + col.prop(self, "soft_max_int", text="Max") - col = layout.column(align=True) - col.enabled = self.use_soft_limits - col.prop(self, "soft_min", text="Soft Min") - col.prop(self, "soft_max", text="Max") - layout.prop(self, "description") + layout.prop(self, "step_int") + elif self.property_type == 'STRING': + layout.prop(self, "default_string") - if is_array and proptype == float: - layout.prop(self, "subtype") + if self.property_type == 'PYTHON': + layout.prop(self, "eval_string") + else: + layout.prop(self, "description") class WM_OT_properties_add(Operator): @@ -1706,7 +1901,7 @@ class WM_OT_properties_remove(Operator): bl_options = {'UNDO', 'INTERNAL'} data_path: rna_path - property: rna_custom_property + property_name: rna_custom_property_name def execute(self, context): from rna_prop_ui import ( @@ -1719,9 +1914,9 @@ class WM_OT_properties_remove(Operator): self.report({'ERROR'}, "Cannot remove properties from override data") return {'CANCELLED'} - prop = self.property - rna_idprop_ui_prop_update(item, prop) - del item[prop] + name = self.property_name + rna_idprop_ui_prop_update(item, name) + del item[name] return {'FINISHED'} diff --git a/release/scripts/startup/bl_ui/properties_data_mesh.py b/release/scripts/startup/bl_ui/properties_data_mesh.py index d9ad094ac4f..ba5ecd1efde 100644 --- a/release/scripts/startup/bl_ui/properties_data_mesh.py +++ b/release/scripts/startup/bl_ui/properties_data_mesh.py @@ -639,7 +639,6 @@ class DATA_PT_mesh_attributes(MeshButtonsPanel, Panel): add_attributes(mesh.attributes) add_attributes(mesh.uv_layers) - add_attributes(mesh.vertex_colors) add_attributes(ob.vertex_groups) colliding_names = [name for name, layers in attributes_by_name.items() if len(layers) >= 2] diff --git a/release/scripts/startup/bl_ui/space_filebrowser.py b/release/scripts/startup/bl_ui/space_filebrowser.py index e52136fc416..5dd8c69f3d5 100644 --- a/release/scripts/startup/bl_ui/space_filebrowser.py +++ b/release/scripts/startup/bl_ui/space_filebrowser.py @@ -648,30 +648,6 @@ class ASSETBROWSER_MT_select(AssetBrowserMenu, Menu): layout.operator("file.select_box") -class ASSETBROWSER_PT_navigation_bar(asset_utils.AssetBrowserPanel, Panel): - bl_label = "Asset Navigation" - bl_region_type = 'TOOLS' - bl_options = {'HIDE_HEADER'} - - @classmethod - def poll(cls, context): - return ( - asset_utils.AssetBrowserPanel.poll(context) and - context.preferences.experimental.use_extended_asset_browser - ) - - def draw(self, context): - layout = self.layout - - space_file = context.space_data - - col = layout.column() - - col.scale_x = 1.3 - col.scale_y = 1.3 - col.prop(space_file.params, "asset_category", expand=True) - - class ASSETBROWSER_PT_metadata(asset_utils.AssetBrowserPanel, Panel): bl_region_type = 'TOOL_PROPS' bl_label = "Asset Metadata" @@ -691,10 +667,23 @@ class ASSETBROWSER_PT_metadata(asset_utils.AssetBrowserPanel, Panel): if asset_file_handle.local_id: # If the active file is an ID, use its name directly so renaming is possible from right here. layout.prop(asset_file_handle.local_id, "name", text="") + + col = layout.column(align=True) + col.label(text="Asset Catalog:") + col.prop(asset_file_handle.local_id.asset_data, "catalog_id", text="UUID") + col.prop(asset_file_handle.local_id.asset_data, "catalog_simple_name", text="Simple Name") + row = layout.row() row.label(text="Source: Current File") else: layout.prop(asset_file_handle, "name", text="") + + col = layout.column(align=True) + col.enabled = False + col.label(text="Asset Catalog:") + col.prop(asset_file_handle.asset_data, "catalog_id", text="UUID") + col.prop(asset_file_handle.asset_data, "catalog_simple_name", text="Simple Name") + col = layout.column(align=True) # Just to reduce margin. col.label(text="Source:") row = col.row() @@ -773,9 +762,10 @@ class ASSETBROWSER_MT_context_menu(AssetBrowserMenu, Menu): layout.separator() - sub = layout.row() + sub = layout.column() sub.operator_context = 'EXEC_DEFAULT' - sub.operator("asset.clear", text="Clear Asset") + sub.operator("asset.clear", text="Clear Asset").set_fake_user = False + sub.operator("asset.clear", text="Clear Asset (Set Fake User)").set_fake_user = True layout.separator() @@ -807,7 +797,6 @@ classes = ( ASSETBROWSER_MT_editor_menus, ASSETBROWSER_MT_view, ASSETBROWSER_MT_select, - ASSETBROWSER_PT_navigation_bar, ASSETBROWSER_PT_metadata, ASSETBROWSER_PT_metadata_preview, ASSETBROWSER_PT_metadata_details, diff --git a/release/scripts/startup/bl_ui/space_image.py b/release/scripts/startup/bl_ui/space_image.py index 3ee668888f3..6a769b1aecc 100644 --- a/release/scripts/startup/bl_ui/space_image.py +++ b/release/scripts/startup/bl_ui/space_image.py @@ -598,16 +598,10 @@ class IMAGE_HT_tool_header(Header): def draw(self, context): layout = self.layout - layout.template_header() - self.draw_tool_settings(context) layout.separator_spacer() - IMAGE_HT_header.draw_xform_template(layout, context) - - layout.separator_spacer() - self.draw_mode_settings(context) def draw_tool_settings(self, context): @@ -762,8 +756,7 @@ class IMAGE_HT_header(Header): show_uvedit = sima.show_uvedit show_maskedit = sima.show_maskedit - if not show_region_tool_header: - layout.template_header() + layout.template_header() if sima.mode != 'UV': layout.prop(sima, "ui_mode", text="") @@ -784,8 +777,7 @@ class IMAGE_HT_header(Header): layout.separator_spacer() - if not show_region_tool_header: - IMAGE_HT_header.draw_xform_template(layout, context) + IMAGE_HT_header.draw_xform_template(layout, context) layout.template_ID(sima, "image", new="image.new", open="image.open") @@ -934,6 +926,10 @@ class IMAGE_PT_snapping(Panel): row = col.row(align=True) row.prop(tool_settings, "snap_target", expand=True) + col.separator() + if 'INCREMENT' in tool_settings.snap_uv_element: + col.prop(tool_settings, "use_snap_uv_grid_absolute") + col.label(text="Affect") row = col.row(align=True) row.prop(tool_settings, "use_snap_translate", text="Move", toggle=True) @@ -1467,6 +1463,33 @@ class IMAGE_PT_udim_grid(Panel): col = layout.column() col.prop(uvedit, "tile_grid_shape", text="Grid Shape") +class IMAGE_PT_custom_grid(Panel): + bl_space_type = 'IMAGE_EDITOR' + bl_region_type = 'UI' + bl_category = "View" + bl_label = "Custom Grid" + + @classmethod + def poll(cls, context): + sima = context.space_data + return sima.show_uvedit + + def draw_header(self, context): + sima = context.space_data + uvedit = sima.uv_editor + self.layout.prop(uvedit, "use_custom_grid", text="") + + def draw(self, context): + layout = self.layout + + sima = context.space_data + uvedit = sima.uv_editor + + layout.use_property_split = True + layout.use_property_decorate = False + + col = layout.column() + col.prop(uvedit, "custom_grid_subdivisions", text="Subdivisions") class IMAGE_PT_overlay(Panel): bl_space_type = 'IMAGE_EDITOR' @@ -1652,6 +1675,7 @@ classes = ( IMAGE_PT_uv_cursor, IMAGE_PT_annotation, IMAGE_PT_udim_grid, + IMAGE_PT_custom_grid, IMAGE_PT_overlay, IMAGE_PT_overlay_uv_edit, IMAGE_PT_overlay_uv_edit_geometry, diff --git a/release/scripts/startup/bl_ui/space_node.py b/release/scripts/startup/bl_ui/space_node.py index 5f36009901a..f806fc345d1 100644 --- a/release/scripts/startup/bl_ui/space_node.py +++ b/release/scripts/startup/bl_ui/space_node.py @@ -754,6 +754,11 @@ class NodeTreeInterfacePanel: # Display descriptions only for Geometry Nodes, since it's only used in the modifier panel. if tree.type == 'GEOMETRY': layout.prop(active_socket, "description") + field_socket_prefixes = { + "NodeSocketInt", "NodeSocketColor", "NodeSocketVector", "NodeSocketBool", "NodeSocketFloat"} + is_field_type = any(active_socket.bl_socket_idname.startswith(prefix) for prefix in field_socket_prefixes) + if in_out == "OUT" and is_field_type: + layout.prop(active_socket, "attribute_domain") active_socket.draw(context, layout) diff --git a/release/scripts/startup/bl_ui/space_outliner.py b/release/scripts/startup/bl_ui/space_outliner.py index febd064147f..3d18160d90f 100644 --- a/release/scripts/startup/bl_ui/space_outliner.py +++ b/release/scripts/startup/bl_ui/space_outliner.py @@ -323,7 +323,8 @@ class OUTLINER_MT_asset(Menu): space = context.space_data layout.operator("asset.mark") - layout.operator("asset.clear") + layout.operator("asset.clear", text="Clear Asset").set_fake_user = False + layout.operator("asset.clear", text="Clear Asset (Set Fake User)").set_fake_user = True class OUTLINER_PT_filter(Panel): diff --git a/release/scripts/startup/bl_ui/space_sequencer.py b/release/scripts/startup/bl_ui/space_sequencer.py index 543164f25fc..197e3efebda 100644 --- a/release/scripts/startup/bl_ui/space_sequencer.py +++ b/release/scripts/startup/bl_ui/space_sequencer.py @@ -46,44 +46,85 @@ def selected_sequences_len(context): def draw_color_balance(layout, color_balance): + layout.prop(color_balance, "correction_method") + layout.use_property_split = False flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False) - col = flow.column() - - box = col.box() - split = box.split(factor=0.35) - col = split.column(align=True) - col.label(text="Lift:") - col.separator() - col.separator() - col.prop(color_balance, "lift", text="") - col.prop(color_balance, "invert_lift", text="Invert", icon='ARROW_LEFTRIGHT') - split.template_color_picker(color_balance, "lift", value_slider=True, cubic=True) - - col = flow.column() - - box = col.box() - split = box.split(factor=0.35) - col = split.column(align=True) - col.label(text="Gamma:") - col.separator() - col.separator() - col.prop(color_balance, "gamma", text="") - col.prop(color_balance, "invert_gamma", text="Invert", icon='ARROW_LEFTRIGHT') - split.template_color_picker(color_balance, "gamma", value_slider=True, lock_luminosity=True, cubic=True) - - col = flow.column() - - box = col.box() - split = box.split(factor=0.35) - col = split.column(align=True) - col.label(text="Gain:") - col.separator() - col.separator() - col.prop(color_balance, "gain", text="") - col.prop(color_balance, "invert_gain", text="Invert", icon='ARROW_LEFTRIGHT') - split.template_color_picker(color_balance, "gain", value_slider=True, lock_luminosity=True, cubic=True) + + if color_balance.correction_method == 'LIFT_GAMMA_GAIN': + col = flow.column() + + box = col.box() + split = box.split(factor=0.35) + col = split.column(align=True) + col.label(text="Lift:") + col.separator() + col.separator() + col.prop(color_balance, "lift", text="") + col.prop(color_balance, "invert_lift", text="Invert", icon='ARROW_LEFTRIGHT') + split.template_color_picker(color_balance, "lift", value_slider=True, cubic=True) + + col = flow.column() + + box = col.box() + split = box.split(factor=0.35) + col = split.column(align=True) + col.label(text="Gamma:") + col.separator() + col.separator() + col.prop(color_balance, "gamma", text="") + col.prop(color_balance, "invert_gamma", text="Invert", icon='ARROW_LEFTRIGHT') + split.template_color_picker(color_balance, "gamma", value_slider=True, lock_luminosity=True, cubic=True) + + col = flow.column() + + box = col.box() + split = box.split(factor=0.35) + col = split.column(align=True) + col.label(text="Gain:") + col.separator() + col.separator() + col.prop(color_balance, "gain", text="") + col.prop(color_balance, "invert_gain", text="Invert", icon='ARROW_LEFTRIGHT') + split.template_color_picker(color_balance, "gain", value_slider=True, lock_luminosity=True, cubic=True) + + elif color_balance.correction_method == 'OFFSET_POWER_SLOPE': + col = flow.column() + + box = col.box() + split = box.split(factor=0.35) + col = split.column(align=True) + col.label(text="Offset:") + col.separator() + col.separator() + col.prop(color_balance, "offset", text="") + col.prop(color_balance, "invert_offset", text="Invert", icon='ARROW_LEFTRIGHT') + split.template_color_picker(color_balance, "offset", value_slider=True, cubic=True) + + col = flow.column() + + box = col.box() + split = box.split(factor=0.35) + col = split.column(align=True) + col.label(text="Power:") + col.separator() + col.separator() + col.prop(color_balance, "power", text="") + col.prop(color_balance, "invert_power", text="Invert", icon='ARROW_LEFTRIGHT') + split.template_color_picker(color_balance, "power", value_slider=True, cubic=True) + + col = flow.column() + + box = col.box() + split = box.split(factor=0.35) + col = split.column(align=True) + col.label(text="Slope:") + col.separator() + col.separator() + col.prop(color_balance, "slope", text="") + col.prop(color_balance, "invert_slope", text="Invert", icon='ARROW_LEFTRIGHT') + split.template_color_picker(color_balance, "slope", value_slider=True, cubic=True) class SEQUENCER_PT_active_tool(ToolActivePanelHelper, Panel): @@ -99,8 +140,6 @@ class SEQUENCER_HT_tool_header(Header): def draw(self, context): layout = self.layout - layout.template_header() - self.draw_tool_settings(context) # TODO: options popover. @@ -132,8 +171,7 @@ class SEQUENCER_HT_header(Header): show_region_tool_header = st.show_region_tool_header - if not show_region_tool_header: - layout.template_header() + layout.template_header() layout.prop(st, "view_type", text="") @@ -151,6 +189,12 @@ class SEQUENCER_HT_header(Header): if st.view_type in {'SEQUENCER', 'SEQUENCER_PREVIEW'}: row = layout.row(align=True) row.prop(sequencer_tool_settings, "overlap_mode", text="") + + if st.view_type == 'SEQUENCER_PREVIEW': + row = layout.row(align=True) + row.prop(sequencer_tool_settings, "pivot_point", text="", icon_only=True) + + if st.view_type in {'SEQUENCER', 'SEQUENCER_PREVIEW'}: row = layout.row(align=True) row.prop(tool_settings, "use_snap_sequencer", text="") sub = row.row(align=True) @@ -242,6 +286,7 @@ class SEQUENCER_PT_sequencer_overlay(Panel): layout.prop(overlay_settings, "show_strip_name", text="Name") layout.prop(overlay_settings, "show_strip_source", text="Source") layout.prop(overlay_settings, "show_strip_duration", text="Duration") + layout.prop(overlay_settings, "show_strip_tag_color", text="Color Tags") layout.separator() @@ -375,9 +420,9 @@ class SEQUENCER_MT_view(Menu): layout.operator("view2d.zoom_border", text="Zoom") layout.menu("SEQUENCER_MT_preview_zoom") - if st.display_mode == 'IMAGE': - layout.prop(st, "use_zoom_to_fit") - elif st.display_mode == 'WAVEFORM': + layout.prop(st, "use_zoom_to_fit") + + if st.display_mode == 'WAVEFORM': layout.separator() layout.prop(st, "show_separate_color", text="Show Separate Color Channels") @@ -870,6 +915,9 @@ class SEQUENCER_MT_strip(Menu): layout.operator("sequencer.meta_toggle", text="Toggle Meta") layout.separator() + layout.menu("SEQUENCER_MT_color_tag_picker") + + layout.separator() layout.menu("SEQUENCER_MT_strip_lock_mute") layout.separator() @@ -966,6 +1014,9 @@ class SEQUENCER_MT_context_menu(Menu): layout.operator("sequencer.meta_toggle", text="Toggle Meta") layout.separator() + layout.menu("SEQUENCER_MT_color_tag_picker") + + layout.separator() layout.menu("SEQUENCER_MT_strip_lock_mute") @@ -997,6 +1048,41 @@ class SequencerButtonsPanel_Output: return cls.has_preview(context) +class SEQUENCER_PT_color_tag_picker(Panel): + bl_label = "Color Tag" + bl_space_type = 'SEQUENCE_EDITOR' + bl_region_type = 'UI' + bl_category = "Strip" + bl_options = {'HIDE_HEADER', 'INSTANCED'} + + @classmethod + def poll(cls, context): + return context.active_sequence_strip is not None + + def draw(self, context): + layout = self.layout + + row = layout.row(align=True) + row.operator("sequencer.strip_color_tag_set", icon="X").color = 'NONE' + for i in range(1, 10): + icon = 'SEQUENCE_COLOR_%02d' % i + row.operator("sequencer.strip_color_tag_set", icon=icon).color = 'COLOR_%02d' % i + + +class SEQUENCER_MT_color_tag_picker(Menu): + bl_label = "Set Color Tag" + + @classmethod + def poll(cls, context): + return context.active_sequence_strip is not None + + def draw(self, context): + layout = self.layout + + row = layout.row(align=True) + row.operator_enum("sequencer.strip_color_tag_set", "color", icon_only=True) + + class SEQUENCER_PT_strip(SequencerButtonsPanel, Panel): bl_label = "" bl_options = {'HIDE_HEADER'} @@ -1040,9 +1126,20 @@ class SEQUENCER_PT_strip(SequencerButtonsPanel, Panel): else: icon_header = 'SEQ_SEQUENCER' - row = layout.row() + row = layout.row(align=True) + row.use_property_decorate = False row.label(text="", icon=icon_header) + row.separator() row.prop(strip, "name", text="") + + sub = row.row(align=True) + if strip.color_tag == 'NONE': + sub.popover(panel="SEQUENCER_PT_color_tag_picker", text="", icon='COLOR') + else: + icon = 'SEQUENCE_' + strip.color_tag + sub.popover(panel="SEQUENCER_PT_color_tag_picker", text="", icon=icon) + + row.separator() row.prop(strip, "mute", toggle=True, icon_only=True, emboss=False) @@ -2328,8 +2425,11 @@ classes = ( SEQUENCER_MT_strip_transform, SEQUENCER_MT_strip_input, SEQUENCER_MT_strip_lock_mute, + SEQUENCER_MT_color_tag_picker, SEQUENCER_MT_context_menu, + SEQUENCER_PT_color_tag_picker, + SEQUENCER_PT_active_tool, SEQUENCER_PT_strip, diff --git a/release/scripts/startup/bl_ui/space_toolsystem_common.py b/release/scripts/startup/bl_ui/space_toolsystem_common.py index 98e29d3baba..4a598d0aa63 100644 --- a/release/scripts/startup/bl_ui/space_toolsystem_common.py +++ b/release/scripts/startup/bl_ui/space_toolsystem_common.py @@ -190,7 +190,7 @@ class ToolActivePanelHelper: ToolSelectPanelHelper.draw_active_tool_header( context, layout.column(), - show_tool_name=True, + show_tool_icon=True, tool_key=ToolSelectPanelHelper._tool_key_from_context(context, space_type=self.bl_space_type), ) @@ -766,7 +766,7 @@ class ToolSelectPanelHelper: def draw_active_tool_header( context, layout, *, - show_tool_name=False, + show_tool_icon=False, tool_key=None, ): if tool_key is None: @@ -783,9 +783,12 @@ class ToolSelectPanelHelper: return None # Note: we could show 'item.text' here but it makes the layout jitter when switching tools. # Add some spacing since the icon is currently assuming regular small icon size. - layout.label(text=" " + item.label if show_tool_name else " ", icon_value=icon_value) - if show_tool_name: + if show_tool_icon: + layout.label(text=" " + item.label, icon_value=icon_value) layout.separator() + else: + layout.label(text=item.label) + draw_settings = item.draw_settings if draw_settings is not None: draw_settings(context, layout, tool) diff --git a/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py b/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py index a4a51cb9910..5970d6fdf2b 100644 --- a/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py +++ b/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py @@ -497,18 +497,14 @@ class _defs_view3d_add: props = tool.operator_properties("view3d.interactive_add") if not extra: row = layout.row() - row.scale_x = 0.8 row.label(text="Depth:") row = layout.row() - row.scale_x = 0.9 row.prop(props, "plane_depth", text="") row = layout.row() - row.scale_x = 0.8 row.label(text="Orientation:") row = layout.row() row.prop(props, "plane_orientation", text="") row = layout.row() - row.scale_x = 0.8 row.prop(props, "snap_target") region_is_header = bpy.context.region.type == 'TOOL_HEADER' @@ -1691,6 +1687,7 @@ class _defs_weight_paint: props = tool.operator_properties("paint.weight_gradient") layout.prop(props, "type", expand=True) + layout.popover("VIEW3D_PT_tools_weight_gradient") return dict( idname="builtin.gradient", diff --git a/release/scripts/startup/bl_ui/space_userpref.py b/release/scripts/startup/bl_ui/space_userpref.py index 0093110d326..7a8b6d42cad 100644 --- a/release/scripts/startup/bl_ui/space_userpref.py +++ b/release/scripts/startup/bl_ui/space_userpref.py @@ -1073,6 +1073,25 @@ class USERPREF_PT_theme_collection_colors(ThemePanel, CenterAlignMixIn, Panel): flow.prop(ui, "color", text=iface_("Color %d") % i, translate=False) +class USERPREF_PT_theme_strip_colors(ThemePanel, CenterAlignMixIn, Panel): + bl_label = "Strip Colors" + bl_options = {'DEFAULT_CLOSED'} + + def draw_header(self, _context): + layout = self.layout + + layout.label(icon='SEQ_STRIP_DUPLICATE') + + def draw_centered(self, context, layout): + theme = context.preferences.themes[0] + + layout.use_property_split = True + + flow = layout.grid_flow(row_major=False, columns=0, even_columns=True, even_rows=False, align=False) + for i, ui in enumerate(theme.strip_color, 1): + flow.prop(ui, "color", text=iface_("Color %d") % i, translate=False) + + # Base class for dynamically defined theme-space panels. # This is not registered. class PreferenceThemeSpacePanel: @@ -2251,7 +2270,6 @@ class USERPREF_PT_experimental_new_features(ExperimentalPanel, Panel): ({"property": "use_sculpt_tools_tilt"}, "T82877"), ({"property": "use_extended_asset_browser"}, ("project/view/130/", "Project Page")), ({"property": "use_override_templates"}, ("T73318", "Milestone 4")), - ({"property": "use_geometry_nodes_fields"}, "T91274"), ), ) @@ -2283,7 +2301,9 @@ class USERPREF_PT_experimental_debugging(ExperimentalPanel, Panel): context, ( ({"property": "use_undo_legacy"}, "T60695"), ({"property": "override_auto_resync"}, "T83811"), + ({"property": "proxy_to_override_auto_conversion"}, "T91671"), ({"property": "use_cycles_debug"}, None), + ({"property": "use_geometry_nodes_legacy"}, "T91274"), ), ) @@ -2347,6 +2367,7 @@ classes = ( USERPREF_PT_theme_text_style, USERPREF_PT_theme_bone_color_sets, USERPREF_PT_theme_collection_colors, + USERPREF_PT_theme_strip_colors, USERPREF_PT_file_paths_data, USERPREF_PT_file_paths_render, diff --git a/release/scripts/startup/bl_ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py index 3879f7de250..281c57b282f 100644 --- a/release/scripts/startup/bl_ui/space_view3d.py +++ b/release/scripts/startup/bl_ui/space_view3d.py @@ -46,16 +46,10 @@ class VIEW3D_HT_tool_header(Header): def draw(self, context): layout = self.layout - layout.row(align=True).template_header() - self.draw_tool_settings(context) layout.separator_spacer() - VIEW3D_HT_header.draw_xform_template(layout, context) - - layout.separator_spacer() - self.draw_mode_settings(context) def draw_tool_settings(self, context): @@ -604,10 +598,8 @@ class VIEW3D_HT_header(Header): tool_settings = context.tool_settings view = context.space_data shading = view.shading - show_region_tool_header = view.show_region_tool_header - if not show_region_tool_header: - layout.row(align=True).template_header() + layout.row(align=True).template_header() row = layout.row(align=True) obj = context.active_object @@ -754,7 +746,7 @@ class VIEW3D_HT_header(Header): ) layout.separator_spacer() - elif not show_region_tool_header: + else: # Transform settings depending on tool header visibility VIEW3D_HT_header.draw_xform_template(layout, context) @@ -2234,8 +2226,6 @@ class VIEW3D_MT_object_relations(Menu): def draw(self, _context): layout = self.layout - layout.operator("object.proxy_make", text="Make Proxy...") - layout.operator("object.make_override_library", text="Make Library Override...") layout.operator("object.convert_proxy_to_override") diff --git a/release/scripts/startup/bl_ui/space_view3d_toolbar.py b/release/scripts/startup/bl_ui/space_view3d_toolbar.py index 16b5ed33f3f..acc3d933b85 100644 --- a/release/scripts/startup/bl_ui/space_view3d_toolbar.py +++ b/release/scripts/startup/bl_ui/space_view3d_toolbar.py @@ -688,6 +688,39 @@ class VIEW3D_PT_tools_brush_stroke_smooth_stroke(Panel, View3DPaintPanel, Smooth bl_options = {'DEFAULT_CLOSED'} +class VIEW3D_PT_tools_weight_gradient(Panel, View3DPaintPanel): + bl_context = ".weightpaint" # dot on purpose (access from topbar) + bl_label = "Falloff" + bl_options = {'DEFAULT_CLOSED'} + + @classmethod + def poll(cls, context): + settings = context.tool_settings.weight_paint + brush = settings.brush + return brush is not None + + def draw(self, context): + layout = self.layout + settings = context.tool_settings.weight_paint + brush = settings.brush + + col = layout.column(align=True) + row = col.row(align=True) + row.prop(brush, "curve_preset", text="") + + if brush.curve_preset == 'CUSTOM': + layout.template_curve_mapping(brush, "curve", brush=True) + + col = layout.column(align=True) + row = col.row(align=True) + row.operator("brush.curve_preset", icon='SMOOTHCURVE', text="").shape = 'SMOOTH' + row.operator("brush.curve_preset", icon='SPHERECURVE', text="").shape = 'ROUND' + row.operator("brush.curve_preset", icon='ROOTCURVE', text="").shape = 'ROOT' + row.operator("brush.curve_preset", icon='SHARPCURVE', text="").shape = 'SHARP' + row.operator("brush.curve_preset", icon='LINCURVE', text="").shape = 'LINE' + row.operator("brush.curve_preset", icon='NOCURVE', text="").shape = 'MAX' + + # TODO, move to space_view3d.py class VIEW3D_PT_tools_brush_falloff(Panel, View3DPaintPanel, FalloffPanel): bl_context = ".paint_common" # dot on purpose (access from topbar) @@ -2219,6 +2252,7 @@ classes = ( VIEW3D_PT_tools_brush_falloff_frontface, VIEW3D_PT_tools_brush_falloff_normal, VIEW3D_PT_tools_brush_display, + VIEW3D_PT_tools_weight_gradient, VIEW3D_PT_sculpt_dyntopo, VIEW3D_PT_sculpt_voxel_remesh, diff --git a/release/scripts/startup/nodeitems_builtins.py b/release/scripts/startup/nodeitems_builtins.py index 9ad162da7dc..37d5c5997ad 100644 --- a/release/scripts/startup/nodeitems_builtins.py +++ b/release/scripts/startup/nodeitems_builtins.py @@ -180,11 +180,8 @@ def object_eevee_cycles_shader_nodes_poll(context): eevee_cycles_shader_nodes_poll(context)) -def geometry_nodes_fields_poll(context): - return context.preferences.experimental.use_geometry_nodes_fields - -def geometry_nodes_fields_legacy_poll(context): - return not context.preferences.experimental.use_geometry_nodes_fields +def geometry_nodes_legacy_poll(context): + return context.preferences.experimental.use_geometry_nodes_legacy # All standard node categories currently used in nodes. @@ -282,6 +279,7 @@ shader_node_categories = [ ]), ShaderNodeCategory("SH_NEW_CONVERTOR", "Converter", items=[ NodeItem("ShaderNodeMapRange"), + NodeItem("ShaderNodeFloatCurve"), NodeItem("ShaderNodeClamp"), NodeItem("ShaderNodeMath"), NodeItem("ShaderNodeValToRGB"), @@ -483,27 +481,27 @@ texture_node_categories = [ geometry_node_categories = [ # Geometry Nodes GeometryNodeCategory("GEO_ATTRIBUTE", "Attribute", items=[ - NodeItem("GeometryNodeLegacyAttributeRandomize", poll=geometry_nodes_fields_legacy_poll), - NodeItem("GeometryNodeLegacyAttributeMath", poll=geometry_nodes_fields_legacy_poll), - NodeItem("GeometryNodeLegacyAttributeClamp", poll=geometry_nodes_fields_legacy_poll), - NodeItem("GeometryNodeLegacyAttributeCompare", poll=geometry_nodes_fields_legacy_poll), - NodeItem("GeometryNodeLegacyAttributeConvert", poll=geometry_nodes_fields_legacy_poll), - NodeItem("GeometryNodeLegacyAttributeCurveMap", poll=geometry_nodes_fields_legacy_poll), - NodeItem("GeometryNodeLegacyAttributeFill", poll=geometry_nodes_fields_legacy_poll), - NodeItem("GeometryNodeLegacyAttributeMix", poll=geometry_nodes_fields_legacy_poll), - NodeItem("GeometryNodeLegacyAttributeProximity", poll=geometry_nodes_fields_legacy_poll), - NodeItem("GeometryNodeLegacyAttributeColorRamp", poll=geometry_nodes_fields_legacy_poll), - NodeItem("GeometryNodeLegacyAttributeVectorMath", poll=geometry_nodes_fields_legacy_poll), - NodeItem("GeometryNodeLegacyAttributeVectorRotate", poll=geometry_nodes_fields_legacy_poll), - NodeItem("GeometryNodeLegacyAttributeSampleTexture", poll=geometry_nodes_fields_legacy_poll), - NodeItem("GeometryNodeLegacyAttributeCombineXYZ", poll=geometry_nodes_fields_legacy_poll), - NodeItem("GeometryNodeLegacyAttributeSeparateXYZ", poll=geometry_nodes_fields_legacy_poll), - NodeItem("GeometryNodeLegacyAttributeMapRange", poll=geometry_nodes_fields_legacy_poll), - NodeItem("GeometryNodeLegacyAttributeTransfer", poll=geometry_nodes_fields_legacy_poll), - NodeItem("GeometryNodeAttributeRemove", poll=geometry_nodes_fields_legacy_poll), - - NodeItem("GeometryNodeAttributeCapture", poll=geometry_nodes_fields_poll), - NodeItem("GeometryNodeAttributeStatistic", poll=geometry_nodes_fields_poll), + NodeItem("GeometryNodeLegacyAttributeRandomize", poll=geometry_nodes_legacy_poll), + NodeItem("GeometryNodeLegacyAttributeMath", poll=geometry_nodes_legacy_poll), + NodeItem("GeometryNodeLegacyAttributeClamp", poll=geometry_nodes_legacy_poll), + NodeItem("GeometryNodeLegacyAttributeCompare", poll=geometry_nodes_legacy_poll), + NodeItem("GeometryNodeLegacyAttributeConvert", poll=geometry_nodes_legacy_poll), + NodeItem("GeometryNodeLegacyAttributeCurveMap", poll=geometry_nodes_legacy_poll), + NodeItem("GeometryNodeLegacyAttributeFill", poll=geometry_nodes_legacy_poll), + NodeItem("GeometryNodeLegacyAttributeMix", poll=geometry_nodes_legacy_poll), + NodeItem("GeometryNodeLegacyAttributeProximity", poll=geometry_nodes_legacy_poll), + NodeItem("GeometryNodeLegacyAttributeColorRamp", poll=geometry_nodes_legacy_poll), + NodeItem("GeometryNodeLegacyAttributeVectorMath", poll=geometry_nodes_legacy_poll), + NodeItem("GeometryNodeLegacyAttributeVectorRotate", poll=geometry_nodes_legacy_poll), + NodeItem("GeometryNodeLegacyAttributeSampleTexture", poll=geometry_nodes_legacy_poll), + NodeItem("GeometryNodeLegacyAttributeCombineXYZ", poll=geometry_nodes_legacy_poll), + NodeItem("GeometryNodeLegacyAttributeSeparateXYZ", poll=geometry_nodes_legacy_poll), + NodeItem("GeometryNodeLegacyAttributeMapRange", poll=geometry_nodes_legacy_poll), + NodeItem("GeometryNodeLegacyAttributeTransfer", poll=geometry_nodes_legacy_poll), + NodeItem("GeometryNodeAttributeRemove", poll=geometry_nodes_legacy_poll), + + NodeItem("GeometryNodeAttributeCapture"), + NodeItem("GeometryNodeAttributeStatistic"), ]), GeometryNodeCategory("GEO_COLOR", "Color", items=[ NodeItem("ShaderNodeMixRGB"), @@ -513,24 +511,29 @@ geometry_node_categories = [ NodeItem("ShaderNodeCombineRGB"), ]), GeometryNodeCategory("GEO_CURVE", "Curve", items=[ - NodeItem("GeometryNodeLegacyCurveSubdivide", poll=geometry_nodes_fields_legacy_poll), - NodeItem("GeometryNodeLegacyCurveReverse", poll=geometry_nodes_fields_legacy_poll), - NodeItem("GeometryNodeLegacyCurveSplineType", poll=geometry_nodes_fields_legacy_poll), - NodeItem("GeometryNodeLegacyCurveSetHandles", poll=geometry_nodes_fields_legacy_poll), - NodeItem("GeometryNodeLegacyCurveSelectHandles", poll=geometry_nodes_fields_legacy_poll), - NodeItem("GeometryNodeLegacyMeshToCurve", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyCurveSubdivide", poll=geometry_nodes_legacy_poll), + NodeItem("GeometryNodeLegacyCurveReverse", poll=geometry_nodes_legacy_poll), + NodeItem("GeometryNodeLegacyCurveSplineType", poll=geometry_nodes_legacy_poll), + NodeItem("GeometryNodeLegacyCurveSetHandles", poll=geometry_nodes_legacy_poll), + NodeItem("GeometryNodeLegacyCurveSelectHandles", poll=geometry_nodes_legacy_poll), + NodeItem("GeometryNodeLegacyMeshToCurve", poll=geometry_nodes_legacy_poll), + NodeItem("GeometryNodeLegacyCurveToPoints", poll=geometry_nodes_legacy_poll), + NodeItem("GeometryNodeLegacyCurveEndpoints", poll=geometry_nodes_legacy_poll), NodeItem("GeometryNodeCurveToMesh"), NodeItem("GeometryNodeCurveResample"), - NodeItem("GeometryNodeCurveToPoints"), - NodeItem("GeometryNodeCurveEndpoints"), NodeItem("GeometryNodeCurveFill"), NodeItem("GeometryNodeCurveTrim"), NodeItem("GeometryNodeCurveLength"), - NodeItem("GeometryNodeCurveParameter", poll=geometry_nodes_fields_poll), - NodeItem("GeometryNodeInputTangent", poll=geometry_nodes_fields_poll), - NodeItem("GeometryNodeCurveSample", poll=geometry_nodes_fields_poll), - NodeItem("GeometryNodeCurveFillet", poll=geometry_nodes_fields_poll), + NodeItem("GeometryNodeCurveSplineType"), + NodeItem("GeometryNodeSplineLength"), + NodeItem("GeometryNodeCurveSubdivide"), + NodeItem("GeometryNodeCurveParameter"), + NodeItem("GeometryNodeCurveSetHandles"), + NodeItem("GeometryNodeInputTangent"), + NodeItem("GeometryNodeCurveSample"), + NodeItem("GeometryNodeCurveFillet"), + NodeItem("GeometryNodeCurveReverse"), ]), GeometryNodeCategory("GEO_PRIMITIVES_CURVE", "Curve Primitives", items=[ NodeItem("GeometryNodeCurvePrimitiveLine"), @@ -542,44 +545,48 @@ geometry_node_categories = [ NodeItem("GeometryNodeCurvePrimitiveBezierSegment"), ]), GeometryNodeCategory("GEO_GEOMETRY", "Geometry", items=[ - NodeItem("GeometryNodeLegacyDeleteGeometry", poll=geometry_nodes_fields_legacy_poll), - NodeItem("GeometryNodeLegacyRaycast", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyDeleteGeometry", poll=geometry_nodes_legacy_poll), + NodeItem("GeometryNodeLegacyRaycast", poll=geometry_nodes_legacy_poll), + NodeItem("GeometryNodeProximity"), NodeItem("GeometryNodeBoundBox"), NodeItem("GeometryNodeConvexHull"), NodeItem("GeometryNodeTransform"), NodeItem("GeometryNodeJoinGeometry"), NodeItem("GeometryNodeSeparateComponents"), - NodeItem("GeometryNodeSetPosition", poll=geometry_nodes_fields_poll), - NodeItem("GeometryNodeRealizeInstances", poll=geometry_nodes_fields_poll), + NodeItem("GeometryNodeSetPosition"), + NodeItem("GeometryNodeRealizeInstances"), ]), GeometryNodeCategory("GEO_INPUT", "Input", items=[ + NodeItem("FunctionNodeLegacyRandomFloat", poll=geometry_nodes_legacy_poll), + NodeItem("GeometryNodeObjectInfo"), NodeItem("GeometryNodeCollectionInfo"), - NodeItem("FunctionNodeRandomFloat"), NodeItem("ShaderNodeValue"), NodeItem("FunctionNodeInputString"), NodeItem("FunctionNodeInputVector"), NodeItem("GeometryNodeInputMaterial"), NodeItem("GeometryNodeIsViewport"), - NodeItem("GeometryNodeInputPosition", poll=geometry_nodes_fields_poll), - NodeItem("GeometryNodeInputIndex", poll=geometry_nodes_fields_poll), - NodeItem("GeometryNodeInputNormal", poll=geometry_nodes_fields_poll), + NodeItem("GeometryNodeInputPosition"), + NodeItem("GeometryNodeInputIndex"), + NodeItem("GeometryNodeInputNormal"), ]), GeometryNodeCategory("GEO_MATERIAL", "Material", items=[ - NodeItem("GeometryNodeLegacyMaterialAssign", poll=geometry_nodes_fields_legacy_poll), - NodeItem("GeometryNodeLegacySelectByMaterial", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyMaterialAssign", poll=geometry_nodes_legacy_poll), + NodeItem("GeometryNodeLegacySelectByMaterial", poll=geometry_nodes_legacy_poll), - NodeItem("GeometryNodeMaterialAssign", poll=geometry_nodes_fields_poll), - NodeItem("GeometryNodeMaterialSelection", poll=geometry_nodes_fields_poll), + NodeItem("GeometryNodeMaterialAssign"), + NodeItem("GeometryNodeMaterialSelection"), NodeItem("GeometryNodeMaterialReplace"), ]), GeometryNodeCategory("GEO_MESH", "Mesh", items=[ + NodeItem("GeometryNodeLegacyEdgeSplit", poll=geometry_nodes_legacy_poll), + NodeItem("GeometryNodeLegacySubdivisionSurface", poll=geometry_nodes_legacy_poll), + NodeItem("GeometryNodeBoolean"), NodeItem("GeometryNodeTriangulate"), - NodeItem("GeometryNodeEdgeSplit"), - NodeItem("GeometryNodeSubdivisionSurface"), NodeItem("GeometryNodeMeshSubdivide"), + NodeItem("GeometryNodePointsToVertices"), ]), GeometryNodeCategory("GEO_PRIMITIVES_MESH", "Mesh Primitives", items=[ NodeItem("GeometryNodeMeshCircle"), @@ -592,31 +599,39 @@ geometry_node_categories = [ NodeItem("GeometryNodeMeshUVSphere"), ]), GeometryNodeCategory("GEO_POINT", "Point", items=[ - NodeItem("GeometryNodeLegacyPointDistribute", poll=geometry_nodes_fields_legacy_poll), - NodeItem("GeometryNodeLegacyPointInstance", poll=geometry_nodes_fields_legacy_poll), - NodeItem("GeometryNodeLegacyPointSeparate", poll=geometry_nodes_fields_legacy_poll), - NodeItem("GeometryNodeLegacyPointScale", poll=geometry_nodes_fields_legacy_poll), - NodeItem("GeometryNodeLegacyPointTranslate", poll=geometry_nodes_fields_legacy_poll), - NodeItem("GeometryNodeLegacyRotatePoints", poll=geometry_nodes_fields_legacy_poll), - NodeItem("GeometryNodeLegacyAlignRotationToVector", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeMeshToPoints"), + NodeItem("GeometryNodeInstanceOnPoints"), + NodeItem("GeometryNodeDistributePointsOnFaces"), + NodeItem("GeometryNodeLegacyPointDistribute", poll=geometry_nodes_legacy_poll), + NodeItem("GeometryNodeLegacyPointInstance", poll=geometry_nodes_legacy_poll), + NodeItem("GeometryNodeLegacyPointSeparate", poll=geometry_nodes_legacy_poll), + NodeItem("GeometryNodeLegacyPointScale", poll=geometry_nodes_legacy_poll), + NodeItem("GeometryNodeLegacyPointTranslate", poll=geometry_nodes_legacy_poll), + NodeItem("GeometryNodeLegacyRotatePoints", poll=geometry_nodes_legacy_poll), + NodeItem("GeometryNodeLegacyAlignRotationToVector", poll=geometry_nodes_legacy_poll), ]), GeometryNodeCategory("GEO_TEXT", "Text", items=[ NodeItem("FunctionNodeStringLength"), NodeItem("FunctionNodeStringSubstring"), NodeItem("FunctionNodeValueToString"), NodeItem("GeometryNodeStringJoin"), + NodeItem("FunctionNodeInputSpecialCharacters"), + NodeItem("GeometryNodeStringToCurves"), ]), GeometryNodeCategory("GEO_UTILITIES", "Utilities", items=[ NodeItem("ShaderNodeMapRange"), + NodeItem("ShaderNodeFloatCurve"), NodeItem("ShaderNodeClamp"), NodeItem("ShaderNodeMath"), NodeItem("FunctionNodeBooleanMath"), + NodeItem("FunctionNodeRotateEuler"), NodeItem("FunctionNodeFloatCompare"), NodeItem("FunctionNodeFloatToInt"), NodeItem("GeometryNodeSwitch"), + NodeItem("FunctionNodeRandomValue"), ]), GeometryNodeCategory("GEO_TEXTURE", "Texture", items=[ - NodeItem("ShaderNodeTexNoise", poll=geometry_nodes_fields_poll), + NodeItem("ShaderNodeTexNoise"), ]), GeometryNodeCategory("GEO_VECTOR", "Vector", items=[ NodeItem("ShaderNodeVectorCurve"), @@ -629,7 +644,7 @@ geometry_node_categories = [ NodeItem("GeometryNodeViewer"), ]), GeometryNodeCategory("GEO_VOLUME", "Volume", items=[ - NodeItem("GeometryNodeLegacyPointsToVolume", poll=geometry_nodes_fields_legacy_poll), + NodeItem("GeometryNodeLegacyPointsToVolume", poll=geometry_nodes_legacy_poll), NodeItem("GeometryNodeVolumeToMesh"), ]), diff --git a/source/blender/blendthumb/src/Dll.cpp b/source/blender/blendthumb/src/Dll.cpp index 6516540034e..7f10777f884 100644 --- a/source/blender/blendthumb/src/Dll.cpp +++ b/source/blender/blendthumb/src/Dll.cpp @@ -14,11 +14,17 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +/** \file + * \ingroup blendthumb + * + * Thumbnail from Blend file extraction for MS-Windows (DLL). + */ + #include <new> #include <objbase.h> -#include <shlobj.h> // For SHChangeNotify +#include <shlobj.h> /* For #SHChangeNotify */ #include <shlwapi.h> -#include <thumbcache.h> // For IThumbnailProvider. +#include <thumbcache.h> /* For IThumbnailProvider */ extern HRESULT CBlendThumb_CreateInstance(REFIID riid, void **ppv); @@ -33,16 +39,16 @@ struct CLASS_OBJECT_INIT { PFNCREATEINSTANCE pfnCreate; }; -// add classes supported by this module here +/* Add classes supported by this module here. */ const CLASS_OBJECT_INIT c_rgClassObjectInit[] = { {&CLSID_BlendThumbHandler, CBlendThumb_CreateInstance}}; long g_cRefModule = 0; -// Handle the DLL's module -HINSTANCE g_hInst = NULL; +/** Handle the DLL's module */ +HINSTANCE g_hInst = nullptr; -// Standard DLL functions +/** Standard DLL functions. */ STDAPI_(BOOL) DllMain(HINSTANCE hInstance, DWORD dwReason, void *) { if (dwReason == DLL_PROCESS_ATTACH) { @@ -54,7 +60,7 @@ STDAPI_(BOOL) DllMain(HINSTANCE hInstance, DWORD dwReason, void *) STDAPI DllCanUnloadNow() { - // Only allow the DLL to be unloaded after all outstanding references have been released + /* Only allow the DLL to be unloaded after all outstanding references have been released. */ return (g_cRefModule == 0) ? S_OK : S_FALSE; } @@ -76,7 +82,7 @@ class CClassFactory : public IClassFactory { REFIID riid, void **ppv) { - *ppv = NULL; + *ppv = nullptr; HRESULT hr = CLASS_E_CLASSNOTAVAILABLE; for (size_t i = 0; i < cClassObjectInits; i++) { if (clsid == *pClassObjectInits[i].pClsid) { @@ -87,7 +93,8 @@ class CClassFactory : public IClassFactory { hr = pClassFactory->QueryInterface(riid, ppv); pClassFactory->Release(); } - break; // match found + /* Match found. */ + break; } } return hr; @@ -98,7 +105,7 @@ class CClassFactory : public IClassFactory { DllAddRef(); } - // IUnknown + /** #IUnknown */ IFACEMETHODIMP QueryInterface(REFIID riid, void **ppv) { static const QITAB qit[] = {QITABENT(CClassFactory, IClassFactory), {0}}; @@ -119,7 +126,7 @@ class CClassFactory : public IClassFactory { return cRef; } - // IClassFactory + /** #IClassFactory */ IFACEMETHODIMP CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppv) { return punkOuter ? CLASS_E_NOAGGREGATION : _pfnCreate(riid, ppv); @@ -152,33 +159,37 @@ STDAPI DllGetClassObject(REFCLSID clsid, REFIID riid, void **ppv) clsid, c_rgClassObjectInit, ARRAYSIZE(c_rgClassObjectInit), riid, ppv); } -// A struct to hold the information required for a registry entry - +/** + * A struct to hold the information required for a registry entry. + */ struct REGISTRY_ENTRY { HKEY hkeyRoot; PCWSTR pszKeyName; PCWSTR pszValueName; DWORD dwValueType; - PCWSTR pszData; // These two fields could/should have been a union, but C++ - DWORD dwData; // only lets you initialize the first field in a union. + /** These two fields could/should have been a union, but C++ */ + PCWSTR pszData; + /** Only lets you initialize the first field in a union. */ + DWORD dwData; }; -// Creates a registry key (if needed) and sets the default value of the key - +/** + * Creates a registry key (if needed) and sets the default value of the key. + */ HRESULT CreateRegKeyAndSetValue(const REGISTRY_ENTRY *pRegistryEntry) { HKEY hKey; HRESULT hr = HRESULT_FROM_WIN32(RegCreateKeyExW(pRegistryEntry->hkeyRoot, pRegistryEntry->pszKeyName, 0, - NULL, + nullptr, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, - NULL, + nullptr, &hKey, - NULL)); + nullptr)); if (SUCCEEDED(hr)) { - // All this just to support REG_DWORD... + /* All this just to support #REG_DWORD. */ DWORD size; DWORD data; BYTE *lpData = (LPBYTE)pRegistryEntry->pszData; @@ -202,9 +213,9 @@ HRESULT CreateRegKeyAndSetValue(const REGISTRY_ENTRY *pRegistryEntry) return hr; } -// -// Registers this COM server -// +/** + * Registers this COM server. + */ STDAPI DllRegisterServer() { HRESULT hr; @@ -216,15 +227,15 @@ STDAPI DllRegisterServer() } else { const REGISTRY_ENTRY rgRegistryEntries[] = { - // RootKey KeyName ValueName ValueType Data + /* `RootKey KeyName ValueName ValueType Data` */ {HKEY_CURRENT_USER, L"Software\\Classes\\CLSID\\" SZ_CLSID_BLENDTHUMBHANDLER, - NULL, + nullptr, REG_SZ, SZ_BLENDTHUMBHANDLER}, {HKEY_CURRENT_USER, L"Software\\Classes\\CLSID\\" SZ_CLSID_BLENDTHUMBHANDLER L"\\InProcServer32", - NULL, + nullptr, REG_SZ, szModuleName}, {HKEY_CURRENT_USER, @@ -237,10 +248,10 @@ STDAPI DllRegisterServer() L"Treatment", REG_DWORD, 0, - 0}, // doesn't appear to do anything... + 0}, /* This doesn't appear to do anything. */ {HKEY_CURRENT_USER, L"Software\\Classes\\.blend\\ShellEx\\{e357fccd-a995-4576-b01f-234630154e96}", - NULL, + nullptr, REG_SZ, SZ_CLSID_BLENDTHUMBHANDLER}, }; @@ -251,17 +262,17 @@ STDAPI DllRegisterServer() } } if (SUCCEEDED(hr)) { - // This tells the shell to invalidate the thumbnail cache. This is important because any - // .blend files viewed before registering this handler would otherwise show cached blank - // thumbnails. - SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL); + /* This tells the shell to invalidate the thumbnail cache. + * This is important because any `.blend` files viewed before registering this handler + * would otherwise show cached blank thumbnails. */ + SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr); } return hr; } -// -// Unregisters this COM server -// +/** + * Unregisters this COM server + */ STDAPI DllUnregisterServer() { HRESULT hr = S_OK; @@ -270,11 +281,11 @@ STDAPI DllUnregisterServer() L"Software\\Classes\\CLSID\\" SZ_CLSID_BLENDTHUMBHANDLER, L"Software\\Classes\\.blend\\ShellEx\\{e357fccd-a995-4576-b01f-234630154e96}"}; - // Delete the registry entries + /* Delete the registry entries. */ for (int i = 0; i < ARRAYSIZE(rgpszKeys) && SUCCEEDED(hr); i++) { hr = HRESULT_FROM_WIN32(RegDeleteTreeW(HKEY_CURRENT_USER, rgpszKeys[i])); if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) { - // If the registry entry has already been deleted, say S_OK. + /* If the registry entry has already been deleted, say S_OK. */ hr = S_OK; } } diff --git a/source/blender/blenkernel/BKE_asset.h b/source/blender/blenkernel/BKE_asset.h index 50eb2859279..42eea41b7a7 100644 --- a/source/blender/blenkernel/BKE_asset.h +++ b/source/blender/blenkernel/BKE_asset.h @@ -22,6 +22,8 @@ #include "BLI_utildefines.h" +#include "DNA_asset_types.h" + #ifdef __cplusplus extern "C" { #endif @@ -46,6 +48,12 @@ struct AssetTagEnsureResult BKE_asset_metadata_tag_ensure(struct AssetMetaData * const char *name); void BKE_asset_metadata_tag_remove(struct AssetMetaData *asset_data, struct AssetTag *tag); +/** Clean up the catalog ID (white-spaces removed, length reduced, etc.) and assign it. */ +void BKE_asset_metadata_catalog_id_clear(struct AssetMetaData *asset_data); +void BKE_asset_metadata_catalog_id_set(struct AssetMetaData *asset_data, + bUUID catalog_id, + const char *catalog_simple_name); + void BKE_asset_library_reference_init_default(struct AssetLibraryReference *library_ref); struct PreviewImage *BKE_asset_metadata_preview_get_from_id(const struct AssetMetaData *asset_data, diff --git a/source/blender/blenkernel/BKE_asset_catalog.hh b/source/blender/blenkernel/BKE_asset_catalog.hh new file mode 100644 index 00000000000..8afc4fe2ad2 --- /dev/null +++ b/source/blender/blenkernel/BKE_asset_catalog.hh @@ -0,0 +1,360 @@ +/* + * 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 bke + */ + +#pragma once + +#ifndef __cplusplus +# error This is a C++ header. The C interface is yet to be implemented/designed. +#endif + +#include "BLI_function_ref.hh" +#include "BLI_map.hh" +#include "BLI_set.hh" +#include "BLI_string_ref.hh" +#include "BLI_uuid.h" +#include "BLI_vector.hh" + +#include "BKE_asset_catalog_path.hh" + +#include <map> +#include <memory> +#include <set> +#include <string> + +namespace blender::bke { + +using CatalogID = bUUID; +using CatalogPathComponent = std::string; +/* Would be nice to be able to use `std::filesystem::path` for this, but it's currently not + * available on the minimum macOS target version. */ +using CatalogFilePath = std::string; + +class AssetCatalog; +class AssetCatalogDefinitionFile; +class AssetCatalogTree; +class AssetCatalogFilter; + +/* Manages the asset catalogs of a single asset library (i.e. of catalogs defined in a single + * directory hierarchy). */ +class AssetCatalogService { + public: + static const CatalogFilePath DEFAULT_CATALOG_FILENAME; + + public: + AssetCatalogService() = default; + explicit AssetCatalogService(const CatalogFilePath &asset_library_root); + + /** Load asset catalog definitions from the files found in the asset library. */ + void load_from_disk(); + /** Load asset catalog definitions from the given file or directory. */ + void load_from_disk(const CatalogFilePath &file_or_directory_path); + + /** + * Write the catalog definitions to disk in response to the blend file being saved. + * + * The location where the catalogs are saved is variable, and depends on the location of the + * blend file. The first matching rule wins: + * + * - Already loaded a CDF from disk? + * -> Always write to that file. + * - The directory containing the blend file has a blender_assets.cats.txt file? + * -> Merge with & write to that file. + * - The directory containing the blend file is part of an asset library, as per + * the user's preferences? + * -> Merge with & write to ${ASSET_LIBRARY_ROOT}/blender_assets.cats.txt + * - Create a new file blender_assets.cats.txt next to the blend file. + * + * Return true on success, which either means there were no in-memory categories to save, + * or the save was successful. */ + bool write_to_disk_on_blendfile_save(const CatalogFilePath &blend_file_path); + + /** + * Merge on-disk changes into the in-memory asset catalogs. + * This should be called before writing the asset catalogs to disk. + * + * - New on-disk catalogs are loaded into memory. + * - Already-known on-disk catalogs are ignored (so will be overwritten with our in-memory + * data). This includes in-memory marked-as-deleted catalogs. + */ + void merge_from_disk_before_writing(); + + /** Return catalog with the given ID. Return nullptr if not found. */ + AssetCatalog *find_catalog(CatalogID catalog_id) const; + + /** Return first catalog with the given path. Return nullptr if not found. This is not an + * efficient call as it's just a linear search over the catalogs. */ + AssetCatalog *find_catalog_by_path(const AssetCatalogPath &path) const; + + /** + * Create a filter object that can be used to determine whether an asset belongs to the given + * catalog, or any of the catalogs in the sub-tree rooted at the given catalog. + * + * \see #AssetCatalogFilter + */ + AssetCatalogFilter create_catalog_filter(CatalogID active_catalog_id) const; + + /** Create a catalog with some sensible auto-generated catalog ID. + * The catalog will be saved to the default catalog file.*/ + AssetCatalog *create_catalog(const AssetCatalogPath &catalog_path); + + /** + * Soft-delete the catalog, ensuring it actually gets deleted when the catalog definition file is + * written. */ + void delete_catalog(CatalogID catalog_id); + + /** + * Update the catalog path, also updating the catalog path of all sub-catalogs. + */ + void update_catalog_path(CatalogID catalog_id, const AssetCatalogPath &new_catalog_path); + + AssetCatalogTree *get_catalog_tree(); + + /** Return true only if there are no catalogs known. */ + bool is_empty() const; + + protected: + /* These pointers are owned by this AssetCatalogService. */ + Map<CatalogID, std::unique_ptr<AssetCatalog>> catalogs_; + Map<CatalogID, std::unique_ptr<AssetCatalog>> deleted_catalogs_; + std::unique_ptr<AssetCatalogDefinitionFile> catalog_definition_file_; + std::unique_ptr<AssetCatalogTree> catalog_tree_ = std::make_unique<AssetCatalogTree>(); + CatalogFilePath asset_library_root_; + + void load_directory_recursive(const CatalogFilePath &directory_path); + void load_single_file(const CatalogFilePath &catalog_definition_file_path); + + std::unique_ptr<AssetCatalogDefinitionFile> parse_catalog_file( + const CatalogFilePath &catalog_definition_file_path); + + /** + * Construct an in-memory catalog definition file (CDF) from the currently known catalogs. + * This object can then be processed further before saving to disk. */ + std::unique_ptr<AssetCatalogDefinitionFile> construct_cdf_in_memory( + const CatalogFilePath &file_path); + + /** + * Find a suitable path to write a CDF to. + * + * This depends on the location of the blend file, and on whether a CDF already exists next to it + * or whether the blend file is saved inside an asset library. + */ + static CatalogFilePath find_suitable_cdf_path_for_writing( + const CatalogFilePath &blend_file_path); + + std::unique_ptr<AssetCatalogTree> read_into_tree(); + void rebuild_tree(); + + /** + * For every catalog, ensure that its parent path also has a known catalog. + */ + void create_missing_catalogs(); +}; + +/** + * Representation of a catalog path in the #AssetCatalogTree. + */ +class AssetCatalogTreeItem { + friend class AssetCatalogTree; + + public: + /** Container for child items. Uses a #std::map to keep items ordered by their name (i.e. their + * last catalog component). */ + using ChildMap = std::map<std::string, AssetCatalogTreeItem>; + using ItemIterFn = FunctionRef<void(AssetCatalogTreeItem &)>; + + AssetCatalogTreeItem(StringRef name, + CatalogID catalog_id, + const AssetCatalogTreeItem *parent = nullptr); + + CatalogID get_catalog_id() const; + StringRef get_name() const; + /** Return the full catalog path, defined as the name of this catalog prefixed by the full + * catalog path of its parent and a separator. */ + AssetCatalogPath catalog_path() const; + int count_parents() const; + bool has_children() const; + + /** Iterate over children calling \a callback for each of them, but do not recurse into their + * children. */ + void foreach_child(const ItemIterFn callback); + + protected: + /** Child tree items, ordered by their names. */ + ChildMap children_; + /** The user visible name of this component. */ + CatalogPathComponent name_; + CatalogID catalog_id_; + + /** Pointer back to the parent item. Used to reconstruct the hierarchy from an item (e.g. to + * build a path). */ + const AssetCatalogTreeItem *parent_ = nullptr; + + private: + static void foreach_item_recursive(ChildMap &children_, ItemIterFn callback); +}; + +/** + * A representation of the catalog paths as tree structure. Each component of the catalog tree is + * represented by an #AssetCatalogTreeItem. The last path component of an item is used as its name, + * which may also be shown to the user. + * An item can not have multiple children with the same name. That means the name uniquely + * identifies an item within its parent. + * + * There is no single root tree element, the #AssetCatalogTree instance itself represents the root. + */ +class AssetCatalogTree { + using ChildMap = AssetCatalogTreeItem::ChildMap; + using ItemIterFn = AssetCatalogTreeItem::ItemIterFn; + + public: + /** Ensure an item representing \a path is in the tree, adding it if necessary. */ + void insert_item(const AssetCatalog &catalog); + + void foreach_item(const AssetCatalogTreeItem::ItemIterFn callback); + /** Iterate over root items calling \a callback for each of them, but do not recurse into their + * children. */ + void foreach_root_item(const ItemIterFn callback); + + protected: + /** Child tree items, ordered by their names. */ + ChildMap root_items_; +}; + +/** Keeps track of which catalogs are defined in a certain file on disk. + * Only contains non-owning pointers to the #AssetCatalog instances, so ensure the lifetime of this + * class is shorter than that of the #`AssetCatalog`s themselves. */ +class AssetCatalogDefinitionFile { + public: + /* For now this is the only version of the catalog definition files that is supported. + * Later versioning code may be added to handle older files. */ + const static int SUPPORTED_VERSION; + const static std::string VERSION_MARKER; + const static std::string HEADER; + + CatalogFilePath file_path; + + AssetCatalogDefinitionFile() = default; + + /** + * Write the catalog definitions to the same file they were read from. + * Return true when the file was written correctly, false when there was a problem. + */ + bool write_to_disk() const; + /** + * Write the catalog definitions to an arbitrary file path. + * + * Any existing file is backed up to "filename~". Any previously existing backup is overwritten. + * + * Return true when the file was written correctly, false when there was a problem. + */ + bool write_to_disk(const CatalogFilePath &dest_file_path) const; + + bool contains(CatalogID catalog_id) const; + /* Add a new catalog. Undefined behavior if a catalog with the same ID was already added. */ + void add_new(AssetCatalog *catalog); + + using AssetCatalogParsedFn = FunctionRef<bool(std::unique_ptr<AssetCatalog>)>; + void parse_catalog_file(const CatalogFilePath &catalog_definition_file_path, + AssetCatalogParsedFn callback); + + protected: + /* Catalogs stored in this file. They are mapped by ID to make it possible to query whether a + * catalog is already known, without having to find the corresponding `AssetCatalog*`. */ + Map<CatalogID, AssetCatalog *> catalogs_; + + bool parse_version_line(StringRef line); + std::unique_ptr<AssetCatalog> parse_catalog_line(StringRef line); + + /** + * Write the catalog definitions to the given file path. + * Return true when the file was written correctly, false when there was a problem. + */ + bool write_to_disk_unsafe(const CatalogFilePath &dest_file_path) const; + bool ensure_directory_exists(const CatalogFilePath directory_path) const; +}; + +/** Asset Catalog definition, containing a symbolic ID and a path that points to a node in the + * catalog hierarchy. */ +class AssetCatalog { + public: + AssetCatalog() = default; + AssetCatalog(CatalogID catalog_id, const AssetCatalogPath &path, const std::string &simple_name); + + CatalogID catalog_id; + AssetCatalogPath path; + /** + * Simple, human-readable name for the asset catalog. This is stored on assets alongside the + * catalog ID; the catalog ID is a UUID that is not human-readable, + * so to avoid complete data-loss when the catalog definition file gets lost, + * we also store a human-readable simple name for the catalog. */ + std::string simple_name; + + struct Flags { + /* Treat this catalog as deleted. Keeping deleted catalogs around is necessary to support + * merging of on-disk changes with in-memory changes. */ + bool is_deleted = false; + } flags; + + /** + * Create a new Catalog with the given path, auto-generating a sensible catalog simple-name. + * + * NOTE: the given path will be cleaned up (trailing spaces removed, etc.), so the returned + * `AssetCatalog`'s path differ from the given one. + */ + static std::unique_ptr<AssetCatalog> from_path(const AssetCatalogPath &path); + + protected: + /** Generate a sensible catalog ID for the given path. */ + static std::string sensible_simple_name_for_path(const AssetCatalogPath &path); +}; + +/** Comparator for asset catalogs, ordering by (path, UUID). */ +struct AssetCatalogPathCmp { + bool operator()(const AssetCatalog *lhs, const AssetCatalog *rhs) const + { + if (lhs->path == rhs->path) { + return lhs->catalog_id < rhs->catalog_id; + } + return lhs->path < rhs->path; + } +}; + +/** + * Set that stores catalogs ordered by (path, UUID). + * Being a set, duplicates are removed. The catalog's simple name is ignored in this. */ +using AssetCatalogOrderedSet = std::set<const AssetCatalog *, AssetCatalogPathCmp>; + +/** + * Filter that can determine whether an asset should be visible or not, based on its catalog ID. + * + * \see AssetCatalogService::create_filter() + */ +class AssetCatalogFilter { + public: + bool contains(CatalogID asset_catalog_id) const; + + protected: + friend AssetCatalogService; + const Set<CatalogID> matching_catalog_ids; + + explicit AssetCatalogFilter(Set<CatalogID> &&matching_catalog_ids); +}; + +} // namespace blender::bke diff --git a/source/blender/blenkernel/BKE_asset_catalog_path.hh b/source/blender/blenkernel/BKE_asset_catalog_path.hh new file mode 100644 index 00000000000..328625b1bfa --- /dev/null +++ b/source/blender/blenkernel/BKE_asset_catalog_path.hh @@ -0,0 +1,143 @@ +/* + * 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 bke + */ + +#pragma once + +#ifndef __cplusplus +# error This is a C++ header. +#endif + +#include "BLI_function_ref.hh" +#include "BLI_string_ref.hh" +#include "BLI_sys_types.h" + +#include <string> + +namespace blender::bke { + +/** + * Location of an Asset Catalog in the catalog tree, denoted by slash-separated path components. + * + * Each path component is a string that is not allowed to have slashes or colons. The latter is to + * make things easy to save in the colon-delimited Catalog Definition File format. + * + * The path of a catalog determines where in the catalog hierarchy the catalog is shown. Examples + * are "Characters/Ellie/Poses/Hand" or "Kit_bash/City/Skyscrapers". The path looks like a + * file-system path, with a few differences: + * + * - Only slashes are used as path component separators. + * - All paths are absolute, so there is no need for a leading slash. + * + * See https://wiki.blender.org/wiki/Source/Architecture/Asset_System/Catalogs + * + * Paths are stored as byte sequences, and assumed to be UTF-8. + */ +class AssetCatalogPath { + friend std::ostream &operator<<(std::ostream &stream, const AssetCatalogPath &path_to_append); + + private: + /** + * The path itself, such as "Agents/Secret/327". + */ + std::string path_; + + public: + static const char SEPARATOR; + + AssetCatalogPath() = delete; + AssetCatalogPath(StringRef path); + AssetCatalogPath(const std::string &path); + AssetCatalogPath(const char *path); + AssetCatalogPath(const AssetCatalogPath &other_path) = default; + AssetCatalogPath(AssetCatalogPath &&other_path) noexcept; + ~AssetCatalogPath() = default; + + uint64_t hash() const; + uint64_t length() const; /* Length of the path in bytes. */ + + /** C-string representation of the path. */ + const char *c_str() const; + const std::string &str() const; + + /* In-class operators, because of the implicit `AssetCatalogPath(StringRef)` constructor. + * Otherwise `string == string` could cast both sides to `AssetCatalogPath`. */ + bool operator==(const AssetCatalogPath &other_path) const; + bool operator!=(const AssetCatalogPath &other_path) const; + bool operator<(const AssetCatalogPath &other_path) const; + AssetCatalogPath &operator=(const AssetCatalogPath &other_path) = default; + AssetCatalogPath &operator=(AssetCatalogPath &&other_path) = default; + + /** Concatenate two paths, returning the new path. */ + AssetCatalogPath operator/(const AssetCatalogPath &path_to_append) const; + + /* False when the path is empty, true otherwise. */ + operator bool() const; + + /** + * Clean up the path. This ensures: + * - Every path component is stripped of its leading/trailing spaces. + * - Empty components (caused by double slashes or leading/trailing slashes) are removed. + * - Invalid characters are replaced with valid ones. + */ + [[nodiscard]] AssetCatalogPath cleanup() const; + + /** + * \return true only if the given path is a parent of this catalog's path. + * When this catalog's path is equal to the given path, return true as well. + * In other words, this defines a weak subset. + * + * True: "some/path/there" is contained in "some/path" and "some". + * False: "path/there" is not contained in "some/path/there". + * + * Note that non-cleaned-up paths (so for example starting or ending with a + * slash) are not supported, and result in undefined behavior. + */ + bool is_contained_in(const AssetCatalogPath &other_path) const; + + /** + * \return the parent path, or an empty path if there is no parent. + */ + AssetCatalogPath parent() const; + + /** + * Change the initial part of the path from `from_path` to `to_path`. + * If this path does not start with `from_path`, return an empty path as result. + * + * Example: + * + * AssetCatalogPath path("some/path/to/some/catalog"); + * path.rebase("some/path", "new/base") -> "new/base/to/some/catalog" + */ + AssetCatalogPath rebase(const AssetCatalogPath &from_path, + const AssetCatalogPath &to_path) const; + + /** Call the callback function for each path component, in left-to-right order. */ + using ComponentIteratorFn = FunctionRef<void(StringRef component_name, bool is_last_component)>; + void iterate_components(ComponentIteratorFn callback) const; + + protected: + /** Strip leading/trailing spaces and replace disallowed characters. */ + static std::string cleanup_component(StringRef component_name); +}; + +/** Output the path as string. */ +std::ostream &operator<<(std::ostream &stream, const AssetCatalogPath &path_to_append); + +} // namespace blender::bke diff --git a/source/blender/blenkernel/BKE_asset_library.h b/source/blender/blenkernel/BKE_asset_library.h new file mode 100644 index 00000000000..3ddfbb1415e --- /dev/null +++ b/source/blender/blenkernel/BKE_asset_library.h @@ -0,0 +1,74 @@ +/* + * 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 bke + */ + +#pragma once + +struct Main; +// + +#ifdef __cplusplus +extern "C" { +#endif + +/** Forward declaration, defined in intern/asset_library.hh */ +typedef struct AssetLibrary AssetLibrary; + +/** TODO(@sybren): properly have a think/discussion about the API for this. */ +struct AssetLibrary *BKE_asset_library_load(const char *library_path); +void BKE_asset_library_free(struct AssetLibrary *asset_library); + +/** + * Try to find an appropriate location for an asset library root from a file or directory path. + * Does not check if \a input_path exists. + * + * The design is made to find an appropriate asset library path from a .blend file path, but + * technically works with any file or directory as \a input_path. + * Design is: + * * If \a input_path lies within a known asset library path (i.e. an asset library registered in + * the Preferences), return the asset library path. + * * Otherwise, if \a input_path has a parent path, return the parent path (e.g. to use the + * directory a .blend file is in as asset library root). + * * If \a input_path is empty or doesn't have a parent path (e.g. because a .blend wasn't saved + * yet), there is no suitable path. The caller has to decide how to handle this case. + * + * \param r_library_path: The returned asset library path with a trailing slash, or an empty string + * if no suitable path is found. Assumed to be a buffer of at least + * #FILE_MAXDIR bytes. + * + * \return True if the function could find a valid, that is, a non-empty path to return in \a + * r_library_path. + */ +bool BKE_asset_library_find_suitable_root_path_from_path( + const char *input_path, char r_library_path[768 /* FILE_MAXDIR */]); +/** + * Uses the current location on disk of the file represented by \a bmain as input to + * #BKE_asset_library_find_suitable_root_path_from_path(). Refer to it for a design + * description. + * + * \return True if the function could find a valid, that is, a non-empty path to return in \a + * r_library_path. If \a bmain wasn't saved into a file yet, the return value will be + * false. + */ +bool BKE_asset_library_find_suitable_root_path_from_main( + const struct Main *bmain, char r_library_path[768 /* FILE_MAXDIR */]); + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/blenkernel/BKE_asset_library.hh b/source/blender/blenkernel/BKE_asset_library.hh new file mode 100644 index 00000000000..1dc02f7aa9b --- /dev/null +++ b/source/blender/blenkernel/BKE_asset_library.hh @@ -0,0 +1,54 @@ +/* + * 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 bke + */ + +#pragma once + +#ifndef __cplusplus +# error This is a C++-only header file. Use BKE_asset_library.h instead. +#endif + +#include "BKE_asset_library.h" + +#include "BKE_asset_catalog.hh" +#include "BKE_callbacks.h" + +#include <memory> + +namespace blender::bke { + +struct AssetLibrary { + std::unique_ptr<AssetCatalogService> catalog_service; + + void load(StringRefNull library_root_directory); + + void on_save_handler_register(); + void on_save_handler_unregister(); + + void on_save_post(struct Main *, struct PointerRNA **pointers, const int num_pointers); + + private: + bCallbackFuncStore on_save_callback_store_; +}; + +} // namespace blender::bke + +blender::bke::AssetCatalogService *BKE_asset_library_get_catalog_service( + const ::AssetLibrary *library); +blender::bke::AssetCatalogTree *BKE_asset_library_get_catalog_tree(const ::AssetLibrary *library); diff --git a/source/blender/blenkernel/BKE_attribute_access.hh b/source/blender/blenkernel/BKE_attribute_access.hh index cf54e7efa0d..da3de2f08bd 100644 --- a/source/blender/blenkernel/BKE_attribute_access.hh +++ b/source/blender/blenkernel/BKE_attribute_access.hh @@ -101,6 +101,8 @@ class AttributeIDRef { BLI_assert(this->is_anonymous()); return *anonymous_id_; } + + friend std::ostream &operator<<(std::ostream &stream, const AttributeIDRef &attribute_id); }; } // namespace blender::bke @@ -120,6 +122,11 @@ struct AttributeMetaData { } }; +struct AttributeKind { + AttributeDomain domain; + CustomDataType data_type; +}; + /** * Base class for the attribute initializer types described below. */ diff --git a/source/blender/blenkernel/BKE_blender_version.h b/source/blender/blenkernel/BKE_blender_version.h index 1f25106404a..31ce1b124e9 100644 --- a/source/blender/blenkernel/BKE_blender_version.h +++ b/source/blender/blenkernel/BKE_blender_version.h @@ -39,13 +39,13 @@ extern "C" { /* Blender file format version. */ #define BLENDER_FILE_VERSION BLENDER_VERSION -#define BLENDER_FILE_SUBVERSION 25 +#define BLENDER_FILE_SUBVERSION 32 /* Minimum Blender version that supports reading file written with the current * version. Older Blender versions will test this and show a warning if the file * was written with too new a version. */ #define BLENDER_FILE_MIN_VERSION 300 -#define BLENDER_FILE_MIN_SUBVERSION 11 +#define BLENDER_FILE_MIN_SUBVERSION 26 /** User readable version string. */ const char *BKE_blender_version_string(void); diff --git a/source/blender/blenkernel/BKE_callbacks.h b/source/blender/blenkernel/BKE_callbacks.h index ef2a0ed34a0..7c518f33c89 100644 --- a/source/blender/blenkernel/BKE_callbacks.h +++ b/source/blender/blenkernel/BKE_callbacks.h @@ -130,6 +130,7 @@ void BKE_callback_exec_id_depsgraph(struct Main *bmain, struct Depsgraph *depsgraph, eCbEvent evt); void BKE_callback_add(bCallbackFuncStore *funcstore, eCbEvent evt); +void BKE_callback_remove(bCallbackFuncStore *funcstore, eCbEvent evt); void BKE_callback_global_init(void); void BKE_callback_global_finalize(void); diff --git a/source/blender/blenkernel/BKE_colortools.h b/source/blender/blenkernel/BKE_colortools.h index ec2262d4f60..109947cece4 100644 --- a/source/blender/blenkernel/BKE_colortools.h +++ b/source/blender/blenkernel/BKE_colortools.h @@ -97,6 +97,7 @@ void BKE_curvemapping_evaluate_premulRGBF(const struct CurveMapping *cumap, float vecout[3], const float vecin[3]); bool BKE_curvemapping_RGBA_does_something(const struct CurveMapping *cumap); +void BKE_curvemapping_table_F(const struct CurveMapping *cumap, float **array, int *size); void BKE_curvemapping_table_RGBA(const struct CurveMapping *cumap, float **array, int *size); /* non-const, these modify the curve */ diff --git a/source/blender/blenkernel/BKE_displist.h b/source/blender/blenkernel/BKE_displist.h index db5663fcc94..8fb596a8096 100644 --- a/source/blender/blenkernel/BKE_displist.h +++ b/source/blender/blenkernel/BKE_displist.h @@ -87,11 +87,6 @@ void BKE_displist_make_curveTypes(struct Depsgraph *depsgraph, const struct Scene *scene, struct Object *ob, const bool for_render); -void BKE_displist_make_curveTypes_forRender(struct Depsgraph *depsgraph, - const struct Scene *scene, - struct Object *ob, - struct ListBase *dispbase, - struct Mesh **r_final); void BKE_displist_make_mball(struct Depsgraph *depsgraph, struct Scene *scene, struct Object *ob); void BKE_curve_calc_modifiers_pre(struct Depsgraph *depsgraph, diff --git a/source/blender/blenkernel/BKE_font.h b/source/blender/blenkernel/BKE_font.h index 522d3843bb2..827ae1b6a0f 100644 --- a/source/blender/blenkernel/BKE_font.h +++ b/source/blender/blenkernel/BKE_font.h @@ -85,6 +85,15 @@ bool BKE_vfont_to_curve_ex(struct Object *ob, struct CharTrans **r_chartransdata); bool BKE_vfont_to_curve_nubase(struct Object *ob, int mode, struct ListBase *r_nubase); bool BKE_vfont_to_curve(struct Object *ob, int mode); +void BKE_vfont_build_char(struct Curve *cu, + struct ListBase *nubase, + unsigned int character, + struct CharInfo *info, + float ofsx, + float ofsy, + float rot, + int charidx, + const float fsize); int BKE_vfont_select_get(struct Object *ob, int *r_start, int *r_end); void BKE_vfont_select_clamp(struct Object *ob); diff --git a/source/blender/blenkernel/BKE_geometry_set.hh b/source/blender/blenkernel/BKE_geometry_set.hh index 317d513dae6..f182fb527e1 100644 --- a/source/blender/blenkernel/BKE_geometry_set.hh +++ b/source/blender/blenkernel/BKE_geometry_set.hh @@ -25,6 +25,7 @@ #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" @@ -280,6 +281,8 @@ struct GeometrySet { return this->remove(Component::static_type); } + void keep_only(const blender::Span<GeometryComponentType> component_types); + void add(const GeometryComponent &component); blender::Vector<const GeometryComponent *> get_components_for_read() const; @@ -293,6 +296,25 @@ struct GeometrySet { bool owns_direct_data() const; void ensure_owns_direct_data(); + using AttributeForeachCallback = + blender::FunctionRef<void(const blender::bke::AttributeIDRef &attribute_id, + const AttributeMetaData &meta_data, + const GeometryComponent &component)>; + + void attribute_foreach(blender::Span<GeometryComponentType> component_types, + bool include_instances, + AttributeForeachCallback callback) const; + + void gather_attributes_for_propagation( + blender::Span<GeometryComponentType> component_types, + GeometryComponentType dst_component_type, + bool include_instances, + blender::Map<blender::bke::AttributeIDRef, AttributeKind> &r_attributes) const; + + using ForeachSubGeometryCallback = blender::FunctionRef<void(GeometrySet &geometry_set)>; + + void modify_geometry_sets(ForeachSubGeometryCallback callback); + /* Utility methods for creation. */ static GeometrySet create_with_mesh( Mesh *mesh, GeometryOwnershipType ownership = GeometryOwnershipType::Owned); @@ -307,6 +329,8 @@ struct GeometrySet { bool has_instances() const; bool has_volume() const; bool has_curve() const; + bool has_realized_data() const; + bool is_empty() const; const Mesh *get_mesh_for_read() const; const PointCloud *get_pointcloud_for_read() const; @@ -481,11 +505,38 @@ class InstanceReference { { } - InstanceReference(const InstanceReference &src) : type_(src.type_), data_(src.data_) + InstanceReference(const InstanceReference &other) : type_(other.type_), data_(other.data_) + { + if (other.geometry_set_) { + geometry_set_ = std::make_unique<GeometrySet>(*other.geometry_set_); + } + } + + InstanceReference(InstanceReference &&other) + : type_(other.type_), data_(other.data_), geometry_set_(std::move(other.geometry_set_)) + { + other.type_ = Type::None; + other.data_ = nullptr; + } + + InstanceReference &operator=(const InstanceReference &other) + { + if (this == &other) { + return *this; + } + this->~InstanceReference(); + new (this) InstanceReference(other); + return *this; + } + + InstanceReference &operator=(InstanceReference &&other) { - if (src.type_ == Type::GeometrySet) { - geometry_set_ = std::make_unique<GeometrySet>(*src.geometry_set_); + if (this == &other) { + return *this; } + this->~InstanceReference(); + new (this) InstanceReference(std::move(other)); + return *this; } Type type() const @@ -579,6 +630,7 @@ class InstancesComponent : public GeometryComponent { void add_instance(int instance_handle, const blender::float4x4 &transform, const int id = -1); blender::Span<InstanceReference> references() const; + void remove_unused_references(); void ensure_geometry_instances(); GeometrySet &geometry_set_from_reference(const int reference_index); @@ -597,6 +649,9 @@ class InstancesComponent : public GeometryComponent { int attribute_domain_size(const AttributeDomain domain) const final; + void foreach_referenced_geometry( + blender::FunctionRef<void(const GeometrySet &geometry_set)> callback) const; + bool is_empty() const final; bool owns_direct_data() const override; @@ -667,6 +722,13 @@ class AttributeFieldInput : public fn::FieldInput { { } + template<typename T> static fn::Field<T> Create(std::string name) + { + const CPPType &type = CPPType::get<T>(); + auto field_input = std::make_shared<AttributeFieldInput>(std::move(name), type); + return fn::Field<T>{field_input}; + } + StringRefNull attribute_name() const { return name_; @@ -696,6 +758,14 @@ class AnonymousAttributeFieldInput : public fn::FieldInput { { } + template<typename T> static fn::Field<T> Create(StrongAnonymousAttributeID anonymous_id) + { + const CPPType &type = CPPType::get<T>(); + auto field_input = std::make_shared<AnonymousAttributeFieldInput>(std::move(anonymous_id), + type); + return fn::Field<T>{field_input}; + } + const GVArray *get_varray_for_context(const fn::FieldContext &context, IndexMask mask, ResourceScope &scope) const override; diff --git a/source/blender/blenkernel/BKE_geometry_set_instances.hh b/source/blender/blenkernel/BKE_geometry_set_instances.hh index 44a0ee30c4c..653450c7d8e 100644 --- a/source/blender/blenkernel/BKE_geometry_set_instances.hh +++ b/source/blender/blenkernel/BKE_geometry_set_instances.hh @@ -39,21 +39,12 @@ struct GeometryInstanceGroup { Vector<float4x4> transforms; }; -void geometry_set_instances_attribute_foreach(const GeometrySet &geometry_set, - const AttributeForeachCallback callback, - const int limit); - void geometry_set_gather_instances(const GeometrySet &geometry_set, Vector<GeometryInstanceGroup> &r_instance_groups); GeometrySet geometry_set_realize_mesh_for_modifier(const GeometrySet &geometry_set); GeometrySet geometry_set_realize_instances(const GeometrySet &geometry_set); -struct AttributeKind { - CustomDataType data_type; - AttributeDomain domain; -}; - /** * Add information about all the attributes on every component of the type. The resulting info * will contain the highest complexity data type and the highest priority domain among every diff --git a/source/blender/blenkernel/BKE_lib_id.h b/source/blender/blenkernel/BKE_lib_id.h index 36f57209e33..d2a8ec2e332 100644 --- a/source/blender/blenkernel/BKE_lib_id.h +++ b/source/blender/blenkernel/BKE_lib_id.h @@ -133,6 +133,9 @@ enum { LIB_ID_COPY_SHAPEKEY = 1 << 26, /** EXCEPTION! Specific deep-copy of node trees used e.g. for rendering purposes. */ LIB_ID_COPY_NODETREE_LOCALIZE = 1 << 27, + /** EXCEPTION! Specific handling of RB objects regarding collections differs depending whether we + duplicate scene/collections, or objects. */ + LIB_ID_COPY_RIGID_BODY_NO_COLLECTION_HANDLING = 1 << 28, /* *** Helper 'defines' gathering most common flag sets. *** */ /** Shapekeys are not real ID's, more like local data to geometry IDs... */ @@ -261,7 +264,8 @@ struct ID *BKE_id_copy_ex(struct Main *bmain, const int flag); struct ID *BKE_id_copy_for_duplicate(struct Main *bmain, struct ID *id, - const uint duplicate_flags); + const uint duplicate_flags, + const int copy_flags); void BKE_lib_id_swap(struct Main *bmain, struct ID *id_a, struct ID *id_b); void BKE_lib_id_swap_full(struct Main *bmain, struct ID *id_a, struct ID *id_b); diff --git a/source/blender/blenkernel/BKE_lib_override.h b/source/blender/blenkernel/BKE_lib_override.h index c6658ff424a..b94a1b33606 100644 --- a/source/blender/blenkernel/BKE_lib_override.h +++ b/source/blender/blenkernel/BKE_lib_override.h @@ -84,6 +84,8 @@ bool BKE_lib_override_library_proxy_convert(struct Main *bmain, struct Scene *scene, struct ViewLayer *view_layer, struct Object *ob_proxy); +void BKE_lib_override_library_main_proxy_convert(struct Main *bmain, + struct BlendFileReadReport *reports); bool BKE_lib_override_library_resync(struct Main *bmain, struct Scene *scene, struct ViewLayer *view_layer, @@ -171,6 +173,8 @@ void BKE_lib_override_library_main_unused_cleanup(struct Main *bmain); void BKE_lib_override_library_update(struct Main *bmain, struct ID *local); void BKE_lib_override_library_main_update(struct Main *bmain); +bool BKE_lib_override_library_id_is_user_deletable(struct Main *bmain, struct ID *id); + /* Storage (.blend file writing) part. */ /* For now, we just use a temp main list. */ diff --git a/source/blender/blenkernel/BKE_material.h b/source/blender/blenkernel/BKE_material.h index 69e2d52e1dd..b1eaf7207fa 100644 --- a/source/blender/blenkernel/BKE_material.h +++ b/source/blender/blenkernel/BKE_material.h @@ -90,7 +90,7 @@ void BKE_object_material_array_assign(struct Main *bmain, short BKE_object_material_slot_find_index(struct Object *ob, struct Material *ma); bool BKE_object_material_slot_add(struct Main *bmain, struct Object *ob); bool BKE_object_material_slot_remove(struct Main *bmain, struct Object *ob); -bool BKE_object_material_slot_used(struct ID *id, short actcol); +bool BKE_object_material_slot_used(struct Object *object, short actcol); struct Material *BKE_gpencil_material(struct Object *ob, short act); struct MaterialGPencilStyle *BKE_gpencil_material_settings(struct Object *ob, short act); diff --git a/source/blender/blenkernel/BKE_mesh.h b/source/blender/blenkernel/BKE_mesh.h index b0a8fee1178..f44d35c62a3 100644 --- a/source/blender/blenkernel/BKE_mesh.h +++ b/source/blender/blenkernel/BKE_mesh.h @@ -651,9 +651,8 @@ extern void (*BKE_mesh_batch_cache_free_cb)(struct Mesh *me); /* Inlines */ -/* Instead of -1 that function uses ORIGINDEX_NONE as defined in BKE_customdata.h, - * but I don't want to force every user of BKE_mesh.h to also include that file. - * ~~ Sybren */ +/* NOTE(@sybren): Instead of -1 that function uses ORIGINDEX_NONE as defined in BKE_customdata.h, + * but I don't want to force every user of BKE_mesh.h to also include that file. */ BLI_INLINE int BKE_mesh_origindex_mface_mpoly(const int *index_mf_to_mpoly, const int *index_mp_to_orig, const int i) diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h index 21ca65baf00..a81a620ab12 100644 --- a/source/blender/blenkernel/BKE_node.h +++ b/source/blender/blenkernel/BKE_node.h @@ -417,7 +417,7 @@ typedef struct bNodeTreeType { void (*local_sync)(struct bNodeTree *localtree, struct bNodeTree *ntree); void (*local_merge)(struct Main *bmain, struct bNodeTree *localtree, struct bNodeTree *ntree); - /* Tree update. Overrides nodetype->updatetreefunc! */ + /* Tree update. Overrides `nodetype->updatetreefunc` ! */ void (*update)(struct bNodeTree *ntree); bool (*validate_link)(struct bNodeTree *ntree, struct bNodeLink *link); @@ -443,7 +443,7 @@ void ntreeTypeFreeLink(const struct bNodeTreeType *nt); bool ntreeIsRegistered(struct bNodeTree *ntree); struct GHashIterator *ntreeTypeGetIterator(void); -/* helper macros for iterating over tree types */ +/* Helper macros for iterating over tree types. */ #define NODE_TREE_TYPES_BEGIN(ntype) \ { \ GHashIterator *__node_tree_type_iter__ = ntreeTypeGetIterator(); \ @@ -480,7 +480,7 @@ bool ntreeHasType(const struct bNodeTree *ntree, int type); bool ntreeHasTree(const struct bNodeTree *ntree, const struct bNodeTree *lookup); void ntreeUpdateTree(struct Main *main, struct bNodeTree *ntree); void ntreeUpdateAllNew(struct Main *main); -void ntreeUpdateAllUsers(struct Main *main, struct ID *id); +void ntreeUpdateAllUsers(struct Main *main, struct ID *id, int tree_update_flag); void ntreeGetDependencyList(struct bNodeTree *ntree, struct bNode ***r_deplist, @@ -548,7 +548,7 @@ void nodeUnregisterType(struct bNodeType *ntype); bool nodeTypeUndefined(struct bNode *node); struct GHashIterator *nodeTypeGetIterator(void); -/* helper macros for iterating over node types */ +/* Helper macros for iterating over node types. */ #define NODE_TYPES_BEGIN(ntype) \ { \ GHashIterator *__node_type_iter__ = nodeTypeGetIterator(); \ @@ -574,7 +574,7 @@ const char *nodeStaticSocketType(int type, int subtype); const char *nodeStaticSocketInterfaceType(int type, int subtype); const char *nodeStaticSocketLabel(int type, int subtype); -/* helper macros for iterating over node types */ +/* Helper macros for iterating over node types. */ #define NODE_SOCKET_TYPES_BEGIN(stype) \ { \ GHashIterator *__node_socket_type_iter__ = nodeSocketTypeGetIterator(); \ @@ -746,7 +746,8 @@ int BKE_node_clipboard_get_type(void); /* Node Instance Hash */ typedef struct bNodeInstanceHash { - GHash *ghash; /* XXX should be made a direct member, GHash allocation needs to support it */ + /** XXX should be made a direct member, #GHash allocation needs to support it */ + GHash *ghash; } bNodeInstanceHash; typedef void (*bNodeInstanceValueFP)(void *value); @@ -1102,6 +1103,7 @@ void BKE_nodetree_remove_layer_n(struct bNodeTree *ntree, #define SH_NODE_VERTEX_COLOR 706 #define SH_NODE_OUTPUT_AOV 707 #define SH_NODE_VECTOR_ROTATE 708 +#define SH_NODE_CURVE_FLOAT 709 /* custom defines options for Material node */ // #define SH_NODE_MAT_DIFF 1 @@ -1346,7 +1348,7 @@ void ntreeCompositCryptomatteLayerPrefix(const Scene *scene, const bNode *node, char *r_prefix, size_t prefix_len); -/* Update the runtime layer names with the cryptomatte layer names of the references +/* Update the runtime layer names with the crypto-matte layer names of the references * render layer or image. */ void ntreeCompositCryptomatteUpdateLayerNames(const Scene *scene, bNode *node); struct CryptomatteSession *ntreeCompositCryptomatteSession(const Scene *scene, bNode *node); @@ -1411,12 +1413,12 @@ int ntreeTexExecTree(struct bNodeTree *ntree, * \{ */ #define GEO_NODE_TRIANGULATE 1000 -#define GEO_NODE_EDGE_SPLIT 1001 +#define GEO_NODE_LEGACY_EDGE_SPLIT 1001 #define GEO_NODE_TRANSFORM 1002 #define GEO_NODE_BOOLEAN 1003 #define GEO_NODE_LEGACY_POINT_DISTRIBUTE 1004 #define GEO_NODE_LEGACY_POINT_INSTANCE 1005 -#define GEO_NODE_SUBDIVISION_SURFACE 1006 +#define GEO_NODE_LEGACY_SUBDIVISION_SURFACE 1006 #define GEO_NODE_OBJECT_INFO 1007 #define GEO_NODE_LEGACY_ATTRIBUTE_RANDOMIZE 1008 #define GEO_NODE_LEGACY_ATTRIBUTE_MATH 1009 @@ -1451,14 +1453,14 @@ int ntreeTexExecTree(struct bNodeTree *ntree, #define GEO_NODE_MESH_PRIMITIVE_LINE 1038 #define GEO_NODE_MESH_PRIMITIVE_GRID 1039 #define GEO_NODE_LEGACY_ATTRIBUTE_MAP_RANGE 1040 -#define GEO_NODE_LECAGY_ATTRIBUTE_CLAMP 1041 +#define GEO_NODE_LEGACY_ATTRIBUTE_CLAMP 1041 #define GEO_NODE_BOUNDING_BOX 1042 #define GEO_NODE_SWITCH 1043 #define GEO_NODE_LEGACY_ATTRIBUTE_TRANSFER 1044 #define GEO_NODE_CURVE_TO_MESH 1045 #define GEO_NODE_LEGACY_ATTRIBUTE_CURVE_MAP 1046 #define GEO_NODE_CURVE_RESAMPLE 1047 -#define GEO_NODE_ATTRIBUTE_VECTOR_ROTATE 1048 +#define GEO_NODE_LEGACY_ATTRIBUTE_VECTOR_ROTATE 1048 #define GEO_NODE_LEGACY_MATERIAL_ASSIGN 1049 #define GEO_NODE_INPUT_MATERIAL 1050 #define GEO_NODE_MATERIAL_REPLACE 1051 @@ -1467,7 +1469,7 @@ int ntreeTexExecTree(struct bNodeTree *ntree, #define GEO_NODE_CURVE_LENGTH 1054 #define GEO_NODE_LEGACY_SELECT_BY_MATERIAL 1055 #define GEO_NODE_CONVEX_HULL 1056 -#define GEO_NODE_CURVE_TO_POINTS 1057 +#define GEO_NODE_LEGACY_CURVE_TO_POINTS 1057 #define GEO_NODE_LEGACY_CURVE_REVERSE 1058 #define GEO_NODE_SEPARATE_COMPONENTS 1059 #define GEO_NODE_LEGACY_CURVE_SUBDIVIDE 1060 @@ -1479,7 +1481,7 @@ int ntreeTexExecTree(struct bNodeTree *ntree, #define GEO_NODE_CURVE_PRIMITIVE_CIRCLE 1066 #define GEO_NODE_VIEWER 1067 #define GEO_NODE_CURVE_PRIMITIVE_LINE 1068 -#define GEO_NODE_CURVE_ENDPOINTS 1069 +#define GEO_NODE_LEGACY_CURVE_ENDPOINTS 1069 #define GEO_NODE_CURVE_PRIMITIVE_QUADRILATERAL 1070 #define GEO_NODE_CURVE_TRIM 1071 #define GEO_NODE_LEGACY_CURVE_SET_HANDLES 1072 @@ -1500,7 +1502,17 @@ int ntreeTexExecTree(struct bNodeTree *ntree, #define GEO_NODE_STRING_JOIN 1087 #define GEO_NODE_CURVE_PARAMETER 1088 #define GEO_NODE_CURVE_FILLET 1089 - +#define GEO_NODE_DISTRIBUTE_POINTS_ON_FACES 1090 +#define GEO_NODE_STRING_TO_CURVES 1091 +#define GEO_NODE_INSTANCE_ON_POINTS 1092 +#define GEO_NODE_MESH_TO_POINTS 1093 +#define GEO_NODE_POINTS_TO_VERTICES 1094 +#define GEO_NODE_CURVE_REVERSE 1095 +#define GEO_NODE_PROXIMITY 1096 +#define GEO_NODE_CURVE_SUBDIVIDE 1097 +#define GEO_NODE_INPUT_SPLINE_LENGTH 1098 +#define GEO_NODE_CURVE_SPLINE_TYPE 1099 +#define GEO_NODE_CURVE_SET_HANDLES 1100 /** \} */ /* -------------------------------------------------------------------- */ @@ -1509,13 +1521,16 @@ int ntreeTexExecTree(struct bNodeTree *ntree, #define FN_NODE_BOOLEAN_MATH 1200 #define FN_NODE_FLOAT_COMPARE 1202 -#define FN_NODE_RANDOM_FLOAT 1206 +#define FN_NODE_LEGACY_RANDOM_FLOAT 1206 #define FN_NODE_INPUT_VECTOR 1207 #define FN_NODE_INPUT_STRING 1208 #define FN_NODE_FLOAT_TO_INT 1209 #define FN_NODE_VALUE_TO_STRING 1210 #define FN_NODE_STRING_LENGTH 1211 #define FN_NODE_STRING_SUBSTRING 1212 +#define FN_NODE_INPUT_SPECIAL_CHARACTERS 1213 +#define FN_NODE_RANDOM_VALUE 1214 +#define FN_NODE_ROTATE_EULER 1215 /** \} */ diff --git a/source/blender/blenkernel/BKE_preferences.h b/source/blender/blenkernel/BKE_preferences.h index 04a41d425bb..bd887c1ea0d 100644 --- a/source/blender/blenkernel/BKE_preferences.h +++ b/source/blender/blenkernel/BKE_preferences.h @@ -29,22 +29,32 @@ extern "C" { struct UserDef; struct bUserAssetLibrary; -void BKE_preferences_asset_library_free(struct bUserAssetLibrary *library) ATTR_NONNULL(); - struct bUserAssetLibrary *BKE_preferences_asset_library_add(struct UserDef *userdef, const char *name, const char *path) ATTR_NONNULL(1); +void BKE_preferences_asset_library_remove(struct UserDef *userdef, + struct bUserAssetLibrary *library) ATTR_NONNULL(); + void BKE_preferences_asset_library_name_set(struct UserDef *userdef, struct bUserAssetLibrary *library, const char *name) ATTR_NONNULL(); -void BKE_preferences_asset_library_remove(struct UserDef *userdef, - struct bUserAssetLibrary *library) ATTR_NONNULL(); - struct bUserAssetLibrary *BKE_preferences_asset_library_find_from_index( const struct UserDef *userdef, int index) ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT; struct bUserAssetLibrary *BKE_preferences_asset_library_find_from_name( const struct UserDef *userdef, const char *name) ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT; + +/** + * Return the bUserAssetLibrary that contains the given file/directory path. The given path can be + * the library's top-level directory, or any path inside that directory. + * + * When more than one asset libraries match, the first matching one is returned (no smartness when + * there nested asset libraries). + * + * Return NULL when no such asset library is found. */ +struct bUserAssetLibrary *BKE_preferences_asset_library_containing_path( + const struct UserDef *userdef, const char *path) ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT; + int BKE_preferences_asset_library_get_index(const struct UserDef *userdef, const struct bUserAssetLibrary *library) ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT; diff --git a/source/blender/blenkernel/BKE_spline.hh b/source/blender/blenkernel/BKE_spline.hh index 541ff19c1cd..97e0d8415a5 100644 --- a/source/blender/blenkernel/BKE_spline.hh +++ b/source/blender/blenkernel/BKE_spline.hh @@ -316,6 +316,9 @@ class BezierSpline final : public Spline { void translate(const blender::float3 &translation) override; void transform(const blender::float4x4 &matrix) override; + void set_handle_position_right(const int index, const blender::float3 &value); + void set_handle_position_left(const int index, const blender::float3 &value); + bool point_is_sharp(const int index) const; void mark_cache_invalid() final; diff --git a/source/blender/blenkernel/BKE_subdiv.h b/source/blender/blenkernel/BKE_subdiv.h index 3d47c8d3bc5..2fb27fad30d 100644 --- a/source/blender/blenkernel/BKE_subdiv.h +++ b/source/blender/blenkernel/BKE_subdiv.h @@ -56,7 +56,7 @@ typedef enum eSubdivFVarLinearInterpolation { } eSubdivFVarLinearInterpolation; typedef struct SubdivSettings { - /* Simple subdivision corresponds to "Simple" option in the interface. When its enabled the + /* Simple subdivision corresponds to "Simple" option in the interface. When it's enabled the * subdivided mesh is not "smoothed": new vertices are added uniformly on the existing surface. * * On an OpenSubdiv implementation level this translates to a subdivision scheme: diff --git a/source/blender/blenkernel/BKE_subdiv_foreach.h b/source/blender/blenkernel/BKE_subdiv_foreach.h index a351b9a3d11..3f74299455d 100644 --- a/source/blender/blenkernel/BKE_subdiv_foreach.h +++ b/source/blender/blenkernel/BKE_subdiv_foreach.h @@ -127,7 +127,7 @@ typedef struct SubdivForeachContext { SubdivForeachVertexFromEdgeCb vertex_edge; /* Called exactly once, always corresponds to a single ptex face. */ SubdivForeachVertexInnerCb vertex_inner; - /* Called once for each loose vertex. One loose coarse vertexcorresponds + /* Called once for each loose vertex. One loose coarse vertex corresponds * to a single subdivision vertex. */ SubdivForeachLooseCb vertex_loose; @@ -144,7 +144,7 @@ typedef struct SubdivForeachContext { SubdivForeachPolygonCb poly; /* User-defined pointer, to allow callbacks know something about context the - * traversal is happening for, + * traversal is happening for. */ void *user_data; @@ -163,7 +163,7 @@ typedef struct SubdivForeachContext { * indices (for vertices, edges, loops, polygons) in the same way as subdivision * modifier will do for a dense mesh. * - * Returns truth if the whole topology was traversed, without any early exits. + * Returns true if the whole topology was traversed, without any early exits. * * TODO(sergey): Need to either get rid of subdiv or of coarse_mesh. * The main point here is to be able to get base level topology, which can be diff --git a/source/blender/blenkernel/BKE_volume.h b/source/blender/blenkernel/BKE_volume.h index d9333996632..5fe0d54c2cf 100644 --- a/source/blender/blenkernel/BKE_volume.h +++ b/source/blender/blenkernel/BKE_volume.h @@ -18,7 +18,7 @@ /** \file * \ingroup bke - * \brief Volume datablock. + * \brief Volume data-block. */ #ifdef __cplusplus extern "C" { @@ -37,7 +37,7 @@ struct VolumeGridVector; void BKE_volumes_init(void); -/* Datablock Management */ +/* Data-block Management */ void BKE_volume_init_grids(struct Volume *volume); void *BKE_volume_add(struct Main *bmain, const char *name); @@ -122,13 +122,13 @@ void BKE_volume_grid_transform_matrix(const struct VolumeGrid *grid, float mat[4 /* Volume Editing * - * These are intended for modifiers to use on evaluated datablocks. + * These are intended for modifiers to use on evaluated data-blocks. * - * new_for_eval creates a volume datablock with no grids or file path, but + * new_for_eval creates a volume data-block with no grids or file path, but * preserves other settings such as viewport display options. * - * copy_for_eval creates a volume datablock preserving everything except the - * file path. Grids are shared with the source datablock, not copied. */ + * copy_for_eval creates a volume data-block preserving everything except the + * file path. Grids are shared with the source data-block, not copied. */ struct Volume *BKE_volume_new_for_eval(const struct Volume *volume_src); struct Volume *BKE_volume_copy_for_eval(struct Volume *volume_src, bool reference); diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt index de7864ef36a..fb7fdd1ac21 100644 --- a/source/blender/blenkernel/CMakeLists.txt +++ b/source/blender/blenkernel/CMakeLists.txt @@ -84,6 +84,9 @@ set(SRC intern/armature_selection.cc intern/armature_update.c intern/asset.cc + intern/asset_catalog.cc + intern/asset_catalog_path.cc + intern/asset_library.cc intern/attribute.c intern/attribute_access.cc intern/attribute_math.cc @@ -303,6 +306,10 @@ set(SRC BKE_armature.h BKE_armature.hh BKE_asset.h + BKE_asset_catalog.hh + BKE_asset_catalog_path.hh + BKE_asset_library.h + BKE_asset_library.hh BKE_attribute.h BKE_attribute_access.hh BKE_attribute_math.hh @@ -783,6 +790,10 @@ if(WITH_GTESTS) set(TEST_SRC intern/action_test.cc intern/armature_test.cc + intern/asset_catalog_test.cc + intern/asset_catalog_path_test.cc + intern/asset_library_test.cc + intern/asset_test.cc intern/cryptomatte_test.cc intern/fcurve_test.cc intern/lattice_deform_test.cc diff --git a/source/blender/blenkernel/intern/asset.cc b/source/blender/blenkernel/intern/asset.cc index f74018b20c5..ae9ded3c754 100644 --- a/source/blender/blenkernel/intern/asset.cc +++ b/source/blender/blenkernel/intern/asset.cc @@ -26,8 +26,10 @@ #include "BLI_listbase.h" #include "BLI_string.h" +#include "BLI_string_ref.hh" #include "BLI_string_utils.h" #include "BLI_utildefines.h" +#include "BLI_uuid.h" #include "BKE_asset.h" #include "BKE_icons.h" @@ -37,6 +39,8 @@ #include "MEM_guardedalloc.h" +using namespace blender; + AssetMetaData *BKE_asset_metadata_create(void) { AssetMetaData *asset_data = (AssetMetaData *)MEM_callocN(sizeof(*asset_data), __func__); @@ -115,6 +119,27 @@ void BKE_asset_library_reference_init_default(AssetLibraryReference *library_ref memcpy(library_ref, DNA_struct_default_get(AssetLibraryReference), sizeof(*library_ref)); } +void BKE_asset_metadata_catalog_id_clear(struct AssetMetaData *asset_data) +{ + asset_data->catalog_id = BLI_uuid_nil(); + asset_data->catalog_simple_name[0] = '\0'; +} + +void BKE_asset_metadata_catalog_id_set(struct AssetMetaData *asset_data, + const ::bUUID catalog_id, + const char *catalog_simple_name) +{ + asset_data->catalog_id = catalog_id; + + constexpr size_t max_simple_name_length = sizeof(asset_data->catalog_simple_name); + + /* The substr() call is necessary to make copy() copy the first N characters (instead of refusing + * to copy and producing an empty string). */ + StringRef trimmed_id = + StringRef(catalog_simple_name).trim().substr(0, max_simple_name_length - 1); + trimmed_id.copy(asset_data->catalog_simple_name, max_simple_name_length); +} + /* Queries -------------------------------------------- */ PreviewImage *BKE_asset_metadata_preview_get_from_id(const AssetMetaData *UNUSED(asset_data), diff --git a/source/blender/blenkernel/intern/asset_catalog.cc b/source/blender/blenkernel/intern/asset_catalog.cc new file mode 100644 index 00000000000..2c7cf28d60d --- /dev/null +++ b/source/blender/blenkernel/intern/asset_catalog.cc @@ -0,0 +1,791 @@ +/* + * 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 bke + */ + +#include "BKE_asset_catalog.hh" +#include "BKE_asset_library.h" +#include "BKE_preferences.h" + +#include "BLI_fileops.h" +#include "BLI_path_util.h" +#include "BLI_string_ref.hh" + +#include "DNA_userdef_types.h" + +/* For S_ISREG() and S_ISDIR() on Windows. */ +#ifdef WIN32 +# include "BLI_winstuff.h" +#endif + +#include <fstream> +#include <set> + +namespace blender::bke { + +const CatalogFilePath AssetCatalogService::DEFAULT_CATALOG_FILENAME = "blender_assets.cats.txt"; + +/* For now this is the only version of the catalog definition files that is supported. + * Later versioning code may be added to handle older files. */ +const int AssetCatalogDefinitionFile::SUPPORTED_VERSION = 1; +/* String that's matched in the catalog definition file to know that the line is the version + * declaration. It has to start with a space to ensure it won't match any hypothetical future field + * that starts with "VERSION". */ +const std::string AssetCatalogDefinitionFile::VERSION_MARKER = "VERSION "; + +const std::string AssetCatalogDefinitionFile::HEADER = + "# This is an Asset Catalog Definition file for Blender.\n" + "#\n" + "# Empty lines and lines starting with `#` will be ignored.\n" + "# The first non-ignored line should be the version indicator.\n" + "# Other lines are of the format \"UUID:catalog/path/for/assets:simple catalog name\"\n"; + +AssetCatalogService::AssetCatalogService(const CatalogFilePath &asset_library_root) + : asset_library_root_(asset_library_root) +{ +} + +bool AssetCatalogService::is_empty() const +{ + return catalogs_.is_empty(); +} + +AssetCatalog *AssetCatalogService::find_catalog(CatalogID catalog_id) const +{ + const std::unique_ptr<AssetCatalog> *catalog_uptr_ptr = this->catalogs_.lookup_ptr(catalog_id); + if (catalog_uptr_ptr == nullptr) { + return nullptr; + } + return catalog_uptr_ptr->get(); +} + +AssetCatalog *AssetCatalogService::find_catalog_by_path(const AssetCatalogPath &path) const +{ + for (const auto &catalog : catalogs_.values()) { + if (catalog->path == path) { + return catalog.get(); + } + } + + return nullptr; +} + +AssetCatalogFilter AssetCatalogService::create_catalog_filter( + const CatalogID active_catalog_id) const +{ + Set<CatalogID> matching_catalog_ids; + matching_catalog_ids.add(active_catalog_id); + + const AssetCatalog *active_catalog = find_catalog(active_catalog_id); + if (!active_catalog) { + /* If the UUID is unknown (i.e. not mapped to an actual Catalog), it is impossible to determine + * its children. The filter can still work on the given UUID. */ + return AssetCatalogFilter(std::move(matching_catalog_ids)); + } + + /* This cannot just iterate over tree items to get all the required data, because tree items only + * represent single UUIDs. It could be used to get the main UUIDs of the children, though, and + * then only do an exact match on the path (instead of the more complex `is_contained_in()` + * call). Without an extra indexed-by-path acceleration structure, this is still going to require + * a linear search, though. */ + for (const auto &catalog_uptr : this->catalogs_.values()) { + if (catalog_uptr->path.is_contained_in(active_catalog->path)) { + matching_catalog_ids.add(catalog_uptr->catalog_id); + } + } + + return AssetCatalogFilter(std::move(matching_catalog_ids)); +} + +void AssetCatalogService::delete_catalog(CatalogID catalog_id) +{ + std::unique_ptr<AssetCatalog> *catalog_uptr_ptr = this->catalogs_.lookup_ptr(catalog_id); + if (catalog_uptr_ptr == nullptr) { + /* Catalog cannot be found, which is fine. */ + return; + } + + /* Mark the catalog as deleted. */ + AssetCatalog *catalog = catalog_uptr_ptr->get(); + catalog->flags.is_deleted = true; + + /* Move ownership from this->catalogs_ to this->deleted_catalogs_. */ + this->deleted_catalogs_.add(catalog_id, std::move(*catalog_uptr_ptr)); + + /* The catalog can now be removed from the map without freeing the actual AssetCatalog. */ + this->catalogs_.remove(catalog_id); + + this->rebuild_tree(); +} + +void AssetCatalogService::update_catalog_path(CatalogID catalog_id, + const AssetCatalogPath &new_catalog_path) +{ + AssetCatalog *renamed_cat = this->find_catalog(catalog_id); + const AssetCatalogPath old_cat_path = renamed_cat->path; + + for (auto &catalog_uptr : catalogs_.values()) { + AssetCatalog *cat = catalog_uptr.get(); + + const AssetCatalogPath new_path = cat->path.rebase(old_cat_path, new_catalog_path); + if (!new_path) { + continue; + } + cat->path = new_path; + } + + this->rebuild_tree(); +} + +AssetCatalog *AssetCatalogService::create_catalog(const AssetCatalogPath &catalog_path) +{ + std::unique_ptr<AssetCatalog> catalog = AssetCatalog::from_path(catalog_path); + + /* So we can std::move(catalog) and still use the non-owning pointer: */ + AssetCatalog *const catalog_ptr = catalog.get(); + + /* TODO(@sybren): move the `AssetCatalog::from_path()` function to another place, that can reuse + * catalogs when a catalog with the given path is already known, and avoid duplicate catalog IDs. + */ + BLI_assert_msg(!catalogs_.contains(catalog->catalog_id), "duplicate catalog ID not supported"); + catalogs_.add_new(catalog->catalog_id, std::move(catalog)); + + if (catalog_definition_file_) { + /* Ensure the new catalog gets written to disk at some point. If there is no CDF in memory yet, + * it's enough to have the catalog known to the service as it'll be saved to a new file. */ + catalog_definition_file_->add_new(catalog_ptr); + } + + BLI_assert_msg(catalog_tree_, "An Asset Catalog tree should always exist."); + catalog_tree_->insert_item(*catalog_ptr); + + return catalog_ptr; +} + +static std::string asset_definition_default_file_path_from_dir(StringRef asset_library_root) +{ + char file_path[PATH_MAX]; + BLI_join_dirfile(file_path, + sizeof(file_path), + asset_library_root.data(), + AssetCatalogService::DEFAULT_CATALOG_FILENAME.data()); + return file_path; +} + +void AssetCatalogService::load_from_disk() +{ + load_from_disk(asset_library_root_); +} + +void AssetCatalogService::load_from_disk(const CatalogFilePath &file_or_directory_path) +{ + BLI_stat_t status; + if (BLI_stat(file_or_directory_path.data(), &status) == -1) { + // TODO(@sybren): throw an appropriate exception. + return; + } + + if (S_ISREG(status.st_mode)) { + load_single_file(file_or_directory_path); + } + else if (S_ISDIR(status.st_mode)) { + load_directory_recursive(file_or_directory_path); + } + else { + // TODO(@sybren): throw an appropriate exception. + } + + /* TODO: Should there be a sanitize step? E.g. to remove catalogs with identical paths? */ + + rebuild_tree(); +} + +void AssetCatalogService::load_directory_recursive(const CatalogFilePath &directory_path) +{ + // TODO(@sybren): implement proper multi-file support. For now, just load + // the default file if it is there. + CatalogFilePath file_path = asset_definition_default_file_path_from_dir(directory_path); + + if (!BLI_exists(file_path.data())) { + /* No file to be loaded is perfectly fine. */ + return; + } + + this->load_single_file(file_path); +} + +void AssetCatalogService::load_single_file(const CatalogFilePath &catalog_definition_file_path) +{ + /* TODO(@sybren): check that #catalog_definition_file_path is contained in #asset_library_root_, + * otherwise some assumptions may fail. */ + std::unique_ptr<AssetCatalogDefinitionFile> cdf = parse_catalog_file( + catalog_definition_file_path); + + BLI_assert_msg(!this->catalog_definition_file_, + "Only loading of a single catalog definition file is supported."); + this->catalog_definition_file_ = std::move(cdf); +} + +std::unique_ptr<AssetCatalogDefinitionFile> AssetCatalogService::parse_catalog_file( + const CatalogFilePath &catalog_definition_file_path) +{ + auto cdf = std::make_unique<AssetCatalogDefinitionFile>(); + cdf->file_path = catalog_definition_file_path; + + auto catalog_parsed_callback = [this, catalog_definition_file_path]( + std::unique_ptr<AssetCatalog> catalog) { + if (this->catalogs_.contains(catalog->catalog_id)) { + // TODO(@sybren): apparently another CDF was already loaded. This is not supported yet. + std::cerr << catalog_definition_file_path << ": multiple definitions of catalog " + << catalog->catalog_id << " in multiple files, ignoring this one." << std::endl; + /* Don't store 'catalog'; unique_ptr will free its memory. */ + return false; + } + + /* The AssetCatalog pointer is now owned by the AssetCatalogService. */ + this->catalogs_.add_new(catalog->catalog_id, std::move(catalog)); + return true; + }; + + cdf->parse_catalog_file(cdf->file_path, catalog_parsed_callback); + + return cdf; +} + +void AssetCatalogService::merge_from_disk_before_writing() +{ + /* TODO(Sybren): expand to support multiple CDFs. */ + + if (!catalog_definition_file_ || catalog_definition_file_->file_path.empty() || + !BLI_is_file(catalog_definition_file_->file_path.c_str())) { + return; + } + + auto catalog_parsed_callback = [this](std::unique_ptr<AssetCatalog> catalog) { + const bUUID catalog_id = catalog->catalog_id; + + /* The following two conditions could be or'ed together. Keeping them separated helps when + * adding debug prints, breakpoints, etc. */ + if (this->catalogs_.contains(catalog_id)) { + /* This catalog was already seen, so just ignore it. */ + return false; + } + if (this->deleted_catalogs_.contains(catalog_id)) { + /* This catalog was already seen and subsequently deleted, so just ignore it. */ + return false; + } + + /* This is a new catalog, so let's keep it around. */ + this->catalogs_.add_new(catalog_id, std::move(catalog)); + return true; + }; + + catalog_definition_file_->parse_catalog_file(catalog_definition_file_->file_path, + catalog_parsed_callback); +} + +bool AssetCatalogService::write_to_disk_on_blendfile_save(const CatalogFilePath &blend_file_path) +{ + /* TODO(Sybren): expand to support multiple CDFs. */ + + /* - Already loaded a CDF from disk? -> Always write to that file. */ + if (this->catalog_definition_file_) { + merge_from_disk_before_writing(); + return catalog_definition_file_->write_to_disk(); + } + + if (catalogs_.is_empty() && deleted_catalogs_.is_empty()) { + /* Avoid saving anything, when there is nothing to save. */ + return true; /* Writing nothing when there is nothing to write is still a success. */ + } + + const CatalogFilePath cdf_path_to_write = find_suitable_cdf_path_for_writing(blend_file_path); + this->catalog_definition_file_ = construct_cdf_in_memory(cdf_path_to_write); + merge_from_disk_before_writing(); + return catalog_definition_file_->write_to_disk(); +} + +CatalogFilePath AssetCatalogService::find_suitable_cdf_path_for_writing( + const CatalogFilePath &blend_file_path) +{ + BLI_assert_msg(!blend_file_path.empty(), + "A non-empty .blend file path is required to be able to determine where the " + "catalog definition file should be put"); + + /* Determine the default CDF path in the same directory of the blend file. */ + char blend_dir_path[PATH_MAX]; + BLI_split_dir_part(blend_file_path.c_str(), blend_dir_path, sizeof(blend_dir_path)); + const CatalogFilePath cdf_path_next_to_blend = asset_definition_default_file_path_from_dir( + blend_dir_path); + + if (BLI_exists(cdf_path_next_to_blend.c_str())) { + /* - The directory containing the blend file has a blender_assets.cats.txt file? + * -> Merge with & write to that file. */ + return cdf_path_next_to_blend; + } + + /* - There's no definition file next to the .blend file. + * -> Ask the asset library API for an appropriate location. */ + char suitable_root_path[PATH_MAX]; + BKE_asset_library_find_suitable_root_path_from_path(blend_file_path.c_str(), suitable_root_path); + char asset_lib_cdf_path[PATH_MAX]; + BLI_path_join(asset_lib_cdf_path, + sizeof(asset_lib_cdf_path), + suitable_root_path, + DEFAULT_CATALOG_FILENAME.c_str(), + NULL); + + return asset_lib_cdf_path; +} + +std::unique_ptr<AssetCatalogDefinitionFile> AssetCatalogService::construct_cdf_in_memory( + const CatalogFilePath &file_path) +{ + auto cdf = std::make_unique<AssetCatalogDefinitionFile>(); + cdf->file_path = file_path; + + for (auto &catalog : catalogs_.values()) { + cdf->add_new(catalog.get()); + } + + return cdf; +} + +std::unique_ptr<AssetCatalogTree> AssetCatalogService::read_into_tree() +{ + auto tree = std::make_unique<AssetCatalogTree>(); + + /* Go through the catalogs, insert each path component into the tree where needed. */ + for (auto &catalog : catalogs_.values()) { + tree->insert_item(*catalog); + } + + return tree; +} + +void AssetCatalogService::rebuild_tree() +{ + create_missing_catalogs(); + this->catalog_tree_ = read_into_tree(); +} + +void AssetCatalogService::create_missing_catalogs() +{ + /* Construct an ordered set of paths to check, so that parents are ordered before children. */ + std::set<AssetCatalogPath> paths_to_check; + for (auto &catalog : catalogs_.values()) { + paths_to_check.insert(catalog->path); + } + + std::set<AssetCatalogPath> seen_paths; + /* The empty parent should never be created, so always be considered "seen". */ + seen_paths.insert(AssetCatalogPath("")); + + /* Find and create missing direct parents (so ignoring parents-of-parents). */ + while (!paths_to_check.empty()) { + /* Pop the first path of the queue. */ + const AssetCatalogPath path = *paths_to_check.begin(); + paths_to_check.erase(paths_to_check.begin()); + + if (seen_paths.find(path) != seen_paths.end()) { + /* This path has been seen already, so it can be ignored. */ + continue; + } + seen_paths.insert(path); + + const AssetCatalogPath parent_path = path.parent(); + if (seen_paths.find(parent_path) != seen_paths.end()) { + /* The parent exists, continue to the next path. */ + continue; + } + + /* The parent doesn't exist, so create it and queue it up for checking its parent. */ + create_catalog(parent_path); + paths_to_check.insert(parent_path); + } + + /* TODO(Sybren): bind the newly created catalogs to a CDF, if we know about it. */ +} + +/* ---------------------------------------------------------------------- */ + +AssetCatalogTreeItem::AssetCatalogTreeItem(StringRef name, + CatalogID catalog_id, + const AssetCatalogTreeItem *parent) + : name_(name), catalog_id_(catalog_id), parent_(parent) +{ +} + +CatalogID AssetCatalogTreeItem::get_catalog_id() const +{ + return catalog_id_; +} + +StringRef AssetCatalogTreeItem::get_name() const +{ + return name_; +} + +AssetCatalogPath AssetCatalogTreeItem::catalog_path() const +{ + AssetCatalogPath current_path = name_; + for (const AssetCatalogTreeItem *parent = parent_; parent; parent = parent->parent_) { + current_path = AssetCatalogPath(parent->name_) / current_path; + } + return current_path; +} + +int AssetCatalogTreeItem::count_parents() const +{ + int i = 0; + for (const AssetCatalogTreeItem *parent = parent_; parent; parent = parent->parent_) { + i++; + } + return i; +} + +bool AssetCatalogTreeItem::has_children() const +{ + return !children_.empty(); +} + +/* ---------------------------------------------------------------------- */ + +void AssetCatalogTree::insert_item(const AssetCatalog &catalog) +{ + const AssetCatalogTreeItem *parent = nullptr; + /* The children for the currently iterated component, where the following component should be + * added to (if not there yet). */ + AssetCatalogTreeItem::ChildMap *current_item_children = &root_items_; + + BLI_assert_msg(!ELEM(catalog.path.str()[0], '/', '\\'), + "Malformed catalog path; should not start with a separator"); + + const CatalogID nil_id{}; + + catalog.path.iterate_components([&](StringRef component_name, const bool is_last_component) { + /* Insert new tree element - if no matching one is there yet! */ + auto [key_and_item, was_inserted] = current_item_children->emplace( + component_name, + AssetCatalogTreeItem( + component_name, is_last_component ? catalog.catalog_id : nil_id, parent)); + AssetCatalogTreeItem &item = key_and_item->second; + + /* If full path of this catalog already exists as parent path of a previously read catalog, + * we can ensure this tree item's UUID is set here. */ + if (is_last_component && BLI_uuid_is_nil(item.catalog_id_)) { + item.catalog_id_ = catalog.catalog_id; + } + + /* Walk further into the path (no matter if a new item was created or not). */ + parent = &item; + current_item_children = &item.children_; + }); +} + +void AssetCatalogTree::foreach_item(AssetCatalogTreeItem::ItemIterFn callback) +{ + AssetCatalogTreeItem::foreach_item_recursive(root_items_, callback); +} + +void AssetCatalogTreeItem::foreach_item_recursive(AssetCatalogTreeItem::ChildMap &children, + const ItemIterFn callback) +{ + for (auto &[key, item] : children) { + callback(item); + foreach_item_recursive(item.children_, callback); + } +} + +void AssetCatalogTree::foreach_root_item(const ItemIterFn callback) +{ + for (auto &[key, item] : root_items_) { + callback(item); + } +} + +void AssetCatalogTreeItem::foreach_child(const ItemIterFn callback) +{ + for (auto &[key, item] : children_) { + callback(item); + } +} + +AssetCatalogTree *AssetCatalogService::get_catalog_tree() +{ + return catalog_tree_.get(); +} + +bool AssetCatalogDefinitionFile::contains(const CatalogID catalog_id) const +{ + return catalogs_.contains(catalog_id); +} + +void AssetCatalogDefinitionFile::add_new(AssetCatalog *catalog) +{ + catalogs_.add_new(catalog->catalog_id, catalog); +} + +void AssetCatalogDefinitionFile::parse_catalog_file( + const CatalogFilePath &catalog_definition_file_path, + AssetCatalogParsedFn catalog_loaded_callback) +{ + std::fstream infile(catalog_definition_file_path); + + bool seen_version_number = false; + std::string line; + while (std::getline(infile, line)) { + const StringRef trimmed_line = StringRef(line).trim(); + if (trimmed_line.is_empty() || trimmed_line[0] == '#') { + continue; + } + + if (!seen_version_number) { + /* The very first non-ignored line should be the version declaration. */ + const bool is_valid_version = this->parse_version_line(trimmed_line); + if (!is_valid_version) { + std::cerr << catalog_definition_file_path + << ": first line should be version declaration; ignoring file." << std::endl; + break; + } + seen_version_number = true; + continue; + } + + std::unique_ptr<AssetCatalog> catalog = this->parse_catalog_line(trimmed_line); + if (!catalog) { + continue; + } + + AssetCatalog *non_owning_ptr = catalog.get(); + const bool keep_catalog = catalog_loaded_callback(std::move(catalog)); + if (!keep_catalog) { + continue; + } + + if (this->contains(non_owning_ptr->catalog_id)) { + std::cerr << catalog_definition_file_path << ": multiple definitions of catalog " + << non_owning_ptr->catalog_id << " in the same file, using first occurrence." + << std::endl; + /* Don't store 'catalog'; unique_ptr will free its memory. */ + continue; + } + + /* The AssetDefinitionFile should include this catalog when writing it back to disk. */ + this->add_new(non_owning_ptr); + } +} + +bool AssetCatalogDefinitionFile::parse_version_line(const StringRef line) +{ + if (!line.startswith(VERSION_MARKER)) { + return false; + } + + const std::string version_string = line.substr(VERSION_MARKER.length()); + const int file_version = std::atoi(version_string.c_str()); + + /* No versioning, just a blunt check whether it's the right one. */ + return file_version == SUPPORTED_VERSION; +} + +std::unique_ptr<AssetCatalog> AssetCatalogDefinitionFile::parse_catalog_line(const StringRef line) +{ + const char delim = ':'; + const int64_t first_delim = line.find_first_of(delim); + if (first_delim == StringRef::not_found) { + std::cerr << "Invalid catalog line in " << this->file_path << ": " << line << std::endl; + return std::unique_ptr<AssetCatalog>(nullptr); + } + + /* Parse the catalog ID. */ + const std::string id_as_string = line.substr(0, first_delim).trim(); + bUUID catalog_id; + const bool uuid_parsed_ok = BLI_uuid_parse_string(&catalog_id, id_as_string.c_str()); + if (!uuid_parsed_ok) { + std::cerr << "Invalid UUID in " << this->file_path << ": " << line << std::endl; + return std::unique_ptr<AssetCatalog>(nullptr); + } + + /* Parse the path and simple name. */ + const StringRef path_and_simple_name = line.substr(first_delim + 1); + const int64_t second_delim = path_and_simple_name.find_first_of(delim); + + std::string path_in_file; + std::string simple_name; + if (second_delim == 0) { + /* Delimiter as first character means there is no path. These lines are to be ignored. */ + return std::unique_ptr<AssetCatalog>(nullptr); + } + + if (second_delim == StringRef::not_found) { + /* No delimiter means no simple name, just treat it as all "path". */ + path_in_file = path_and_simple_name; + simple_name = ""; + } + else { + path_in_file = path_and_simple_name.substr(0, second_delim); + simple_name = path_and_simple_name.substr(second_delim + 1).trim(); + } + + AssetCatalogPath catalog_path = path_in_file; + return std::make_unique<AssetCatalog>(catalog_id, catalog_path.cleanup(), simple_name); +} + +bool AssetCatalogDefinitionFile::write_to_disk() const +{ + BLI_assert_msg(!this->file_path.empty(), "Writing to CDF requires its file path to be known"); + return this->write_to_disk(this->file_path); +} + +bool AssetCatalogDefinitionFile::write_to_disk(const CatalogFilePath &dest_file_path) const +{ + const CatalogFilePath writable_path = dest_file_path + ".writing"; + const CatalogFilePath backup_path = dest_file_path + "~"; + + if (!this->write_to_disk_unsafe(writable_path)) { + /* TODO: communicate what went wrong. */ + return false; + } + if (BLI_exists(dest_file_path.c_str())) { + if (BLI_rename(dest_file_path.c_str(), backup_path.c_str())) { + /* TODO: communicate what went wrong. */ + return false; + } + } + if (BLI_rename(writable_path.c_str(), dest_file_path.c_str())) { + /* TODO: communicate what went wrong. */ + return false; + } + + return true; +} + +bool AssetCatalogDefinitionFile::write_to_disk_unsafe(const CatalogFilePath &dest_file_path) const +{ + char directory[PATH_MAX]; + BLI_split_dir_part(dest_file_path.c_str(), directory, sizeof(directory)); + if (!ensure_directory_exists(directory)) { + /* TODO(Sybren): pass errors to the UI somehow. */ + return false; + } + + std::ofstream output(dest_file_path); + + // TODO(@sybren): remember the line ending style that was originally read, then use that to write + // the file again. + + // Write the header. + output << HEADER; + output << "" << std::endl; + output << VERSION_MARKER << SUPPORTED_VERSION << std::endl; + output << "" << std::endl; + + // Write the catalogs, ordered by path (primary) and UUID (secondary). + AssetCatalogOrderedSet catalogs_by_path; + for (const AssetCatalog *catalog : catalogs_.values()) { + if (catalog->flags.is_deleted) { + continue; + } + catalogs_by_path.insert(catalog); + } + + for (const AssetCatalog *catalog : catalogs_by_path) { + output << catalog->catalog_id << ":" << catalog->path << ":" << catalog->simple_name + << std::endl; + } + output.close(); + return !output.bad(); +} + +bool AssetCatalogDefinitionFile::ensure_directory_exists( + const CatalogFilePath directory_path) const +{ + /* TODO(@sybren): design a way to get such errors presented to users (or ensure that they never + * occur). */ + if (directory_path.empty()) { + std::cerr + << "AssetCatalogService: no asset library root configured, unable to ensure it exists." + << std::endl; + return false; + } + + if (BLI_exists(directory_path.data())) { + if (!BLI_is_dir(directory_path.data())) { + std::cerr << "AssetCatalogService: " << directory_path + << " exists but is not a directory, this is not a supported situation." + << std::endl; + return false; + } + + /* Root directory exists, work is done. */ + return true; + } + + /* Ensure the root directory exists. */ + std::error_code err_code; + if (!BLI_dir_create_recursive(directory_path.data())) { + std::cerr << "AssetCatalogService: error creating directory " << directory_path << ": " + << err_code << std::endl; + return false; + } + + /* Root directory has been created, work is done. */ + return true; +} + +AssetCatalog::AssetCatalog(const CatalogID catalog_id, + const AssetCatalogPath &path, + const std::string &simple_name) + : catalog_id(catalog_id), path(path), simple_name(simple_name) +{ +} + +std::unique_ptr<AssetCatalog> AssetCatalog::from_path(const AssetCatalogPath &path) +{ + const AssetCatalogPath clean_path = path.cleanup(); + const CatalogID cat_id = BLI_uuid_generate_random(); + const std::string simple_name = sensible_simple_name_for_path(clean_path); + auto catalog = std::make_unique<AssetCatalog>(cat_id, clean_path, simple_name); + return catalog; +} + +std::string AssetCatalog::sensible_simple_name_for_path(const AssetCatalogPath &path) +{ + std::string name = path.str(); + std::replace(name.begin(), name.end(), AssetCatalogPath::SEPARATOR, '-'); + if (name.length() < MAX_NAME - 1) { + return name; + } + + /* Trim off the start of the path, as that's the most generic part and thus contains the least + * information. */ + return "..." + name.substr(name.length() - 60); +} + +AssetCatalogFilter::AssetCatalogFilter(Set<CatalogID> &&matching_catalog_ids) + : matching_catalog_ids(std::move(matching_catalog_ids)) +{ +} + +bool AssetCatalogFilter::contains(const CatalogID asset_catalog_id) const +{ + return matching_catalog_ids.contains(asset_catalog_id); +} + +} // namespace blender::bke diff --git a/source/blender/blenkernel/intern/asset_catalog_path.cc b/source/blender/blenkernel/intern/asset_catalog_path.cc new file mode 100644 index 00000000000..85b8969cb8c --- /dev/null +++ b/source/blender/blenkernel/intern/asset_catalog_path.cc @@ -0,0 +1,228 @@ +/* + * 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 bke + */ + +#include "BKE_asset_catalog_path.hh" + +#include "BLI_path_util.h" + +namespace blender::bke { + +const char AssetCatalogPath::SEPARATOR = '/'; + +AssetCatalogPath::AssetCatalogPath(const std::string &path) : path_(path) +{ +} + +AssetCatalogPath::AssetCatalogPath(StringRef path) : path_(path) +{ +} + +AssetCatalogPath::AssetCatalogPath(const char *path) : path_(path) +{ +} + +AssetCatalogPath::AssetCatalogPath(AssetCatalogPath &&other_path) noexcept + : path_(std::move(other_path.path_)) +{ +} + +uint64_t AssetCatalogPath::hash() const +{ + std::hash<std::string> hasher{}; + return hasher(this->path_); +} + +uint64_t AssetCatalogPath::length() const +{ + return this->path_.length(); +} + +const char *AssetCatalogPath::c_str() const +{ + return this->path_.c_str(); +} + +const std::string &AssetCatalogPath::str() const +{ + return this->path_; +} + +/* In-class operators, because of the implicit `AssetCatalogPath(StringRef)` constructor. + * Otherwise `string == string` could cast both sides to `AssetCatalogPath`. */ +bool AssetCatalogPath::operator==(const AssetCatalogPath &other_path) const +{ + return this->path_ == other_path.path_; +} + +bool AssetCatalogPath::operator!=(const AssetCatalogPath &other_path) const +{ + return !(*this == other_path); +} + +bool AssetCatalogPath::operator<(const AssetCatalogPath &other_path) const +{ + return this->path_ < other_path.path_; +} + +AssetCatalogPath AssetCatalogPath::operator/(const AssetCatalogPath &path_to_append) const +{ + /* `"" / "path"` or `"path" / ""` should just result in `"path"` */ + if (!*this) { + return path_to_append; + } + if (!path_to_append) { + return *this; + } + + std::stringstream new_path; + new_path << this->path_ << SEPARATOR << path_to_append.path_; + return AssetCatalogPath(new_path.str()); +} + +AssetCatalogPath::operator bool() const +{ + return !this->path_.empty(); +} + +std::ostream &operator<<(std::ostream &stream, const AssetCatalogPath &path_to_append) +{ + stream << path_to_append.path_; + return stream; +} + +AssetCatalogPath AssetCatalogPath::cleanup() const +{ + std::stringstream clean_components; + bool first_component_seen = false; + + this->iterate_components([&clean_components, &first_component_seen](StringRef component_name, + bool /*is_last_component*/) { + const std::string clean_component = cleanup_component(component_name); + + if (clean_component.empty()) { + /* These are caused by leading, trailing, or double slashes. */ + return; + } + + /* If a previous path component has been streamed already, we need a path separator. This + * cannot use the `is_last_component` boolean, because the last component might be skipped due + * to the condition above. */ + if (first_component_seen) { + clean_components << SEPARATOR; + } + first_component_seen = true; + + clean_components << clean_component; + }); + + return AssetCatalogPath(clean_components.str()); +} + +std::string AssetCatalogPath::cleanup_component(StringRef component) +{ + std::string cleaned = component.trim(); + /* Replace colons with something else, as those are used in the CDF file as delimiter. */ + std::replace(cleaned.begin(), cleaned.end(), ':', '-'); + return cleaned; +} + +bool AssetCatalogPath::is_contained_in(const AssetCatalogPath &other_path) const +{ + if (!other_path) { + /* The empty path contains all other paths. */ + return true; + } + + if (this->path_ == other_path.path_) { + /* Weak is-in relation: equal paths contain each other. */ + return true; + } + + /* To be a child path of 'other_path', our path must be at least a separator and another + * character longer. */ + if (this->length() < other_path.length() + 2) { + return false; + } + + /* Create StringRef to be able to use .startswith(). */ + const StringRef this_path(this->path_); + const bool prefix_ok = this_path.startswith(other_path.path_); + const char next_char = this_path[other_path.length()]; + return prefix_ok && next_char == SEPARATOR; +} + +AssetCatalogPath AssetCatalogPath::parent() const +{ + if (!*this) { + return AssetCatalogPath(""); + } + std::string::size_type last_sep_index = this->path_.rfind(SEPARATOR); + if (last_sep_index == std::string::npos) { + return AssetCatalogPath(""); + } + return AssetCatalogPath(this->path_.substr(0, last_sep_index)); +} + +void AssetCatalogPath::iterate_components(ComponentIteratorFn callback) const +{ + const char *next_slash_ptr; + + for (const char *path_component = this->path_.data(); path_component && path_component[0]; + /* Jump to one after the next slash if there is any. */ + path_component = next_slash_ptr ? next_slash_ptr + 1 : nullptr) { + next_slash_ptr = BLI_path_slash_find(path_component); + + const bool is_last_component = next_slash_ptr == nullptr; + /* Note that this won't be null terminated. */ + const StringRef component_name = is_last_component ? + path_component : + StringRef(path_component, + next_slash_ptr - path_component); + + callback(component_name, is_last_component); + } +} + +AssetCatalogPath AssetCatalogPath::rebase(const AssetCatalogPath &from_path, + const AssetCatalogPath &to_path) const +{ + if (!from_path) { + if (!to_path) { + return AssetCatalogPath(""); + } + return to_path / *this; + } + + if (!this->is_contained_in(from_path)) { + return AssetCatalogPath(""); + } + + if (*this == from_path) { + /* Early return, because otherwise the length+1 below is going to cause problems. */ + return to_path; + } + + /* When from_path = "test", we need to skip "test/" to get the rest of the path, hence the +1. */ + const StringRef suffix = StringRef(this->path_).substr(from_path.length() + 1); + const AssetCatalogPath path_suffix(suffix); + return to_path / path_suffix; +} + +} // namespace blender::bke diff --git a/source/blender/blenkernel/intern/asset_catalog_path_test.cc b/source/blender/blenkernel/intern/asset_catalog_path_test.cc new file mode 100644 index 00000000000..af15cbf405a --- /dev/null +++ b/source/blender/blenkernel/intern/asset_catalog_path_test.cc @@ -0,0 +1,251 @@ +/* + * 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 Blender Foundation + * All rights reserved. + */ + +#include "BKE_asset_catalog_path.hh" + +#include "BLI_set.hh" +#include "BLI_vector.hh" + +#include <set> +#include <sstream> + +#include "testing/testing.h" + +namespace blender::bke::tests { + +TEST(AssetCatalogPathTest, construction) +{ + AssetCatalogPath from_char_literal("the/path"); + + const std::string str_const = "the/path"; + AssetCatalogPath from_string_constant(str_const); + + std::string str_variable = "the/path"; + AssetCatalogPath from_string_variable(str_variable); + + std::string long_string = "this is a long/string/with/a/path in the middle"; + StringRef long_string_ref(long_string); + StringRef middle_bit = long_string_ref.substr(10, 23); + AssetCatalogPath from_string_ref(middle_bit); + EXPECT_EQ(from_string_ref, "long/string/with/a/path"); +} + +TEST(AssetCatalogPathTest, length) +{ + const AssetCatalogPath one("1"); + EXPECT_EQ(1, one.length()); + + const AssetCatalogPath empty(""); + EXPECT_EQ(0, empty.length()); + + const AssetCatalogPath utf8("some/родитель"); + EXPECT_EQ(21, utf8.length()) << "13 characters should be 21 bytes."; +} + +TEST(AssetCatalogPathTest, comparison_operators) +{ + const AssetCatalogPath empty(""); + const AssetCatalogPath the_path("the/path"); + const AssetCatalogPath the_path_child("the/path/child"); + const AssetCatalogPath unrelated_path("unrelated/path"); + const AssetCatalogPath other_instance_same_path("the/path"); + + EXPECT_LT(empty, the_path); + EXPECT_LT(the_path, the_path_child); + EXPECT_LT(the_path, unrelated_path); + + EXPECT_EQ(empty, empty) << "Identical empty instances should compare equal."; + EXPECT_EQ(empty, "") << "Comparison to empty string should be possible."; + EXPECT_EQ(the_path, the_path) << "Identical non-empty instances should compare equal."; + EXPECT_EQ(the_path, "the/path") << "Comparison to string should be possible."; + EXPECT_EQ(the_path, other_instance_same_path) + << "Different instances with equal path should compare equal."; + + EXPECT_NE(the_path, the_path_child); + EXPECT_NE(the_path, unrelated_path); + EXPECT_NE(the_path, empty); + + EXPECT_FALSE(empty); + EXPECT_TRUE(the_path); +} + +TEST(AssetCatalogPathTest, move_semantics) +{ + AssetCatalogPath source_path("source/path"); + EXPECT_TRUE(source_path); + + AssetCatalogPath dest_path = std::move(source_path); + EXPECT_FALSE(source_path); + EXPECT_TRUE(dest_path); +} + +TEST(AssetCatalogPathTest, concatenation) +{ + AssetCatalogPath some_parent("some/родитель"); + AssetCatalogPath child = some_parent / "ребенок"; + + EXPECT_EQ(some_parent, "some/родитель") + << "Appending a child path should not modify the parent."; + EXPECT_EQ(child, "some/родитель/ребенок"); + + AssetCatalogPath appended_compound_path = some_parent / "ребенок/внук"; + EXPECT_EQ(appended_compound_path, "some/родитель/ребенок/внук"); + + AssetCatalogPath empty(""); + AssetCatalogPath child_of_the_void = empty / "child"; + EXPECT_EQ(child_of_the_void, "child") + << "Appending to an empty path should not create an initial slash."; + + AssetCatalogPath parent_of_the_void = some_parent / empty; + EXPECT_EQ(parent_of_the_void, "some/родитель") + << "Prepending to an empty path should not create a trailing slash."; + + std::string subpath = "child"; + AssetCatalogPath concatenated_with_string = some_parent / subpath; + EXPECT_EQ(concatenated_with_string, "some/родитель/child"); +} + +TEST(AssetCatalogPathTest, hashable) +{ + AssetCatalogPath path("heyyyyy"); + + std::set<AssetCatalogPath> path_std_set; + path_std_set.insert(path); + + blender::Set<AssetCatalogPath> path_blender_set; + path_blender_set.add(path); +} + +TEST(AssetCatalogPathTest, stream_operator) +{ + AssetCatalogPath path("путь/в/Пермь"); + std::stringstream sstream; + sstream << path; + EXPECT_EQ("путь/в/Пермь", sstream.str()); +} + +TEST(AssetCatalogPathTest, is_contained_in) +{ + const AssetCatalogPath catpath("simple/path/child"); + EXPECT_FALSE(catpath.is_contained_in("unrelated")); + EXPECT_FALSE(catpath.is_contained_in("sim")); + EXPECT_FALSE(catpath.is_contained_in("simple/pathx")); + EXPECT_FALSE(catpath.is_contained_in("simple/path/c")); + EXPECT_FALSE(catpath.is_contained_in("simple/path/child/grandchild")); + EXPECT_FALSE(catpath.is_contained_in("simple/path/")) + << "Non-normalized paths are not expected to work."; + + EXPECT_TRUE(catpath.is_contained_in("")); + EXPECT_TRUE(catpath.is_contained_in("simple")); + EXPECT_TRUE(catpath.is_contained_in("simple/path")); + + /* Test with some UTF8 non-ASCII characters. */ + AssetCatalogPath some_parent("some/родитель"); + AssetCatalogPath child = some_parent / "ребенок"; + + EXPECT_TRUE(child.is_contained_in(some_parent)); + EXPECT_TRUE(child.is_contained_in("some")); + + AssetCatalogPath appended_compound_path = some_parent / "ребенок/внук"; + EXPECT_TRUE(appended_compound_path.is_contained_in(some_parent)); + EXPECT_TRUE(appended_compound_path.is_contained_in(child)); + + /* Test "going up" directory-style. */ + AssetCatalogPath child_with_dotdot = some_parent / "../../other/hierarchy/part"; + EXPECT_TRUE(child_with_dotdot.is_contained_in(some_parent)) + << "dotdot path components should have no meaning"; +} + +TEST(AssetCatalogPathTest, cleanup) +{ + AssetCatalogPath ugly_path("/ some / родитель / "); + AssetCatalogPath clean_path = ugly_path.cleanup(); + + EXPECT_EQ(AssetCatalogPath("/ some / родитель / "), ugly_path) + << "cleanup should not modify the path instance itself"; + + EXPECT_EQ(AssetCatalogPath("some/родитель"), clean_path); + + AssetCatalogPath double_slashed("some//родитель"); + EXPECT_EQ(AssetCatalogPath("some/родитель"), double_slashed.cleanup()); + + AssetCatalogPath with_colons("some/key:subkey=value/path"); + EXPECT_EQ(AssetCatalogPath("some/key-subkey=value/path"), with_colons.cleanup()); +} + +TEST(AssetCatalogPathTest, iterate_components) +{ + AssetCatalogPath path("путь/в/Пермь"); + Vector<std::pair<std::string, bool>> seen_components; + + path.iterate_components([&seen_components](StringRef component_name, bool is_last_component) { + std::pair<std::string, bool> parameter_pair = std::make_pair<std::string, bool>( + component_name, bool(is_last_component)); + seen_components.append(parameter_pair); + }); + + ASSERT_EQ(3, seen_components.size()); + + EXPECT_EQ("путь", seen_components[0].first); + EXPECT_EQ("в", seen_components[1].first); + EXPECT_EQ("Пермь", seen_components[2].first); + + EXPECT_FALSE(seen_components[0].second); + EXPECT_FALSE(seen_components[1].second); + EXPECT_TRUE(seen_components[2].second); +} + +TEST(AssetCatalogPathTest, rebase) +{ + AssetCatalogPath path("some/path/to/some/catalog"); + EXPECT_EQ(path.rebase("some/path", "new/base"), "new/base/to/some/catalog"); + EXPECT_EQ(path.rebase("", "new/base"), "new/base/some/path/to/some/catalog"); + + EXPECT_EQ(path.rebase("some/path/to/some/catalog", "some/path/to/some/catalog"), + "some/path/to/some/catalog") + << "Rebasing to itself should not change the path."; + + EXPECT_EQ(path.rebase("path/to", "new/base"), "") + << "Non-matching base path should return empty string to indicate 'NO'."; + + /* Empty strings should be handled without crashing or other nasty side-effects. */ + AssetCatalogPath empty(""); + EXPECT_EQ(empty.rebase("path/to", "new/base"), ""); + EXPECT_EQ(empty.rebase("", "new/base"), "new/base"); + EXPECT_EQ(empty.rebase("", ""), ""); +} + +TEST(AssetCatalogPathTest, parent) +{ + const AssetCatalogPath ascii_path("path/with/missing/parents"); + EXPECT_EQ(ascii_path.parent(), "path/with/missing"); + + const AssetCatalogPath path("путь/в/Пермь/долог/и/далек"); + EXPECT_EQ(path.parent(), "путь/в/Пермь/долог/и"); + EXPECT_EQ(path.parent().parent(), "путь/в/Пермь/долог"); + EXPECT_EQ(path.parent().parent().parent(), "путь/в/Пермь"); + + const AssetCatalogPath one_level("one"); + EXPECT_EQ(one_level.parent(), ""); + + const AssetCatalogPath empty(""); + EXPECT_EQ(empty.parent(), ""); +} + +} // namespace blender::bke::tests diff --git a/source/blender/blenkernel/intern/asset_catalog_test.cc b/source/blender/blenkernel/intern/asset_catalog_test.cc new file mode 100644 index 00000000000..fb471a8ee7b --- /dev/null +++ b/source/blender/blenkernel/intern/asset_catalog_test.cc @@ -0,0 +1,965 @@ +/* + * 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 Blender Foundation + * All rights reserved. + */ + +#include "BKE_appdir.h" +#include "BKE_asset_catalog.hh" +#include "BKE_preferences.h" + +#include "BLI_fileops.h" +#include "BLI_path_util.h" + +#include "DNA_userdef_types.h" + +#include "testing/testing.h" + +namespace blender::bke::tests { + +/* UUIDs from lib/tests/asset_library/blender_assets.cats.txt */ +const bUUID UUID_ID_WITHOUT_PATH("e34dd2c5-5d2e-4668-9794-1db5de2a4f71"); +const bUUID UUID_POSES_ELLIE("df60e1f6-2259-475b-93d9-69a1b4a8db78"); +const bUUID UUID_POSES_ELLIE_WHITESPACE("b06132f6-5687-4751-a6dd-392740eb3c46"); +const bUUID UUID_POSES_ELLIE_TRAILING_SLASH("3376b94b-a28d-4d05-86c1-bf30b937130d"); +const bUUID UUID_POSES_RUZENA("79a4f887-ab60-4bd4-94da-d572e27d6aed"); +const bUUID UUID_POSES_RUZENA_HAND("81811c31-1a88-4bd7-bb34-c6fc2607a12e"); +const bUUID UUID_POSES_RUZENA_FACE("82162c1f-06cc-4d91-a9bf-4f72c104e348"); +const bUUID UUID_WITHOUT_SIMPLENAME("d7916a31-6ca9-4909-955f-182ca2b81fa3"); + +/* UUIDs from lib/tests/asset_library/modified_assets.cats.txt */ +const bUUID UUID_AGENT_47("c5744ba5-43f5-4f73-8e52-010ad4a61b34"); + +/* Subclass that adds accessors such that protected fields can be used in tests. */ +class TestableAssetCatalogService : public AssetCatalogService { + public: + TestableAssetCatalogService() = default; + + explicit TestableAssetCatalogService(const CatalogFilePath &asset_library_root) + : AssetCatalogService(asset_library_root) + { + } + + AssetCatalogDefinitionFile *get_catalog_definition_file() + { + return catalog_definition_file_.get(); + } + + void create_missing_catalogs() + { + AssetCatalogService::create_missing_catalogs(); + } + + int64_t count_catalogs_with_path(const CatalogFilePath &path) + { + int64_t count = 0; + for (auto &catalog_uptr : catalogs_.values()) { + if (catalog_uptr->path == path) { + count++; + } + } + return count; + } +}; + +class AssetCatalogTest : public testing::Test { + protected: + CatalogFilePath asset_library_root_; + CatalogFilePath temp_library_path_; + + void SetUp() override + { + const std::string test_files_dir = blender::tests::flags_test_asset_dir(); + if (test_files_dir.empty()) { + FAIL(); + } + + asset_library_root_ = test_files_dir + "/" + "asset_library"; + temp_library_path_ = ""; + } + + /* Register a temporary path, which will be removed at the end of the test. + * The returned path ends in a slash. */ + CatalogFilePath use_temp_path() + { + BKE_tempdir_init(""); + const CatalogFilePath tempdir = BKE_tempdir_session(); + temp_library_path_ = tempdir + "test-temporary-path/"; + return temp_library_path_; + } + + CatalogFilePath create_temp_path() + { + CatalogFilePath path = use_temp_path(); + BLI_dir_create_recursive(path.c_str()); + return path; + } + + struct CatalogPathInfo { + StringRef name; + int parent_count; + }; + + void assert_expected_item(const CatalogPathInfo &expected_path, + const AssetCatalogTreeItem &actual_item) + { + char expected_filename[FILE_MAXFILE]; + /* Is the catalog name as expected? "character", "Ellie", ... */ + BLI_split_file_part(expected_path.name.data(), expected_filename, sizeof(expected_filename)); + EXPECT_EQ(expected_filename, actual_item.get_name()); + /* Does the computed number of parents match? */ + EXPECT_EQ(expected_path.parent_count, actual_item.count_parents()); + EXPECT_EQ(expected_path.name, actual_item.catalog_path().str()); + } + + /** + * Recursively iterate over all tree items using #AssetCatalogTree::foreach_item() and check if + * the items map exactly to \a expected_paths. + */ + void assert_expected_tree_items(AssetCatalogTree *tree, + const std::vector<CatalogPathInfo> &expected_paths) + { + int i = 0; + tree->foreach_item([&](const AssetCatalogTreeItem &actual_item) { + ASSERT_LT(i, expected_paths.size()) + << "More catalogs in tree than expected; did not expect " << actual_item.catalog_path(); + assert_expected_item(expected_paths[i], actual_item); + i++; + }); + } + + /** + * Iterate over the root items of \a tree and check if the items map exactly to \a + * expected_paths. Similar to #assert_expected_tree_items() but calls + * #AssetCatalogTree::foreach_root_item() instead of #AssetCatalogTree::foreach_item(). + */ + void assert_expected_tree_root_items(AssetCatalogTree *tree, + const std::vector<CatalogPathInfo> &expected_paths) + { + int i = 0; + tree->foreach_root_item([&](const AssetCatalogTreeItem &actual_item) { + ASSERT_LT(i, expected_paths.size()) + << "More catalogs in tree root than expected; did not expect " + << actual_item.catalog_path(); + assert_expected_item(expected_paths[i], actual_item); + i++; + }); + } + + /** + * Iterate over the child items of \a parent_item and check if the items map exactly to \a + * expected_paths. Similar to #assert_expected_tree_items() but calls + * #AssetCatalogTreeItem::foreach_child() instead of #AssetCatalogTree::foreach_item(). + */ + void assert_expected_tree_item_child_items(AssetCatalogTreeItem *parent_item, + const std::vector<CatalogPathInfo> &expected_paths) + { + int i = 0; + parent_item->foreach_child([&](const AssetCatalogTreeItem &actual_item) { + ASSERT_LT(i, expected_paths.size()) + << "More catalogs in tree item than expected; did not expect " + << actual_item.catalog_path(); + assert_expected_item(expected_paths[i], actual_item); + i++; + }); + } + + void TearDown() override + { + if (!temp_library_path_.empty()) { + BLI_delete(temp_library_path_.c_str(), true, true); + temp_library_path_ = ""; + } + } +}; + +TEST_F(AssetCatalogTest, load_single_file) +{ + AssetCatalogService service(asset_library_root_); + service.load_from_disk(asset_library_root_ + "/" + "blender_assets.cats.txt"); + + /* Test getting a non-existent catalog ID. */ + EXPECT_EQ(nullptr, service.find_catalog(BLI_uuid_generate_random())); + + /* Test getting an invalid catalog (without path definition). */ + AssetCatalog *cat_without_path = service.find_catalog(UUID_ID_WITHOUT_PATH); + ASSERT_EQ(nullptr, cat_without_path); + + /* Test getting a regular catalog. */ + AssetCatalog *poses_ellie = service.find_catalog(UUID_POSES_ELLIE); + ASSERT_NE(nullptr, poses_ellie); + EXPECT_EQ(UUID_POSES_ELLIE, poses_ellie->catalog_id); + EXPECT_EQ("character/Ellie/poselib", poses_ellie->path.str()); + EXPECT_EQ("POSES_ELLIE", poses_ellie->simple_name); + + /* Test white-space stripping and support in the path. */ + AssetCatalog *poses_whitespace = service.find_catalog(UUID_POSES_ELLIE_WHITESPACE); + ASSERT_NE(nullptr, poses_whitespace); + EXPECT_EQ(UUID_POSES_ELLIE_WHITESPACE, poses_whitespace->catalog_id); + EXPECT_EQ("character/Ellie/poselib/white space", poses_whitespace->path.str()); + EXPECT_EQ("POSES_ELLIE WHITESPACE", poses_whitespace->simple_name); + + /* Test getting a UTF-8 catalog ID. */ + AssetCatalog *poses_ruzena = service.find_catalog(UUID_POSES_RUZENA); + ASSERT_NE(nullptr, poses_ruzena); + EXPECT_EQ(UUID_POSES_RUZENA, poses_ruzena->catalog_id); + EXPECT_EQ("character/Ružena/poselib", poses_ruzena->path.str()); + EXPECT_EQ("POSES_RUŽENA", poses_ruzena->simple_name); +} + +TEST_F(AssetCatalogTest, insert_item_into_tree) +{ + { + AssetCatalogTree tree; + std::unique_ptr<AssetCatalog> catalog_empty_path = AssetCatalog::from_path(""); + tree.insert_item(*catalog_empty_path); + + assert_expected_tree_items(&tree, {}); + } + + { + AssetCatalogTree tree; + + std::unique_ptr<AssetCatalog> catalog = AssetCatalog::from_path("item"); + tree.insert_item(*catalog); + assert_expected_tree_items(&tree, {{"item", 0}}); + + /* Insert child after parent already exists. */ + std::unique_ptr<AssetCatalog> child_catalog = AssetCatalog::from_path("item/child"); + tree.insert_item(*catalog); + assert_expected_tree_items(&tree, {{"item", 0}, {"item/child", 1}}); + + std::vector<CatalogPathInfo> expected_paths; + + /* Test inserting multi-component sub-path. */ + std::unique_ptr<AssetCatalog> grandgrandchild_catalog = AssetCatalog::from_path( + "item/child/grandchild/grandgrandchild"); + tree.insert_item(*catalog); + expected_paths = {{"item", 0}, + {"item/child", 1}, + {"item/child/grandchild", 2}, + {"item/child/grandchild/grandgrandchild", 3}}; + assert_expected_tree_items(&tree, expected_paths); + + std::unique_ptr<AssetCatalog> root_level_catalog = AssetCatalog::from_path("root level"); + tree.insert_item(*catalog); + expected_paths = {{"item", 0}, + {"item/child", 1}, + {"item/child/grandchild", 2}, + {"item/child/grandchild/grandgrandchild", 3}, + {"root level", 0}}; + assert_expected_tree_items(&tree, expected_paths); + } + + { + AssetCatalogTree tree; + + std::unique_ptr<AssetCatalog> catalog = AssetCatalog::from_path("item/child"); + tree.insert_item(*catalog); + assert_expected_tree_items(&tree, {{"item", 0}, {"item/child", 1}}); + } + + { + AssetCatalogTree tree; + + std::unique_ptr<AssetCatalog> catalog = AssetCatalog::from_path("white space"); + tree.insert_item(*catalog); + assert_expected_tree_items(&tree, {{"white space", 0}}); + } + + { + AssetCatalogTree tree; + + std::unique_ptr<AssetCatalog> catalog = AssetCatalog::from_path("/item/white space"); + tree.insert_item(*catalog); + assert_expected_tree_items(&tree, {{"item", 0}, {"item/white space", 1}}); + } + + { + AssetCatalogTree tree; + + std::unique_ptr<AssetCatalog> catalog_unicode_path = AssetCatalog::from_path("Ružena"); + tree.insert_item(*catalog_unicode_path); + assert_expected_tree_items(&tree, {{"Ružena", 0}}); + + catalog_unicode_path = AssetCatalog::from_path("Ružena/Ružena"); + tree.insert_item(*catalog_unicode_path); + assert_expected_tree_items(&tree, {{"Ružena", 0}, {"Ružena/Ružena", 1}}); + } +} + +TEST_F(AssetCatalogTest, load_single_file_into_tree) +{ + AssetCatalogService service(asset_library_root_); + service.load_from_disk(asset_library_root_ + "/" + "blender_assets.cats.txt"); + + /* Contains not only paths from the CDF but also the missing parents (implicitly defined + * catalogs). */ + std::vector<CatalogPathInfo> expected_paths{ + {"character", 0}, + {"character/Ellie", 1}, + {"character/Ellie/poselib", 2}, + {"character/Ellie/poselib/tailslash", 3}, + {"character/Ellie/poselib/white space", 3}, + {"character/Ružena", 1}, + {"character/Ružena/poselib", 2}, + {"character/Ružena/poselib/face", 3}, + {"character/Ružena/poselib/hand", 3}, + {"path", 0}, /* Implicit. */ + {"path/without", 1}, /* Implicit. */ + {"path/without/simplename", 2}, /* From CDF. */ + }; + + AssetCatalogTree *tree = service.get_catalog_tree(); + assert_expected_tree_items(tree, expected_paths); +} + +TEST_F(AssetCatalogTest, foreach_in_tree) +{ + { + AssetCatalogTree tree{}; + const std::vector<CatalogPathInfo> no_catalogs{}; + + assert_expected_tree_items(&tree, no_catalogs); + assert_expected_tree_root_items(&tree, no_catalogs); + /* Need a root item to check child items. */ + std::unique_ptr<AssetCatalog> catalog = AssetCatalog::from_path("something"); + tree.insert_item(*catalog); + tree.foreach_root_item([&no_catalogs, this](AssetCatalogTreeItem &item) { + assert_expected_tree_item_child_items(&item, no_catalogs); + }); + } + + AssetCatalogService service(asset_library_root_); + service.load_from_disk(asset_library_root_ + "/" + "blender_assets.cats.txt"); + + std::vector<CatalogPathInfo> expected_root_items{{"character", 0}, {"path", 0}}; + AssetCatalogTree *tree = service.get_catalog_tree(); + assert_expected_tree_root_items(tree, expected_root_items); + + /* Test if the direct children of the root item are what's expected. */ + std::vector<std::vector<CatalogPathInfo>> expected_root_child_items = { + /* Children of the "character" root item. */ + {{"character/Ellie", 1}, {"character/Ružena", 1}}, + /* Children of the "path" root item. */ + {{"path/without", 1}}, + }; + int i = 0; + tree->foreach_root_item([&expected_root_child_items, &i, this](AssetCatalogTreeItem &item) { + assert_expected_tree_item_child_items(&item, expected_root_child_items[i]); + i++; + }); +} + +TEST_F(AssetCatalogTest, find_catalog_by_path) +{ + TestableAssetCatalogService service(asset_library_root_); + service.load_from_disk(asset_library_root_ + "/" + + AssetCatalogService::DEFAULT_CATALOG_FILENAME); + + AssetCatalog *catalog; + + EXPECT_EQ(nullptr, service.find_catalog_by_path("")); + catalog = service.find_catalog_by_path("character/Ellie/poselib/white space"); + EXPECT_NE(nullptr, catalog); + EXPECT_EQ(UUID_POSES_ELLIE_WHITESPACE, catalog->catalog_id); + catalog = service.find_catalog_by_path("character/Ružena/poselib"); + EXPECT_NE(nullptr, catalog); + EXPECT_EQ(UUID_POSES_RUZENA, catalog->catalog_id); + + /* "character/Ellie/poselib" is used by two catalogs. Check if it's using the first one. */ + catalog = service.find_catalog_by_path("character/Ellie/poselib"); + EXPECT_NE(nullptr, catalog); + EXPECT_EQ(UUID_POSES_ELLIE, catalog->catalog_id); + EXPECT_NE(UUID_POSES_ELLIE_TRAILING_SLASH, catalog->catalog_id); +} + +TEST_F(AssetCatalogTest, write_single_file) +{ + TestableAssetCatalogService service(asset_library_root_); + service.load_from_disk(asset_library_root_ + "/" + + AssetCatalogService::DEFAULT_CATALOG_FILENAME); + + const CatalogFilePath save_to_path = use_temp_path() + + AssetCatalogService::DEFAULT_CATALOG_FILENAME; + AssetCatalogDefinitionFile *cdf = service.get_catalog_definition_file(); + cdf->write_to_disk(save_to_path); + + AssetCatalogService loaded_service(save_to_path); + loaded_service.load_from_disk(); + + /* Test that the expected catalogs are there. */ + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE)); + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE_WHITESPACE)); + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE_TRAILING_SLASH)); + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_RUZENA)); + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_RUZENA_HAND)); + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_RUZENA_FACE)); + + /* Test that the invalid catalog definition wasn't copied. */ + EXPECT_EQ(nullptr, loaded_service.find_catalog(UUID_ID_WITHOUT_PATH)); + + /* TODO(@sybren): test ordering of catalogs in the file. */ +} + +TEST_F(AssetCatalogTest, no_writing_empty_files) +{ + const CatalogFilePath temp_lib_root = create_temp_path(); + AssetCatalogService service(temp_lib_root); + service.write_to_disk_on_blendfile_save(temp_lib_root + "phony.blend"); + + const CatalogFilePath default_cdf_path = temp_lib_root + + AssetCatalogService::DEFAULT_CATALOG_FILENAME; + EXPECT_FALSE(BLI_exists(default_cdf_path.c_str())); +} + +/* Already loaded a CDF, saving to some unrelated directory. */ +TEST_F(AssetCatalogTest, on_blendfile_save__with_existing_cdf) +{ + const CatalogFilePath top_level_dir = create_temp_path(); /* Has trailing slash. */ + + /* Create a copy of the CDF in SVN, so we can safely write to it. */ + const CatalogFilePath original_cdf_file = asset_library_root_ + "/blender_assets.cats.txt"; + const CatalogFilePath cdf_dirname = top_level_dir + "other_dir/"; + const CatalogFilePath cdf_filename = cdf_dirname + AssetCatalogService::DEFAULT_CATALOG_FILENAME; + ASSERT_TRUE(BLI_dir_create_recursive(cdf_dirname.c_str())); + ASSERT_EQ(0, BLI_copy(original_cdf_file.c_str(), cdf_filename.c_str())) + << "Unable to copy " << original_cdf_file << " to " << cdf_filename; + + /* Load the CDF, add a catalog, and trigger a write. This should write to the loaded CDF. */ + TestableAssetCatalogService service(cdf_filename); + service.load_from_disk(); + const AssetCatalog *cat = service.create_catalog("some/catalog/path"); + + const CatalogFilePath blendfilename = top_level_dir + "subdir/some_file.blend"; + ASSERT_TRUE(service.write_to_disk_on_blendfile_save(blendfilename)); + EXPECT_EQ(cdf_filename, service.get_catalog_definition_file()->file_path); + + /* Test that the CDF was created in the expected location. */ + const CatalogFilePath backup_filename = cdf_filename + "~"; + EXPECT_TRUE(BLI_exists(cdf_filename.c_str())); + EXPECT_TRUE(BLI_exists(backup_filename.c_str())) + << "Overwritten CDF should have been backed up."; + + /* Test that the on-disk CDF contains the expected catalogs. */ + AssetCatalogService loaded_service(cdf_filename); + loaded_service.load_from_disk(); + EXPECT_NE(nullptr, loaded_service.find_catalog(cat->catalog_id)) + << "Expected to see the newly-created catalog."; + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE)) + << "Expected to see the already-existing catalog."; +} + +/* Create some catalogs in memory, save to directory that doesn't contain anything else. */ +TEST_F(AssetCatalogTest, on_blendfile_save__from_memory_into_empty_directory) +{ + const CatalogFilePath target_dir = create_temp_path(); /* Has trailing slash. */ + + TestableAssetCatalogService service; + const AssetCatalog *cat = service.create_catalog("some/catalog/path"); + + const CatalogFilePath blendfilename = target_dir + "some_file.blend"; + ASSERT_TRUE(service.write_to_disk_on_blendfile_save(blendfilename)); + + /* Test that the CDF was created in the expected location. */ + const CatalogFilePath expected_cdf_path = target_dir + + AssetCatalogService::DEFAULT_CATALOG_FILENAME; + EXPECT_TRUE(BLI_exists(expected_cdf_path.c_str())); + + /* Test that the in-memory CDF has been created, and contains the expected catalog. */ + AssetCatalogDefinitionFile *cdf = service.get_catalog_definition_file(); + ASSERT_NE(nullptr, cdf); + EXPECT_TRUE(cdf->contains(cat->catalog_id)); + + /* Test that the on-disk CDF contains the expected catalog. */ + AssetCatalogService loaded_service(expected_cdf_path); + loaded_service.load_from_disk(); + EXPECT_NE(nullptr, loaded_service.find_catalog(cat->catalog_id)); +} + +/* Create some catalogs in memory, save to directory that contains a default CDF. */ +TEST_F(AssetCatalogTest, on_blendfile_save__from_memory_into_existing_cdf_and_merge) +{ + const CatalogFilePath target_dir = create_temp_path(); /* Has trailing slash. */ + const CatalogFilePath original_cdf_file = asset_library_root_ + "/blender_assets.cats.txt"; + const CatalogFilePath writable_cdf_file = target_dir + + AssetCatalogService::DEFAULT_CATALOG_FILENAME; + ASSERT_EQ(0, BLI_copy(original_cdf_file.c_str(), writable_cdf_file.c_str())); + + /* Create the catalog service without loading the already-existing CDF. */ + TestableAssetCatalogService service; + const AssetCatalog *cat = service.create_catalog("some/catalog/path"); + + /* Mock that the blend file is written to a subdirectory of the asset library. */ + const CatalogFilePath blendfilename = target_dir + "some_file.blend"; + ASSERT_TRUE(service.write_to_disk_on_blendfile_save(blendfilename)); + + /* Test that the CDF still exists in the expected location. */ + const CatalogFilePath backup_filename = writable_cdf_file + "~"; + EXPECT_TRUE(BLI_exists(writable_cdf_file.c_str())); + EXPECT_TRUE(BLI_exists(backup_filename.c_str())) + << "Overwritten CDF should have been backed up."; + + /* Test that the in-memory CDF has the expected file path. */ + AssetCatalogDefinitionFile *cdf = service.get_catalog_definition_file(); + ASSERT_NE(nullptr, cdf); + EXPECT_EQ(writable_cdf_file, cdf->file_path); + + /* Test that the in-memory catalogs have been merged with the on-disk one. */ + AssetCatalogService loaded_service(writable_cdf_file); + loaded_service.load_from_disk(); + EXPECT_NE(nullptr, loaded_service.find_catalog(cat->catalog_id)); + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE)); +} + +/* Create some catalogs in memory, save to subdirectory of a registered asset library. */ +TEST_F(AssetCatalogTest, on_blendfile_save__from_memory_into_existing_asset_lib) +{ + const CatalogFilePath target_dir = create_temp_path(); /* Has trailing slash. */ + const CatalogFilePath original_cdf_file = asset_library_root_ + "/blender_assets.cats.txt"; + const CatalogFilePath registered_asset_lib = target_dir + "my_asset_library/"; + CatalogFilePath writable_cdf_file = registered_asset_lib + + AssetCatalogService::DEFAULT_CATALOG_FILENAME; + BLI_path_slash_native(writable_cdf_file.data()); + + /* Set up a temporary asset library for testing. */ + bUserAssetLibrary *asset_lib_pref = BKE_preferences_asset_library_add( + &U, "Test", registered_asset_lib.c_str()); + ASSERT_NE(nullptr, asset_lib_pref); + ASSERT_TRUE(BLI_dir_create_recursive(registered_asset_lib.c_str())); + ASSERT_EQ(0, BLI_copy(original_cdf_file.c_str(), writable_cdf_file.c_str())); + + /* Create the catalog service without loading the already-existing CDF. */ + TestableAssetCatalogService service; + const CatalogFilePath blenddirname = registered_asset_lib + "subdirectory/"; + const CatalogFilePath blendfilename = blenddirname + "some_file.blend"; + ASSERT_TRUE(BLI_dir_create_recursive(blenddirname.c_str())); + const AssetCatalog *cat = service.create_catalog("some/catalog/path"); + + /* Mock that the blend file is written to the directory already containing a CDF. */ + ASSERT_TRUE(service.write_to_disk_on_blendfile_save(blendfilename)); + + /* Test that the CDF still exists in the expected location. */ + EXPECT_TRUE(BLI_exists(writable_cdf_file.c_str())); + const CatalogFilePath backup_filename = writable_cdf_file + "~"; + EXPECT_TRUE(BLI_exists(backup_filename.c_str())) + << "Overwritten CDF should have been backed up."; + + /* Test that the in-memory CDF has the expected file path. */ + AssetCatalogDefinitionFile *cdf = service.get_catalog_definition_file(); + BLI_path_slash_native(cdf->file_path.data()); + EXPECT_EQ(writable_cdf_file, cdf->file_path); + + /* Test that the in-memory catalogs have been merged with the on-disk one. */ + AssetCatalogService loaded_service(writable_cdf_file); + loaded_service.load_from_disk(); + EXPECT_NE(nullptr, loaded_service.find_catalog(cat->catalog_id)); + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE)); + + BKE_preferences_asset_library_remove(&U, asset_lib_pref); +} + +TEST_F(AssetCatalogTest, create_first_catalog_from_scratch) +{ + /* Even from scratch a root directory should be known. */ + const CatalogFilePath temp_lib_root = use_temp_path(); + AssetCatalogService service; + + /* Just creating the service should NOT create the path. */ + EXPECT_FALSE(BLI_exists(temp_lib_root.c_str())); + + AssetCatalog *cat = service.create_catalog("some/catalog/path"); + ASSERT_NE(nullptr, cat); + EXPECT_EQ(cat->path, "some/catalog/path"); + EXPECT_EQ(cat->simple_name, "some-catalog-path"); + + /* Creating a new catalog should not save anything to disk yet. */ + EXPECT_FALSE(BLI_exists(temp_lib_root.c_str())); + + /* Writing to disk should create the directory + the default file. */ + service.write_to_disk_on_blendfile_save(temp_lib_root + "phony.blend"); + EXPECT_TRUE(BLI_is_dir(temp_lib_root.c_str())); + + const CatalogFilePath definition_file_path = temp_lib_root + "/" + + AssetCatalogService::DEFAULT_CATALOG_FILENAME; + EXPECT_TRUE(BLI_is_file(definition_file_path.c_str())); + + AssetCatalogService loaded_service(temp_lib_root); + loaded_service.load_from_disk(); + + /* Test that the expected catalog is there. */ + AssetCatalog *written_cat = loaded_service.find_catalog(cat->catalog_id); + ASSERT_NE(nullptr, written_cat); + EXPECT_EQ(written_cat->catalog_id, cat->catalog_id); + EXPECT_EQ(written_cat->path, cat->path.str()); +} + +TEST_F(AssetCatalogTest, create_catalog_after_loading_file) +{ + const CatalogFilePath temp_lib_root = create_temp_path(); + + /* Copy the asset catalog definition files to a separate location, so that we can test without + * overwriting the test file in SVN. */ + const CatalogFilePath default_catalog_path = asset_library_root_ + "/" + + AssetCatalogService::DEFAULT_CATALOG_FILENAME; + const CatalogFilePath writable_catalog_path = temp_lib_root + + AssetCatalogService::DEFAULT_CATALOG_FILENAME; + ASSERT_EQ(0, BLI_copy(default_catalog_path.c_str(), writable_catalog_path.c_str())); + EXPECT_TRUE(BLI_is_dir(temp_lib_root.c_str())); + EXPECT_TRUE(BLI_is_file(writable_catalog_path.c_str())); + + TestableAssetCatalogService service(temp_lib_root); + service.load_from_disk(); + EXPECT_EQ(writable_catalog_path, service.get_catalog_definition_file()->file_path); + EXPECT_NE(nullptr, service.find_catalog(UUID_POSES_ELLIE)) << "expected catalogs to be loaded"; + + /* This should create a new catalog but not write to disk. */ + const AssetCatalog *new_catalog = service.create_catalog("new/catalog"); + const bUUID new_catalog_id = new_catalog->catalog_id; + + /* Reload the on-disk catalog file. */ + TestableAssetCatalogService loaded_service(temp_lib_root); + loaded_service.load_from_disk(); + EXPECT_EQ(writable_catalog_path, loaded_service.get_catalog_definition_file()->file_path); + + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE)) + << "expected pre-existing catalogs to be kept in the file"; + EXPECT_EQ(nullptr, loaded_service.find_catalog(new_catalog_id)) + << "expecting newly added catalog to not yet be saved to " << temp_lib_root; + + /* Write and reload the catalog file. */ + service.write_to_disk_on_blendfile_save(temp_lib_root + "phony.blend"); + AssetCatalogService reloaded_service(temp_lib_root); + reloaded_service.load_from_disk(); + EXPECT_NE(nullptr, reloaded_service.find_catalog(UUID_POSES_ELLIE)) + << "expected pre-existing catalogs to be kept in the file"; + EXPECT_NE(nullptr, reloaded_service.find_catalog(new_catalog_id)) + << "expecting newly added catalog to exist in the file"; +} + +TEST_F(AssetCatalogTest, create_catalog_path_cleanup) +{ + AssetCatalogService service; + AssetCatalog *cat = service.create_catalog(" /some/path / "); + + EXPECT_FALSE(BLI_uuid_is_nil(cat->catalog_id)); + EXPECT_EQ("some/path", cat->path.str()); + EXPECT_EQ("some-path", cat->simple_name); +} + +TEST_F(AssetCatalogTest, create_catalog_simple_name) +{ + AssetCatalogService service; + AssetCatalog *cat = service.create_catalog( + "production/Spite Fright/Characters/Victora/Pose Library/Approved/Body Parts/Hands"); + + EXPECT_FALSE(BLI_uuid_is_nil(cat->catalog_id)); + EXPECT_EQ("production/Spite Fright/Characters/Victora/Pose Library/Approved/Body Parts/Hands", + cat->path.str()); + EXPECT_EQ("...ht-Characters-Victora-Pose Library-Approved-Body Parts-Hands", cat->simple_name); +} + +TEST_F(AssetCatalogTest, delete_catalog_leaf) +{ + AssetCatalogService service(asset_library_root_); + service.load_from_disk(asset_library_root_ + "/" + "blender_assets.cats.txt"); + + /* Delete a leaf catalog, i.e. one that is not a parent of another catalog. + * This keeps this particular test easy. */ + service.delete_catalog(UUID_POSES_RUZENA_HAND); + EXPECT_EQ(nullptr, service.find_catalog(UUID_POSES_RUZENA_HAND)); + + /* Contains not only paths from the CDF but also the missing parents (implicitly defined + * catalogs). This is why a leaf catalog was deleted. */ + std::vector<CatalogPathInfo> expected_paths{ + {"character", 0}, + {"character/Ellie", 1}, + {"character/Ellie/poselib", 2}, + {"character/Ellie/poselib/tailslash", 3}, + {"character/Ellie/poselib/white space", 3}, + {"character/Ružena", 1}, + {"character/Ružena/poselib", 2}, + {"character/Ružena/poselib/face", 3}, + // {"character/Ružena/poselib/hand", 3}, /* This is the deleted one. */ + {"path", 0}, + {"path/without", 1}, + {"path/without/simplename", 2}, + }; + + AssetCatalogTree *tree = service.get_catalog_tree(); + assert_expected_tree_items(tree, expected_paths); +} + +TEST_F(AssetCatalogTest, delete_catalog_write_to_disk) +{ + TestableAssetCatalogService service(asset_library_root_); + service.load_from_disk(asset_library_root_ + "/" + + AssetCatalogService::DEFAULT_CATALOG_FILENAME); + + service.delete_catalog(UUID_POSES_ELLIE); + + const CatalogFilePath save_to_path = use_temp_path(); + AssetCatalogDefinitionFile *cdf = service.get_catalog_definition_file(); + cdf->write_to_disk(save_to_path + "/" + AssetCatalogService::DEFAULT_CATALOG_FILENAME); + + AssetCatalogService loaded_service(save_to_path); + loaded_service.load_from_disk(); + + /* Test that the expected catalogs are there, except the deleted one. */ + EXPECT_EQ(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE)); + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE_WHITESPACE)); + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE_TRAILING_SLASH)); + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_RUZENA)); + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_RUZENA_HAND)); + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_RUZENA_FACE)); +} + +TEST_F(AssetCatalogTest, update_catalog_path) +{ + AssetCatalogService service(asset_library_root_); + service.load_from_disk(asset_library_root_ + "/" + + AssetCatalogService::DEFAULT_CATALOG_FILENAME); + + const AssetCatalog *orig_cat = service.find_catalog(UUID_POSES_RUZENA); + const AssetCatalogPath orig_path = orig_cat->path; + + service.update_catalog_path(UUID_POSES_RUZENA, "charlib/Ružena"); + + EXPECT_EQ(nullptr, service.find_catalog_by_path(orig_path)) + << "The original (pre-rename) path should not be associated with a catalog any more."; + + const AssetCatalog *renamed_cat = service.find_catalog(UUID_POSES_RUZENA); + ASSERT_NE(nullptr, renamed_cat); + ASSERT_EQ(orig_cat, renamed_cat) << "Changing the path should not reallocate the catalog."; + EXPECT_EQ(orig_cat->simple_name, renamed_cat->simple_name) + << "Changing the path should not change the simple name."; + EXPECT_EQ(orig_cat->catalog_id, renamed_cat->catalog_id) + << "Changing the path should not change the catalog ID."; + + EXPECT_EQ("charlib/Ružena", renamed_cat->path.str()) + << "Changing the path should change the path. Surprise."; + + EXPECT_EQ("charlib/Ružena/hand", service.find_catalog(UUID_POSES_RUZENA_HAND)->path.str()) + << "Changing the path should update children."; + EXPECT_EQ("charlib/Ružena/face", service.find_catalog(UUID_POSES_RUZENA_FACE)->path.str()) + << "Changing the path should update children."; +} + +TEST_F(AssetCatalogTest, merge_catalog_files) +{ + const CatalogFilePath cdf_dir = create_temp_path(); + const CatalogFilePath original_cdf_file = asset_library_root_ + "/blender_assets.cats.txt"; + const CatalogFilePath modified_cdf_file = asset_library_root_ + "/modified_assets.cats.txt"; + const CatalogFilePath temp_cdf_file = cdf_dir + "blender_assets.cats.txt"; + ASSERT_EQ(0, BLI_copy(original_cdf_file.c_str(), temp_cdf_file.c_str())); + + /* Load the unmodified, original CDF. */ + TestableAssetCatalogService service(asset_library_root_); + service.load_from_disk(cdf_dir); + + /* Copy a modified file, to mimic a situation where someone changed the + * CDF after we loaded it. */ + ASSERT_EQ(0, BLI_copy(modified_cdf_file.c_str(), temp_cdf_file.c_str())); + + /* Overwrite the modified file. This should merge the on-disk file with our catalogs. */ + service.write_to_disk_on_blendfile_save(cdf_dir + "phony.blend"); + + AssetCatalogService loaded_service(cdf_dir); + loaded_service.load_from_disk(); + + /* Test that the expected catalogs are there. */ + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE)); + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE_WHITESPACE)); + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE_TRAILING_SLASH)); + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_RUZENA)); + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_RUZENA_HAND)); + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_RUZENA_FACE)); + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_AGENT_47)); /* New in the modified file. */ + + /* When there are overlaps, the in-memory (i.e. last-saved) paths should win. */ + const AssetCatalog *ruzena_face = loaded_service.find_catalog(UUID_POSES_RUZENA_FACE); + EXPECT_EQ("character/Ružena/poselib/face", ruzena_face->path.str()); +} + +TEST_F(AssetCatalogTest, backups) +{ + const CatalogFilePath cdf_dir = create_temp_path(); + const CatalogFilePath original_cdf_file = asset_library_root_ + "/blender_assets.cats.txt"; + const CatalogFilePath writable_cdf_file = cdf_dir + "/blender_assets.cats.txt"; + ASSERT_EQ(0, BLI_copy(original_cdf_file.c_str(), writable_cdf_file.c_str())); + + /* Read a CDF, modify, and write it. */ + AssetCatalogService service(cdf_dir); + service.load_from_disk(); + service.delete_catalog(UUID_POSES_ELLIE); + service.write_to_disk_on_blendfile_save(cdf_dir + "phony.blend"); + + const CatalogFilePath backup_path = writable_cdf_file + "~"; + ASSERT_TRUE(BLI_is_file(backup_path.c_str())); + + AssetCatalogService loaded_service; + loaded_service.load_from_disk(backup_path); + + /* Test that the expected catalogs are there, including the deleted one. + * This is the backup, after all. */ + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE)); + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE_WHITESPACE)); + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE_TRAILING_SLASH)); + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_RUZENA)); + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_RUZENA_HAND)); + EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_RUZENA_FACE)); +} + +TEST_F(AssetCatalogTest, order_by_path) +{ + const bUUID cat2_uuid("22222222-b847-44d9-bdca-ff04db1c24f5"); + const bUUID cat4_uuid("11111111-b847-44d9-bdca-ff04db1c24f5"); /* Sorts earlier than above. */ + const AssetCatalog cat1(BLI_uuid_generate_random(), "simple/path/child", ""); + const AssetCatalog cat2(cat2_uuid, "simple/path", ""); + const AssetCatalog cat3(BLI_uuid_generate_random(), "complex/path/...or/is/it?", ""); + const AssetCatalog cat4( + cat4_uuid, "simple/path", "different ID, same path"); /* should be kept */ + const AssetCatalog cat5(cat4_uuid, "simple/path", "same ID, same path"); /* disappears */ + + AssetCatalogOrderedSet by_path; + by_path.insert(&cat1); + by_path.insert(&cat2); + by_path.insert(&cat3); + by_path.insert(&cat4); + by_path.insert(&cat5); + + AssetCatalogOrderedSet::const_iterator set_iter = by_path.begin(); + + EXPECT_EQ(1, by_path.count(&cat1)); + EXPECT_EQ(1, by_path.count(&cat2)); + EXPECT_EQ(1, by_path.count(&cat3)); + EXPECT_EQ(1, by_path.count(&cat4)); + ASSERT_EQ(4, by_path.size()) << "Expecting cat5 to not be stored in the set, as it duplicates " + "an already-existing path + UUID"; + + EXPECT_EQ(cat3.catalog_id, (*(set_iter++))->catalog_id); /* complex/path */ + EXPECT_EQ(cat4.catalog_id, (*(set_iter++))->catalog_id); /* simple/path with 111.. ID */ + EXPECT_EQ(cat2.catalog_id, (*(set_iter++))->catalog_id); /* simple/path with 222.. ID */ + EXPECT_EQ(cat1.catalog_id, (*(set_iter++))->catalog_id); /* simple/path/child */ + + if (set_iter != by_path.end()) { + const AssetCatalog *next_cat = *set_iter; + FAIL() << "Did not expect more items in the set, had at least " << next_cat->catalog_id << ":" + << next_cat->path; + } +} + +TEST_F(AssetCatalogTest, create_missing_catalogs) +{ + TestableAssetCatalogService new_service; + new_service.create_catalog("path/with/missing/parents"); + + EXPECT_EQ(nullptr, new_service.find_catalog_by_path("path/with/missing")) + << "Missing parents should not be immediately created."; + EXPECT_EQ(nullptr, new_service.find_catalog_by_path("")) << "Empty path should never be valid"; + + new_service.create_missing_catalogs(); + + EXPECT_NE(nullptr, new_service.find_catalog_by_path("path/with/missing")); + EXPECT_NE(nullptr, new_service.find_catalog_by_path("path/with")); + EXPECT_NE(nullptr, new_service.find_catalog_by_path("path")); + EXPECT_EQ(nullptr, new_service.find_catalog_by_path("")) + << "Empty path should never be valid, even when after missing catalogs"; +} + +TEST_F(AssetCatalogTest, create_missing_catalogs_after_loading) +{ + TestableAssetCatalogService loaded_service(asset_library_root_); + loaded_service.load_from_disk(); + + const AssetCatalog *cat_char = loaded_service.find_catalog_by_path("character"); + const AssetCatalog *cat_ellie = loaded_service.find_catalog_by_path("character/Ellie"); + const AssetCatalog *cat_ruzena = loaded_service.find_catalog_by_path("character/Ružena"); + ASSERT_NE(nullptr, cat_char) << "Missing parents should be created immediately after loading."; + ASSERT_NE(nullptr, cat_ellie) << "Missing parents should be created immediately after loading."; + ASSERT_NE(nullptr, cat_ruzena) << "Missing parents should be created immediately after loading."; + + AssetCatalogDefinitionFile *cdf = loaded_service.get_catalog_definition_file(); + ASSERT_NE(nullptr, cdf); + EXPECT_TRUE(cdf->contains(cat_char->catalog_id)) << "Missing parents should be saved to a CDF."; + EXPECT_TRUE(cdf->contains(cat_ellie->catalog_id)) << "Missing parents should be saved to a CDF."; + EXPECT_TRUE(cdf->contains(cat_ruzena->catalog_id)) + << "Missing parents should be saved to a CDF."; + + /* Check that each missing parent is only created once. The CDF contains multiple paths that + * could trigger the creation of missing parents, so this test makes sense. */ + EXPECT_EQ(1, loaded_service.count_catalogs_with_path("character")); + EXPECT_EQ(1, loaded_service.count_catalogs_with_path("character/Ellie")); + EXPECT_EQ(1, loaded_service.count_catalogs_with_path("character/Ružena")); +} + +TEST_F(AssetCatalogTest, create_catalog_filter) +{ + AssetCatalogService service(asset_library_root_); + service.load_from_disk(); + + /* Alias for the same catalog as the main one. */ + AssetCatalog *alias_ruzena = service.create_catalog("character/Ružena/poselib"); + /* Alias for a sub-catalog. */ + AssetCatalog *alias_ruzena_hand = service.create_catalog("character/Ružena/poselib/hand"); + + AssetCatalogFilter filter = service.create_catalog_filter(UUID_POSES_RUZENA); + + /* Positive test for loaded-from-disk catalogs. */ + EXPECT_TRUE(filter.contains(UUID_POSES_RUZENA)) + << "Main catalog should be included in the filter."; + EXPECT_TRUE(filter.contains(UUID_POSES_RUZENA_HAND)) + << "Sub-catalog should be included in the filter."; + EXPECT_TRUE(filter.contains(UUID_POSES_RUZENA_FACE)) + << "Sub-catalog should be included in the filter."; + + /* Positive test for newly-created catalogs. */ + EXPECT_TRUE(filter.contains(alias_ruzena->catalog_id)) + << "Alias of main catalog should be included in the filter."; + EXPECT_TRUE(filter.contains(alias_ruzena_hand->catalog_id)) + << "Alias of sub-catalog should be included in the filter."; + + /* Negative test for unrelated catalogs. */ + EXPECT_FALSE(filter.contains(BLI_uuid_nil())) << "Nil catalog should not be included."; + EXPECT_FALSE(filter.contains(UUID_ID_WITHOUT_PATH)); + EXPECT_FALSE(filter.contains(UUID_POSES_ELLIE)); + EXPECT_FALSE(filter.contains(UUID_POSES_ELLIE_WHITESPACE)); + EXPECT_FALSE(filter.contains(UUID_POSES_ELLIE_TRAILING_SLASH)); + EXPECT_FALSE(filter.contains(UUID_WITHOUT_SIMPLENAME)); +} + +TEST_F(AssetCatalogTest, create_catalog_filter_for_unknown_uuid) +{ + AssetCatalogService service; + const bUUID unknown_uuid = BLI_uuid_generate_random(); + + AssetCatalogFilter filter = service.create_catalog_filter(unknown_uuid); + EXPECT_TRUE(filter.contains(unknown_uuid)); + + EXPECT_FALSE(filter.contains(BLI_uuid_nil())) << "Nil catalog should not be included."; + EXPECT_FALSE(filter.contains(UUID_POSES_ELLIE)); +} + +TEST_F(AssetCatalogTest, create_catalog_filter_for_unassigned_assets) +{ + AssetCatalogService service; + + AssetCatalogFilter filter = service.create_catalog_filter(BLI_uuid_nil()); + EXPECT_TRUE(filter.contains(BLI_uuid_nil())); + EXPECT_FALSE(filter.contains(UUID_POSES_ELLIE)); +} + +} // namespace blender::bke::tests diff --git a/source/blender/blenkernel/intern/asset_library.cc b/source/blender/blenkernel/intern/asset_library.cc new file mode 100644 index 00000000000..27e66ee5725 --- /dev/null +++ b/source/blender/blenkernel/intern/asset_library.cc @@ -0,0 +1,141 @@ +/* + * 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 bke + */ + +#include "BKE_asset_library.hh" +#include "BKE_callbacks.h" +#include "BKE_main.h" +#include "BKE_preferences.h" + +#include "BLI_path_util.h" + +#include "DNA_userdef_types.h" + +#include "MEM_guardedalloc.h" + +#include <memory> + +/** + * Loading an asset library at this point only means loading the catalogs. Later on this should + * invoke reading of asset representations too. + */ +struct AssetLibrary *BKE_asset_library_load(const char *library_path) +{ + blender::bke::AssetLibrary *lib = new blender::bke::AssetLibrary(); + lib->on_save_handler_register(); + lib->load(library_path); + return reinterpret_cast<struct AssetLibrary *>(lib); +} + +void BKE_asset_library_free(struct AssetLibrary *asset_library) +{ + blender::bke::AssetLibrary *lib = reinterpret_cast<blender::bke::AssetLibrary *>(asset_library); + lib->on_save_handler_unregister(); + delete lib; +} + +bool BKE_asset_library_find_suitable_root_path_from_path(const char *input_path, + char *r_library_path) +{ + if (bUserAssetLibrary *preferences_lib = BKE_preferences_asset_library_containing_path( + &U, input_path)) { + BLI_strncpy(r_library_path, preferences_lib->path, FILE_MAXDIR); + return true; + } + + BLI_split_dir_part(input_path, r_library_path, FILE_MAXDIR); + return r_library_path[0] != '\0'; +} + +bool BKE_asset_library_find_suitable_root_path_from_main(const Main *bmain, char *r_library_path) +{ + return BKE_asset_library_find_suitable_root_path_from_path(bmain->name, r_library_path); +} + +blender::bke::AssetCatalogService *BKE_asset_library_get_catalog_service( + const ::AssetLibrary *library_c) +{ + if (library_c == nullptr) { + return nullptr; + } + + const blender::bke::AssetLibrary &library = reinterpret_cast<const blender::bke::AssetLibrary &>( + *library_c); + return library.catalog_service.get(); +} + +blender::bke::AssetCatalogTree *BKE_asset_library_get_catalog_tree(const ::AssetLibrary *library) +{ + blender::bke::AssetCatalogService *catalog_service = BKE_asset_library_get_catalog_service( + library); + if (catalog_service == nullptr) { + return nullptr; + } + + return catalog_service->get_catalog_tree(); +} + +namespace blender::bke { + +void AssetLibrary::load(StringRefNull library_root_directory) +{ + auto catalog_service = std::make_unique<AssetCatalogService>(library_root_directory); + catalog_service->load_from_disk(); + this->catalog_service = std::move(catalog_service); +} + +namespace { +void asset_library_on_save_post(struct Main *main, + struct PointerRNA **pointers, + const int num_pointers, + void *arg) +{ + AssetLibrary *asset_lib = static_cast<AssetLibrary *>(arg); + asset_lib->on_save_post(main, pointers, num_pointers); +} +} // namespace + +void AssetLibrary::on_save_handler_register() +{ + /* The callback system doesn't own `on_save_callback_store_`. */ + on_save_callback_store_.alloc = false; + + on_save_callback_store_.func = asset_library_on_save_post; + on_save_callback_store_.arg = this; + + BKE_callback_add(&on_save_callback_store_, BKE_CB_EVT_SAVE_POST); +} + +void AssetLibrary::on_save_handler_unregister() +{ + BKE_callback_remove(&on_save_callback_store_, BKE_CB_EVT_SAVE_POST); +} + +void AssetLibrary::on_save_post(struct Main *main, + struct PointerRNA ** /*pointers*/, + const int /*num_pointers*/) +{ + if (this->catalog_service == nullptr) { + return; + } + + this->catalog_service->write_to_disk_on_blendfile_save(main->name); +} + +} // namespace blender::bke diff --git a/source/blender/blenkernel/intern/asset_library_test.cc b/source/blender/blenkernel/intern/asset_library_test.cc new file mode 100644 index 00000000000..30ac4dc6ad8 --- /dev/null +++ b/source/blender/blenkernel/intern/asset_library_test.cc @@ -0,0 +1,82 @@ +/* + * 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 Blender Foundation + * All rights reserved. + */ + +#include "BKE_appdir.h" +#include "BKE_asset_catalog.hh" +#include "BKE_asset_library.hh" + +#include "testing/testing.h" + +namespace blender::bke::tests { + +TEST(AssetLibraryTest, load_and_free_c_functions) +{ + const std::string test_files_dir = blender::tests::flags_test_asset_dir(); + if (test_files_dir.empty()) { + FAIL(); + } + + /* Load the asset library. */ + const std::string library_path = test_files_dir + "/" + "asset_library"; + ::AssetLibrary *library_c_ptr = BKE_asset_library_load(library_path.data()); + ASSERT_NE(nullptr, library_c_ptr); + + /* Check that it can be cast to the C++ type and has a Catalog Service. */ + blender::bke::AssetLibrary *library_cpp_ptr = reinterpret_cast<blender::bke::AssetLibrary *>( + library_c_ptr); + AssetCatalogService *service = library_cpp_ptr->catalog_service.get(); + ASSERT_NE(nullptr, service); + + /* Check that the catalogs defined in the library are actually loaded. This just tests one single + * catalog, as that indicates the file has been loaded. Testing that that loading went OK is for + * the asset catalog service tests. */ + const bUUID uuid_poses_ellie("df60e1f6-2259-475b-93d9-69a1b4a8db78"); + AssetCatalog *poses_ellie = service->find_catalog(uuid_poses_ellie); + ASSERT_NE(nullptr, poses_ellie) << "unable to find POSES_ELLIE catalog"; + EXPECT_EQ("character/Ellie/poselib", poses_ellie->path.str()); + + BKE_asset_library_free(library_c_ptr); +} + +TEST(AssetLibraryTest, load_nonexistent_directory) +{ + const std::string test_files_dir = blender::tests::flags_test_asset_dir(); + if (test_files_dir.empty()) { + FAIL(); + } + + /* Load the asset library. */ + const std::string library_path = test_files_dir + "/" + + "asset_library/this/subdir/does/not/exist"; + ::AssetLibrary *library_c_ptr = BKE_asset_library_load(library_path.data()); + ASSERT_NE(nullptr, library_c_ptr); + + /* Check that it can be cast to the C++ type and has a Catalog Service. */ + blender::bke::AssetLibrary *library_cpp_ptr = reinterpret_cast<blender::bke::AssetLibrary *>( + library_c_ptr); + AssetCatalogService *service = library_cpp_ptr->catalog_service.get(); + ASSERT_NE(nullptr, service); + + /* Check that the catalog service doesn't have any catalogs. */ + EXPECT_TRUE(service->is_empty()); + + BKE_asset_library_free(library_c_ptr); +} + +} // namespace blender::bke::tests diff --git a/source/blender/blenkernel/intern/asset_test.cc b/source/blender/blenkernel/intern/asset_test.cc new file mode 100644 index 00000000000..77b98a8ac0a --- /dev/null +++ b/source/blender/blenkernel/intern/asset_test.cc @@ -0,0 +1,70 @@ +/* + * 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 Blender Foundation + * All rights reserved. + */ + +#include "BKE_asset.h" + +#include "BLI_uuid.h" + +#include "DNA_asset_types.h" + +#include "testing/testing.h" + +namespace blender::bke::tests { + +TEST(AssetMetadataTest, set_catalog_id) +{ + AssetMetaData meta; + const bUUID uuid = BLI_uuid_generate_random(); + + /* Test trivial values. */ + BKE_asset_metadata_catalog_id_clear(&meta); + EXPECT_TRUE(BLI_uuid_is_nil(meta.catalog_id)); + EXPECT_STREQ("", meta.catalog_simple_name); + + /* Test simple situation where the given short name is used as-is. */ + BKE_asset_metadata_catalog_id_set(&meta, uuid, "simple"); + EXPECT_TRUE(BLI_uuid_equal(uuid, meta.catalog_id)); + EXPECT_STREQ("simple", meta.catalog_simple_name); + + /* Test white-space trimming. */ + BKE_asset_metadata_catalog_id_set(&meta, uuid, " GovoriÅ¡ angleÅ¡ko? "); + EXPECT_STREQ("GovoriÅ¡ angleÅ¡ko?", meta.catalog_simple_name); + + /* Test length trimming to 63 chars + terminating zero. */ + constexpr char len66[] = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"; + constexpr char len63[] = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1"; + BKE_asset_metadata_catalog_id_set(&meta, uuid, len66); + EXPECT_STREQ(len63, meta.catalog_simple_name); + + /* Test length trimming happens after white-space trimming. */ + constexpr char len68[] = + " \ + 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20 "; + BKE_asset_metadata_catalog_id_set(&meta, uuid, len68); + EXPECT_STREQ(len63, meta.catalog_simple_name); + + /* Test length trimming to 63 bytes, and not 63 characters. ✓ in UTF-8 is three bytes long. */ + constexpr char with_utf8[] = + "00010203040506✓0708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"; + BKE_asset_metadata_catalog_id_set(&meta, uuid, with_utf8); + EXPECT_STREQ("00010203040506✓0708090a0b0c0d0e0f101112131415161718191a1b1c1d", + meta.catalog_simple_name); +} + +} // namespace blender::bke::tests diff --git a/source/blender/blenkernel/intern/attribute_access.cc b/source/blender/blenkernel/intern/attribute_access.cc index 8c4f87be91f..c2837b522c4 100644 --- a/source/blender/blenkernel/intern/attribute_access.cc +++ b/source/blender/blenkernel/intern/attribute_access.cc @@ -55,6 +55,21 @@ using blender::fn::GVArray_For_SingleValue; namespace blender::bke { +std::ostream &operator<<(std::ostream &stream, const AttributeIDRef &attribute_id) +{ + if (attribute_id.is_named()) { + stream << attribute_id.name(); + } + else if (attribute_id.is_anonymous()) { + const AnonymousAttributeID &anonymous_id = attribute_id.anonymous_id(); + stream << "<" << BKE_anonymous_attribute_id_debug_name(&anonymous_id) << ">"; + } + else { + stream << "<none>"; + } + return stream; +} + const blender::fn::CPPType *custom_data_type_to_cpp_type(const CustomDataType type) { switch (type) { diff --git a/source/blender/blenkernel/intern/blendfile.c b/source/blender/blenkernel/intern/blendfile.c index 1c5d8804280..6957f9b5a69 100644 --- a/source/blender/blenkernel/intern/blendfile.c +++ b/source/blender/blenkernel/intern/blendfile.c @@ -344,6 +344,13 @@ static void setup_app_data(bContext *C, do_versions_ipos_to_animato(bmain); } + /* FIXME: Same as above, readfile's `do_version` do not allow to create new IDs. */ + /* TODO: Once this is definitively validated for 3.0 and option to not do it is removed, add a + * version bump and check here. */ + if (!USER_EXPERIMENTAL_TEST(&U, no_proxy_to_override_conversion)) { + BKE_lib_override_library_main_proxy_convert(bmain, reports); + } + bmain->recovered = 0; /* startup.blend or recovered startup */ diff --git a/source/blender/blenkernel/intern/callbacks.c b/source/blender/blenkernel/intern/callbacks.c index 11ee9492b44..87d5961b12e 100644 --- a/source/blender/blenkernel/intern/callbacks.c +++ b/source/blender/blenkernel/intern/callbacks.c @@ -80,6 +80,15 @@ void BKE_callback_add(bCallbackFuncStore *funcstore, eCbEvent evt) BLI_addtail(lb, funcstore); } +void BKE_callback_remove(bCallbackFuncStore *funcstore, eCbEvent evt) +{ + ListBase *lb = &callback_slots[evt]; + BLI_remlink(lb, funcstore); + if (funcstore->alloc) { + MEM_freeN(funcstore); + } +} + void BKE_callback_global_init(void) { /* do nothing */ @@ -95,10 +104,7 @@ void BKE_callback_global_finalize(void) bCallbackFuncStore *funcstore_next; for (funcstore = lb->first; funcstore; funcstore = funcstore_next) { funcstore_next = funcstore->next; - BLI_remlink(lb, funcstore); - if (funcstore->alloc) { - MEM_freeN(funcstore); - } + BKE_callback_remove(funcstore, evt); } } } diff --git a/source/blender/blenkernel/intern/collection.c b/source/blender/blenkernel/intern/collection.c index 2d172f23428..8e50b9e9534 100644 --- a/source/blender/blenkernel/intern/collection.c +++ b/source/blender/blenkernel/intern/collection.c @@ -597,7 +597,7 @@ static Collection *collection_duplicate_recursive(Main *bmain, } else if (collection_old->id.newid == NULL) { collection_new = (Collection *)BKE_id_copy_for_duplicate( - bmain, (ID *)collection_old, duplicate_flags); + bmain, (ID *)collection_old, duplicate_flags, LIB_ID_COPY_DEFAULT); if (collection_new == collection_old) { return collection_new; diff --git a/source/blender/blenkernel/intern/colortools.c b/source/blender/blenkernel/intern/colortools.c index f2c2e552a9f..62b817487fc 100644 --- a/source/blender/blenkernel/intern/colortools.c +++ b/source/blender/blenkernel/intern/colortools.c @@ -1212,6 +1212,20 @@ void BKE_curvemapping_init(CurveMapping *cumap) } } +void BKE_curvemapping_table_F(const CurveMapping *cumap, float **array, int *size) +{ + int a; + + *size = CM_TABLE + 1; + *array = MEM_callocN(sizeof(float) * (*size) * 4, "CurveMapping"); + + for (a = 0; a < *size; a++) { + if (cumap->cm[0].table) { + (*array)[a * 4 + 0] = cumap->cm[0].table[a].y; + } + } +} + void BKE_curvemapping_table_RGBA(const CurveMapping *cumap, float **array, int *size) { int a; diff --git a/source/blender/blenkernel/intern/constraint.c b/source/blender/blenkernel/intern/constraint.c index b9b15eba6a4..b2b03d28483 100644 --- a/source/blender/blenkernel/intern/constraint.c +++ b/source/blender/blenkernel/intern/constraint.c @@ -3499,7 +3499,7 @@ static void stretchto_new_data(void *cdata) bStretchToConstraint *data = (bStretchToConstraint *)cdata; data->volmode = 0; - data->plane = 0; + data->plane = SWING_Y; data->orglength = 0.0; data->bulge = 1.0; data->bulge_max = 1.0f; diff --git a/source/blender/blenkernel/intern/curve.c b/source/blender/blenkernel/intern/curve.c index b0d196b2bb0..0dcfea78ca5 100644 --- a/source/blender/blenkernel/intern/curve.c +++ b/source/blender/blenkernel/intern/curve.c @@ -404,6 +404,7 @@ void BKE_curve_init(Curve *cu, const short curve_type) } else if (cu->type == OB_SURF) { cu->flag |= CU_3D; + cu->resolu = 4; cu->resolv = 4; } cu->bevel_profile = NULL; diff --git a/source/blender/blenkernel/intern/customdata.c b/source/blender/blenkernel/intern/customdata.c index ad2d5d267d5..3bb02e1856b 100644 --- a/source/blender/blenkernel/intern/customdata.c +++ b/source/blender/blenkernel/intern/customdata.c @@ -1856,6 +1856,8 @@ static const LayerTypeInfo LAYERTYPEINFO[CD_NUMTYPES] = { NULL, NULL, NULL}, + /* 51: CD_HAIRLENGTH */ + {sizeof(float), "float", 1, NULL, NULL, NULL, NULL, NULL, NULL}, }; static const char *LAYERTYPENAMES[CD_NUMTYPES] = { @@ -1912,6 +1914,7 @@ static const char *LAYERTYPENAMES[CD_NUMTYPES] = { "CDPropFloat3", "CDPropFloat2", "CDPropBoolean", + "CDHairLength", }; const CustomData_MeshMasks CD_MASK_BAREMESH = { diff --git a/source/blender/blenkernel/intern/displist.cc b/source/blender/blenkernel/intern/displist.cc index e756daa1156..0776f3b9a68 100644 --- a/source/blender/blenkernel/intern/displist.cc +++ b/source/blender/blenkernel/intern/displist.cc @@ -261,7 +261,7 @@ bool BKE_displist_surfindex_get( return true; } -/* ****************** make displists ********************* */ +/* ****************** Make #DispList ********************* */ #ifdef __INTEL_COMPILER /* ICC with the optimization -02 causes crashes. */ # pragma intel optimization_level 1 @@ -1540,23 +1540,6 @@ void BKE_displist_make_curveTypes(Depsgraph *depsgraph, boundbox_displist_object(ob); } -void BKE_displist_make_curveTypes_forRender( - Depsgraph *depsgraph, const Scene *scene, Object *ob, ListBase *r_dispbase, Mesh **r_final) -{ - if (ob->runtime.curve_cache == nullptr) { - ob->runtime.curve_cache = (CurveCache *)MEM_callocN(sizeof(CurveCache), __func__); - } - - if (ob->type == OB_SURF) { - evaluate_surface_object(depsgraph, scene, ob, true, r_dispbase, r_final); - } - else { - GeometrySet geometry_set = evaluate_curve_type_object(depsgraph, scene, ob, true, r_dispbase); - MeshComponent &mesh_component = geometry_set.get_component_for_write<MeshComponent>(); - *r_final = mesh_component.release(); - } -} - void BKE_displist_minmax(const ListBase *dispbase, float min[3], float max[3]) { bool doit = false; diff --git a/source/blender/blenkernel/intern/dynamicpaint.c b/source/blender/blenkernel/intern/dynamicpaint.c index d75b3259148..9083c507160 100644 --- a/source/blender/blenkernel/intern/dynamicpaint.c +++ b/source/blender/blenkernel/intern/dynamicpaint.c @@ -317,7 +317,7 @@ static bool setError(DynamicPaintCanvasSettings *canvas, const char *string) static int dynamicPaint_surfaceNumOfPoints(DynamicPaintSurface *surface) { if (surface->format == MOD_DPAINT_SURFACE_F_PTEX) { - return 0; /* not supported atm */ + return 0; /* Not supported at the moment. */ } if (surface->format == MOD_DPAINT_SURFACE_F_VERTEX) { const Mesh *canvas_mesh = dynamicPaint_canvas_mesh_get(surface->canvas); @@ -1231,7 +1231,7 @@ void dynamicPaint_Modifier_copy(const struct DynamicPaintModifierData *pmd, /* copy existing surfaces */ for (surface = pmd->canvas->surfaces.first; surface; surface = surface->next) { DynamicPaintSurface *t_surface = dynamicPaint_createNewSurface(tpmd->canvas, NULL); - if (flag & LIB_ID_CREATE_NO_MAIN) { + if (flag & LIB_ID_COPY_SET_COPIED_ON_WRITE) { /* TODO(sergey): Consider passing some tips to the surface * creation to avoid this allocate-and-free cache behavior. */ BKE_ptcache_free_list(&t_surface->ptcaches); diff --git a/source/blender/blenkernel/intern/fluid.c b/source/blender/blenkernel/intern/fluid.c index 1324b37f39c..e272b71acb8 100644 --- a/source/blender/blenkernel/intern/fluid.c +++ b/source/blender/blenkernel/intern/fluid.c @@ -5094,7 +5094,7 @@ void BKE_fluid_modifier_copy(const struct FluidModifierData *fmd, /* pointcache options */ BKE_ptcache_free_list(&(tfds->ptcaches[0])); - if (flag & LIB_ID_CREATE_NO_MAIN) { + if (flag & LIB_ID_COPY_SET_COPIED_ON_WRITE) { /* Share the cache with the original object's modifier. */ tfmd->modifier.flag |= eModifierFlag_SharedCaches; tfds->point_cache[0] = fds->point_cache[0]; diff --git a/source/blender/blenkernel/intern/font.c b/source/blender/blenkernel/intern/font.c index aa13f86523a..0e159418724 100644 --- a/source/blender/blenkernel/intern/font.c +++ b/source/blender/blenkernel/intern/font.c @@ -34,6 +34,7 @@ #include "BLI_ghash.h" #include "BLI_listbase.h" #include "BLI_math.h" +#include "BLI_math_base_safe.h" #include "BLI_path_util.h" #include "BLI_string.h" #include "BLI_string_utf8.h" @@ -490,15 +491,15 @@ static void build_underline(Curve *cu, mul_v2_fl(bp[3].vec, font_size); } -static void buildchar(Curve *cu, - ListBase *nubase, - unsigned int character, - CharInfo *info, - float ofsx, - float ofsy, - float rot, - int charidx, - const float fsize) +void BKE_vfont_build_char(Curve *cu, + ListBase *nubase, + unsigned int character, + CharInfo *info, + float ofsx, + float ofsy, + float rot, + int charidx, + const float fsize) { VFontData *vfd = vfont_get_data(which_vfont(cu, info)); if (!vfd) { @@ -794,8 +795,8 @@ static bool vfont_to_curve(Object *ob, bool ok = false; const float font_size = cu->fsize * iter_data->scale_to_fit; const bool word_wrap = iter_data->word_wrap; - const float xof_scale = cu->xof / font_size; - const float yof_scale = cu->yof / font_size; + const float xof_scale = safe_divide(cu->xof, font_size); + const float yof_scale = safe_divide(cu->yof, font_size); int last_line = -1; /* Length of the text disregarding \n breaks. */ float current_line_length = 0.0f; @@ -889,7 +890,7 @@ static bool vfont_to_curve(Object *ob, linedist = cu->linedist; curbox = 0; - textbox_scale(&tb_scale, &cu->tb[curbox], 1.0f / font_size); + textbox_scale(&tb_scale, &cu->tb[curbox], safe_divide(1.0f, font_size)); use_textbox = (tb_scale.w != 0.0f); xof = MARGIN_X_MIN; @@ -1525,7 +1526,7 @@ static bool vfont_to_curve(Object *ob, } /* We do not want to see any character for \n or \r */ if (cha != '\n') { - buildchar(cu, r_nubase, cha, info, ct->xof, ct->yof, ct->rot, i, font_size); + BKE_vfont_build_char(cu, r_nubase, cha, info, ct->xof, ct->yof, ct->rot, i, font_size); } if ((info->flag & CU_CHINFO_UNDERLINE) && (cha != '\n')) { diff --git a/source/blender/blenkernel/intern/geometry_component_curve.cc b/source/blender/blenkernel/intern/geometry_component_curve.cc index 7d0537178ef..73c628d3f0f 100644 --- a/source/blender/blenkernel/intern/geometry_component_curve.cc +++ b/source/blender/blenkernel/intern/geometry_component_curve.cc @@ -535,6 +535,9 @@ static GVMutableArrayPtr make_cyclic_write_attribute(CurveEval &curve) * array implementations try to make it workable in common situations. * \{ */ +/** + * Individual spans in \a data may be empty if that spline contains no data for the attribute. + */ template<typename T> static void point_attribute_materialize(Span<Span<T>> data, Span<int> offsets, @@ -546,7 +549,15 @@ static void point_attribute_materialize(Span<Span<T>> data, for (const int spline_index : data.index_range()) { const int offset = offsets[spline_index]; const int next_offset = offsets[spline_index + 1]; - r_span.slice(offset, next_offset - offset).copy_from(data[spline_index]); + + Span<T> src = data[spline_index]; + MutableSpan<T> dst = r_span.slice(offset, next_offset - offset); + if (src.is_empty()) { + dst.fill(T()); + } + else { + dst.copy_from(src); + } } } else { @@ -557,11 +568,20 @@ static void point_attribute_materialize(Span<Span<T>> data, } const int index_in_spline = dst_index - offsets[spline_index]; - r_span[dst_index] = data[spline_index][index_in_spline]; + Span<T> src = data[spline_index]; + if (src.is_empty()) { + r_span[dst_index] = T(); + } + else { + r_span[dst_index] = src[index_in_spline]; + } } } } +/** + * Individual spans in \a data may be empty if that spline contains no data for the attribute. + */ template<typename T> static void point_attribute_materialize_to_uninitialized(Span<Span<T>> data, Span<int> offsets, @@ -574,7 +594,14 @@ static void point_attribute_materialize_to_uninitialized(Span<Span<T>> data, 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); + + Span<T> src = data[spline_index]; + if (src.is_empty()) { + uninitialized_fill_n(dst + offset, next_offset - offset, T()); + } + else { + uninitialized_copy_n(src.data(), next_offset - offset, dst + offset); + } } } else { @@ -585,7 +612,13 @@ static void point_attribute_materialize_to_uninitialized(Span<Span<T>> data, } const int index_in_spline = dst_index - offsets[spline_index]; - new (dst + dst_index) T(data[spline_index][index_in_spline]); + Span<T> src = data[spline_index]; + if (src.is_empty()) { + new (dst + dst_index) T(); + } + else { + new (dst + dst_index) T(src[index_in_spline]); + } } } } @@ -769,6 +802,169 @@ class VMutableArray_For_SplinePosition final : public VMutableArray<float3> { } }; +class VArray_For_BezierHandle final : public VArray<float3> { + private: + Span<SplinePtr> splines_; + Array<int> offsets_; + bool is_right_; + + public: + VArray_For_BezierHandle(Span<SplinePtr> splines, Array<int> offsets, const bool is_right) + : VArray<float3>(offsets.last()), + splines_(std::move(splines)), + offsets_(std::move(offsets)), + is_right_(is_right) + { + } + + static float3 get_internal(const int64_t index, + Span<SplinePtr> splines, + Span<int> offsets, + const bool is_right) + { + const PointIndices indices = lookup_point_indices(offsets, index); + const Spline &spline = *splines[indices.spline_index]; + if (spline.type() == Spline::Type::Bezier) { + const BezierSpline &bezier_spline = static_cast<const BezierSpline &>(spline); + return is_right ? bezier_spline.handle_positions_right()[indices.point_index] : + bezier_spline.handle_positions_left()[indices.point_index]; + } + return float3(0); + } + + float3 get_impl(const int64_t index) const final + { + return get_internal(index, splines_, offsets_, is_right_); + } + + /** + * Utility so we can pass handle positions to the materialize functions above. + * + * \note This relies on the ability of the materialize implementations to + * handle empty spans, since only Bezier splines have handles. + */ + static Array<Span<float3>> get_handle_spans(Span<SplinePtr> splines, const bool is_right) + { + Array<Span<float3>> spans(splines.size()); + for (const int i : spans.index_range()) { + if (splines[i]->type() == Spline::Type::Bezier) { + BezierSpline &bezier_spline = static_cast<BezierSpline &>(*splines[i]); + spans[i] = is_right ? bezier_spline.handle_positions_right() : + bezier_spline.handle_positions_left(); + } + else { + spans[i] = {}; + } + } + return spans; + } + + static void materialize_internal(const IndexMask mask, + Span<SplinePtr> splines, + Span<int> offsets, + const bool is_right, + MutableSpan<float3> r_span) + { + Array<Span<float3>> spans = get_handle_spans(splines, is_right); + point_attribute_materialize(spans.as_span(), offsets, mask, r_span); + } + + static void materialize_to_uninitialized_internal(const IndexMask mask, + Span<SplinePtr> splines, + Span<int> offsets, + const bool is_right, + MutableSpan<float3> r_span) + { + Array<Span<float3>> spans = get_handle_spans(splines, is_right); + point_attribute_materialize_to_uninitialized(spans.as_span(), offsets, mask, r_span); + } + + void materialize_impl(const IndexMask mask, MutableSpan<float3> r_span) const final + { + materialize_internal(mask, splines_, offsets_, is_right_, r_span); + } + + void materialize_to_uninitialized_impl(const IndexMask mask, + MutableSpan<float3> r_span) const final + { + materialize_to_uninitialized_internal(mask, splines_, offsets_, is_right_, r_span); + } +}; + +class VMutableArray_For_BezierHandles final : public VMutableArray<float3> { + private: + MutableSpan<SplinePtr> splines_; + Array<int> offsets_; + bool is_right_; + + public: + VMutableArray_For_BezierHandles(MutableSpan<SplinePtr> splines, + Array<int> offsets, + const bool is_right) + : VMutableArray<float3>(offsets.last()), + splines_(splines), + offsets_(std::move(offsets)), + is_right_(is_right) + { + } + + float3 get_impl(const int64_t index) const final + { + return VArray_For_BezierHandle::get_internal(index, splines_, offsets_, is_right_); + } + + 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 (spline.type() == Spline::Type::Bezier) { + BezierSpline &bezier_spline = static_cast<BezierSpline &>(spline); + if (is_right_) { + bezier_spline.set_handle_position_right(indices.point_index, value); + } + else { + bezier_spline.set_handle_position_left(indices.point_index, value); + } + bezier_spline.mark_cache_invalid(); + } + } + + void set_all_impl(Span<float3> src) final + { + for (const int spline_index : splines_.index_range()) { + Spline &spline = *splines_[spline_index]; + if (spline.type() == Spline::Type::Bezier) { + const int offset = offsets_[spline_index]; + + BezierSpline &bezier_spline = static_cast<BezierSpline &>(spline); + if (is_right_) { + for (const int i : IndexRange(bezier_spline.size())) { + bezier_spline.set_handle_position_right(i, src[offset + i]); + } + } + else { + for (const int i : IndexRange(bezier_spline.size())) { + bezier_spline.set_handle_position_left(i, src[offset + i]); + } + } + bezier_spline.mark_cache_invalid(); + } + } + } + + void materialize_impl(const IndexMask mask, MutableSpan<float3> r_span) const final + { + VArray_For_BezierHandle::materialize_internal(mask, splines_, offsets_, is_right_, r_span); + } + + void materialize_to_uninitialized_impl(const IndexMask mask, + MutableSpan<float3> r_span) const final + { + VArray_For_BezierHandle::materialize_to_uninitialized_internal( + mask, splines_, offsets_, is_right_, r_span); + } +}; + /** * Provider for any builtin control point attribute that doesn't need * special handling like access to other arrays in the spline. @@ -906,6 +1102,78 @@ class PositionAttributeProvider final : public BuiltinPointAttributeProvider<flo } }; +class BezierHandleAttributeProvider : public BuiltinAttributeProvider { + private: + bool is_right_; + + public: + BezierHandleAttributeProvider(const bool is_right) + : BuiltinAttributeProvider(is_right ? "handle_right" : "handle_left", + ATTR_DOMAIN_POINT, + CD_PROP_FLOAT3, + BuiltinAttributeProvider::NonCreatable, + BuiltinAttributeProvider::Writable, + BuiltinAttributeProvider::NonDeletable), + is_right_(is_right) + { + } + + GVArrayPtr try_get_for_read(const GeometryComponent &component) const override + { + const CurveEval *curve = get_curve_from_component_for_read(component); + if (curve == nullptr) { + return {}; + } + + if (!curve->has_spline_with_type(Spline::Type::Bezier)) { + return {}; + } + + Array<int> offsets = curve->control_point_offsets(); + return std::make_unique<fn::GVArray_For_EmbeddedVArray<float3, VArray_For_BezierHandle>>( + offsets.last(), curve->splines(), std::move(offsets), is_right_); + } + + GVMutableArrayPtr try_get_for_write(GeometryComponent &component) const override + { + CurveEval *curve = get_curve_from_component_for_write(component); + if (curve == nullptr) { + return {}; + } + + if (!curve->has_spline_with_type(Spline::Type::Bezier)) { + return {}; + } + + Array<int> offsets = curve->control_point_offsets(); + return std::make_unique< + fn::GVMutableArray_For_EmbeddedVMutableArray<float3, VMutableArray_For_BezierHandles>>( + offsets.last(), curve->splines(), std::move(offsets), is_right_); + } + + 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 + { + const CurveEval *curve = get_curve_from_component_for_read(component); + if (curve == nullptr) { + return false; + } + + return curve->has_spline_with_type(Spline::Type::Bezier) && + component.attribute_domain_size(ATTR_DOMAIN_POINT) != 0; + } +}; + /** \} */ /* -------------------------------------------------------------------- */ @@ -1196,6 +1464,8 @@ static ComponentAttributeProviders create_attribute_providers_for_curve() spline_custom_data_access); static PositionAttributeProvider position; + static BezierHandleAttributeProvider handles_start(false); + static BezierHandleAttributeProvider handles_end(true); static BuiltinPointAttributeProvider<float> radius( "radius", @@ -1213,8 +1483,9 @@ static ComponentAttributeProviders create_attribute_providers_for_curve() static DynamicPointAttributeProvider point_custom_data; - return ComponentAttributeProviders({&position, &radius, &tilt, &resolution, &cyclic}, - {&spline_custom_data, &point_custom_data}); + return ComponentAttributeProviders( + {&position, &radius, &tilt, &handles_start, &handles_end, &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 9479d012cb8..4204d62e1a7 100644 --- a/source/blender/blenkernel/intern/geometry_component_instances.cc +++ b/source/blender/blenkernel/intern/geometry_component_instances.cc @@ -14,11 +14,14 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#include <mutex> + #include "BLI_float4x4.hh" #include "BLI_map.hh" #include "BLI_rand.hh" #include "BLI_set.hh" #include "BLI_span.hh" +#include "BLI_task.hh" #include "BLI_vector.hh" #include "DNA_collection_types.h" @@ -122,44 +125,14 @@ blender::Span<int> InstancesComponent::instance_ids() const } /** - * If references have a collection or object type, convert them into geometry instances. This - * will join geometry components from nested instances if necessary. After that, the geometry - * sets can be edited. - */ -void InstancesComponent::ensure_geometry_instances() -{ - VectorSet<InstanceReference> new_references; - new_references.reserve(references_.size()); - for (const InstanceReference &reference : references_) { - if (reference.type() == InstanceReference::Type::Object) { - GeometrySet geometry_set; - InstancesComponent &instances = geometry_set.get_component_for_write<InstancesComponent>(); - const int handle = instances.add_reference(reference.object()); - instances.add_instance(handle, float4x4::identity()); - new_references.add_new(geometry_set); - } - else if (reference.type() == InstanceReference::Type::Collection) { - GeometrySet geometry_set; - InstancesComponent &instances = geometry_set.get_component_for_write<InstancesComponent>(); - const int handle = instances.add_reference(reference.collection()); - instances.add_instance(handle, float4x4::identity()); - new_references.add_new(geometry_set); - } - else { - new_references.add_new(reference); - } - } - references_ = std::move(new_references); -} - -/** * With write access to the instances component, the data in the instanced geometry sets can be * changed. This is a function on the component rather than each reference to ensure `const` * correctness for that reason. */ GeometrySet &InstancesComponent::geometry_set_from_reference(const int reference_index) { - /* If this assert fails, it means #ensure_geometry_instances must be called first. */ + /* If this assert fails, it means #ensure_geometry_instances must be called first or that the + * reference can't be converted to a geometry set. */ BLI_assert(references_[reference_index].type() == InstanceReference::Type::GeometrySet); /* The const cast is okay because the instance's hash in the set @@ -182,6 +155,86 @@ blender::Span<InstanceReference> InstancesComponent::references() const return references_; } +void InstancesComponent::remove_unused_references() +{ + using namespace blender; + using namespace blender::bke; + + const int tot_instances = this->instances_amount(); + const int tot_references_before = references_.size(); + + if (tot_instances == 0) { + /* If there are no instances, no reference is needed. */ + references_.clear(); + return; + } + if (tot_references_before == 1) { + /* There is only one reference and at least one instance. So the only existing reference is + * used. Nothing to do here. */ + return; + } + + Array<bool> usage_by_handle(tot_references_before, false); + std::mutex mutex; + + /* Loop over all instances to see which references are used. */ + threading::parallel_for(IndexRange(tot_instances), 1000, [&](IndexRange range) { + /* Use local counter to avoid lock contention. */ + Array<bool> local_usage_by_handle(tot_references_before, false); + + for (const int i : range) { + const int handle = instance_reference_handles_[i]; + BLI_assert(handle >= 0 && handle < tot_references_before); + local_usage_by_handle[handle] = true; + } + + std::lock_guard lock{mutex}; + for (const int i : IndexRange(tot_references_before)) { + usage_by_handle[i] |= local_usage_by_handle[i]; + } + }); + + if (!usage_by_handle.as_span().contains(false)) { + /* All references are used. */ + return; + } + + /* Create new references and a mapping for the handles. */ + Vector<int> handle_mapping; + VectorSet<InstanceReference> new_references; + int next_new_handle = 0; + bool handles_have_to_be_updated = false; + for (const int old_handle : IndexRange(tot_references_before)) { + if (!usage_by_handle[old_handle]) { + /* Add some dummy value. It won't be read again. */ + handle_mapping.append(-1); + } + else { + const InstanceReference &reference = references_[old_handle]; + handle_mapping.append(next_new_handle); + new_references.add_new(reference); + if (old_handle != next_new_handle) { + handles_have_to_be_updated = true; + } + next_new_handle++; + } + } + references_ = new_references; + + if (!handles_have_to_be_updated) { + /* All remaining handles are the same as before, so they don't have to be updated. This happens + * when unused handles are only at the end. */ + return; + } + + /* Update handles of instances. */ + threading::parallel_for(IndexRange(tot_instances), 1000, [&](IndexRange range) { + for (const int i : range) { + instance_reference_handles_[i] = handle_mapping[instance_reference_handles_[i]]; + } + }); +} + int InstancesComponent::instances_amount() const { return instance_transforms_.size(); diff --git a/source/blender/blenkernel/intern/geometry_set.cc b/source/blender/blenkernel/intern/geometry_set.cc index e717d289894..0aac6ae3adf 100644 --- a/source/blender/blenkernel/intern/geometry_set.cc +++ b/source/blender/blenkernel/intern/geometry_set.cc @@ -15,6 +15,7 @@ */ #include "BLI_map.hh" +#include "BLI_task.hh" #include "BKE_attribute.h" #include "BKE_attribute_access.hh" @@ -151,6 +152,19 @@ void GeometrySet::remove(const GeometryComponentType component_type) components_.remove(component_type); } +/** + * Remove all geometry components with types that are not in the provided list. + */ +void GeometrySet::keep_only(const blender::Span<GeometryComponentType> component_types) +{ + for (auto it = components_.keys().begin(); it != components_.keys().end(); ++it) { + const GeometryComponentType type = *it; + if (!component_types.contains(type)) { + components_.remove(it); + } + } +} + void GeometrySet::add(const GeometryComponent &component) { BLI_assert(!components_.contains(component.type())); @@ -291,6 +305,29 @@ bool GeometrySet::has_curve() const return component != nullptr && component->has_curve(); } +/* Returns true when the geometry set has any data that is not an instance. */ +bool GeometrySet::has_realized_data() const +{ + if (components_.is_empty()) { + return false; + } + if (components_.size() > 1) { + return true; + } + /* Check if the only component is an #InstancesComponent. */ + return this->get_component_for_read<InstancesComponent>() == nullptr; +} + +/* Return true if the geometry set has any component that isn't empty. */ +bool GeometrySet::is_empty() const +{ + if (components_.is_empty()) { + return true; + } + return !(this->has_mesh() || this->has_curve() || this->has_pointcloud() || + this->has_instances()); +} + /* Create a new geometry set that only contains the given mesh. */ GeometrySet GeometrySet::create_with_mesh(Mesh *mesh, GeometryOwnershipType ownership) { @@ -375,6 +412,108 @@ CurveEval *GeometrySet::get_curve_for_write() return component.get_for_write(); } +void GeometrySet::attribute_foreach(const Span<GeometryComponentType> component_types, + const bool include_instances, + const AttributeForeachCallback callback) const +{ + using namespace blender; + using namespace blender::bke; + for (const GeometryComponentType component_type : component_types) { + if (!this->has(component_type)) { + continue; + } + const GeometryComponent &component = *this->get_component_for_read(component_type); + component.attribute_foreach( + [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { + callback(attribute_id, meta_data, component); + return true; + }); + } + if (include_instances && this->has_instances()) { + const InstancesComponent &instances = *this->get_component_for_read<InstancesComponent>(); + instances.foreach_referenced_geometry([&](const GeometrySet &instance_geometry_set) { + instance_geometry_set.attribute_foreach(component_types, include_instances, callback); + }); + } +} + +void GeometrySet::gather_attributes_for_propagation( + const Span<GeometryComponentType> component_types, + const GeometryComponentType dst_component_type, + bool include_instances, + blender::Map<blender::bke::AttributeIDRef, AttributeKind> &r_attributes) const +{ + using namespace blender; + using namespace blender::bke; + /* Only needed right now to check if an attribute is built-in on this component type. + * TODO: Get rid of the dummy component. */ + const GeometryComponent *dummy_component = GeometryComponent::create(dst_component_type); + this->attribute_foreach( + component_types, + include_instances, + [&](const AttributeIDRef &attribute_id, + const AttributeMetaData &meta_data, + const GeometryComponent &component) { + if (component.attribute_is_builtin(attribute_id)) { + if (!dummy_component->attribute_is_builtin(attribute_id)) { + /* Don't propagate built-in attributes that are not built-in on the destination + * component. */ + return; + } + } + if (attribute_id.is_anonymous()) { + if (!BKE_anonymous_attribute_id_has_strong_references(&attribute_id.anonymous_id())) { + /* Don't propagate anonymous attributes that are not used anymore. */ + return; + } + } + auto add_info = [&](AttributeKind *attribute_kind) { + attribute_kind->domain = meta_data.domain; + attribute_kind->data_type = meta_data.data_type; + }; + auto modify_info = [&](AttributeKind *attribute_kind) { + attribute_kind->domain = bke::attribute_domain_highest_priority( + {attribute_kind->domain, meta_data.domain}); + attribute_kind->data_type = bke::attribute_data_type_highest_complexity( + {attribute_kind->data_type, meta_data.data_type}); + }; + r_attributes.add_or_modify(attribute_id, add_info, modify_info); + }); + delete dummy_component; +} + +static void gather_mutable_geometry_sets(GeometrySet &geometry_set, + Vector<GeometrySet *> &r_geometry_sets) +{ + r_geometry_sets.append(&geometry_set); + if (!geometry_set.has_instances()) { + return; + } + /* In the future this can be improved by deduplicating instance references across different + * instances. */ + InstancesComponent &instances_component = + geometry_set.get_component_for_write<InstancesComponent>(); + instances_component.ensure_geometry_instances(); + for (const int handle : instances_component.references().index_range()) { + if (instances_component.references()[handle].type() == InstanceReference::Type::GeometrySet) { + GeometrySet &instance_geometry = instances_component.geometry_set_from_reference(handle); + gather_mutable_geometry_sets(instance_geometry, r_geometry_sets); + } + } +} + +/** + * Modify every (recursive) instance separately. This is often more efficient than realizing all + * instances just to change the same thing on all of them. + */ +void GeometrySet::modify_geometry_sets(ForeachSubGeometryCallback callback) +{ + Vector<GeometrySet *> geometry_sets; + gather_mutable_geometry_sets(*this, geometry_sets); + blender::threading::parallel_for_each( + geometry_sets, [&](GeometrySet *geometry_set) { callback(*geometry_set); }); +} + /** \} */ /* -------------------------------------------------------------------- */ diff --git a/source/blender/blenkernel/intern/geometry_set_instances.cc b/source/blender/blenkernel/intern/geometry_set_instances.cc index 9dca2c2907e..77348c3d22c 100644 --- a/source/blender/blenkernel/intern/geometry_set_instances.cc +++ b/source/blender/blenkernel/intern/geometry_set_instances.cc @@ -14,6 +14,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#include "BKE_collection.h" #include "BKE_geometry_set_instances.hh" #include "BKE_material.h" #include "BKE_mesh.h" @@ -23,6 +24,7 @@ #include "BKE_spline.hh" #include "DNA_collection_types.h" +#include "DNA_layer_types.h" #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" #include "DNA_object_types.h" @@ -187,134 +189,6 @@ void geometry_set_gather_instances(const GeometrySet &geometry_set, geometry_set_collect_recursive(geometry_set, unit_transform, r_instance_groups); } -static bool collection_instance_attribute_foreach(const Collection &collection, - const AttributeForeachCallback callback, - const int limit, - int &count); - -static bool instances_attribute_foreach_recursive(const GeometrySet &geometry_set, - const AttributeForeachCallback callback, - const int limit, - int &count); - -static bool object_instance_attribute_foreach(const Object &object, - const AttributeForeachCallback callback, - const int limit, - int &count) -{ - GeometrySet instance_geometry_set = object_get_geometry_set_for_read(object); - if (!instances_attribute_foreach_recursive(instance_geometry_set, callback, limit, count)) { - return false; - } - - if (object.type == OB_EMPTY) { - const Collection *collection_instance = object.instance_collection; - if (collection_instance != nullptr) { - if (!collection_instance_attribute_foreach(*collection_instance, callback, limit, count)) { - return false; - } - } - } - return true; -} - -static bool collection_instance_attribute_foreach(const Collection &collection, - const AttributeForeachCallback callback, - const int limit, - int &count) -{ - LISTBASE_FOREACH (const CollectionObject *, collection_object, &collection.gobject) { - BLI_assert(collection_object->ob != nullptr); - const Object &object = *collection_object->ob; - if (!object_instance_attribute_foreach(object, callback, limit, count)) { - return false; - } - } - LISTBASE_FOREACH (const CollectionChild *, collection_child, &collection.children) { - BLI_assert(collection_child->collection != nullptr); - const Collection &collection = *collection_child->collection; - if (!collection_instance_attribute_foreach(collection, callback, limit, count)) { - return false; - } - } - return true; -} - -/** - * \return True if the recursive iteration should continue, false if the limit is reached or the - * callback has returned false indicating it should stop. - */ -static bool instances_attribute_foreach_recursive(const GeometrySet &geometry_set, - const AttributeForeachCallback callback, - const int limit, - int &count) -{ - for (const GeometryComponent *component : geometry_set.get_components_for_read()) { - if (!component->attribute_foreach(callback)) { - return false; - } - } - - /* Now that this geometry set is visited, increase the count and check with the limit. */ - if (limit > 0 && count++ > limit) { - return false; - } - - const InstancesComponent *instances_component = - geometry_set.get_component_for_read<InstancesComponent>(); - if (instances_component == nullptr) { - return true; - } - - for (const InstanceReference &reference : instances_component->references()) { - switch (reference.type()) { - case InstanceReference::Type::Object: { - const Object &object = reference.object(); - if (!object_instance_attribute_foreach(object, callback, limit, count)) { - return false; - } - break; - } - case InstanceReference::Type::Collection: { - const Collection &collection = reference.collection(); - if (!collection_instance_attribute_foreach(collection, callback, limit, count)) { - return false; - } - break; - } - case InstanceReference::Type::GeometrySet: { - const GeometrySet &geometry_set = reference.geometry_set(); - if (!instances_attribute_foreach_recursive(geometry_set, callback, limit, count)) { - return false; - } - break; - } - case InstanceReference::Type::None: { - break; - } - } - } - - return true; -} - -/** - * Call the callback on all of this geometry set's components, including geometry sets from - * instances and recursive instances. This is necessary to access available attributes without - * making all of the set's geometry real. - * - * \param limit: The total number of geometry sets to visit before returning early. This is used - * to avoid looking through too many geometry sets recursively, as an explicit tradeoff in favor - * of performance at the cost of visiting every unique attribute. - */ -void geometry_set_instances_attribute_foreach(const GeometrySet &geometry_set, - const AttributeForeachCallback callback, - const int limit) -{ - int count = 0; - instances_attribute_foreach_recursive(geometry_set, callback, limit, count); -} - void geometry_set_gather_instances_attribute_info(Span<GeometryInstanceGroup> set_groups, Span<GeometryComponentType> component_types, const Set<std::string> &ignored_attributes, @@ -700,7 +574,7 @@ static void join_instance_groups_curve(Span<GeometryInstanceGroup> set_groups, G geometry_set_gather_instances_attribute_info( set_groups, {GEO_COMPONENT_TYPE_CURVE}, - {"position", "radius", "tilt", "cyclic", "resolution"}, + {"position", "radius", "tilt", "handle_left", "handle_right", "cyclic", "resolution"}, attributes); join_attributes(set_groups, {GEO_COMPONENT_TYPE_CURVE}, @@ -745,3 +619,91 @@ GeometrySet geometry_set_realize_instances(const GeometrySet &geometry_set) } } // namespace blender::bke + +void InstancesComponent::foreach_referenced_geometry( + blender::FunctionRef<void(const GeometrySet &geometry_set)> callback) const +{ + using namespace blender::bke; + for (const InstanceReference &reference : references_) { + switch (reference.type()) { + case InstanceReference::Type::Object: { + const Object &object = reference.object(); + const GeometrySet object_geometry_set = object_get_geometry_set_for_read(object); + callback(object_geometry_set); + break; + } + case InstanceReference::Type::Collection: { + Collection &collection = reference.collection(); + FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN (&collection, object) { + const GeometrySet object_geometry_set = object_get_geometry_set_for_read(*object); + callback(object_geometry_set); + } + FOREACH_COLLECTION_OBJECT_RECURSIVE_END; + break; + } + case InstanceReference::Type::GeometrySet: { + const GeometrySet &instance_geometry_set = reference.geometry_set(); + callback(instance_geometry_set); + break; + } + case InstanceReference::Type::None: { + break; + } + } + } +} + +/** + * If references have a collection or object type, convert them into geometry instances + * recursively. After that, the geometry sets can be edited. There may still be instances of other + * types of they can't be converted to geometry sets. + */ +void InstancesComponent::ensure_geometry_instances() +{ + using namespace blender; + using namespace blender::bke; + VectorSet<InstanceReference> new_references; + new_references.reserve(references_.size()); + for (const InstanceReference &reference : references_) { + switch (reference.type()) { + case InstanceReference::Type::None: + case InstanceReference::Type::GeometrySet: { + /* Those references can stay as their were. */ + new_references.add_new(reference); + break; + } + case InstanceReference::Type::Object: { + /* Create a new reference that contains the geometry set of the object. We may want to + * treat e.g. lamps and similar object types separately here. */ + const Object &object = reference.object(); + GeometrySet object_geometry_set = object_get_geometry_set_for_read(object); + if (object_geometry_set.has_instances()) { + InstancesComponent &component = + object_geometry_set.get_component_for_write<InstancesComponent>(); + component.ensure_geometry_instances(); + } + new_references.add_new(std::move(object_geometry_set)); + break; + } + case InstanceReference::Type::Collection: { + /* Create a new reference that contains a geometry set that contains all objects from the + * collection as instances. */ + GeometrySet collection_geometry_set; + InstancesComponent &component = + collection_geometry_set.get_component_for_write<InstancesComponent>(); + Collection &collection = reference.collection(); + FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN (&collection, object) { + const int handle = component.add_reference(*object); + component.add_instance(handle, object->obmat); + float4x4 &transform = component.instance_transforms().last(); + sub_v3_v3(transform.values[3], collection.instance_offset); + } + FOREACH_COLLECTION_OBJECT_RECURSIVE_END; + component.ensure_geometry_instances(); + new_references.add_new(std::move(collection_geometry_set)); + break; + } + } + } + references_ = std::move(new_references); +} diff --git a/source/blender/blenkernel/intern/gpencil.c b/source/blender/blenkernel/intern/gpencil.c index 82a44afbbb1..ed84694a919 100644 --- a/source/blender/blenkernel/intern/gpencil.c +++ b/source/blender/blenkernel/intern/gpencil.c @@ -319,7 +319,7 @@ IDTypeInfo IDType_ID_GD = { .name = "GPencil", .name_plural = "grease_pencils", .translation_context = BLT_I18NCONTEXT_ID_GPENCIL, - .flags = 0, + .flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE, .init_data = NULL, .copy_data = greasepencil_copy_data, diff --git a/source/blender/blenkernel/intern/gpencil_curve.c b/source/blender/blenkernel/intern/gpencil_curve.c index 0752424df71..3819c0699f4 100644 --- a/source/blender/blenkernel/intern/gpencil_curve.c +++ b/source/blender/blenkernel/intern/gpencil_curve.c @@ -543,7 +543,7 @@ void BKE_gpencil_convert_curve(Main *bmain, int actcol = ob_gp->actcol; for (int slot = 1; slot <= ob_gp->totcol; slot++) { - while (slot <= ob_gp->totcol && !BKE_object_material_slot_used(ob_gp->data, slot)) { + while (slot <= ob_gp->totcol && !BKE_object_material_slot_used(ob_gp, slot)) { ob_gp->actcol = slot; BKE_object_material_slot_remove(bmain, ob_gp); diff --git a/source/blender/blenkernel/intern/gpencil_geom.cc b/source/blender/blenkernel/intern/gpencil_geom.cc index 976b26a1f3a..debdf44b0bb 100644 --- a/source/blender/blenkernel/intern/gpencil_geom.cc +++ b/source/blender/blenkernel/intern/gpencil_geom.cc @@ -738,8 +738,8 @@ bool BKE_gpencil_stroke_stretch(bGPDstroke *gps, sub_v3_v3v3(vec1, &gps->points[start_i].x, &gps->points[start_i + dir_i].x); /* In general curvature = 1/radius. For the case without the - * weights introduced by #segment_influence, the calculation is - * curvature = delta angle/delta arclength = len_v3(total_angle) / overshoot_length */ + * weights introduced by #segment_influence, the calculation is: + * `curvature = delta angle/delta arclength = len_v3(total_angle) / overshoot_length` */ float curvature = normalize_v3(total_angle) / overshoot_length; /* Compensate for the weights powf(added_len, segment_influence). */ curvature /= powf(overshoot_length / fminf(overshoot_parameter, (float)j), segment_influence); diff --git a/source/blender/blenkernel/intern/gpencil_modifier.c b/source/blender/blenkernel/intern/gpencil_modifier.c index 6be03bffb3c..a6164340477 100644 --- a/source/blender/blenkernel/intern/gpencil_modifier.c +++ b/source/blender/blenkernel/intern/gpencil_modifier.c @@ -65,7 +65,7 @@ static CLG_LogRef LOG = {"bke.gpencil_modifier"}; static GpencilModifierTypeInfo *modifier_gpencil_types[NUM_GREASEPENCIL_MODIFIER_TYPES] = {NULL}; #if 0 -/* Note that GPencil actually does not support these atm, but might do in the future. */ +/* Note that GPencil actually does not support these at the moment, but might do in the future. */ static GpencilVirtualModifierData virtualModifierCommonData; #endif @@ -129,7 +129,8 @@ GpencilModifierData *BKE_gpencil_modifiers_get_virtual_modifierlist( GpencilModifierData *md = ob->greasepencil_modifiers.first; #if 0 - /* Note that GPencil actually does not support these atm, but might do in the future. */ + /* Note that GPencil actually does not support these at the moment, + * but might do in the future. */ *virtualModifierData = virtualModifierCommonData; if (ob->parent) { if (ob->parent->type == OB_ARMATURE && ob->partype == PARSKEL) { @@ -328,8 +329,9 @@ void BKE_gpencil_modifier_init(void) gpencil_modifier_type_init(modifier_gpencil_types); /* MOD_gpencil_util.c */ #if 0 - /* Note that GPencil actually does not support these atm, but might do in the future. */ - /* Initialize global cmmon storage used for virtual modifier list */ + /* Note that GPencil actually does not support these at the moment, + * but might do in the future. */ + /* Initialize global common storage used for virtual modifier list. */ GpencilModifierData *md; md = BKE_gpencil_modifier_new(eGpencilModifierType_Armature); virtualModifierCommonData.amd = *((ArmatureGpencilModifierData *)md); @@ -518,7 +520,7 @@ static void gpencil_modifier_copy_data_id_us_cb(void *UNUSED(userData), * Copy grease pencil modifier data. * \param md: Source modifier data * \param target: Target modifier data - * \parm flag: Flags + * \param flag: Flags */ void BKE_gpencil_modifier_copydata_ex(GpencilModifierData *md, GpencilModifierData *target, diff --git a/source/blender/blenkernel/intern/ipo.c b/source/blender/blenkernel/intern/ipo.c index 9b72a2d1a72..26a1240080f 100644 --- a/source/blender/blenkernel/intern/ipo.c +++ b/source/blender/blenkernel/intern/ipo.c @@ -2013,7 +2013,8 @@ static void nlastrips_to_animdata(ID *id, ListBase *strips) } } - /* try to add this strip to the current NLA-Track (i.e. the 'last' one on the stack atm) */ + /* Try to add this strip to the current NLA-Track + * (i.e. the 'last' one on the stack at the moment). */ if (BKE_nlatrack_add_strip(nlt, strip, false) == 0) { /* trying to add to the current failed (no space), * so add a new track to the stack, and add to that... diff --git a/source/blender/blenkernel/intern/key.c b/source/blender/blenkernel/intern/key.c index 44fc86877a7..c09fcf0715e 100644 --- a/source/blender/blenkernel/intern/key.c +++ b/source/blender/blenkernel/intern/key.c @@ -1904,7 +1904,7 @@ KeyBlock *BKE_keyblock_add_ctime(Key *key, const char *name, const bool do_force return kb; } -/* only the active keyblock */ +/* Only the active key-block. */ KeyBlock *BKE_keyblock_from_object(Object *ob) { Key *key = BKE_key_from_object(ob); @@ -2247,7 +2247,7 @@ void BKE_keyblock_convert_to_mesh(KeyBlock *kb, Mesh *me) * Computes normals (vertices, polygons and/or loops ones) of given mesh for given shape key. * * \param kb: the KeyBlock to use to compute normals. - * \param mesh: the Mesh to apply keyblock to. + * \param mesh: the Mesh to apply key-block to. * \param r_vertnors: if non-NULL, an array of vectors, same length as number of vertices. * \param r_polynors: if non-NULL, an array of vectors, same length as number of polygons. * \param r_loopnors: if non-NULL, an array of vectors, same length as number of loops. @@ -2345,7 +2345,7 @@ void BKE_keyblock_update_from_vertcos(Object *ob, KeyBlock *kb, const float (*ve return; } - /* Copy coords to keyblock */ + /* Copy coords to key-block. */ if (ELEM(ob->type, OB_MESH, OB_LATTICE)) { for (a = 0; a < tot; a++, fp += 3, co++) { copy_v3_v3(fp, *co); @@ -2405,7 +2405,7 @@ void BKE_keyblock_convert_from_vertcos(Object *ob, KeyBlock *kb, const float (*v kb->data = MEM_mallocN(tot * elemsize, __func__); - /* Copy coords to keyblock */ + /* Copy coords to key-block. */ BKE_keyblock_update_from_vertcos(ob, kb, vertCos); } @@ -2594,7 +2594,7 @@ bool BKE_keyblock_move(Object *ob, int org_index, int new_index) } /** - * Check if given keyblock (as index) is used as basis by others in given key. + * Check if given key-block (as index) is used as basis by others in given key. */ bool BKE_keyblock_is_basis(Key *key, const int index) { diff --git a/source/blender/blenkernel/intern/lib_id.c b/source/blender/blenkernel/intern/lib_id.c index 18824e73ee5..3b2d2c5d2c3 100644 --- a/source/blender/blenkernel/intern/lib_id.c +++ b/source/blender/blenkernel/intern/lib_id.c @@ -674,7 +674,10 @@ ID *BKE_id_copy(Main *bmain, const ID *id) * Invokes the appropriate copy method for the block and returns the result in * newid, unless test. Returns true if the block can be copied. */ -ID *BKE_id_copy_for_duplicate(Main *bmain, ID *id, const eDupli_ID_Flags duplicate_flags) +ID *BKE_id_copy_for_duplicate(Main *bmain, + ID *id, + const eDupli_ID_Flags duplicate_flags, + const int copy_flags) { if (id == NULL) { return id; @@ -685,7 +688,7 @@ ID *BKE_id_copy_for_duplicate(Main *bmain, ID *id, const eDupli_ID_Flags duplica return id; } - ID *id_new = BKE_id_copy(bmain, id); + ID *id_new = BKE_id_copy_ex(bmain, id, NULL, copy_flags); /* Copying add one user by default, need to get rid of that one. */ id_us_min(id_new); ID_NEW_SET(id, id_new); diff --git a/source/blender/blenkernel/intern/lib_override.c b/source/blender/blenkernel/intern/lib_override.c index c60a9104144..68675e5fc91 100644 --- a/source/blender/blenkernel/intern/lib_override.c +++ b/source/blender/blenkernel/intern/lib_override.c @@ -1000,9 +1000,92 @@ bool BKE_lib_override_library_proxy_convert(Main *bmain, DEG_id_tag_update(&ob_proxy->id, ID_RECALC_COPY_ON_WRITE); + /* In case of proxy conversion, remap all local ID usages to linked IDs to their newly created + * overrides. + * While this might not be 100% the desired behavior, it is likely to be the case most of the + * time. Ref: T91711. */ + ID *id_iter; + FOREACH_MAIN_ID_BEGIN (bmain, id_iter) { + if (!ID_IS_LINKED(id_iter)) { + id_iter->tag |= LIB_TAG_DOIT; + } + } + FOREACH_MAIN_ID_END; + return BKE_lib_override_library_create(bmain, scene, view_layer, id_root, id_reference, NULL); } +static void lib_override_library_proxy_convert_do(Main *bmain, + Scene *scene, + Object *ob_proxy, + BlendFileReadReport *reports) +{ + Object *ob_proxy_group = ob_proxy->proxy_group; + const bool is_override_instancing_object = ob_proxy_group != NULL; + + const bool success = BKE_lib_override_library_proxy_convert(bmain, scene, NULL, ob_proxy); + + if (success) { + CLOG_INFO(&LOG, + 4, + "Proxy object '%s' successfuly converted to library overrides", + ob_proxy->id.name); + /* Remove the instance empty from this scene, the items now have an overridden collection + * instead. */ + if (is_override_instancing_object) { + BKE_scene_collections_object_remove(bmain, scene, ob_proxy_group, true); + } + reports->count.proxies_to_lib_overrides_success++; + } +} + +/** + * Convert all proxy objects into library overrides. + * + * \note Only affects local proxies, linked ones are not affected. + * + * \param view_layer: the active view layer to search instantiated collections in, can be NULL (in + * which case \a scene's master collection children hierarchy is used instead). + */ +void BKE_lib_override_library_main_proxy_convert(Main *bmain, BlendFileReadReport *reports) +{ + LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { + FOREACH_SCENE_OBJECT_BEGIN (scene, object) { + if (object->proxy_group == NULL) { + continue; + } + + lib_override_library_proxy_convert_do(bmain, scene, object, reports); + } + FOREACH_SCENE_OBJECT_END; + + FOREACH_SCENE_OBJECT_BEGIN (scene, object) { + if (object->proxy == NULL) { + continue; + } + + lib_override_library_proxy_convert_do(bmain, scene, object, reports); + } + FOREACH_SCENE_OBJECT_END; + } + + LISTBASE_FOREACH (Object *, object, &bmain->objects) { + if (ID_IS_LINKED(object)) { + if (object->proxy != NULL) { + CLOG_WARN(&LOG, "Did not try to convert linked proxy object '%s'", object->id.name); + reports->count.linked_proxies++; + } + continue; + } + + if (object->proxy_group != NULL || object->proxy != NULL) { + CLOG_WARN( + &LOG, "Proxy object '%s' failed to be converted to library override", object->id.name); + reports->count.proxies_to_lib_overrides_failures++; + } + } +} + /** * Advanced 'smart' function to resync, re-create fully functional overrides up-to-date with linked * data, from an existing override hierarchy. @@ -2889,6 +2972,31 @@ void BKE_lib_override_library_main_update(Main *bmain) G_MAIN = orig_gmain; } +/** In case an ID is used by another liboverride ID, user may not be allowed to delete it. */ +bool BKE_lib_override_library_id_is_user_deletable(struct Main *bmain, struct ID *id) +{ + if (!(ID_IS_LINKED(id) || ID_IS_OVERRIDE_LIBRARY(id))) { + return true; + } + + /* The only strong known case currently are objects used by override collections. */ + /* TODO: There are most likely other cases... This may need to be addressed in a better way at + * some point. */ + if (GS(id->name) != ID_OB) { + return true; + } + Object *ob = (Object *)id; + LISTBASE_FOREACH (Collection *, collection, &bmain->collections) { + if (!ID_IS_OVERRIDE_LIBRARY(collection)) { + continue; + } + if (BKE_collection_has_object(collection, ob)) { + return false; + } + } + return true; +} + /** * Storage (how to store overriding data into `.blend` files). * diff --git a/source/blender/blenkernel/intern/lib_remap.c b/source/blender/blenkernel/intern/lib_remap.c index 250b8d4d515..48396c5e6d9 100644 --- a/source/blender/blenkernel/intern/lib_remap.c +++ b/source/blender/blenkernel/intern/lib_remap.c @@ -345,7 +345,7 @@ static void libblock_remap_data_postprocess_obdata_relink(Main *bmain, Object *o static void libblock_remap_data_postprocess_nodetree_update(Main *bmain, ID *new_id) { /* Update all group nodes using a node group. */ - ntreeUpdateAllUsers(bmain, new_id); + ntreeUpdateAllUsers(bmain, new_id, 0); } /** diff --git a/source/blender/blenkernel/intern/main.c b/source/blender/blenkernel/intern/main.c index 26dcadcc77b..9c3291edbcc 100644 --- a/source/blender/blenkernel/intern/main.c +++ b/source/blender/blenkernel/intern/main.c @@ -485,6 +485,7 @@ void BKE_main_library_weak_reference_add_item(GHash *library_weak_reference_mapp const bool already_exist_in_mapping = BLI_ghash_ensure_p( library_weak_reference_mapping, key, &id_p); BLI_assert(!already_exist_in_mapping); + UNUSED_VARS_NDEBUG(already_exist_in_mapping); BLI_strncpy(new_id->library_weak_reference->library_filepath, library_filepath, diff --git a/source/blender/blenkernel/intern/material.c b/source/blender/blenkernel/intern/material.c index 5f53d5e1ae8..fa3fbd457d1 100644 --- a/source/blender/blenkernel/intern/material.c +++ b/source/blender/blenkernel/intern/material.c @@ -46,6 +46,7 @@ #include "DNA_meta_types.h" #include "DNA_node_types.h" #include "DNA_object_types.h" +#include "DNA_particle_types.h" #include "DNA_pointcloud_types.h" #include "DNA_scene_types.h" #include "DNA_volume_types.h" @@ -73,6 +74,7 @@ #include "BKE_material.h" #include "BKE_mesh.h" #include "BKE_node.h" +#include "BKE_object.h" #include "BKE_scene.h" #include "DEG_depsgraph.h" @@ -462,21 +464,33 @@ static void material_data_index_remove_id(ID *id, short index) } } -bool BKE_object_material_slot_used(ID *id, short actcol) +bool BKE_object_material_slot_used(Object *object, short actcol) { - /* ensure we don't try get materials from non-obdata */ - BLI_assert(OB_DATA_SUPPORT_ID(GS(id->name))); + if (!BKE_object_supports_material_slots(object)) { + return false; + } - switch (GS(id->name)) { + LISTBASE_FOREACH (ParticleSystem *, psys, &object->particlesystem) { + if (psys->part->omat == actcol) { + return true; + } + } + + ID *ob_data = object->data; + if (ob_data == NULL || !OB_DATA_SUPPORT_ID(GS(ob_data->name))) { + return false; + } + + switch (GS(ob_data->name)) { case ID_ME: - return BKE_mesh_material_index_used((Mesh *)id, actcol - 1); + return BKE_mesh_material_index_used((Mesh *)ob_data, actcol - 1); case ID_CU: - return BKE_curve_material_index_used((Curve *)id, actcol - 1); + return BKE_curve_material_index_used((Curve *)ob_data, actcol - 1); case ID_MB: - /* meta-elems don't have materials atm */ + /* Meta-elements don't support materials at the moment. */ return false; case ID_GD: - return BKE_gpencil_material_index_used((bGPdata *)id, actcol - 1); + return BKE_gpencil_material_index_used((bGPdata *)ob_data, actcol - 1); default: return false; } diff --git a/source/blender/blenkernel/intern/mball_tessellate.c b/source/blender/blenkernel/intern/mball_tessellate.c index 9dd583b4c6b..a2590171abd 100644 --- a/source/blender/blenkernel/intern/mball_tessellate.c +++ b/source/blender/blenkernel/intern/mball_tessellate.c @@ -454,7 +454,7 @@ static void make_face(PROCESS *process, int i1, int i2, int i3, int i4) cur = process->indices[process->curindex++]; - /* displists now support array drawing, we treat tri's as fake quad */ + /* #DispList supports array drawing, treat tri's as fake quad. */ cur[0] = i1; cur[1] = i2; diff --git a/source/blender/blenkernel/intern/mesh_convert.cc b/source/blender/blenkernel/intern/mesh_convert.cc index 467f7d4543e..59cdb6a2b27 100644 --- a/source/blender/blenkernel/intern/mesh_convert.cc +++ b/source/blender/blenkernel/intern/mesh_convert.cc @@ -41,6 +41,7 @@ #include "BKE_deform.h" #include "BKE_displist.h" #include "BKE_editmesh.h" +#include "BKE_geometry_set.hh" #include "BKE_key.h" #include "BKE_lib_id.h" #include "BKE_lib_query.h" @@ -51,6 +52,7 @@ #include "BKE_mesh_runtime.h" #include "BKE_mesh_wrapper.h" #include "BKE_modifier.h" +#include "BKE_spline.hh" /* these 2 are only used by conversion functions */ #include "BKE_curve.h" /* -- */ @@ -58,6 +60,8 @@ /* -- */ #include "BKE_pointcloud.h" +#include "BKE_curve_to_mesh.hh" + #include "DEG_depsgraph.h" #include "DEG_depsgraph_query.h" @@ -237,7 +241,7 @@ static int mesh_nurbs_displist_to_mdata(const Curve *cu, int a, b, ofs, vertcount, startvert, totvert = 0, totedge = 0, totloop = 0, totpoly = 0; int p1, p2, p3, p4, *index; const bool conv_polys = ( - /* 2d polys are filled with DL_INDEX3 displists */ + /* 2D polys are filled with #DispList.type == #DL_INDEX3. */ (CU_DO_2DFILL(cu) == false) || /* surf polys are never filled */ BKE_curve_type_get(cu) == OB_SURF); @@ -573,90 +577,6 @@ Mesh *BKE_mesh_new_nomain_from_curve(const Object *ob) return BKE_mesh_new_nomain_from_curve_displist(ob, &disp); } -static void mesh_from_nurbs_displist(Object *ob, ListBase *dispbase, const char *obdata_name) -{ - if (ob->runtime.data_eval && GS(((ID *)ob->runtime.data_eval)->name) != ID_ME) { - return; - } - - Mesh *me_eval = (Mesh *)ob->runtime.data_eval; - Mesh *me; - MVert *allvert = nullptr; - MEdge *alledge = nullptr; - MLoop *allloop = nullptr; - MLoopUV *alluv = nullptr; - MPoly *allpoly = nullptr; - int totvert, totedge, totloop, totpoly; - - Curve *cu = (Curve *)ob->data; - - if (me_eval == nullptr) { - if (mesh_nurbs_displist_to_mdata(cu, - dispbase, - &allvert, - &totvert, - &alledge, - &totedge, - &allloop, - &allpoly, - &alluv, - &totloop, - &totpoly) != 0) { - /* Error initializing */ - return; - } - - /* make mesh */ - me = (Mesh *)BKE_id_new_nomain(ID_ME, obdata_name); - - me->totvert = totvert; - me->totedge = totedge; - me->totloop = totloop; - me->totpoly = totpoly; - - me->mvert = (MVert *)CustomData_add_layer( - &me->vdata, CD_MVERT, CD_ASSIGN, allvert, me->totvert); - me->medge = (MEdge *)CustomData_add_layer( - &me->edata, CD_MEDGE, CD_ASSIGN, alledge, me->totedge); - me->mloop = (MLoop *)CustomData_add_layer( - &me->ldata, CD_MLOOP, CD_ASSIGN, allloop, me->totloop); - me->mpoly = (MPoly *)CustomData_add_layer( - &me->pdata, CD_MPOLY, CD_ASSIGN, allpoly, me->totpoly); - - if (alluv) { - const char *uvname = "UVMap"; - me->mloopuv = (MLoopUV *)CustomData_add_layer_named( - &me->ldata, CD_MLOOPUV, CD_ASSIGN, alluv, me->totloop, uvname); - } - - BKE_mesh_calc_normals(me); - } - else { - me = (Mesh *)BKE_id_new_nomain(ID_ME, obdata_name); - - ob->runtime.data_eval = nullptr; - BKE_mesh_nomain_to_mesh(me_eval, me, ob, &CD_MASK_MESH, true); - } - - me->totcol = cu->totcol; - me->mat = cu->mat; - - mesh_copy_texture_space_from_curve_type(cu, me); - - cu->mat = nullptr; - cu->totcol = 0; - - /* Do not decrement ob->data usercount here, - * it's done at end of func with BKE_id_free_us() call. */ - ob->data = me; - ob->type = OB_MESH; - - /* For temporary objects in BKE_mesh_new_from_object don't remap - * the entire scene with associated depsgraph updates, which are - * problematic for renderers exporting data. */ - BKE_id_free(nullptr, cu); -} - struct EdgeLink { struct EdgeLink *next, *prev; void *edge; @@ -948,54 +868,32 @@ void BKE_pointcloud_to_mesh(Main *bmain, Depsgraph *depsgraph, Scene *UNUSED(sce BKE_object_free_derived_caches(ob); } -/* Create a temporary object to be used for nurbs-to-mesh conversion. - * - * This is more complex that it should be because #mesh_from_nurbs_displist will do more than - * simply conversion and will attempt to take over ownership of evaluated result and will also - * modify the input object. */ -static Object *object_for_curve_to_mesh_create(Object *object) +/* Create a temporary object to be used for nurbs-to-mesh conversion. */ +static Object *object_for_curve_to_mesh_create(const Object *object) { - Curve *curve = (Curve *)object->data; + const Curve *curve = (const Curve *)object->data; - /* Create object itself. */ + /* Create a temporary object which can be evaluated and modified by generic + * curve evaluation (hence the #LIB_ID_COPY_SET_COPIED_ON_WRITE flag). */ Object *temp_object = (Object *)BKE_id_copy_ex( - nullptr, &object->id, nullptr, LIB_ID_COPY_LOCALIZE); + nullptr, &object->id, nullptr, LIB_ID_COPY_LOCALIZE | LIB_ID_COPY_SET_COPIED_ON_WRITE); /* Remove all modifiers, since we don't want them to be applied. */ BKE_object_free_modifiers(temp_object, LIB_ID_CREATE_NO_USER_REFCOUNT); - /* Copy relevant evaluated fields of curve cache. - * - * Note that there are extra fields in there like bevel and path, but those are not needed during - * conversion, so they are not copied to save unnecessary allocations. */ - if (temp_object->runtime.curve_cache == nullptr) { - temp_object->runtime.curve_cache = (CurveCache *)MEM_callocN(sizeof(CurveCache), - "CurveCache for curve types"); - } - - if (object->runtime.curve_cache != nullptr) { - BKE_displist_copy(&temp_object->runtime.curve_cache->disp, &object->runtime.curve_cache->disp); - } - - /* Constructive modifiers will use mesh to store result. */ - if (object->runtime.data_eval != nullptr) { - BKE_id_copy_ex( - nullptr, object->runtime.data_eval, &temp_object->runtime.data_eval, LIB_ID_COPY_LOCALIZE); - } - - /* Need to create copy of curve itself as well, it will be freed by underlying conversion - * functions. - * - * NOTE: Copies the data, but not the shapekeys. */ - BKE_id_copy_ex( - nullptr, (const ID *)object->data, (ID **)&temp_object->data, LIB_ID_COPY_LOCALIZE); + /* Need to create copy of curve itself as well, since it will be changed by the curve evaluation + * process. NOTE: Copies the data, but not the shape-keys. */ + temp_object->data = BKE_id_copy_ex(nullptr, + (const ID *)object->data, + nullptr, + LIB_ID_COPY_LOCALIZE | LIB_ID_COPY_SET_COPIED_ON_WRITE); Curve *temp_curve = (Curve *)temp_object->data; /* Make sure texture space is calculated for a copy of curve, it will be used for the final * result. */ BKE_curve_texspace_calc(temp_curve); - /* Temporarily set edit so we get updates from edit mode, but also because for text datablocks + /* Temporarily set edit so we get updates from edit mode, but also because for text data-blocks * copying it while in edit mode gives invalid data structures. */ temp_curve->editfont = curve->editfont; temp_curve->editnurb = curve->editnurb; @@ -1006,23 +904,10 @@ static Object *object_for_curve_to_mesh_create(Object *object) /** * Populate `object->runtime.curve_cache` which is then used to create the mesh. */ -static void curve_to_mesh_eval_ensure(Object *object) +static void curve_to_mesh_eval_ensure(Object &object) { - Curve *curve = (Curve *)object->data; - Curve remapped_curve = *curve; - Object remapped_object = *object; - BKE_object_runtime_reset(&remapped_object); - - remapped_object.data = &remapped_curve; - - if (object->runtime.curve_cache == nullptr) { - object->runtime.curve_cache = (CurveCache *)MEM_callocN(sizeof(CurveCache), - "CurveCache for Curve"); - } - - /* Temporarily share the curve-cache with the temporary object, owned by `object`. */ - remapped_object.runtime.curve_cache = object->runtime.curve_cache; - + BLI_assert(GS(static_cast<ID *>(object.data)->name) == ID_CU); + Curve &curve = *static_cast<Curve *>(object.data); /* Clear all modifiers for the bevel object. * * This is because they can not be reliably evaluated for an original object (at least because @@ -1031,83 +916,97 @@ static void curve_to_mesh_eval_ensure(Object *object) * So we create temporary copy of the object which will use same data as the original bevel, but * will have no modifiers. */ Object bevel_object = {{nullptr}}; - if (remapped_curve.bevobj != nullptr) { - bevel_object = *remapped_curve.bevobj; + if (curve.bevobj != nullptr) { + bevel_object = *curve.bevobj; BLI_listbase_clear(&bevel_object.modifiers); BKE_object_runtime_reset(&bevel_object); - remapped_curve.bevobj = &bevel_object; + curve.bevobj = &bevel_object; } /* Same thing for taper. */ Object taper_object = {{nullptr}}; - if (remapped_curve.taperobj != nullptr) { - taper_object = *remapped_curve.taperobj; + if (curve.taperobj != nullptr) { + taper_object = *curve.taperobj; BLI_listbase_clear(&taper_object.modifiers); BKE_object_runtime_reset(&taper_object); - remapped_curve.taperobj = &taper_object; + curve.taperobj = &taper_object; } /* NOTE: We don't have dependency graph or scene here, so we pass nullptr. This is all fine since * they are only used for modifier stack, which we have explicitly disabled for all objects. * * TODO(sergey): This is a very fragile logic, but proper solution requires re-writing quite a - * bit of internal functions (#mesh_from_nurbs_displist, BKE_mesh_nomain_to_mesh) and also - * Mesh From Curve operator. + * bit of internal functions (#BKE_mesh_nomain_to_mesh) and also Mesh From Curve operator. * Brecht says hold off with that. */ - Mesh *mesh_eval = nullptr; - BKE_displist_make_curveTypes_forRender( - nullptr, nullptr, &remapped_object, &remapped_object.runtime.curve_cache->disp, &mesh_eval); + BKE_displist_make_curveTypes(nullptr, nullptr, &object, true); - /* NOTE: this is to be consistent with `BKE_displist_make_curveTypes()`, however that is not a - * real issue currently, code here is broken in more than one way, fix(es) will be done - * separately. */ - if (mesh_eval != nullptr) { - BKE_object_eval_assign_data(&remapped_object, &mesh_eval->id, true); - } - - /* Owned by `object` & needed by the caller to create the mesh. */ - remapped_object.runtime.curve_cache = nullptr; - - BKE_object_runtime_free_data(&remapped_object); - BKE_object_runtime_free_data(&taper_object); + BKE_object_runtime_free_data(&bevel_object); BKE_object_runtime_free_data(&taper_object); } -static Mesh *mesh_new_from_curve_type_object(Object *object) +/* Necessary because #BKE_object_get_evaluated_mesh doesn't look in the geometry set yet. */ +static const Mesh *get_evaluated_mesh_from_object(const Object *object) { - Curve *curve = (Curve *)object->data; - Object *temp_object = object_for_curve_to_mesh_create(object); - Curve *temp_curve = (Curve *)temp_object->data; + const Mesh *mesh = BKE_object_get_evaluated_mesh(object); + if (mesh) { + return mesh; + } + GeometrySet *geometry_set_eval = object->runtime.geometry_set_eval; + if (geometry_set_eval) { + return geometry_set_eval->get_mesh_for_read(); + } + return nullptr; +} - /* When input object is an original one, we don't have evaluated curve cache yet, so need to - * create it in the temporary object. */ - if (!DEG_is_evaluated_object(object)) { - curve_to_mesh_eval_ensure(temp_object); +static const CurveEval *get_evaluated_curve_from_object(const Object *object) +{ + GeometrySet *geometry_set_eval = object->runtime.geometry_set_eval; + if (geometry_set_eval) { + return geometry_set_eval->get_curve_for_read(); } + return nullptr; +} - /* Reset pointers before conversion. */ - temp_curve->editfont = nullptr; - temp_curve->editnurb = nullptr; +static Mesh *mesh_new_from_evaluated_curve_type_object(const Object *evaluated_object) +{ + const Mesh *mesh = get_evaluated_mesh_from_object(evaluated_object); + if (mesh) { + return BKE_mesh_copy_for_eval(mesh, false); + } + const CurveEval *curve = get_evaluated_curve_from_object(evaluated_object); + if (curve) { + return blender::bke::curve_to_wire_mesh(*curve); + } + return nullptr; +} - /* Convert to mesh. */ - mesh_from_nurbs_displist( - temp_object, &temp_object->runtime.curve_cache->disp, curve->id.name + 2); +static Mesh *mesh_new_from_curve_type_object(const Object *object) +{ + /* If the object is evaluated, it should either have an evaluated mesh or curve data already. + * The mesh can be duplicated, or the curve converted to wire mesh edges. */ + if (DEG_is_evaluated_object(object)) { + return mesh_new_from_evaluated_curve_type_object(object); + } - /* #mesh_from_nurbs_displist changes the type to a mesh, check it worked. If it didn't - * the curve did not have any segments or otherwise would have generated an empty mesh. */ - if (temp_object->type != OB_MESH) { - BKE_id_free(nullptr, temp_object->data); - BKE_id_free(nullptr, temp_object); - return nullptr; + /* Otherwise, create a temporary "fake" evaluated object and try again. This might have + * different results, since in order to avoid having adverse affects to other original objects, + * modifiers are cleared. An alternative would be to create a temporary depsgraph only for this + * object and its dependencies. */ + Object *temp_object = object_for_curve_to_mesh_create(object); + ID *temp_data = static_cast<ID *>(temp_object->data); + curve_to_mesh_eval_ensure(*temp_object); + + /* If evaluating the curve replaced object data with different data, free the original data. */ + if (temp_data != temp_object->data) { + BKE_id_free(nullptr, temp_data); } - Mesh *mesh_result = (Mesh *)temp_object->data; + Mesh *mesh = mesh_new_from_evaluated_curve_type_object(temp_object); + BKE_id_free(nullptr, temp_object->data); BKE_id_free(nullptr, temp_object); - /* NOTE: Materials are copied in #mesh_from_nurbs_displist(). */ - - return mesh_result; + return mesh; } static Mesh *mesh_new_from_mball_object(Object *object) @@ -1290,7 +1189,7 @@ Mesh *BKE_mesh_new_from_object_to_bmain(Main *bmain, return mesh_in_bmain; } - /* Make sure mesh only points original datablocks, also increase users of materials and other + /* Make sure mesh only points original data-blocks, also increase users of materials and other * possibly referenced data-blocks. * * Going to original data-blocks is required to have bmain in a consistent state, where diff --git a/source/blender/blenkernel/intern/modifier.c b/source/blender/blenkernel/intern/modifier.c index b55b02c7bf2..6f6cf12f023 100644 --- a/source/blender/blenkernel/intern/modifier.c +++ b/source/blender/blenkernel/intern/modifier.c @@ -100,7 +100,7 @@ void BKE_modifier_init(void) /* Initialize modifier types */ modifier_type_init(modifier_types); /* MOD_utils.c */ - /* Initialize global cmmon storage used for virtual modifier list */ + /* Initialize global common storage used for virtual modifier list. */ md = BKE_modifier_new(eModifierType_Armature); virtualModifierCommonData.amd = *((ArmatureModifierData *)md); BKE_modifier_free(md); diff --git a/source/blender/blenkernel/intern/nla.c b/source/blender/blenkernel/intern/nla.c index 4ce2ae3c11f..487e925df79 100644 --- a/source/blender/blenkernel/intern/nla.c +++ b/source/blender/blenkernel/intern/nla.c @@ -1484,7 +1484,7 @@ void BKE_nlastrip_recalculate_bounds(NlaStrip *strip) } /* Is the given NLA-strip the first one to occur for the given AnimData block */ -// TODO: make this an api method if necessary, but need to add prefix first +/* TODO: make this an api method if necessary, but need to add prefix first */ static bool nlastrip_is_first(AnimData *adt, NlaStrip *strip) { NlaTrack *nlt; diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index d56a7bf8fb4..73060caa2f8 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -52,9 +52,12 @@ #include "BLI_map.hh" #include "BLI_math.h" #include "BLI_path_util.h" +#include "BLI_set.hh" +#include "BLI_stack.hh" #include "BLI_string.h" #include "BLI_string_utils.h" #include "BLI_utildefines.h" +#include "BLI_vector_set.hh" #include "BLT_translation.h" @@ -80,6 +83,7 @@ #include "NOD_function.h" #include "NOD_geometry.h" #include "NOD_node_declaration.hh" +#include "NOD_node_tree_ref.hh" #include "NOD_shader.h" #include "NOD_socket.h" #include "NOD_texture.h" @@ -93,6 +97,21 @@ #define NODE_DEFAULT_MAX_WIDTH 700 +using blender::Array; +using blender::MutableSpan; +using blender::Set; +using blender::Span; +using blender::Stack; +using blender::Vector; +using blender::VectorSet; +using blender::nodes::FieldInferencingInterface; +using blender::nodes::InputSocketFieldType; +using blender::nodes::NodeDeclaration; +using blender::nodes::OutputFieldDependency; +using blender::nodes::OutputSocketFieldType; +using blender::nodes::SocketDeclaration; +using namespace blender::nodes::node_tree_ref_types; + /* Fallback types for undefined tree, nodes, sockets */ static bNodeTreeType NodeTreeTypeUndefined; bNodeType NodeTypeUndefined; @@ -110,6 +129,10 @@ static void node_socket_interface_free(bNodeTree *UNUSED(ntree), static void nodeMuteRerouteOutputLinks(struct bNodeTree *ntree, struct bNode *node, const bool mute); +static FieldInferencingInterface *node_field_inferencing_interface_copy( + const FieldInferencingInterface &field_inferencing_interface); +static void node_field_inferencing_interface_free( + const FieldInferencingInterface *field_inferencing_interface); static void ntree_init_data(ID *id) { @@ -220,6 +243,11 @@ static void ntree_copy_data(Main *UNUSED(bmain), ID *id_dst, const ID *id_src, c /* node tree will generate its own interface type */ ntree_dst->interface_type = nullptr; + + if (ntree_src->field_inferencing_interface) { + ntree_dst->field_inferencing_interface = node_field_inferencing_interface_copy( + *ntree_src->field_inferencing_interface); + } } static void ntree_free_data(ID *id) @@ -265,6 +293,8 @@ static void ntree_free_data(ID *id) MEM_freeN(sock); } + node_field_inferencing_interface_free(ntree->field_inferencing_interface); + /* free preview hash */ if (ntree->previews) { BKE_node_instance_hash_free(ntree->previews, (bNodeInstanceValueFP)BKE_node_preview_free); @@ -508,7 +538,7 @@ void ntreeBlendWrite(BlendWriter *writer, bNodeTree *ntree) if (node->storage) { /* could be handlerized at some point, now only 1 exception still */ if (ELEM(ntree->type, NTREE_SHADER, NTREE_GEOMETRY) && - ELEM(node->type, SH_NODE_CURVE_VEC, SH_NODE_CURVE_RGB)) { + ELEM(node->type, SH_NODE_CURVE_VEC, SH_NODE_CURVE_RGB, SH_NODE_CURVE_FLOAT)) { BKE_curvemapping_blend_write(writer, (const CurveMapping *)node->storage); } else if ((ntree->type == NTREE_GEOMETRY) && @@ -647,6 +677,8 @@ void ntreeBlendReadData(BlendDataReader *reader, bNodeTree *ntree) ntree->progress = nullptr; ntree->execdata = nullptr; + ntree->field_inferencing_interface = nullptr; + BLO_read_data_address(reader, &ntree->adt); BKE_animdata_blend_read_data(reader, ntree->adt); @@ -682,6 +714,7 @@ void ntreeBlendReadData(BlendDataReader *reader, bNodeTree *ntree) switch (node->type) { case SH_NODE_CURVE_VEC: case SH_NODE_CURVE_RGB: + case SH_NODE_CURVE_FLOAT: case CMP_NODE_TIME: case CMP_NODE_CURVE_VEC: case CMP_NODE_CURVE_RGB: @@ -792,6 +825,11 @@ void ntreeBlendReadData(BlendDataReader *reader, bNodeTree *ntree) /* TODO: should be dealt by new generic cache handling of IDs... */ ntree->previews = nullptr; + if (ntree->type == NTREE_GEOMETRY) { + /* Update field referencing for the geometry nodes modifier. */ + ntree->update |= NTREE_UPDATE_FIELD_INFERENCING; + } + /* type verification is in lib-link */ } @@ -1092,7 +1130,7 @@ static void node_init(const struct bContext *C, bNodeTree *ntree, bNode *node) RNA_pointer_create((ID *)ntree, &RNA_Node, node, &ptr); /* XXX Warning: context can be nullptr in case nodes are added in do_versions. - * Delayed init is not supported for nodes with context-based initfunc_api atm. + * Delayed init is not supported for nodes with context-based `initfunc_api` at the moment. */ BLI_assert(C != nullptr); ntype->initfunc_api(C, &ptr); @@ -4425,7 +4463,510 @@ void ntreeUpdateAllNew(Main *main) FOREACH_NODETREE_END; } -void ntreeUpdateAllUsers(Main *main, ID *id) +static FieldInferencingInterface *node_field_inferencing_interface_copy( + const FieldInferencingInterface &field_inferencing_interface) +{ + return new FieldInferencingInterface(field_inferencing_interface); +} + +static void node_field_inferencing_interface_free( + const FieldInferencingInterface *field_inferencing_interface) +{ + delete field_inferencing_interface; +} + +namespace blender::bke::node_field_inferencing { + +static bool is_field_socket_type(eNodeSocketDatatype type) +{ + return ELEM(type, SOCK_FLOAT, SOCK_INT, SOCK_BOOLEAN, SOCK_VECTOR, SOCK_RGBA); +} + +static bool is_field_socket_type(const SocketRef &socket) +{ + return is_field_socket_type((eNodeSocketDatatype)socket.typeinfo()->type); +} + +static bool update_field_inferencing(bNodeTree &btree); + +static InputSocketFieldType get_interface_input_field_type(const NodeRef &node, + const InputSocketRef &socket) +{ + if (!is_field_socket_type(socket)) { + return InputSocketFieldType::None; + } + if (node.is_reroute_node()) { + return InputSocketFieldType::IsSupported; + } + if (node.is_group_output_node()) { + /* Outputs always support fields when the data type is correct. */ + return InputSocketFieldType::IsSupported; + } + if (node.is_undefined()) { + return InputSocketFieldType::None; + } + + const NodeDeclaration *node_decl = node.declaration(); + + /* Node declarations should be implemented for nodes involved here. */ + BLI_assert(node_decl != nullptr); + + /* Get the field type from the declaration. */ + const SocketDeclaration &socket_decl = *node_decl->inputs()[socket.index()]; + const InputSocketFieldType field_type = socket_decl.input_field_type(); + if (field_type == InputSocketFieldType::Implicit) { + return field_type; + } + if (node_decl->is_function_node()) { + /* In a function node, every socket supports fields. */ + return InputSocketFieldType::IsSupported; + } + return field_type; +} + +static OutputFieldDependency get_interface_output_field_dependency(const NodeRef &node, + const OutputSocketRef &socket) +{ + if (!is_field_socket_type(socket)) { + /* Non-field sockets always output data. */ + return OutputFieldDependency::ForDataSource(); + } + if (node.is_reroute_node()) { + /* The reroute just forwards what is passed in. */ + return OutputFieldDependency::ForDependentField(); + } + if (node.is_group_input_node()) { + /* Input nodes get special treatment in #determine_group_input_states. */ + return OutputFieldDependency::ForDependentField(); + } + if (node.is_undefined()) { + return OutputFieldDependency::ForDataSource(); + } + + const NodeDeclaration *node_decl = node.declaration(); + + /* Node declarations should be implemented for nodes involved here. */ + BLI_assert(node_decl != nullptr); + + if (node_decl->is_function_node()) { + /* In a generic function node, all outputs depend on all inputs. */ + return OutputFieldDependency::ForDependentField(); + } + + /* Use the socket declaration. */ + const SocketDeclaration &socket_decl = *node_decl->outputs()[socket.index()]; + return socket_decl.output_field_dependency(); +} + +/** + * Retrieves information about how the node interacts with fields. + * In the future, this information can be stored in the node declaration. This would allow this + * function to return a reference, making it more efficient. + */ +static FieldInferencingInterface get_node_field_inferencing_interface(const NodeRef &node) +{ + /* Node groups already reference all required information, so just return that. */ + if (node.is_group_node()) { + bNodeTree *group = (bNodeTree *)node.bnode()->id; + if (group == nullptr) { + return FieldInferencingInterface(); + } + if (group->field_inferencing_interface == nullptr) { + /* Update group recursively. */ + update_field_inferencing(*group); + } + return *group->field_inferencing_interface; + } + + FieldInferencingInterface inferencing_interface; + for (const InputSocketRef *input_socket : node.inputs()) { + inferencing_interface.inputs.append(get_interface_input_field_type(node, *input_socket)); + } + + for (const OutputSocketRef *output_socket : node.outputs()) { + inferencing_interface.outputs.append( + get_interface_output_field_dependency(node, *output_socket)); + } + return inferencing_interface; +} + +/** + * This struct contains information for every socket. The values are propagated through the + * network. + */ +struct SocketFieldState { + /* This socket is currently a single value. It could become a field though. */ + bool is_single = true; + /* This socket is required to be a single value. It must not be a field. */ + bool requires_single = false; + /* This socket starts a new field. */ + bool is_field_source = false; +}; + +static Vector<const InputSocketRef *> gather_input_socket_dependencies( + const OutputFieldDependency &field_dependency, const NodeRef &node) +{ + const OutputSocketFieldType type = field_dependency.field_type(); + Vector<const InputSocketRef *> input_sockets; + switch (type) { + case OutputSocketFieldType::FieldSource: + case OutputSocketFieldType::None: { + break; + } + case OutputSocketFieldType::DependentField: { + /* This output depends on all inputs. */ + input_sockets.extend(node.inputs()); + break; + } + case OutputSocketFieldType::PartiallyDependent: { + /* This output depends only on a few inputs. */ + for (const int i : field_dependency.linked_input_indices()) { + input_sockets.append(&node.input(i)); + } + break; + } + } + return input_sockets; +} + +/** + * Check what the group output socket depends on. Potentially traverses the node tree + * to figure out if it is always a field or if it depends on any group inputs. + */ +static OutputFieldDependency find_group_output_dependencies( + const InputSocketRef &group_output_socket, + const Span<SocketFieldState> field_state_by_socket_id) +{ + if (!is_field_socket_type(group_output_socket)) { + return OutputFieldDependency::ForDataSource(); + } + + /* Use a Set here instead of an array indexed by socket id, because we my only need to look at + * very few sockets. */ + Set<const InputSocketRef *> handled_sockets; + Stack<const InputSocketRef *> sockets_to_check; + + handled_sockets.add(&group_output_socket); + sockets_to_check.push(&group_output_socket); + + /* Keeps track of group input indices that are (indirectly) connected to the output. */ + Vector<int> linked_input_indices; + + while (!sockets_to_check.is_empty()) { + const InputSocketRef *input_socket = sockets_to_check.pop(); + + for (const OutputSocketRef *origin_socket : input_socket->logically_linked_sockets()) { + const NodeRef &origin_node = origin_socket->node(); + const SocketFieldState &origin_state = field_state_by_socket_id[origin_socket->id()]; + + if (origin_state.is_field_source) { + if (origin_node.is_group_input_node()) { + /* Found a group input that the group output depends on. */ + linked_input_indices.append_non_duplicates(origin_socket->index()); + } + else { + /* Found a field source that is not the group input. So the output is always a field. */ + return OutputFieldDependency::ForFieldSource(); + } + } + else if (!origin_state.is_single) { + const FieldInferencingInterface inferencing_interface = + get_node_field_inferencing_interface(origin_node); + const OutputFieldDependency &field_dependency = + inferencing_interface.outputs[origin_socket->index()]; + + /* Propagate search further to the left. */ + for (const InputSocketRef *origin_input_socket : + gather_input_socket_dependencies(field_dependency, origin_node)) { + if (!field_state_by_socket_id[origin_input_socket->id()].is_single) { + if (handled_sockets.add(origin_input_socket)) { + sockets_to_check.push(origin_input_socket); + } + } + } + } + } + } + return OutputFieldDependency::ForPartiallyDependentField(std::move(linked_input_indices)); +} + +static void propagate_data_requirements_from_right_to_left( + const NodeTreeRef &tree, const MutableSpan<SocketFieldState> field_state_by_socket_id) +{ + const Vector<const NodeRef *> sorted_nodes = tree.toposort( + NodeTreeRef::ToposortDirection::RightToLeft); + + for (const NodeRef *node : sorted_nodes) { + const FieldInferencingInterface inferencing_interface = get_node_field_inferencing_interface( + *node); + + for (const OutputSocketRef *output_socket : node->outputs()) { + SocketFieldState &state = field_state_by_socket_id[output_socket->id()]; + + const OutputFieldDependency &field_dependency = + inferencing_interface.outputs[output_socket->index()]; + + if (field_dependency.field_type() == OutputSocketFieldType::FieldSource) { + continue; + } + if (field_dependency.field_type() == OutputSocketFieldType::None) { + state.requires_single = true; + continue; + } + + /* The output is required to be a single value when it is connected to any input that does + * not support fields. */ + for (const InputSocketRef *target_socket : output_socket->directly_linked_sockets()) { + state.requires_single |= field_state_by_socket_id[target_socket->id()].requires_single; + } + + if (state.requires_single) { + bool any_input_is_field_implicitly = false; + const Vector<const InputSocketRef *> connected_inputs = gather_input_socket_dependencies( + field_dependency, *node); + for (const InputSocketRef *input_socket : connected_inputs) { + if (inferencing_interface.inputs[input_socket->index()] == + InputSocketFieldType::Implicit) { + if (!input_socket->is_logically_linked()) { + any_input_is_field_implicitly = true; + break; + } + } + } + if (any_input_is_field_implicitly) { + /* This output isn't a single value actually. */ + state.requires_single = false; + } + else { + /* If the output is required to be a single value, the connected inputs in the same node + * must not be fields as well. */ + for (const InputSocketRef *input_socket : connected_inputs) { + field_state_by_socket_id[input_socket->id()].requires_single = true; + } + } + } + } + + /* Some inputs do not require fields independent of what the outputs are connected to. */ + for (const InputSocketRef *input_socket : node->inputs()) { + SocketFieldState &state = field_state_by_socket_id[input_socket->id()]; + if (inferencing_interface.inputs[input_socket->index()] == InputSocketFieldType::None) { + state.requires_single = true; + } + } + } +} + +static void determine_group_input_states( + const NodeTreeRef &tree, + FieldInferencingInterface &new_inferencing_interface, + const MutableSpan<SocketFieldState> field_state_by_socket_id) +{ + { + /* Non-field inputs never support fields. */ + int index; + LISTBASE_FOREACH_INDEX (bNodeSocket *, group_input, &tree.btree()->inputs, index) { + if (!is_field_socket_type((eNodeSocketDatatype)group_input->type)) { + new_inferencing_interface.inputs[index] = InputSocketFieldType::None; + } + } + } + /* Check if group inputs are required to be single values, because they are (indirectly) + * connected to some socket that does not support fields. */ + for (const NodeRef *node : tree.nodes_by_type("NodeGroupInput")) { + for (const OutputSocketRef *output_socket : node->outputs().drop_back(1)) { + SocketFieldState &state = field_state_by_socket_id[output_socket->id()]; + if (state.requires_single) { + new_inferencing_interface.inputs[output_socket->index()] = InputSocketFieldType::None; + } + } + } + /* If an input does not support fields, this should be reflected in all Group Input nodes. */ + for (const NodeRef *node : tree.nodes_by_type("NodeGroupInput")) { + for (const OutputSocketRef *output_socket : node->outputs().drop_back(1)) { + SocketFieldState &state = field_state_by_socket_id[output_socket->id()]; + const bool supports_field = new_inferencing_interface.inputs[output_socket->index()] != + InputSocketFieldType::None; + if (supports_field) { + state.is_single = false; + state.is_field_source = true; + } + else { + state.requires_single = true; + } + } + SocketFieldState &dummy_socket_state = field_state_by_socket_id[node->outputs().last()->id()]; + dummy_socket_state.requires_single = true; + } +} + +static void propagate_field_status_from_left_to_right( + const NodeTreeRef &tree, const MutableSpan<SocketFieldState> field_state_by_socket_id) +{ + Vector<const NodeRef *> sorted_nodes = tree.toposort( + NodeTreeRef::ToposortDirection::LeftToRight); + + for (const NodeRef *node : sorted_nodes) { + if (node->is_group_input_node()) { + continue; + } + + const FieldInferencingInterface inferencing_interface = get_node_field_inferencing_interface( + *node); + + /* Update field state of input sockets, also taking into account linked origin sockets. */ + for (const InputSocketRef *input_socket : node->inputs()) { + SocketFieldState &state = field_state_by_socket_id[input_socket->id()]; + if (state.requires_single) { + state.is_single = true; + continue; + } + state.is_single = true; + if (input_socket->logically_linked_sockets().is_empty()) { + if (inferencing_interface.inputs[input_socket->index()] == + InputSocketFieldType::Implicit) { + state.is_single = false; + } + } + else { + for (const OutputSocketRef *origin_socket : input_socket->logically_linked_sockets()) { + if (!field_state_by_socket_id[origin_socket->id()].is_single) { + state.is_single = false; + break; + } + } + } + } + + /* Update field state of output sockets, also taking into account input sockets. */ + for (const OutputSocketRef *output_socket : node->outputs()) { + SocketFieldState &state = field_state_by_socket_id[output_socket->id()]; + const OutputFieldDependency &field_dependency = + inferencing_interface.outputs[output_socket->index()]; + + switch (field_dependency.field_type()) { + case OutputSocketFieldType::None: { + state.is_single = true; + break; + } + case OutputSocketFieldType::FieldSource: { + state.is_single = false; + state.is_field_source = true; + break; + } + case OutputSocketFieldType::PartiallyDependent: + case OutputSocketFieldType::DependentField: { + for (const InputSocketRef *input_socket : + gather_input_socket_dependencies(field_dependency, *node)) { + if (!field_state_by_socket_id[input_socket->id()].is_single) { + state.is_single = false; + break; + } + } + break; + } + } + } + } +} + +static void determine_group_output_states(const NodeTreeRef &tree, + FieldInferencingInterface &new_inferencing_interface, + const Span<SocketFieldState> field_state_by_socket_id) +{ + for (const NodeRef *group_output_node : tree.nodes_by_type("NodeGroupOutput")) { + /* Ignore inactive group output nodes. */ + if (!(group_output_node->bnode()->flag & NODE_DO_OUTPUT)) { + continue; + } + /* Determine dependencies of all group outputs. */ + for (const InputSocketRef *group_output_socket : group_output_node->inputs().drop_back(1)) { + OutputFieldDependency field_dependency = find_group_output_dependencies( + *group_output_socket, field_state_by_socket_id); + new_inferencing_interface.outputs[group_output_socket->index()] = std::move( + field_dependency); + } + break; + } +} + +static void update_socket_shapes(const NodeTreeRef &tree, + const Span<SocketFieldState> field_state_by_socket_id) +{ + const eNodeSocketDisplayShape requires_data_shape = SOCK_DISPLAY_SHAPE_CIRCLE; + const eNodeSocketDisplayShape data_but_can_be_field_shape = SOCK_DISPLAY_SHAPE_DIAMOND_DOT; + const eNodeSocketDisplayShape is_field_shape = SOCK_DISPLAY_SHAPE_DIAMOND; + + for (const InputSocketRef *socket : tree.input_sockets()) { + bNodeSocket *bsocket = socket->bsocket(); + const SocketFieldState &state = field_state_by_socket_id[socket->id()]; + if (state.requires_single) { + bsocket->display_shape = requires_data_shape; + } + else if (state.is_single) { + bsocket->display_shape = data_but_can_be_field_shape; + } + else { + bsocket->display_shape = is_field_shape; + } + } + for (const OutputSocketRef *socket : tree.output_sockets()) { + bNodeSocket *bsocket = socket->bsocket(); + const SocketFieldState &state = field_state_by_socket_id[socket->id()]; + if (state.requires_single) { + bsocket->display_shape = requires_data_shape; + } + else if (state.is_single) { + bsocket->display_shape = data_but_can_be_field_shape; + } + else { + bsocket->display_shape = is_field_shape; + } + } +} + +static bool update_field_inferencing(bNodeTree &btree) +{ + using namespace blender::nodes; + if (btree.type != NTREE_GEOMETRY) { + return false; + } + + /* Create new inferencing interface for this node group. */ + FieldInferencingInterface *new_inferencing_interface = new FieldInferencingInterface(); + new_inferencing_interface->inputs.resize(BLI_listbase_count(&btree.inputs), + InputSocketFieldType::IsSupported); + new_inferencing_interface->outputs.resize(BLI_listbase_count(&btree.outputs), + OutputFieldDependency::ForDataSource()); + + /* Create #NodeTreeRef to accelerate various queries on the node tree (e.g. linked sockets). */ + const NodeTreeRef tree{&btree}; + + /* Keep track of the state of all sockets. The index into this array is #SocketRef::id(). */ + Array<SocketFieldState> field_state_by_socket_id(tree.sockets().size()); + + propagate_data_requirements_from_right_to_left(tree, field_state_by_socket_id); + determine_group_input_states(tree, *new_inferencing_interface, field_state_by_socket_id); + propagate_field_status_from_left_to_right(tree, field_state_by_socket_id); + determine_group_output_states(tree, *new_inferencing_interface, field_state_by_socket_id); + update_socket_shapes(tree, field_state_by_socket_id); + + /* Update the previous group interface. */ + const bool group_interface_changed = btree.field_inferencing_interface == nullptr || + *btree.field_inferencing_interface != + *new_inferencing_interface; + delete btree.field_inferencing_interface; + btree.field_inferencing_interface = new_inferencing_interface; + + return group_interface_changed; +} + +} // namespace blender::bke::node_field_inferencing + +/** + * \param tree_update_flag: #eNodeTreeUpdate enum. + */ +void ntreeUpdateAllUsers(Main *main, ID *id, const int tree_update_flag) { if (id == nullptr) { return; @@ -4446,7 +4987,8 @@ void ntreeUpdateAllUsers(Main *main, ID *id) } if (need_update) { - ntreeUpdateTree(nullptr, ntree); + ntree->update |= tree_update_flag; + ntreeUpdateTree(tree_update_flag ? main : nullptr, ntree); } } FOREACH_NODETREE_END; @@ -4508,8 +5050,18 @@ void ntreeUpdateTree(Main *bmain, bNodeTree *ntree) ntreeInterfaceTypeUpdate(ntree); } + int tree_user_update_flag = 0; + + if (ntree->update & NTREE_UPDATE) { + /* If the field interface of this node tree has changed, all node trees using + * this group will need to recalculate their interface as well. */ + if (blender::bke::node_field_inferencing::update_field_inferencing(*ntree)) { + tree_user_update_flag |= NTREE_UPDATE_FIELD_INFERENCING; + } + } + if (bmain) { - ntreeUpdateAllUsers(bmain, &ntree->id); + ntreeUpdateAllUsers(bmain, &ntree->id, tree_user_update_flag); } if (ntree->update & (NTREE_UPDATE_LINKS | NTREE_UPDATE_NODES)) { @@ -5023,6 +5575,7 @@ static void registerShaderNodes() register_node_type_sh_shadertorgb(); register_node_type_sh_normal(); register_node_type_sh_mapping(); + register_node_type_sh_curve_float(); register_node_type_sh_curve_vec(); register_node_type_sh_curve_rgb(); register_node_type_sh_map_range(); @@ -5156,10 +5709,17 @@ static void registerGeometryNodes() { register_node_type_geo_group(); + register_node_type_geo_legacy_curve_set_handles(); + register_node_type_geo_legacy_attribute_proximity(); + register_node_type_geo_legacy_attribute_randomize(); register_node_type_geo_legacy_material_assign(); register_node_type_geo_legacy_select_by_material(); + register_node_type_geo_legacy_curve_spline_type(); + register_node_type_geo_legacy_curve_reverse(); + register_node_type_geo_legacy_curve_subdivide(); register_node_type_geo_align_rotation_to_vector(); + register_node_type_geo_attribute_capture(); register_node_type_geo_attribute_clamp(); register_node_type_geo_attribute_color_ramp(); register_node_type_geo_attribute_combine_xyz(); @@ -5167,12 +5727,9 @@ static void registerGeometryNodes() register_node_type_geo_attribute_convert(); register_node_type_geo_attribute_curve_map(); register_node_type_geo_attribute_fill(); - register_node_type_geo_attribute_capture(); register_node_type_geo_attribute_map_range(); register_node_type_geo_attribute_math(); register_node_type_geo_attribute_mix(); - register_node_type_geo_attribute_proximity(); - register_node_type_geo_attribute_randomize(); register_node_type_geo_attribute_remove(); register_node_type_geo_attribute_separate_xyz(); register_node_type_geo_attribute_statistic(); @@ -5183,9 +5740,9 @@ static void registerGeometryNodes() register_node_type_geo_bounding_box(); register_node_type_geo_collection_info(); register_node_type_geo_convex_hull(); - register_node_type_geo_curve_sample(); register_node_type_geo_curve_endpoints(); register_node_type_geo_curve_fill(); + register_node_type_geo_curve_fillet(); register_node_type_geo_curve_length(); register_node_type_geo_curve_parameter(); register_node_type_geo_curve_primitive_bezier_segment(); @@ -5197,24 +5754,28 @@ static void registerGeometryNodes() register_node_type_geo_curve_primitive_star(); register_node_type_geo_curve_resample(); register_node_type_geo_curve_reverse(); + register_node_type_geo_curve_sample(); register_node_type_geo_curve_set_handles(); register_node_type_geo_curve_spline_type(); register_node_type_geo_curve_subdivide(); - register_node_type_geo_curve_fillet(); register_node_type_geo_curve_to_mesh(); register_node_type_geo_curve_to_points(); register_node_type_geo_curve_trim(); register_node_type_geo_delete_geometry(); + register_node_type_geo_distribute_points_on_faces(); register_node_type_geo_edge_split(); register_node_type_geo_input_index(); register_node_type_geo_input_material(); register_node_type_geo_input_normal(); register_node_type_geo_input_position(); register_node_type_geo_input_tangent(); + register_node_type_geo_input_spline_length(); + register_node_type_geo_instance_on_points(); register_node_type_geo_is_viewport(); register_node_type_geo_join_geometry(); register_node_type_geo_material_assign(); register_node_type_geo_material_replace(); + register_node_type_geo_material_selection(); register_node_type_geo_mesh_primitive_circle(); register_node_type_geo_mesh_primitive_cone(); register_node_type_geo_mesh_primitive_cube(); @@ -5225,6 +5786,7 @@ static void registerGeometryNodes() register_node_type_geo_mesh_primitive_uv_sphere(); register_node_type_geo_mesh_subdivide(); register_node_type_geo_mesh_to_curve(); + register_node_type_geo_mesh_to_points(); register_node_type_geo_object_info(); register_node_type_geo_point_distribute(); register_node_type_geo_point_instance(); @@ -5232,15 +5794,17 @@ static void registerGeometryNodes() register_node_type_geo_point_scale(); register_node_type_geo_point_separate(); register_node_type_geo_point_translate(); + register_node_type_geo_points_to_vertices(); register_node_type_geo_points_to_volume(); + register_node_type_geo_proximity(); register_node_type_geo_raycast(); register_node_type_geo_realize_instances(); register_node_type_geo_sample_texture(); register_node_type_geo_select_by_handle_type(); - register_node_type_geo_string_join(); - register_node_type_geo_material_selection(); register_node_type_geo_separate_components(); register_node_type_geo_set_position(); + register_node_type_geo_string_join(); + register_node_type_geo_string_to_curves(); register_node_type_geo_subdivision_surface(); register_node_type_geo_switch(); register_node_type_geo_transform(); @@ -5251,12 +5815,16 @@ static void registerGeometryNodes() static void registerFunctionNodes() { + register_node_type_fn_legacy_random_float(); + register_node_type_fn_boolean_math(); register_node_type_fn_float_compare(); register_node_type_fn_float_to_int(); + register_node_type_fn_input_special_characters(); register_node_type_fn_input_string(); register_node_type_fn_input_vector(); - register_node_type_fn_random_float(); + register_node_type_fn_random_value(); + register_node_type_fn_rotate_euler(); register_node_type_fn_string_length(); register_node_type_fn_string_substring(); register_node_type_fn_value_to_string(); diff --git a/source/blender/blenkernel/intern/object.c b/source/blender/blenkernel/intern/object.c index 465ec9dc665..ec39c5b45c4 100644 --- a/source/blender/blenkernel/intern/object.c +++ b/source/blender/blenkernel/intern/object.c @@ -2634,10 +2634,16 @@ Object *BKE_object_duplicate(Main *bmain, { const bool is_subprocess = (duplicate_options & LIB_ID_DUPLICATE_IS_SUBPROCESS) != 0; const bool is_root_id = (duplicate_options & LIB_ID_DUPLICATE_IS_ROOT_ID) != 0; + int copy_flags = LIB_ID_COPY_DEFAULT; if (!is_subprocess) { BKE_main_id_newptr_and_tag_clear(bmain); } + else { + /* In case copying object is a sub-process of collection (or scene) copying, do not try to + * re-assign RB objects to existing RBW collections. */ + copy_flags |= LIB_ID_COPY_RIGID_BODY_NO_COLLECTION_HANDLING; + } if (is_root_id) { /* In case root duplicated ID is linked, assume we want to get a local copy of it and duplicate * all expected linked data. */ @@ -2649,24 +2655,22 @@ Object *BKE_object_duplicate(Main *bmain, Material ***matarar; - Object *obn = (Object *)BKE_id_copy_for_duplicate(bmain, &ob->id, dupflag); + Object *obn = (Object *)BKE_id_copy_for_duplicate(bmain, &ob->id, dupflag, copy_flags); /* 0 == full linked. */ if (dupflag == 0) { return obn; } - BKE_animdata_duplicate_id_action(bmain, &obn->id, dupflag); - if (dupflag & USER_DUP_MAT) { for (int i = 0; i < obn->totcol; i++) { - BKE_id_copy_for_duplicate(bmain, (ID *)obn->mat[i], dupflag); + BKE_id_copy_for_duplicate(bmain, (ID *)obn->mat[i], dupflag, copy_flags); } } if (dupflag & USER_DUP_PSYS) { ParticleSystem *psys; for (psys = obn->particlesystem.first; psys; psys = psys->next) { - BKE_id_copy_for_duplicate(bmain, (ID *)psys->part, dupflag); + BKE_id_copy_for_duplicate(bmain, (ID *)psys->part, dupflag, copy_flags); } } @@ -2677,77 +2681,77 @@ Object *BKE_object_duplicate(Main *bmain, switch (obn->type) { case OB_MESH: if (dupflag & USER_DUP_MESH) { - id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag); + id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags); } break; case OB_CURVE: if (dupflag & USER_DUP_CURVE) { - id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag); + id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags); } break; case OB_SURF: if (dupflag & USER_DUP_SURF) { - id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag); + id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags); } break; case OB_FONT: if (dupflag & USER_DUP_FONT) { - id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag); + id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags); } break; case OB_MBALL: if (dupflag & USER_DUP_MBALL) { - id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag); + id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags); } break; case OB_LAMP: if (dupflag & USER_DUP_LAMP) { - id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag); + id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags); } break; case OB_ARMATURE: if (dupflag & USER_DUP_ARM) { - id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag); + id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags); } break; case OB_LATTICE: if (dupflag != 0) { - id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag); + id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags); } break; case OB_CAMERA: if (dupflag != 0) { - id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag); + id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags); } break; case OB_LIGHTPROBE: if (dupflag & USER_DUP_LIGHTPROBE) { - id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag); + id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags); } break; case OB_SPEAKER: if (dupflag != 0) { - id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag); + id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags); } break; case OB_GPENCIL: if (dupflag & USER_DUP_GPENCIL) { - id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag); + id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags); } break; case OB_HAIR: if (dupflag & USER_DUP_HAIR) { - id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag); + id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags); } break; case OB_POINTCLOUD: if (dupflag & USER_DUP_POINTCLOUD) { - id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag); + id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags); } break; case OB_VOLUME: if (dupflag & USER_DUP_VOLUME) { - id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag); + id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags); } break; } @@ -2758,7 +2762,7 @@ Object *BKE_object_duplicate(Main *bmain, matarar = BKE_object_material_array_p(obn); if (matarar) { for (int i = 0; i < obn->totcol; i++) { - BKE_id_copy_for_duplicate(bmain, (ID *)(*matarar)[i], dupflag); + BKE_id_copy_for_duplicate(bmain, (ID *)(*matarar)[i], dupflag, copy_flags); } } } diff --git a/source/blender/blenkernel/intern/particle.c b/source/blender/blenkernel/intern/particle.c index 50b0fb1c9f5..7b2a1af7086 100644 --- a/source/blender/blenkernel/intern/particle.c +++ b/source/blender/blenkernel/intern/particle.c @@ -4619,11 +4619,11 @@ void psys_get_particle_on_path(ParticleSimulationData *sim, pind.cache = cached ? psys->pointcache : NULL; pind.epoint = NULL; pind.bspline = (psys->part->flag & PART_HAIR_BSPLINE); - /* pind.dm disabled in editmode means we don't get effectors taken into - * account when subdividing for instance */ + /* `pind.dm` disabled in edit-mode means we don't get effectors taken into + * account when subdividing for instance. */ pind.mesh = psys_in_edit_mode(sim->depsgraph, psys) ? NULL : - psys->hair_out_mesh; /* XXX Sybren EEK */ + psys->hair_out_mesh; /* XXX(@sybren) EEK. */ init_particle_interpolation(sim->ob, psys, pa, &pind); do_particle_interpolation(psys, p, pa, t, &pind, state); diff --git a/source/blender/blenkernel/intern/preferences.c b/source/blender/blenkernel/intern/preferences.c index 8dcf6de164a..0b8e8d7c311 100644 --- a/source/blender/blenkernel/intern/preferences.c +++ b/source/blender/blenkernel/intern/preferences.c @@ -61,6 +61,15 @@ bUserAssetLibrary *BKE_preferences_asset_library_add(UserDef *userdef, return library; } +/** + * Unlink and free a library preference member. + * \note Free's \a library itself. + */ +void BKE_preferences_asset_library_remove(UserDef *userdef, bUserAssetLibrary *library) +{ + BLI_freelinkN(&userdef->asset_libraries, library); +} + void BKE_preferences_asset_library_name_set(UserDef *userdef, bUserAssetLibrary *library, const char *name) @@ -74,15 +83,6 @@ void BKE_preferences_asset_library_name_set(UserDef *userdef, sizeof(library->name)); } -/** - * Unlink and free a library preference member. - * \note Free's \a library itself. - */ -void BKE_preferences_asset_library_remove(UserDef *userdef, bUserAssetLibrary *library) -{ - BLI_freelinkN(&userdef->asset_libraries, library); -} - bUserAssetLibrary *BKE_preferences_asset_library_find_from_index(const UserDef *userdef, int index) { return BLI_findlink(&userdef->asset_libraries, index); @@ -94,6 +94,17 @@ bUserAssetLibrary *BKE_preferences_asset_library_find_from_name(const UserDef *u return BLI_findstring(&userdef->asset_libraries, name, offsetof(bUserAssetLibrary, name)); } +bUserAssetLibrary *BKE_preferences_asset_library_containing_path(const UserDef *userdef, + const char *path) +{ + LISTBASE_FOREACH (bUserAssetLibrary *, asset_lib_pref, &userdef->asset_libraries) { + if (BLI_path_contains(asset_lib_pref->path, path)) { + return asset_lib_pref; + } + } + return NULL; +} + int BKE_preferences_asset_library_get_index(const UserDef *userdef, const bUserAssetLibrary *library) { diff --git a/source/blender/blenkernel/intern/rigidbody.c b/source/blender/blenkernel/intern/rigidbody.c index 328c54fc21b..1ea659b2d41 100644 --- a/source/blender/blenkernel/intern/rigidbody.c +++ b/source/blender/blenkernel/intern/rigidbody.c @@ -302,7 +302,7 @@ void BKE_rigidbody_object_copy(Main *bmain, Object *ob_dst, const Object *ob_src ob_dst->rigidbody_object = rigidbody_copy_object(ob_src, flag); ob_dst->rigidbody_constraint = rigidbody_copy_constraint(ob_src, flag); - if (flag & LIB_ID_CREATE_NO_MAIN) { + if ((flag & (LIB_ID_CREATE_NO_MAIN | LIB_ID_COPY_RIGID_BODY_NO_COLLECTION_HANDLING)) != 0) { return; } @@ -1211,8 +1211,8 @@ RigidBodyWorld *BKE_rigidbody_world_copy(RigidBodyWorld *rbw, const int flag) id_us_plus((ID *)rbw_copy->constraints); } - if ((flag & LIB_ID_CREATE_NO_MAIN) == 0) { - /* This is a regular copy, and not a CoW copy for depsgraph evaluation */ + if ((flag & LIB_ID_COPY_SET_COPIED_ON_WRITE) == 0) { + /* This is a regular copy, and not a CoW copy for depsgraph evaluation. */ rbw_copy->shared = MEM_callocN(sizeof(*rbw_copy->shared), "RigidBodyWorld_Shared"); BKE_ptcache_copy_list(&rbw_copy->shared->ptcaches, &rbw->shared->ptcaches, LIB_ID_COPY_CACHES); rbw_copy->shared->pointcache = rbw_copy->shared->ptcaches.first; diff --git a/source/blender/blenkernel/intern/scene.c b/source/blender/blenkernel/intern/scene.c index 6b5c94a2786..a9a8cd93b1d 100644 --- a/source/blender/blenkernel/intern/scene.c +++ b/source/blender/blenkernel/intern/scene.c @@ -63,6 +63,8 @@ #include "BLI_threads.h" #include "BLI_utildefines.h" +#include "BLO_readfile.h" + #include "BLT_translation.h" #include "BKE_action.h" @@ -993,8 +995,13 @@ static void link_recurs_seq(BlendDataReader *reader, ListBase *lb) { BLO_read_list(reader, lb); - LISTBASE_FOREACH (Sequence *, seq, lb) { - if (seq->seqbase.first) { + LISTBASE_FOREACH_MUTABLE (Sequence *, seq, lb) { + /* Sanity check. */ + if (!SEQ_valid_strip_channel(seq)) { + BLI_freelinkN(lb, seq); + BLO_read_data_reports(reader)->count.vse_strips_skipped++; + } + else if (seq->seqbase.first) { link_recurs_seq(reader, &seq->seqbase); } } @@ -1794,6 +1801,7 @@ Scene *BKE_scene_duplicate(Main *bmain, Scene *sce, eSceneCopyMethod type) /* Scene duplication is always root of duplication currently. */ const bool is_subprocess = false; const bool is_root_id = true; + const int copy_flags = LIB_ID_COPY_DEFAULT; if (!is_subprocess) { BKE_main_id_newptr_and_tag_clear(bmain); @@ -1809,21 +1817,40 @@ Scene *BKE_scene_duplicate(Main *bmain, Scene *sce, eSceneCopyMethod type) /* Copy Freestyle LineStyle datablocks. */ LISTBASE_FOREACH (ViewLayer *, view_layer_dst, &sce_copy->view_layers) { LISTBASE_FOREACH (FreestyleLineSet *, lineset, &view_layer_dst->freestyle_config.linesets) { - BKE_id_copy_for_duplicate(bmain, (ID *)lineset->linestyle, duplicate_flags); + BKE_id_copy_for_duplicate(bmain, (ID *)lineset->linestyle, duplicate_flags, copy_flags); } } /* Full copy of world (included animations) */ - BKE_id_copy_for_duplicate(bmain, (ID *)sce->world, duplicate_flags); + BKE_id_copy_for_duplicate(bmain, (ID *)sce->world, duplicate_flags, copy_flags); /* Full copy of GreasePencil. */ - BKE_id_copy_for_duplicate(bmain, (ID *)sce->gpd, duplicate_flags); + BKE_id_copy_for_duplicate(bmain, (ID *)sce->gpd, duplicate_flags, copy_flags); /* Deep-duplicate collections and objects (using preferences' settings for which sub-data to * duplicate along the object itself). */ BKE_collection_duplicate( bmain, NULL, sce_copy->master_collection, duplicate_flags, LIB_ID_DUPLICATE_IS_SUBPROCESS); + /* Rigid body world collections may not be instantiated as scene's collections, ensure they + * also get properly duplicated. */ + if (sce_copy->rigidbody_world != NULL) { + if (sce_copy->rigidbody_world->group != NULL) { + BKE_collection_duplicate(bmain, + NULL, + sce_copy->rigidbody_world->group, + duplicate_flags, + LIB_ID_DUPLICATE_IS_SUBPROCESS); + } + if (sce_copy->rigidbody_world->constraints != NULL) { + BKE_collection_duplicate(bmain, + NULL, + sce_copy->rigidbody_world->constraints, + duplicate_flags, + LIB_ID_DUPLICATE_IS_SUBPROCESS); + } + } + if (!is_subprocess) { /* This code will follow into all ID links using an ID tagged with LIB_TAG_NEW. */ BKE_libblock_relink_to_newid(&sce_copy->id); @@ -2465,7 +2492,7 @@ static void scene_graph_update_tagged(Depsgraph *depsgraph, Main *bmain, bool on // DEG_debug_graph_relations_validate(depsgraph, bmain, scene); /* Flush editing data if needed. */ prepare_mesh_for_viewport_render(bmain, view_layer); - /* Update all objects: drivers, matrices, displists, etc. flags set + /* Update all objects: drivers, matrices, #DispList, etc. flags set * by depsgraph or manual, no layer check here, gets correct flushed. */ DEG_evaluate_on_refresh(depsgraph); /* Update sound system. */ @@ -2541,7 +2568,7 @@ void BKE_scene_graph_update_for_newframe_ex(Depsgraph *depsgraph, const bool cle BKE_image_editors_update_frame(bmain, scene->r.cfra); BKE_sound_set_cfra(scene->r.cfra); DEG_graph_relations_update(depsgraph); - /* Update all objects: drivers, matrices, displists, etc. flags set + /* Update all objects: drivers, matrices, #DispList, etc. flags set * by depgraph or manual, no layer check here, gets correct flushed. * * NOTE: Only update for new frame on first iteration. Second iteration is for ensuring user diff --git a/source/blender/blenkernel/intern/softbody.c b/source/blender/blenkernel/intern/softbody.c index fbc781f5eb9..b7eb9d31b23 100644 --- a/source/blender/blenkernel/intern/softbody.c +++ b/source/blender/blenkernel/intern/softbody.c @@ -2295,7 +2295,7 @@ static void softbody_calc_forces( sb_sfesf_threads_run(depsgraph, scene, ob, timenow, sb->totspring, NULL); } - /* after spring scan because it uses Effoctors too */ + /* After spring scan because it uses effectors too. */ ListBase *effectors = BKE_effectors_create(depsgraph, ob, NULL, sb->effector_weights, false); if (do_deflector) { diff --git a/source/blender/blenkernel/intern/spline_bezier.cc b/source/blender/blenkernel/intern/spline_bezier.cc index b36d7a21669..f719a1cfda2 100644 --- a/source/blender/blenkernel/intern/spline_bezier.cc +++ b/source/blender/blenkernel/intern/spline_bezier.cc @@ -289,6 +289,56 @@ void BezierSpline::transform(const blender::float4x4 &matrix) this->mark_cache_invalid(); } +static void set_handle_position(const float3 &position, + const BezierSpline::HandleType type, + const BezierSpline::HandleType type_other, + const float3 &new_value, + float3 &handle, + float3 &handle_other) +{ + /* Don't bother when the handle positions are calculated automatically anyway. */ + if (ELEM(type, BezierSpline::HandleType::Auto, BezierSpline::HandleType::Vector)) { + return; + } + + handle = new_value; + if (type_other == BezierSpline::HandleType::Align) { + /* Keep track of the old length of the opposite handle. */ + const float length = float3::distance(handle_other, position); + /* Set the other handle to directly opposite from the current handle. */ + const float3 dir = (handle - position).normalized(); + handle_other = position - dir * length; + } +} + +/** + * Set positions for the right handle of the control point, ensuring that + * aligned handles stay aligned. Has no effect for auto and vector type handles. + */ +void BezierSpline::set_handle_position_right(const int index, const blender::float3 &value) +{ + set_handle_position(positions_[index], + handle_types_right_[index], + handle_types_left_[index], + value, + handle_positions_right_[index], + handle_positions_left_[index]); +} + +/** + * Set positions for the left handle of the control point, ensuring that + * aligned handles stay aligned. Has no effect for auto and vector type handles. + */ +void BezierSpline::set_handle_position_left(const int index, const blender::float3 &value) +{ + set_handle_position(positions_[index], + handle_types_left_[index], + handle_types_right_[index], + value, + handle_positions_left_[index], + handle_positions_right_[index]); +} + bool BezierSpline::point_is_sharp(const int index) const { return ELEM(handle_types_left_[index], HandleType::Vector, HandleType::Free) || diff --git a/source/blender/blenkernel/intern/subdiv_mesh.c b/source/blender/blenkernel/intern/subdiv_mesh.c index e9cd0b70019..01bccab1bbd 100644 --- a/source/blender/blenkernel/intern/subdiv_mesh.c +++ b/source/blender/blenkernel/intern/subdiv_mesh.c @@ -50,7 +50,7 @@ typedef struct SubdivMeshContext { const Mesh *coarse_mesh; Subdiv *subdiv; Mesh *subdiv_mesh; - /* Cached custom data arrays for fastter access. */ + /* Cached custom data arrays for faster access. */ int *vert_origindex; int *edge_origindex; int *loop_origindex; diff --git a/source/blender/blenkernel/intern/tracking.c b/source/blender/blenkernel/intern/tracking.c index 068d048fd08..3cdb8e927a6 100644 --- a/source/blender/blenkernel/intern/tracking.c +++ b/source/blender/blenkernel/intern/tracking.c @@ -3014,6 +3014,61 @@ static int channels_average_error_sort(const void *a, const void *b) return 0; } +static int compare_firstlast_putting_undefined_first( + bool inverse, bool a_markerless, int a_value, bool b_markerless, int b_value) +{ + if (a_markerless && b_markerless) { + /* Neither channel has not-disabled markers, return whatever. */ + return 0; + } + if (a_markerless) { + /* Put the markerless channel first. */ + return 0; + } + if (b_markerless) { + /* Put the markerless channel first. */ + return 1; + } + + /* Both channels have markers. */ + + if (inverse) { + if (a_value < b_value) { + return 1; + } + return 0; + } + + if (a_value > b_value) { + return 1; + } + return 0; +} + +static int channels_start_sort(const void *a, const void *b) +{ + const MovieTrackingDopesheetChannel *channel_a = a; + const MovieTrackingDopesheetChannel *channel_b = b; + + return compare_firstlast_putting_undefined_first(false, + channel_a->tot_segment == 0, + channel_a->first_not_disabled_marker_framenr, + channel_b->tot_segment == 0, + channel_b->first_not_disabled_marker_framenr); +} + +static int channels_end_sort(const void *a, const void *b) +{ + const MovieTrackingDopesheetChannel *channel_a = a; + const MovieTrackingDopesheetChannel *channel_b = b; + + return compare_firstlast_putting_undefined_first(false, + channel_a->tot_segment == 0, + channel_a->last_not_disabled_marker_framenr, + channel_b->tot_segment == 0, + channel_b->last_not_disabled_marker_framenr); +} + static int channels_alpha_inverse_sort(const void *a, const void *b) { if (channels_alpha_sort(a, b)) { @@ -3053,22 +3108,51 @@ static int channels_average_error_inverse_sort(const void *a, const void *b) return 0; } +static int channels_start_inverse_sort(const void *a, const void *b) +{ + const MovieTrackingDopesheetChannel *channel_a = a; + const MovieTrackingDopesheetChannel *channel_b = b; + + return compare_firstlast_putting_undefined_first(true, + channel_a->tot_segment == 0, + channel_a->first_not_disabled_marker_framenr, + channel_b->tot_segment == 0, + channel_b->first_not_disabled_marker_framenr); +} + +static int channels_end_inverse_sort(const void *a, const void *b) +{ + const MovieTrackingDopesheetChannel *channel_a = a; + const MovieTrackingDopesheetChannel *channel_b = b; + + return compare_firstlast_putting_undefined_first(true, + channel_a->tot_segment == 0, + channel_a->last_not_disabled_marker_framenr, + channel_b->tot_segment == 0, + channel_b->last_not_disabled_marker_framenr); +} + /* Calculate frames segments at which track is tracked continuously. */ static void tracking_dopesheet_channels_segments_calc(MovieTrackingDopesheetChannel *channel) { MovieTrackingTrack *track = channel->track; int i, segment; + bool first_not_disabled_marker_framenr_set; channel->tot_segment = 0; channel->max_segment = 0; channel->total_frames = 0; + channel->first_not_disabled_marker_framenr = 0; + channel->last_not_disabled_marker_framenr = 0; + /* TODO(sergey): looks a bit code-duplicated, need to look into * logic de-duplication here. */ /* count */ i = 0; + first_not_disabled_marker_framenr_set = false; while (i < track->markersnr) { MovieTrackingMarker *marker = &track->markers[i]; @@ -3086,6 +3170,12 @@ static void tracking_dopesheet_channels_segments_calc(MovieTrackingDopesheetChan break; } + if (!first_not_disabled_marker_framenr_set) { + channel->first_not_disabled_marker_framenr = marker->framenr; + first_not_disabled_marker_framenr_set = true; + } + channel->last_not_disabled_marker_framenr = marker->framenr; + prev_fra = marker->framenr; len++; i++; @@ -3203,6 +3293,12 @@ static void tracking_dopesheet_channels_sort(MovieTracking *tracking, else if (sort_method == TRACKING_DOPE_SORT_AVERAGE_ERROR) { BLI_listbase_sort(&dopesheet->channels, channels_average_error_inverse_sort); } + else if (sort_method == TRACKING_DOPE_SORT_START) { + BLI_listbase_sort(&dopesheet->channels, channels_start_inverse_sort); + } + else if (sort_method == TRACKING_DOPE_SORT_END) { + BLI_listbase_sort(&dopesheet->channels, channels_end_inverse_sort); + } } else { if (sort_method == TRACKING_DOPE_SORT_NAME) { @@ -3217,6 +3313,12 @@ static void tracking_dopesheet_channels_sort(MovieTracking *tracking, else if (sort_method == TRACKING_DOPE_SORT_AVERAGE_ERROR) { BLI_listbase_sort(&dopesheet->channels, channels_average_error_sort); } + else if (sort_method == TRACKING_DOPE_SORT_START) { + BLI_listbase_sort(&dopesheet->channels, channels_start_sort); + } + else if (sort_method == TRACKING_DOPE_SORT_END) { + BLI_listbase_sort(&dopesheet->channels, channels_end_sort); + } } } diff --git a/source/blender/blenlib/BLI_float4x4.hh b/source/blender/blenlib/BLI_float4x4.hh index 347ce2caa34..14e61d53845 100644 --- a/source/blender/blenlib/BLI_float4x4.hh +++ b/source/blender/blenlib/BLI_float4x4.hh @@ -45,6 +45,13 @@ struct float4x4 { return mat; } + static float4x4 from_location(const float3 location) + { + float4x4 mat = float4x4::identity(); + copy_v3_v3(mat.values[3], location); + return mat; + } + static float4x4 from_normalized_axis_data(const float3 location, const float3 forward, const float3 up) diff --git a/source/blender/blenlib/BLI_noise.hh b/source/blender/blenlib/BLI_noise.hh index 760ff082d06..7e1655f7864 100644 --- a/source/blender/blenlib/BLI_noise.hh +++ b/source/blender/blenlib/BLI_noise.hh @@ -22,36 +22,66 @@ namespace blender::noise { -/* Perlin noise in the range [-1, 1]. */ +/* -------------------------------------------------------------------- + * Hash functions. + + * Create a randomized hash from the given inputs. Contrary to hash functions in `BLI_hash.hh` + * these functions produce better randomness but are more expensive to compute. + */ + +/* Hash integers to `uint32_t`. */ +uint32_t hash(uint32_t kx); +uint32_t hash(uint32_t kx, uint32_t ky); +uint32_t hash(uint32_t kx, uint32_t ky, uint32_t kz); +uint32_t hash(uint32_t kx, uint32_t ky, uint32_t kz, uint32_t kw); + +/* Hash floats to `uint32_t`. */ +uint32_t hash_float(float kx); +uint32_t hash_float(float2 k); +uint32_t hash_float(float3 k); +uint32_t hash_float(float4 k); + +/* Hash integers to `float` between 0 and 1. */ +float hash_to_float(uint32_t kx); +float hash_to_float(uint32_t kx, uint32_t ky); +float hash_to_float(uint32_t kx, uint32_t ky, uint32_t kz); +float hash_to_float(uint32_t kx, uint32_t ky, uint32_t kz, uint32_t kw); + +/* Hash floats to `float` between 0 and 1. */ +float hash_float_to_float(float k); +float hash_float_to_float(float2 k); +float hash_float_to_float(float3 k); +float hash_float_to_float(float4 k); +/* -------------------------------------------------------------------- + * Perlin noise. + */ + +/* Perlin noise in the range [-1, 1]. */ float perlin_signed(float position); float perlin_signed(float2 position); float perlin_signed(float3 position); float perlin_signed(float4 position); /* Perlin noise in the range [0, 1]. */ - float perlin(float position); float perlin(float2 position); float perlin(float3 position); float perlin(float4 position); /* Fractal perlin noise in the range [0, 1]. */ - float perlin_fractal(float position, float octaves, float roughness); float perlin_fractal(float2 position, float octaves, float roughness); float perlin_fractal(float3 position, float octaves, float roughness); float perlin_fractal(float4 position, float octaves, float roughness); /* Positive distorted fractal perlin noise. */ - float perlin_fractal_distorted(float position, float octaves, float roughness, float distortion); float perlin_fractal_distorted(float2 position, float octaves, float roughness, float distortion); float perlin_fractal_distorted(float3 position, float octaves, float roughness, float distortion); float perlin_fractal_distorted(float4 position, float octaves, float roughness, float distortion); /* Positive distorted fractal perlin noise that outputs a float3. */ - float3 perlin_float3_fractal_distorted(float position, float octaves, float roughness, diff --git a/source/blender/blenlib/BLI_path_util.h b/source/blender/blenlib/BLI_path_util.h index 2a56d11276a..e4774c58e84 100644 --- a/source/blender/blenlib/BLI_path_util.h +++ b/source/blender/blenlib/BLI_path_util.h @@ -55,6 +55,10 @@ bool BLI_path_name_at_index(const char *__restrict path, int *__restrict r_offset, int *__restrict r_len) ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT; +/** Return true only if #containee_path is contained in #container_path. */ +bool BLI_path_contains(const char *container_path, + const char *containee_path) ATTR_WARN_UNUSED_RESULT; + const char *BLI_path_slash_rfind(const char *string) ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT; int BLI_path_slash_ensure(char *string) ATTR_NONNULL(); void BLI_path_slash_rstrip(char *string) ATTR_NONNULL(); diff --git a/source/blender/blenlib/BLI_resource_scope.hh b/source/blender/blenlib/BLI_resource_scope.hh index edffb148477..761e1ef834c 100644 --- a/source/blender/blenlib/BLI_resource_scope.hh +++ b/source/blender/blenlib/BLI_resource_scope.hh @@ -73,7 +73,6 @@ class ResourceScope : NonCopyable, NonMovable { */ template<typename T> T *add(std::unique_ptr<T> resource) { - BLI_assert(resource.get() != nullptr); T *ptr = resource.release(); if (ptr == nullptr) { return nullptr; diff --git a/source/blender/blenlib/BLI_uuid.h b/source/blender/blenlib/BLI_uuid.h index 9b85f8e65bc..ed0d31b625f 100644 --- a/source/blender/blenlib/BLI_uuid.h +++ b/source/blender/blenlib/BLI_uuid.h @@ -68,9 +68,41 @@ bool BLI_uuid_parse_string(bUUID *uuid, const char *buffer) ATTR_NONNULL(); #ifdef __cplusplus } +# include <initializer_list> # include <ostream> /** Output the UUID as formatted ASCII string, see #BLI_uuid_format(). */ std::ostream &operator<<(std::ostream &stream, bUUID uuid); +namespace blender { + +class bUUID : public ::bUUID { + public: + /** + * Default constructor, used with `bUUID value{};`, will initialize to the nil UUID. + */ + bUUID() = default; + + /** Initialize from the bUUID DNA struct. */ + bUUID(const ::bUUID &struct_uuid); + + /** Initialize from 11 integers, 5 for the regular fields and 6 for the `node` array. */ + bUUID(std::initializer_list<uint32_t> field_values); + + /** Initialize by parsing the string; undefined behavior when the string is invalid. */ + explicit bUUID(const std::string &string_formatted_uuid); + + uint64_t hash() const; +}; // namespace blender + +bool operator==(bUUID uuid1, bUUID uuid2); +bool operator!=(bUUID uuid1, bUUID uuid2); + +/** + * Lexicographic comparison of the UUIDs. + * Equivalent to string comparison on the formatted UUIDs. */ +bool operator<(bUUID uuid1, bUUID uuid2); + +} // namespace blender + #endif diff --git a/source/blender/blenlib/intern/fileops.c b/source/blender/blenlib/intern/fileops.c index 532a29b8147..2ef4d1093a8 100644 --- a/source/blender/blenlib/intern/fileops.c +++ b/source/blender/blenlib/intern/fileops.c @@ -570,6 +570,7 @@ bool BLI_dir_create_recursive(const char *dirname) * blah1/blah2 (without slash) */ BLI_strncpy(tmp, dirname, sizeof(tmp)); + BLI_path_slash_native(tmp); BLI_path_slash_rstrip(tmp); /* check special case "c:\foo", don't try create "c:", harmless but prints an error below */ @@ -760,7 +761,7 @@ static int recursive_operation(const char *startfrom, # endif if (is_dir) { - /* recursively dig into a subfolder */ + /* Recurse into sub-directories. */ ret = recursive_operation( from_path, to_path, callback_dir_pre, callback_file, callback_dir_post); } diff --git a/source/blender/blenlib/intern/kdtree_impl.h b/source/blender/blenlib/intern/kdtree_impl.h index 0c9de0aa128..0b47be1f7ea 100644 --- a/source/blender/blenlib/intern/kdtree_impl.h +++ b/source/blender/blenlib/intern/kdtree_impl.h @@ -190,7 +190,7 @@ static uint kdtree_balance(KDTreeNode *nodes, uint nodes_len, uint axis, const u } } - /* set node and sort subnodes */ + /* Set node and sort sub-nodes. */ node = &nodes[median]; node->d = axis; axis = (axis + 1) % KD_DIMS; diff --git a/source/blender/blenlib/intern/list_sort_impl.h b/source/blender/blenlib/intern/list_sort_impl.h index 680044f9ccb..71f7f0e29a8 100644 --- a/source/blender/blenlib/intern/list_sort_impl.h +++ b/source/blender/blenlib/intern/list_sort_impl.h @@ -202,7 +202,7 @@ BLI_INLINE list_node *sweep_up(struct SortInfo *si, list_node *list, unsigned in } /** - * The 'ranks' array essentially captures the recursion stack of a mergesort. + * The 'ranks' array essentially captures the recursion stack of a merge-sort. * The merge tree is built in a bottom-up manner. The control loop for * updating the 'ranks' array is analogous to incrementing a binary integer, * and the `O(n)` time for counting `upto` n translates to `O(n)` merges when diff --git a/source/blender/blenlib/intern/noise.cc b/source/blender/blenlib/intern/noise.cc index c057c12e543..e80975f618c 100644 --- a/source/blender/blenlib/intern/noise.cc +++ b/source/blender/blenlib/intern/noise.cc @@ -109,7 +109,7 @@ BLI_INLINE void hash_bit_final(uint32_t &a, uint32_t &b, uint32_t &c) c -= hash_bit_rotate(b, 24); } -BLI_INLINE uint32_t hash(uint32_t kx) +uint32_t hash(uint32_t kx) { uint32_t a, b, c; a = b = c = 0xdeadbeef + (1 << 2) + 13; @@ -120,7 +120,7 @@ BLI_INLINE uint32_t hash(uint32_t kx) return c; } -BLI_INLINE uint32_t hash(uint32_t kx, uint32_t ky) +uint32_t hash(uint32_t kx, uint32_t ky) { uint32_t a, b, c; a = b = c = 0xdeadbeef + (2 << 2) + 13; @@ -132,7 +132,7 @@ BLI_INLINE uint32_t hash(uint32_t kx, uint32_t ky) return c; } -BLI_INLINE uint32_t hash(uint32_t kx, uint32_t ky, uint32_t kz) +uint32_t hash(uint32_t kx, uint32_t ky, uint32_t kz) { uint32_t a, b, c; a = b = c = 0xdeadbeef + (3 << 2) + 13; @@ -145,7 +145,7 @@ BLI_INLINE uint32_t hash(uint32_t kx, uint32_t ky, uint32_t kz) return c; } -BLI_INLINE uint32_t hash(uint32_t kx, uint32_t ky, uint32_t kz, uint32_t kw) +uint32_t hash(uint32_t kx, uint32_t ky, uint32_t kz, uint32_t kw) { uint32_t a, b, c; a = b = c = 0xdeadbeef + (4 << 2) + 13; @@ -161,59 +161,83 @@ BLI_INLINE uint32_t hash(uint32_t kx, uint32_t ky, uint32_t kz, uint32_t kw) return c; } -/* Hashing a number of uint32_t into a float in the range [0, 1]. */ +BLI_INLINE uint32_t float_as_uint(float f) +{ + union { + uint32_t i; + float f; + } u; + u.f = f; + return u.i; +} -BLI_INLINE float hash_to_float(uint32_t kx) +uint32_t hash_float(float kx) { - return static_cast<float>(hash(kx)) / static_cast<float>(0xFFFFFFFFu); + return hash(float_as_uint(kx)); } -BLI_INLINE float hash_to_float(uint32_t kx, uint32_t ky) +uint32_t hash_float(float2 k) { - return static_cast<float>(hash(kx, ky)) / static_cast<float>(0xFFFFFFFFu); + return hash(float_as_uint(k.x), float_as_uint(k.y)); } -BLI_INLINE float hash_to_float(uint32_t kx, uint32_t ky, uint32_t kz) +uint32_t hash_float(float3 k) { - return static_cast<float>(hash(kx, ky, kz)) / static_cast<float>(0xFFFFFFFFu); + return hash(float_as_uint(k.x), float_as_uint(k.y), float_as_uint(k.z)); } -BLI_INLINE float hash_to_float(uint32_t kx, uint32_t ky, uint32_t kz, uint32_t kw) +uint32_t hash_float(float4 k) { - return static_cast<float>(hash(kx, ky, kz, kw)) / static_cast<float>(0xFFFFFFFFu); + return hash(float_as_uint(k.x), float_as_uint(k.y), float_as_uint(k.z), float_as_uint(k.w)); } -/* Hashing a number of floats into a float in the range [0, 1]. */ +/* Hashing a number of uint32_t into a float in the range [0, 1]. */ -BLI_INLINE uint32_t float_as_uint(float f) +BLI_INLINE float uint_to_float_01(uint32_t k) { - union { - uint32_t i; - float f; - } u; - u.f = f; - return u.i; + return static_cast<float>(k) / static_cast<float>(0xFFFFFFFFu); +} + +float hash_to_float(uint32_t kx) +{ + return uint_to_float_01(hash(kx)); +} + +float hash_to_float(uint32_t kx, uint32_t ky) +{ + return uint_to_float_01(hash(kx, ky)); } -BLI_INLINE float hash_to_float(float k) +float hash_to_float(uint32_t kx, uint32_t ky, uint32_t kz) +{ + return uint_to_float_01(hash(kx, ky, kz)); +} + +float hash_to_float(uint32_t kx, uint32_t ky, uint32_t kz, uint32_t kw) +{ + return uint_to_float_01(hash(kx, ky, kz, kw)); +} + +/* Hashing a number of floats into a float in the range [0, 1]. */ + +float hash_float_to_float(float k) { - return hash_to_float(float_as_uint(k)); + return hash_to_float(hash_float(k)); } -BLI_INLINE float hash_to_float(float2 k) +float hash_float_to_float(float2 k) { - return hash_to_float(float_as_uint(k.x), float_as_uint(k.y)); + return hash_to_float(hash_float(k)); } -BLI_INLINE float hash_to_float(float3 k) +float hash_float_to_float(float3 k) { - return hash_to_float(float_as_uint(k.x), float_as_uint(k.y), float_as_uint(k.z)); + return hash_to_float(hash_float(k)); } -BLI_INLINE float hash_to_float(float4 k) +float hash_float_to_float(float4 k) { - return hash_to_float( - float_as_uint(k.x), float_as_uint(k.y), float_as_uint(k.z), float_as_uint(k.w)); + return hash_to_float(hash_float(k)); } /* ------------ @@ -565,28 +589,28 @@ float perlin_fractal(float4 position, float octaves, float roughness) BLI_INLINE float random_float_offset(float seed) { - return 100.0f + hash_to_float(seed) * 100.0f; + return 100.0f + hash_float_to_float(seed) * 100.0f; } BLI_INLINE float2 random_float2_offset(float seed) { - return float2(100.0f + hash_to_float(float2(seed, 0.0f)) * 100.0f, - 100.0f + hash_to_float(float2(seed, 1.0f)) * 100.0f); + return float2(100.0f + hash_float_to_float(float2(seed, 0.0f)) * 100.0f, + 100.0f + hash_float_to_float(float2(seed, 1.0f)) * 100.0f); } BLI_INLINE float3 random_float3_offset(float seed) { - return float3(100.0f + hash_to_float(float2(seed, 0.0f)) * 100.0f, - 100.0f + hash_to_float(float2(seed, 1.0f)) * 100.0f, - 100.0f + hash_to_float(float2(seed, 2.0f)) * 100.0f); + return float3(100.0f + hash_float_to_float(float2(seed, 0.0f)) * 100.0f, + 100.0f + hash_float_to_float(float2(seed, 1.0f)) * 100.0f, + 100.0f + hash_float_to_float(float2(seed, 2.0f)) * 100.0f); } BLI_INLINE float4 random_float4_offset(float seed) { - return float4(100.0f + hash_to_float(float2(seed, 0.0f)) * 100.0f, - 100.0f + hash_to_float(float2(seed, 1.0f)) * 100.0f, - 100.0f + hash_to_float(float2(seed, 2.0f)) * 100.0f, - 100.0f + hash_to_float(float2(seed, 3.0f)) * 100.0f); + return float4(100.0f + hash_float_to_float(float2(seed, 0.0f)) * 100.0f, + 100.0f + hash_float_to_float(float2(seed, 1.0f)) * 100.0f, + 100.0f + hash_float_to_float(float2(seed, 2.0f)) * 100.0f, + 100.0f + hash_float_to_float(float2(seed, 3.0f)) * 100.0f); } /* Perlin noises to be added to the position to distort other noises. */ diff --git a/source/blender/blenlib/intern/path_util.c b/source/blender/blenlib/intern/path_util.c index 4d0678035ba..066749f3a94 100644 --- a/source/blender/blenlib/intern/path_util.c +++ b/source/blender/blenlib/intern/path_util.c @@ -1935,6 +1935,39 @@ bool BLI_path_name_at_index(const char *__restrict path, return false; } +bool BLI_path_contains(const char *container_path, const char *containee_path) +{ + char container_native[PATH_MAX]; + char containee_native[PATH_MAX]; + + /* Keep space for a trailing slash. If the path is truncated by this, the containee path is + * longer than PATH_MAX and the result is ill-defined. */ + BLI_strncpy(container_native, container_path, PATH_MAX - 1); + BLI_strncpy(containee_native, containee_path, PATH_MAX); + + BLI_path_slash_native(container_native); + BLI_path_slash_native(containee_native); + + BLI_path_normalize(NULL, container_native); + BLI_path_normalize(NULL, containee_native); + +#ifdef WIN32 + BLI_str_tolower_ascii(container_native, PATH_MAX); + BLI_str_tolower_ascii(containee_native, PATH_MAX); +#endif + + if (STREQ(container_native, containee_native)) { + /* The paths are equal, they contain each other. */ + return true; + } + + /* Add a trailing slash to prevent same-prefix directories from matching. + * e.g. "/some/path" doesn't contain "/some/path_lib". */ + BLI_path_slash_ensure(container_native); + + return BLI_str_startswith(containee_native, container_native); +} + /** * Returns pointer to the leftmost path separator in string. Not actually used anywhere. */ diff --git a/source/blender/blenlib/intern/scanfill.c b/source/blender/blenlib/intern/scanfill.c index f0cf19bf508..8845167f536 100644 --- a/source/blender/blenlib/intern/scanfill.c +++ b/source/blender/blenlib/intern/scanfill.c @@ -1040,13 +1040,13 @@ unsigned int BLI_scanfill_calc_ex(ScanFillContext *sf_ctx, const int flag, const } /* CURRENT STATUS: - * - eve->f :1 = available in edges - * - eve->poly_nr :polynumber - * - eve->edge_tot :amount of edges connected to vertex - * - eve->tmp.v :store! original vertex number + * - `eve->f`: 1 = available in edges. + * - `eve->poly_nr`: poly-number. + * - `eve->edge_tot`: amount of edges connected to vertex. + * - `eve->tmp.v`: store! original vertex number. * - * - eed->f :1 = boundary edge (optionally set by caller) - * - eed->poly_nr :poly number + * - `eed->f`: 1 = boundary edge (optionally set by caller). + * - `eed->poly_nr`: poly number. */ /* STEP 3: MAKE POLYFILL STRUCT */ diff --git a/source/blender/blenlib/intern/uuid.cc b/source/blender/blenlib/intern/uuid.cc index ae34bcb3d32..3c86238036c 100644 --- a/source/blender/blenlib/intern/uuid.cc +++ b/source/blender/blenlib/intern/uuid.cc @@ -18,13 +18,16 @@ * \ingroup bli */ +#include "BLI_assert.h" #include "BLI_uuid.h" #include <cstdio> #include <cstring> #include <ctime> #include <random> +#include <sstream> #include <string> +#include <tuple> /* Ensure the UUID struct doesn't have any padding, to be compatible with memcmp(). */ static_assert(sizeof(bUUID) == 16, "expect UUIDs to be 128 bit exactly"); @@ -137,3 +140,72 @@ std::ostream &operator<<(std::ostream &stream, bUUID uuid) stream << buffer; return stream; } + +namespace blender { + +bUUID::bUUID(const std::initializer_list<uint32_t> field_values) +{ + BLI_assert_msg(field_values.size() == 11, "bUUID requires 5 regular fields + 6 `node` values"); + + const auto *field_iter = field_values.begin(); + + this->time_low = *field_iter++; + this->time_mid = static_cast<uint16_t>(*field_iter++); + this->time_hi_and_version = static_cast<uint16_t>(*field_iter++); + this->clock_seq_hi_and_reserved = static_cast<uint8_t>(*field_iter++); + this->clock_seq_low = static_cast<uint8_t>(*field_iter++); + + std::copy(field_iter, field_values.end(), this->node); +} + +bUUID::bUUID(const std::string &string_formatted_uuid) +{ + const bool parsed_ok = BLI_uuid_parse_string(this, string_formatted_uuid.c_str()); + if (!parsed_ok) { + std::stringstream ss; + ss << "invalid UUID string " << string_formatted_uuid; + throw std::runtime_error(ss.str()); + } +} + +bUUID::bUUID(const ::bUUID &struct_uuid) +{ + *(static_cast<::bUUID *>(this)) = struct_uuid; +} + +uint64_t bUUID::hash() const +{ + /* Convert the struct into two 64-bit numbers, and XOR them to get the hash. */ + const uint64_t *uuid_as_int64 = reinterpret_cast<const uint64_t *>(this); + return uuid_as_int64[0] ^ uuid_as_int64[1]; +} + +bool operator==(const bUUID uuid1, const bUUID uuid2) +{ + return BLI_uuid_equal(uuid1, uuid2); +} + +bool operator!=(const bUUID uuid1, const bUUID uuid2) +{ + return !(uuid1 == uuid2); +} + +bool operator<(const bUUID uuid1, const bUUID uuid2) +{ + auto simple_fields1 = std::tie(uuid1.time_low, + uuid1.time_mid, + uuid1.time_hi_and_version, + uuid1.clock_seq_hi_and_reserved, + uuid1.clock_seq_low); + auto simple_fields2 = std::tie(uuid2.time_low, + uuid2.time_mid, + uuid2.time_hi_and_version, + uuid2.clock_seq_hi_and_reserved, + uuid2.clock_seq_low); + if (simple_fields1 == simple_fields2) { + return std::memcmp(uuid1.node, uuid2.node, sizeof(uuid1.node)) < 0; + } + return simple_fields1 < simple_fields2; +} + +} // namespace blender diff --git a/source/blender/blenlib/tests/BLI_mesh_intersect_test.cc b/source/blender/blenlib/tests/BLI_mesh_intersect_test.cc index 0329fc156c0..68111fb8eb1 100644 --- a/source/blender/blenlib/tests/BLI_mesh_intersect_test.cc +++ b/source/blender/blenlib/tests/BLI_mesh_intersect_test.cc @@ -457,8 +457,8 @@ TEST(mesh_intersect, TwoTris) {4, 11, 6, 4}, /* 9: T11 edge (-1,1,1)(0,1/2,1/2) inside T4 edge. */ {4, 12, 6, 2}, /* 10: parallel planes, not intersecting. */ {4, 13, 6, 2}, /* 11: non-parallel planes, not intersecting, all one side. */ - {0, 14, 6, 2}, /* 12: non-paralel planes, not intersecting, alternate sides. */ - /* Following are all coplanar cases. */ + {0, 14, 6, 2}, /* 12: non-parallel planes, not intersecting, alternate sides. */ + /* Following are all co-planar cases. */ {15, 16, 6, 8}, /* 13: T16 inside T15. NOTE: dup'd tri is expected. */ {15, 17, 8, 8}, /* 14: T17 intersects one edge of T15 at (1,1,0)(3,3,0). */ {15, 18, 10, 12}, /* 15: T18 intersects T15 at (1,1,0)(3,3,0)(3,15/4,1/2)(0,3,2). */ @@ -970,7 +970,7 @@ static void fill_sphere_data(int nrings, static void spheresphere_test(int nrings, double y_offset, bool use_self) { - /* Make two uvspheres with nrings rings ad 2*nrings segments. */ + /* Make two UV-spheres with nrings rings ad 2*nrings segments. */ if (nrings < 2) { return; } diff --git a/source/blender/blenlib/tests/BLI_path_util_test.cc b/source/blender/blenlib/tests/BLI_path_util_test.cc index cf5135731e2..65b02a19960 100644 --- a/source/blender/blenlib/tests/BLI_path_util_test.cc +++ b/source/blender/blenlib/tests/BLI_path_util_test.cc @@ -655,3 +655,34 @@ TEST(path_util, PathRelPath) # undef PATH_REL #endif + +/* BLI_path_contains */ +TEST(path_util, PathContains) +{ + EXPECT_TRUE(BLI_path_contains("/some/path", "/some/path")) << "A path contains itself"; + EXPECT_TRUE(BLI_path_contains("/some/path", "/some/path/inside")) + << "A path contains its subdirectory"; + EXPECT_TRUE(BLI_path_contains("/some/path", "/some/path/../path/inside")) + << "Paths should be normalised"; + EXPECT_TRUE(BLI_path_contains("C:\\some\\path", "C:\\some\\path\\inside")) + << "Windows paths should be supported as well"; + + EXPECT_FALSE(BLI_path_contains("C:\\some\\path", "C:\\some\\other\\path")) + << "Windows paths should be supported as well"; + EXPECT_FALSE(BLI_path_contains("/some/path", "/")) + << "Root directory not be contained in a subdirectory"; + EXPECT_FALSE(BLI_path_contains("/some/path", "/some/path/../outside")) + << "Paths should be normalised"; + EXPECT_FALSE(BLI_path_contains("/some/path", "/some/path_library")) + << "Just sharing a suffix is not enough, path semantics should be followed"; + EXPECT_FALSE(BLI_path_contains("/some/path", "./contents")) + << "Relative paths are not supported"; +} + +#ifdef WIN32 +TEST(path_util, PathContains_Windows_case_insensitive) +{ + EXPECT_TRUE(BLI_path_contains("C:\\some\\path", "c:\\SOME\\path\\inside")) + << "On Windows path comparison should ignore case"; +} +#endif diff --git a/source/blender/blenlib/tests/BLI_uuid_test.cc b/source/blender/blenlib/tests/BLI_uuid_test.cc index 731489c6c9e..b406a0521a1 100644 --- a/source/blender/blenlib/tests/BLI_uuid_test.cc +++ b/source/blender/blenlib/tests/BLI_uuid_test.cc @@ -18,6 +18,8 @@ #include "BLI_uuid.h" +namespace blender::tests { + TEST(BLI_uuid, generate_random) { const bUUID uuid = BLI_uuid_generate_random(); @@ -38,7 +40,7 @@ TEST(BLI_uuid, generate_many_random) /* Generate lots of UUIDs to get some indication that the randomness is okay. */ for (int i = 0; i < 1000000; ++i) { const bUUID uuid = BLI_uuid_generate_random(); - EXPECT_FALSE(BLI_uuid_equal(first_uuid, uuid)); + EXPECT_NE(first_uuid, uuid); // Check that the non-random bits are set according to RFC4122. const uint16_t version = uuid.time_hi_and_version >> 12; @@ -51,10 +53,13 @@ TEST(BLI_uuid, generate_many_random) TEST(BLI_uuid, nil_value) { const bUUID nil_uuid = BLI_uuid_nil(); - const bUUID zeroes_uuid = {0, 0, 0, 0, 0, 0}; + const bUUID zeroes_uuid{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + const bUUID default_constructed{}; - EXPECT_TRUE(BLI_uuid_equal(nil_uuid, zeroes_uuid)); + EXPECT_EQ(nil_uuid, zeroes_uuid); EXPECT_TRUE(BLI_uuid_is_nil(nil_uuid)); + EXPECT_TRUE(BLI_uuid_is_nil(default_constructed)) + << "Default constructor should produce the nil value."; std::string buffer(36, '\0'); BLI_uuid_format(buffer.data(), nil_uuid); @@ -66,8 +71,31 @@ TEST(BLI_uuid, equality) const bUUID uuid1 = BLI_uuid_generate_random(); const bUUID uuid2 = BLI_uuid_generate_random(); - EXPECT_TRUE(BLI_uuid_equal(uuid1, uuid1)); - EXPECT_FALSE(BLI_uuid_equal(uuid1, uuid2)); + EXPECT_EQ(uuid1, uuid1); + EXPECT_NE(uuid1, uuid2); +} + +TEST(BLI_uuid, comparison_trivial) +{ + const bUUID uuid0{}; + const bUUID uuid1("11111111-1111-1111-1111-111111111111"); + const bUUID uuid2("22222222-2222-2222-2222-222222222222"); + + EXPECT_LT(uuid0, uuid1); + EXPECT_LT(uuid0, uuid2); + EXPECT_LT(uuid1, uuid2); +} + +TEST(BLI_uuid, comparison_byte_order_check) +{ + const bUUID uuid0{}; + /* Chosen to test byte ordering is taken into account correctly when comparing. */ + const bUUID uuid12("12222222-2222-2222-2222-222222222222"); + const bUUID uuid21("21111111-1111-1111-1111-111111111111"); + + EXPECT_LT(uuid0, uuid12); + EXPECT_LT(uuid0, uuid21); + EXPECT_LT(uuid12, uuid21); } TEST(BLI_uuid, string_formatting) @@ -91,13 +119,13 @@ TEST(BLI_uuid, string_formatting) EXPECT_EQ("00000001-0002-0003-0405-060000000007", buffer); /* Somewhat more complex bit patterns. This is a version 1 UUID generated from Python. */ - const bUUID uuid1 = {3540651616, 5282, 4588, 139, 153, {0xf7, 0x73, 0x69, 0x44, 0xdb, 0x8b}}; + const bUUID uuid1 = {3540651616, 5282, 4588, 139, 153, 0xf7, 0x73, 0x69, 0x44, 0xdb, 0x8b}; BLI_uuid_format(buffer.data(), uuid1); EXPECT_EQ("d30a0e60-14a2-11ec-8b99-f7736944db8b", buffer); /* Namespace UUID, example listed in RFC4211. */ const bUUID namespace_dns = { - 0x6ba7b810, 0x9dad, 0x11d1, 0x80, 0xb4, {0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8}}; + 0x6ba7b810, 0x9dad, 0x11d1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8}; BLI_uuid_format(buffer.data(), namespace_dns); EXPECT_EQ("6ba7b810-9dad-11d1-80b4-00c04fd430c8", buffer); } @@ -139,7 +167,9 @@ TEST(BLI_uuid, string_parsing_fail) TEST(BLI_uuid, stream_operator) { std::stringstream ss; - const bUUID uuid = {3540651616, 5282, 4588, 139, 153, {0xf7, 0x73, 0x69, 0x44, 0xdb, 0x8b}}; + const bUUID uuid = {3540651616, 5282, 4588, 139, 153, 0xf7, 0x73, 0x69, 0x44, 0xdb, 0x8b}; ss << uuid; EXPECT_EQ(ss.str(), "d30a0e60-14a2-11ec-8b99-f7736944db8b"); } + +} // namespace blender::tests diff --git a/source/blender/blenloader/BLO_readfile.h b/source/blender/blenloader/BLO_readfile.h index 4b7f29dd7dc..fa29b09af02 100644 --- a/source/blender/blenloader/BLO_readfile.h +++ b/source/blender/blenloader/BLO_readfile.h @@ -111,8 +111,18 @@ typedef struct BlendFileReadReport { /* Some sub-categories of the above `missing_linked_id` counter. */ int missing_obdata; int missing_obproxies; + /* Number of root override IDs that were resynced. */ int resynced_lib_overrides; + + /* Number of (non-converted) linked proxies. */ + int linked_proxies; + /* Number of proxies converted to library overrides. */ + int proxies_to_lib_overrides_success; + /* Number of proxies that failed to convert to library overrides. */ + int proxies_to_lib_overrides_failures; + /* Number of sequencer strips that were not read because were in non-supported channels. */ + int vse_strips_skipped; } count; /* Number of libraries which had overrides that needed to be resynced, and a single linked list @@ -168,9 +178,8 @@ struct LinkNode *BLO_blendhandle_get_datablock_names(BlendHandle *bh, const bool use_assets_only, int *r_tot_names); -struct LinkNode *BLO_blendhandle_get_datablock_info(BlendHandle *bh, - int ofblocktype, - int *r_tot_info_items); +struct LinkNode * /*BLODataBlockInfo */ BLO_blendhandle_get_datablock_info( + BlendHandle *bh, int ofblocktype, const bool use_assets_only, int *r_tot_info_items); struct LinkNode *BLO_blendhandle_get_previews(BlendHandle *bh, int ofblocktype, int *r_tot_prev); struct PreviewImage *BLO_blendhandle_get_preview_for_id(BlendHandle *bh, int ofblocktype, @@ -209,6 +218,16 @@ typedef enum eBLOLibLinkFlags { * don't need to remember to set this flag. */ BLO_LIBLINK_NEEDS_ID_TAG_DOIT = 1 << 18, + /** Set fake user on appended IDs. */ + BLO_LIBLINK_APPEND_SET_FAKEUSER = 1 << 19, + /** Append (make local) also indirect dependencies of appended IDs. */ + BLO_LIBLINK_APPEND_RECURSIVE = 1 << 20, + /** Try to re-use previously appended matching ID on new append. */ + BLO_LIBLINK_APPEND_LOCAL_ID_REUSE = 1 << 21, + /** Instantiate object data IDs (i.e. create objects for them if needed). */ + BLO_LIBLINK_OBDATA_INSTANCE = 1 << 24, + /** Instantiate collections as empties, instead of linking them into current view layer. */ + BLO_LIBLINK_COLLECTION_INSTANCE = 1 << 25, } eBLOLibLinkFlags; /** diff --git a/source/blender/blenloader/intern/readblenentry.c b/source/blender/blenloader/intern/readblenentry.c index f88b470809c..3306eb9e454 100644 --- a/source/blender/blenloader/intern/readblenentry.c +++ b/source/blender/blenloader/intern/readblenentry.c @@ -170,17 +170,19 @@ LinkNode *BLO_blendhandle_get_datablock_names(BlendHandle *bh, } /** - * Gets the names and asset-data (if ID is an asset) of all the data-blocks in a file of a certain - * type (e.g. all the scene names in a file). + * Gets the names and asset-data (if ID is an asset) of data-blocks in a file of a certain type. + * The data-blocks can be limited to assets. * * \param bh: The blendhandle to access. * \param ofblocktype: The type of names to get. + * \param use_assets_only: Limit the result to assets only. * \param tot_info_items: The length of the returned list. * \return A BLI_linklist of BLODataBlockInfo *. The links and #BLODataBlockInfo.asset_data should * be freed with MEM_freeN. */ LinkNode *BLO_blendhandle_get_datablock_info(BlendHandle *bh, int ofblocktype, + const bool use_assets_only, int *r_tot_info_items) { FileData *fd = (FileData *)bh; @@ -189,27 +191,34 @@ LinkNode *BLO_blendhandle_get_datablock_info(BlendHandle *bh, int tot = 0; for (bhead = blo_bhead_first(fd); bhead; bhead = blo_bhead_next(fd, bhead)) { + if (bhead->code == ENDB) { + break; + } if (bhead->code == ofblocktype) { - struct BLODataBlockInfo *info = MEM_mallocN(sizeof(*info), __func__); const char *name = blo_bhead_id_name(fd, bhead) + 2; + AssetMetaData *asset_meta_data = blo_bhead_id_asset_data_address(fd, bhead); - STRNCPY(info->name, name); + const bool is_asset = asset_meta_data != NULL; + const bool skip_datablock = use_assets_only && !is_asset; + if (skip_datablock) { + continue; + } + struct BLODataBlockInfo *info = MEM_mallocN(sizeof(*info), __func__); /* Lastly, read asset data from the following blocks. */ - info->asset_data = blo_bhead_id_asset_data_address(fd, bhead); - if (info->asset_data) { - bhead = blo_read_asset_data_block(fd, bhead, &info->asset_data); - /* blo_read_asset_data_block() reads all DATA heads and already advances bhead to the next - * non-DATA one. Go back, so the loop doesn't skip the non-DATA head. */ + if (asset_meta_data) { + bhead = blo_read_asset_data_block(fd, bhead, &asset_meta_data); + /* blo_read_asset_data_block() reads all DATA heads and already advances bhead to the + * next non-DATA one. Go back, so the loop doesn't skip the non-DATA head. */ bhead = blo_bhead_prev(fd, bhead); } + STRNCPY(info->name, name); + info->asset_data = asset_meta_data; + BLI_linklist_prepend(&infos, info); tot++; } - else if (bhead->code == ENDB) { - break; - } } *r_tot_info_items = tot; diff --git a/source/blender/blenloader/intern/readfile.c b/source/blender/blenloader/intern/readfile.c index 8203f988eed..cb3e81ba6d5 100644 --- a/source/blender/blenloader/intern/readfile.c +++ b/source/blender/blenloader/intern/readfile.c @@ -4500,7 +4500,7 @@ static void add_loose_objects_to_scene(Main *mainvar, ViewLayer *view_layer, const View3D *v3d, Library *lib, - const short flag) + const int flag) { Collection *active_collection = NULL; const bool do_append = (flag & FILE_LINK) == 0; @@ -4510,7 +4510,10 @@ static void add_loose_objects_to_scene(Main *mainvar, /* Give all objects which are LIB_TAG_INDIRECT a base, * or for a collection when *lib has been set. */ LISTBASE_FOREACH (Object *, ob, &mainvar->objects) { - bool do_it = (ob->id.tag & LIB_TAG_DOIT) != 0; + /* NOTE: Even if this is a directly linked object and is tagged for instantiation, it might + * have already been instantiated through one of its owner collections, in which case we do not + * want to re-instantiate it in the active collection here. */ + bool do_it = (ob->id.tag & LIB_TAG_DOIT) != 0 && !BKE_scene_object_find(scene, ob); if (do_it || ((ob->id.tag & LIB_TAG_INDIRECT) != 0 && (ob->id.tag & LIB_TAG_PRE_EXISTING) == 0)) { if (do_append) { @@ -4560,9 +4563,9 @@ static void add_loose_object_data_to_scene(Main *mainvar, Scene *scene, ViewLayer *view_layer, const View3D *v3d, - const short flag) + const int flag) { - if ((flag & FILE_OBDATA_INSTANCE) == 0) { + if ((flag & BLO_LIBLINK_OBDATA_INSTANCE) == 0) { return; } @@ -4621,7 +4624,7 @@ static void add_collections_to_scene(Main *mainvar, ViewLayer *view_layer, const View3D *v3d, Library *lib, - const short flag) + const int flag) { Collection *active_collection = scene->master_collection; if (flag & FILE_ACTIVE_COLLECTION) { @@ -4631,7 +4634,7 @@ static void add_collections_to_scene(Main *mainvar, /* Give all objects which are tagged a base. */ LISTBASE_FOREACH (Collection *, collection, &mainvar->collections) { - if ((flag & FILE_COLLECTION_INSTANCE) && (collection->id.tag & LIB_TAG_DOIT)) { + if ((flag & BLO_LIBLINK_COLLECTION_INSTANCE) && (collection->id.tag & LIB_TAG_DOIT)) { /* Any indirect collection should not have been tagged. */ BLI_assert((collection->id.tag & LIB_TAG_INDIRECT) == 0); @@ -4780,7 +4783,7 @@ int BLO_library_link_copypaste(Main *mainl, BlendHandle *bh, const uint64_t id_t if (blo_bhead_is_id_valid_type(bhead) && BKE_idtype_idcode_is_linkable((short)bhead->code) && (id_types_mask == 0 || (BKE_idtype_idcode_to_idfilter((short)bhead->code) & id_types_mask) != 0)) { - read_libblock(fd, mainl, bhead, LIB_TAG_NEED_EXPAND | LIB_TAG_INDIRECT, false, &id); + read_libblock(fd, mainl, bhead, LIB_TAG_NEED_EXPAND | LIB_TAG_EXTERN, false, &id); num_directly_linked++; } @@ -4789,6 +4792,13 @@ int BLO_library_link_copypaste(Main *mainl, BlendHandle *bh, const uint64_t id_t ListBase *lb = which_libbase(mainl, GS(id->name)); id_sort_by_name(lb, id, NULL); + /* Tag as loose object (or data associated with objects) + * needing to be instantiated (see also #link_named_part and its usage of + * #BLO_LIBLINK_NEEDS_ID_TAG_DOIT above). */ + if (library_link_idcode_needs_tag_check(GS(id->name), BLO_LIBLINK_NEEDS_ID_TAG_DOIT)) { + id->tag |= LIB_TAG_DOIT; + } + if (bhead->code == ID_OB) { /* Instead of instancing Base's directly, postpone until after collections are loaded * otherwise the base's flag is set incorrectly when collections are used */ @@ -4834,7 +4844,7 @@ static bool library_link_idcode_needs_tag_check(const short idcode, const int fl if (ELEM(idcode, ID_OB, ID_GR)) { return true; } - if (flag & FILE_OBDATA_INSTANCE) { + if (flag & BLO_LIBLINK_OBDATA_INSTANCE) { if (OB_DATA_SUPPORT_ID(idcode)) { return true; } diff --git a/source/blender/blenloader/intern/versioning_280.c b/source/blender/blenloader/intern/versioning_280.c index 69b67460a5d..d8f4d01a2e9 100644 --- a/source/blender/blenloader/intern/versioning_280.c +++ b/source/blender/blenloader/intern/versioning_280.c @@ -3394,7 +3394,7 @@ void blo_do_versions_280(FileData *fd, Library *UNUSED(lib), Main *bmain) SpaceImage *sima = (SpaceImage *)sl; sima->flag &= ~(SI_FLAG_UNUSED_0 | SI_FLAG_UNUSED_1 | SI_FLAG_UNUSED_3 | SI_FLAG_UNUSED_6 | SI_FLAG_UNUSED_7 | SI_FLAG_UNUSED_8 | - SI_FLAG_UNUSED_17 | SI_FLAG_UNUSED_18 | SI_FLAG_UNUSED_23 | + SI_FLAG_UNUSED_17 | SI_CUSTOM_GRID | SI_FLAG_UNUSED_23 | SI_FLAG_UNUSED_24); break; } @@ -3416,8 +3416,8 @@ void blo_do_versions_280(FileData *fd, Library *UNUSED(lib), Main *bmain) case SPACE_FILE: { SpaceFile *sfile = (SpaceFile *)sl; if (sfile->params) { - sfile->params->flag &= ~(FILE_APPEND_SET_FAKEUSER | FILE_APPEND_RECURSIVE | - FILE_OBDATA_INSTANCE); + sfile->params->flag &= ~(FILE_PARAMS_FLAG_UNUSED_1 | FILE_PARAMS_FLAG_UNUSED_2 | + FILE_PARAMS_FLAG_UNUSED_3); } break; } diff --git a/source/blender/blenloader/intern/versioning_290.c b/source/blender/blenloader/intern/versioning_290.c index be8c4b735be..bf5b0bdbf3c 100644 --- a/source/blender/blenloader/intern/versioning_290.c +++ b/source/blender/blenloader/intern/versioning_290.c @@ -1540,7 +1540,7 @@ void blo_do_versions_290(FileData *fd, Library *UNUSED(lib), Main *bmain) LISTBASE_FOREACH (bNodeTree *, ntree, &bmain->nodetrees) { LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { if (STREQ(node->idname, "GeometryNodeRandomAttribute")) { - STRNCPY(node->idname, "GeometryNodeAttributeRandomize"); + STRNCPY(node->idname, "GeometryLegacyNodeAttributeRandomize"); } } } diff --git a/source/blender/blenloader/intern/versioning_300.c b/source/blender/blenloader/intern/versioning_300.c index 3bb76a4d4b6..323391d9715 100644 --- a/source/blender/blenloader/intern/versioning_300.c +++ b/source/blender/blenloader/intern/versioning_300.c @@ -376,6 +376,7 @@ static void move_vertex_group_names_to_object_data(Main *bmain) /* Clear the list in case the it was already assigned from another object. */ BLI_freelistN(new_defbase); *new_defbase = object->defbase; + BKE_object_defgroup_active_index_set(object, object->actdef); } } } @@ -449,6 +450,79 @@ static void do_versions_sequencer_speed_effect_recursive(Scene *scene, const Lis #undef SEQ_SPEED_COMPRESS_IPO_Y } +static bool do_versions_sequencer_color_tags(Sequence *seq, void *UNUSED(user_data)) +{ + seq->color_tag = SEQUENCE_COLOR_NONE; + return true; +} + +static bNodeLink *find_connected_link(bNodeTree *ntree, bNodeSocket *in_socket) +{ + LISTBASE_FOREACH (bNodeLink *, link, &ntree->links) { + if (link->tosock == in_socket) { + return link; + } + } + return NULL; +} + +static void add_realize_instances_before_socket(bNodeTree *ntree, + bNode *node, + bNodeSocket *geometry_socket) +{ + BLI_assert(geometry_socket->type == SOCK_GEOMETRY); + bNodeLink *link = find_connected_link(ntree, geometry_socket); + if (link == NULL) { + return; + } + + /* If the realize instances node is already before this socket, no need to continue. */ + if (link->fromnode->type == GEO_NODE_REALIZE_INSTANCES) { + return; + } + + bNode *realize_node = nodeAddStaticNode(NULL, ntree, GEO_NODE_REALIZE_INSTANCES); + realize_node->parent = node->parent; + realize_node->locx = node->locx - 100; + realize_node->locy = node->locy; + nodeAddLink(ntree, link->fromnode, link->fromsock, realize_node, realize_node->inputs.first); + link->fromnode = realize_node; + link->fromsock = realize_node->outputs.first; +} + +/** + * If a node used to realize instances implicitly and will no longer do so in 3.0, add a "Realize + * Instances" node in front of it to avoid changing behavior. Don't do this if the node will be + * replaced anyway though. + */ +static void version_geometry_nodes_add_realize_instance_nodes(bNodeTree *ntree) +{ + LISTBASE_FOREACH_MUTABLE (bNode *, node, &ntree->nodes) { + if (ELEM(node->type, + GEO_NODE_ATTRIBUTE_CAPTURE, + GEO_NODE_SEPARATE_COMPONENTS, + GEO_NODE_CONVEX_HULL, + GEO_NODE_CURVE_LENGTH, + GEO_NODE_BOOLEAN, + GEO_NODE_CURVE_FILLET, + GEO_NODE_CURVE_RESAMPLE, + GEO_NODE_CURVE_TO_MESH, + GEO_NODE_CURVE_TRIM, + GEO_NODE_MATERIAL_REPLACE, + GEO_NODE_MESH_SUBDIVIDE, + GEO_NODE_ATTRIBUTE_REMOVE, + GEO_NODE_TRIANGULATE)) { + bNodeSocket *geometry_socket = node->inputs.first; + add_realize_instances_before_socket(ntree, node, geometry_socket); + } + /* Also realize instances for the profile input of the curve to mesh node. */ + if (node->type == GEO_NODE_CURVE_TO_MESH) { + bNodeSocket *profile_socket = node->inputs.last; + add_realize_instances_before_socket(ntree, node, profile_socket); + } + } +} + void do_versions_after_linking_300(Main *bmain, ReportList *UNUSED(reports)) { if (MAIN_VERSION_ATLEAST(bmain, 300, 0) && !MAIN_VERSION_ATLEAST(bmain, 300, 1)) { @@ -505,6 +579,70 @@ void do_versions_after_linking_300(Main *bmain, ReportList *UNUSED(reports)) } } + if (!MAIN_VERSION_ATLEAST(bmain, 300, 26)) { + LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { + ToolSettings *tool_settings = scene->toolsettings; + ImagePaintSettings *imapaint = &tool_settings->imapaint; + if (imapaint->canvas != NULL && + ELEM(imapaint->canvas->type, IMA_TYPE_R_RESULT, IMA_TYPE_COMPOSITE)) { + imapaint->canvas = NULL; + } + if (imapaint->stencil != NULL && + ELEM(imapaint->stencil->type, IMA_TYPE_R_RESULT, IMA_TYPE_COMPOSITE)) { + imapaint->stencil = NULL; + } + if (imapaint->clone != NULL && + ELEM(imapaint->clone->type, IMA_TYPE_R_RESULT, IMA_TYPE_COMPOSITE)) { + imapaint->clone = NULL; + } + } + + LISTBASE_FOREACH (Brush *, brush, &bmain->brushes) { + if (brush->clone.image != NULL && + ELEM(brush->clone.image->type, IMA_TYPE_R_RESULT, IMA_TYPE_COMPOSITE)) { + brush->clone.image = NULL; + } + } + } + + if (!MAIN_VERSION_ATLEAST(bmain, 300, 28)) { + LISTBASE_FOREACH (bNodeTree *, ntree, &bmain->nodetrees) { + if (ntree->type == NTREE_GEOMETRY) { + version_geometry_nodes_add_realize_instance_nodes(ntree); + } + } + } + + if (!MAIN_VERSION_ATLEAST(bmain, 300, 30)) { + do_versions_idproperty_ui_data(bmain); + } + + if (!MAIN_VERSION_ATLEAST(bmain, 300, 32)) { + /* Update Switch Node Non-Fields switch input to Switch_001. */ + LISTBASE_FOREACH (bNodeTree *, ntree, &bmain->nodetrees) { + if (ntree->type != NTREE_GEOMETRY) { + continue; + } + + LISTBASE_FOREACH (bNodeLink *, link, &ntree->links) { + if (link->tonode->type == GEO_NODE_SWITCH) { + if (STREQ(link->tosock->identifier, "Switch")) { + bNode *to_node = link->tonode; + + uint8_t mode = ((NodeSwitch *)to_node->storage)->input_type; + if (ELEM(mode, + SOCK_GEOMETRY, + SOCK_OBJECT, + SOCK_COLLECTION, + SOCK_TEXTURE, + SOCK_MATERIAL)) { + link->tosock = link->tosock->next; + } + } + } + } + } + } /** * Versioning code until next subversion bump goes here. * @@ -517,7 +655,16 @@ void do_versions_after_linking_300(Main *bmain, ReportList *UNUSED(reports)) */ { /* Keep this block, even when empty. */ - do_versions_idproperty_ui_data(bmain); + + /* This was missing from #move_vertex_group_names_to_object_data. */ + LISTBASE_FOREACH (Object *, object, &bmain->objects) { + if (ELEM(object->type, OB_MESH, OB_LATTICE, OB_GPENCIL)) { + /* This uses the fact that the active vertex group index starts counting at 1. */ + if (BKE_object_defgroup_active_index_get(object) == 0) { + BKE_object_defgroup_active_index_set(object, object->actdef); + } + } + } } } @@ -656,10 +803,8 @@ static bool geometry_node_is_293_legacy(const short node_type) switch (node_type) { /* Not legacy: No attribute inputs or outputs. */ case GEO_NODE_TRIANGULATE: - case GEO_NODE_EDGE_SPLIT: case GEO_NODE_TRANSFORM: case GEO_NODE_BOOLEAN: - case GEO_NODE_SUBDIVISION_SURFACE: case GEO_NODE_IS_VIEWPORT: case GEO_NODE_MESH_SUBDIVIDE: case GEO_NODE_MESH_PRIMITIVE_CUBE: @@ -706,16 +851,16 @@ static bool geometry_node_is_293_legacy(const short node_type) case GEO_NODE_COLLECTION_INFO: return false; - /* Maybe legacy: Transferred *all* attributes before, will not transfer all built-ins now. */ - case GEO_NODE_CURVE_ENDPOINTS: - case GEO_NODE_CURVE_TO_POINTS: - return false; - - /* Maybe legacy: Special case for grid names? Or finish patch from level set branch to generate - * a mesh for all grids in the volume. */ + /* Maybe legacy: Special case for grid names? Or finish patch from level set branch to + * generate a mesh for all grids in the volume. */ case GEO_NODE_VOLUME_TO_MESH: return false; + /* Legacy: Transferred *all* attributes before, will not transfer all built-ins now. */ + case GEO_NODE_LEGACY_CURVE_ENDPOINTS: + case GEO_NODE_LEGACY_CURVE_TO_POINTS: + return true; + /* Legacy: Attribute operation completely replaced by field nodes. */ case GEO_NODE_LEGACY_ATTRIBUTE_RANDOMIZE: case GEO_NODE_LEGACY_ATTRIBUTE_MATH: @@ -727,10 +872,10 @@ static bool geometry_node_is_293_legacy(const short node_type) case GEO_NODE_LEGACY_ALIGN_ROTATION_TO_VECTOR: case GEO_NODE_LEGACY_POINT_SCALE: case GEO_NODE_LEGACY_ATTRIBUTE_SAMPLE_TEXTURE: - case GEO_NODE_ATTRIBUTE_VECTOR_ROTATE: + case GEO_NODE_LEGACY_ATTRIBUTE_VECTOR_ROTATE: case GEO_NODE_LEGACY_ATTRIBUTE_CURVE_MAP: case GEO_NODE_LEGACY_ATTRIBUTE_MAP_RANGE: - case GEO_NODE_LECAGY_ATTRIBUTE_CLAMP: + case GEO_NODE_LEGACY_ATTRIBUTE_CLAMP: case GEO_NODE_LEGACY_ATTRIBUTE_VECTOR_MATH: case GEO_NODE_LEGACY_ATTRIBUTE_COMBINE_XYZ: case GEO_NODE_LEGACY_ATTRIBUTE_SEPARATE_XYZ: @@ -752,15 +897,17 @@ static bool geometry_node_is_293_legacy(const short node_type) case GEO_NODE_LEGACY_CURVE_SET_HANDLES: return true; - /* Legacy: More complex attribute inputs or outputs. */ - case GEO_NODE_LEGACY_DELETE_GEOMETRY: /* Needs field input, domain drop-down. */ - case GEO_NODE_LEGACY_CURVE_SUBDIVIDE: /* Needs field count input. */ - case GEO_NODE_LEGACY_POINTS_TO_VOLUME: /* Needs field radius input. */ - case GEO_NODE_LEGACY_SELECT_BY_MATERIAL: /* Output anonymous attribute. */ - case GEO_NODE_LEGACY_POINT_TRANSLATE: /* Needs field inputs. */ - case GEO_NODE_LEGACY_POINT_INSTANCE: /* Needs field inputs. */ - case GEO_NODE_LEGACY_POINT_DISTRIBUTE: /* Needs field input, remove max for random mode. */ - case GEO_NODE_LEGACY_ATTRIBUTE_CONVERT: /* Attribute Capture, Store Attribute. */ + /* Legacy: More complex attribute inputs or outputs. */ + case GEO_NODE_LEGACY_SUBDIVISION_SURFACE: /* Used "crease" attribute. */ + case GEO_NODE_LEGACY_EDGE_SPLIT: /* Needs selection input version. */ + case GEO_NODE_LEGACY_DELETE_GEOMETRY: /* Needs field input, domain drop-down. */ + case GEO_NODE_LEGACY_CURVE_SUBDIVIDE: /* Needs field count input. */ + case GEO_NODE_LEGACY_POINTS_TO_VOLUME: /* Needs field radius input. */ + case GEO_NODE_LEGACY_SELECT_BY_MATERIAL: /* Output anonymous attribute. */ + case GEO_NODE_LEGACY_POINT_TRANSLATE: /* Needs field inputs. */ + case GEO_NODE_LEGACY_POINT_INSTANCE: /* Needs field inputs. */ + case GEO_NODE_LEGACY_POINT_DISTRIBUTE: /* Needs field input, remove max for random mode. */ + case GEO_NODE_LEGACY_ATTRIBUTE_CONVERT: /* Attribute Capture, Store Attribute. */ return true; } return false; @@ -785,6 +932,7 @@ static void version_geometry_nodes_change_legacy_names(bNodeTree *ntree) } } } + static bool seq_transform_origin_set(Sequence *seq, void *UNUSED(user_data)) { StripTransform *transform = seq->strip->transform; @@ -1111,6 +1259,15 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain) } } } + if (ob->type == OB_GPENCIL) { + LISTBASE_FOREACH (GpencilModifierData *, md, &ob->greasepencil_modifiers) { + if (md->type == eGpencilModifierType_Lineart) { + LineartGpencilModifierData *lmd = (LineartGpencilModifierData *)md; + lmd->flags |= LRT_GPENCIL_USE_CACHE; + lmd->chain_smooth_tolerance = 0.2f; + } + } + } } } @@ -1241,7 +1398,7 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain) FOREACH_NODETREE_BEGIN (bmain, ntree, id) { if (ntree->type == NTREE_GEOMETRY) { LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { - if (node->type == GEO_NODE_SUBDIVISION_SURFACE) { + if (node->type == GEO_NODE_LEGACY_SUBDIVISION_SURFACE) { if (node->storage == NULL) { NodeGeometrySubdivisionSurface *data = MEM_callocN( sizeof(NodeGeometrySubdivisionSurface), __func__); @@ -1312,11 +1469,6 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain) } if (!MAIN_VERSION_ATLEAST(bmain, 300, 22)) { - LISTBASE_FOREACH (bNodeTree *, ntree, &bmain->nodetrees) { - if (ntree->type == NTREE_GEOMETRY) { - version_geometry_nodes_change_legacy_names(ntree); - } - } if (!DNA_struct_elem_find( fd->filesdna, "LineartGpencilModifierData", "bool", "use_crease_on_smooth")) { LISTBASE_FOREACH (Object *, ob, &bmain->objects) { @@ -1428,6 +1580,142 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain) } } + if (!MAIN_VERSION_ATLEAST(bmain, 300, 26)) { + LISTBASE_FOREACH (Object *, ob, &bmain->objects) { + LISTBASE_FOREACH (ModifierData *, md, &ob->modifiers) { + if (md->type == eModifierType_Nodes) { + version_geometry_nodes_add_attribute_input_settings((NodesModifierData *)md); + } + } + } + + LISTBASE_FOREACH (bScreen *, screen, &bmain->screens) { + LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { + LISTBASE_FOREACH (SpaceLink *, sl, &area->spacedata) { + switch (sl->spacetype) { + case SPACE_FILE: { + SpaceFile *sfile = (SpaceFile *)sl; + if (sfile->params) { + sfile->params->flag &= ~(FILE_PARAMS_FLAG_UNUSED_1 | FILE_PARAMS_FLAG_UNUSED_2 | + FILE_PARAMS_FLAG_UNUSED_3 | FILE_PARAMS_FLAG_UNUSED_4); + } + + /* New default import type: Append with reuse. */ + if (sfile->asset_params) { + sfile->asset_params->import_type = FILE_ASSET_IMPORT_APPEND_REUSE; + } + break; + } + default: + break; + } + } + } + } + + /* Deprecate the random float node in favor of the random value node. */ + LISTBASE_FOREACH (bNodeTree *, ntree, &bmain->nodetrees) { + if (ntree->type != NTREE_GEOMETRY) { + continue; + } + LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { + if (node->type != FN_NODE_LEGACY_RANDOM_FLOAT) { + continue; + } + if (strstr(node->idname, "Legacy")) { + /* Make sure we haven't changed this idname already. */ + continue; + } + + char temp_idname[sizeof(node->idname)]; + BLI_strncpy(temp_idname, node->idname, sizeof(node->idname)); + + BLI_snprintf(node->idname, + sizeof(node->idname), + "FunctionNodeLegacy%s", + temp_idname + strlen("FunctionNode")); + } + } + } + + if (!MAIN_VERSION_ATLEAST(bmain, 300, 29)) { + LISTBASE_FOREACH (bScreen *, screen, &bmain->screens) { + LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { + LISTBASE_FOREACH (SpaceLink *, sl, &area->spacedata) { + switch (sl->spacetype) { + case SPACE_SEQ: { + ListBase *regionbase = (sl == area->spacedata.first) ? &area->regionbase : + &sl->regionbase; + LISTBASE_FOREACH (ARegion *, region, regionbase) { + if (region->regiontype == RGN_TYPE_WINDOW) { + region->v2d.max[1] = MAXSEQ; + } + } + break; + } + case SPACE_IMAGE: { + SpaceImage *sima = (SpaceImage *)sl; + sima->custom_grid_subdiv = 10; + break; + } + } + } + } + } + + LISTBASE_FOREACH (bNodeTree *, ntree, &bmain->nodetrees) { + if (ntree->type == NTREE_GEOMETRY) { + version_geometry_nodes_change_legacy_names(ntree); + } + } + } + + if (!MAIN_VERSION_ATLEAST(bmain, 300, 31)) { + /* Swap header with the tool header so the regular header is always on the edge. */ + for (bScreen *screen = bmain->screens.first; screen; screen = screen->id.next) { + LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { + LISTBASE_FOREACH (SpaceLink *, sl, &area->spacedata) { + ListBase *regionbase = (sl == area->spacedata.first) ? &area->regionbase : + &sl->regionbase; + ARegion *region_tool = NULL, *region_head = NULL; + int region_tool_index = -1, region_head_index = -1, i; + LISTBASE_FOREACH_INDEX (ARegion *, region, regionbase, i) { + if (region->regiontype == RGN_TYPE_TOOL_HEADER) { + region_tool = region; + region_tool_index = i; + } + else if (region->regiontype == RGN_TYPE_HEADER) { + region_head = region; + region_head_index = i; + } + } + if ((region_tool && region_head) && (region_head_index > region_tool_index)) { + BLI_listbase_swaplinks(regionbase, region_tool, region_head); + } + } + } + } + + /* Set strip color tags to SEQUENCE_COLOR_NONE. */ + LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { + if (scene->ed != NULL) { + SEQ_for_each_callback(&scene->ed->seqbase, do_versions_sequencer_color_tags, NULL); + } + } + + /* Show sequencer color tags by default. */ + LISTBASE_FOREACH (bScreen *, screen, &bmain->screens) { + LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { + LISTBASE_FOREACH (SpaceLink *, sl, &area->spacedata) { + if (sl->spacetype == SPACE_SEQ) { + SpaceSeq *sseq = (SpaceSeq *)sl; + sseq->timeline_overlay.flag |= SEQ_TIMELINE_SHOW_STRIP_COLOR_TAG; + } + } + } + } + } + /** * Versioning code until next subversion bump goes here. * @@ -1439,12 +1727,5 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain) */ { /* Keep this block, even when empty. */ - LISTBASE_FOREACH (Object *, ob, &bmain->objects) { - LISTBASE_FOREACH (ModifierData *, md, &ob->modifiers) { - if (md->type == eModifierType_Nodes) { - version_geometry_nodes_add_attribute_input_settings((NodesModifierData *)md); - } - } - } } } diff --git a/source/blender/blenloader/intern/versioning_defaults.c b/source/blender/blenloader/intern/versioning_defaults.c index 152ef79a38f..c383c1cc4e5 100644 --- a/source/blender/blenloader/intern/versioning_defaults.c +++ b/source/blender/blenloader/intern/versioning_defaults.c @@ -160,7 +160,8 @@ static void blo_update_defaults_screen(bScreen *screen, seq->flag |= SEQ_SHOW_MARKERS | SEQ_ZOOM_TO_FIT | SEQ_USE_PROXIES | SEQ_SHOW_OVERLAY; seq->render_size = SEQ_RENDER_SIZE_PROXY_100; seq->timeline_overlay.flag |= SEQ_TIMELINE_SHOW_STRIP_SOURCE | SEQ_TIMELINE_SHOW_STRIP_NAME | - SEQ_TIMELINE_SHOW_STRIP_DURATION | SEQ_TIMELINE_SHOW_GRID; + SEQ_TIMELINE_SHOW_STRIP_DURATION | SEQ_TIMELINE_SHOW_GRID | + SEQ_TIMELINE_SHOW_STRIP_COLOR_TAG; seq->preview_overlay.flag |= SEQ_PREVIEW_SHOW_OUTLINE_SELECTED; } else if (area->spacetype == SPACE_TEXT) { diff --git a/source/blender/blenloader/intern/versioning_userdef.c b/source/blender/blenloader/intern/versioning_userdef.c index f4853ff803f..cd365b6be78 100644 --- a/source/blender/blenloader/intern/versioning_userdef.c +++ b/source/blender/blenloader/intern/versioning_userdef.c @@ -291,6 +291,16 @@ static void do_versions_theme(const UserDef *userdef, bTheme *btheme) btheme->space_sequencer.grid[3] = 255; } + if (!USER_VERSION_ATLEAST(300, 30)) { + FROM_DEFAULT_V4_UCHAR(space_node.wire); + } + + if (!USER_VERSION_ATLEAST(300, 31)) { + for (int i = 0; i < SEQUENCE_COLOR_TOT; ++i) { + FROM_DEFAULT_V4_UCHAR(strip_color[i].color); + } + } + /** * Versioning code until next subversion bump goes here. * @@ -728,7 +738,7 @@ void blo_do_versions_userdef(UserDef *userdef) } } - /* patch to set Dupli Lightprobes and Grease Pencil */ + /* Patch to set dupli light-probes and grease-pencil. */ if (!USER_VERSION_ATLEAST(280, 58)) { userdef->dupflag |= USER_DUP_LIGHTPROBE; userdef->dupflag |= USER_DUP_GPENCIL; diff --git a/source/blender/bmesh/intern/bmesh_polygon_edgenet.c b/source/blender/bmesh/intern/bmesh_polygon_edgenet.c index 86a7d8153f0..103d7621f87 100644 --- a/source/blender/bmesh/intern/bmesh_polygon_edgenet.c +++ b/source/blender/bmesh/intern/bmesh_polygon_edgenet.c @@ -1135,11 +1135,13 @@ static BMVert *bm_face_split_edgenet_partial_connect(BMesh *bm, BMVert *v_delimi BMVert *v_other = BM_edge_other_vert(e_face_init ? e_face_init : v_delimit->e, v_delimit); BLI_SMALLSTACK_PUSH(search, v_other); - BM_elem_flag_disable(v_other, VERT_NOT_IN_STACK); + if (BM_elem_flag_test(v_other, VERT_NOT_IN_STACK)) { + BM_elem_flag_disable(v_other, VERT_NOT_IN_STACK); + BLI_linklist_prepend_alloca(&vert_stack, v_other); + } while ((v_other = BLI_SMALLSTACK_POP(search))) { BLI_assert(BM_elem_flag_test(v_other, VERT_NOT_IN_STACK) == false); - BLI_linklist_prepend_alloca(&vert_stack, v_other); BMEdge *e_iter = v_other->e; do { BMVert *v_step = BM_edge_other_vert(e_iter, v_other); @@ -1147,6 +1149,7 @@ static BMVert *bm_face_split_edgenet_partial_connect(BMesh *bm, BMVert *v_delimi if (BM_elem_flag_test(v_step, VERT_NOT_IN_STACK)) { BM_elem_flag_disable(v_step, VERT_NOT_IN_STACK); BLI_SMALLSTACK_PUSH(search, v_step); + BLI_linklist_prepend_alloca(&vert_stack, v_step); } } } while ((e_iter = BM_DISK_EDGE_NEXT(e_iter, v_other)) != v_other->e); diff --git a/source/blender/bmesh/intern/bmesh_structure.c b/source/blender/bmesh/intern/bmesh_structure.c index d5d72cd4ba3..1f1ad0bae5b 100644 --- a/source/blender/bmesh/intern/bmesh_structure.c +++ b/source/blender/bmesh/intern/bmesh_structure.c @@ -86,7 +86,8 @@ void bmesh_disk_vert_replace(BMEdge *e, BMVert *v_dst, BMVert *v_src) /** * \section bm_cycles BMesh Cycles - * (this is somewhat outdate, though bits of its API are still used) - joeedh + * + * NOTE(@joeedh): this is somewhat outdated, though bits of its API are still used. * * Cycles are circular doubly linked lists that form the basis of adjacency * information in the BME modeler. Full adjacency relations can be derived diff --git a/source/blender/bmesh/intern/bmesh_walkers.c b/source/blender/bmesh/intern/bmesh_walkers.c index 8bdf205babd..b8fdd534842 100644 --- a/source/blender/bmesh/intern/bmesh_walkers.c +++ b/source/blender/bmesh/intern/bmesh_walkers.c @@ -31,8 +31,7 @@ #include "bmesh_walkers_private.h" /** - * - joeedh - - * design notes: + * NOTE(@joeedh): Details on design. * * original design: walkers directly emulation recursive functions. * functions save their state onto a worklist, and also add new states diff --git a/source/blender/bmesh/tools/bmesh_bevel.c b/source/blender/bmesh/tools/bmesh_bevel.c index e306fe47770..1f759e9ef43 100644 --- a/source/blender/bmesh/tools/bmesh_bevel.c +++ b/source/blender/bmesh/tools/bmesh_bevel.c @@ -111,7 +111,7 @@ typedef struct EdgeHalf { bool is_bev; /** Is e->v2 the vertex at this end? */ bool is_rev; - /** Is e a seam for custom loopdata (e.g., UVs)? */ + /** Is e a seam for custom loop-data (e.g., UV's). */ bool is_seam; /** Used during the custom profile orientation pass. */ bool visited_rpo; @@ -3582,7 +3582,7 @@ static void adjust_the_cycle_or_chain(BoundVert *vstart, bool iscycle) } /* Residue np + 2*i (if cycle) else np - 1 + 2*i: - * right offset for parm i matches its spec; weighted. */ + * right offset for parameter i matches its spec; weighted. */ int row = iscycle ? np + 2 * i : np - 1 + 2 * i; EIG_linear_solver_matrix_add(solver, row, i, weight); EIG_linear_solver_right_hand_side_add(solver, 0, row, weight * eright->offset_r); @@ -3595,7 +3595,7 @@ static void adjust_the_cycle_or_chain(BoundVert *vstart, bool iscycle) #endif /* Residue np + 2*i + 1 (if cycle) else np - 1 + 2*i + 1: - * left offset for parm i matches its spec; weighted. */ + * left offset for parameter i matches its spec; weighted. */ row = row + 1; EIG_linear_solver_matrix_add( solver, row, (i == np - 1) ? 0 : i + 1, weight * v->adjchain->sinratio); diff --git a/source/blender/compositor/COM_defines.h b/source/blender/compositor/COM_defines.h index 73c4343a230..9991414aba4 100644 --- a/source/blender/compositor/COM_defines.h +++ b/source/blender/compositor/COM_defines.h @@ -18,11 +18,14 @@ #pragma once +#include "BLI_float2.hh" #include "BLI_index_range.hh" #include "BLI_rect.h" namespace blender::compositor { +using Size2f = float2; + enum class eExecutionModel { /** * Operations are executed from outputs to inputs grouped in execution groups and rendered @@ -121,26 +124,7 @@ constexpr float COM_PREVIEW_SIZE = 140.f; constexpr float COM_RULE_OF_THIRDS_DIVIDER = 100.0f; constexpr float COM_BLUR_BOKEH_PIXELS = 512; -constexpr rcti COM_SINGLE_ELEM_AREA = {0, 1, 0, 1}; - -constexpr IndexRange XRange(const rcti &area) -{ - return IndexRange(area.xmin, area.xmax - area.xmin); -} - -constexpr IndexRange YRange(const rcti &area) -{ - return IndexRange(area.ymin, area.ymax - area.ymin); -} - -constexpr IndexRange XRange(const rcti *area) -{ - return XRange(*area); -} - -constexpr IndexRange YRange(const rcti *area) -{ - return YRange(*area); -} +constexpr rcti COM_AREA_NONE = {0, 0, 0, 0}; +constexpr rcti COM_CONSTANT_INPUT_AREA_OF_INTEREST = COM_AREA_NONE; } // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_BufferOperation.cc b/source/blender/compositor/intern/COM_BufferOperation.cc index cafdff89c8e..c6530cf6bd1 100644 --- a/source/blender/compositor/intern/COM_BufferOperation.cc +++ b/source/blender/compositor/intern/COM_BufferOperation.cc @@ -24,12 +24,7 @@ BufferOperation::BufferOperation(MemoryBuffer *buffer, DataType data_type) { buffer_ = buffer; inflated_buffer_ = nullptr; - /* TODO: Implement a MemoryBuffer get_size() method returning a Size2d type. Shorten following - * code to: set_resolution(buffer.get_size()) */ - unsigned int resolution[2]; - resolution[0] = buffer->getWidth(); - resolution[1] = buffer->getHeight(); - setResolution(resolution); + set_canvas(buffer->get_rect()); addOutputSocket(data_type); flags.is_constant_operation = buffer_->is_a_single_elem(); flags.is_fullframe_operation = false; diff --git a/source/blender/compositor/intern/COM_CompositorContext.cc b/source/blender/compositor/intern/COM_CompositorContext.cc index a93820b66dc..f5f490b0bf6 100644 --- a/source/blender/compositor/intern/COM_CompositorContext.cc +++ b/source/blender/compositor/intern/COM_CompositorContext.cc @@ -43,6 +43,12 @@ int CompositorContext::getFramenumber() const return m_rd->cfra; } +Size2f CompositorContext::get_render_size() const +{ + return {getRenderData()->xsch * getRenderPercentageAsFactor(), + getRenderData()->ysch * getRenderPercentageAsFactor()}; +} + eExecutionModel CompositorContext::get_execution_model() const { if (U.experimental.use_full_frame_compositor) { diff --git a/source/blender/compositor/intern/COM_CompositorContext.h b/source/blender/compositor/intern/COM_CompositorContext.h index c6e83f93777..ae298c5a65a 100644 --- a/source/blender/compositor/intern/COM_CompositorContext.h +++ b/source/blender/compositor/intern/COM_CompositorContext.h @@ -288,6 +288,8 @@ class CompositorContext { return m_rd->size * 0.01f; } + Size2f get_render_size() const; + /** * Get active execution model. */ diff --git a/source/blender/compositor/intern/COM_Converter.cc b/source/blender/compositor/intern/COM_Converter.cc index 4b103c21c75..ee77beb8a82 100644 --- a/source/blender/compositor/intern/COM_Converter.cc +++ b/source/blender/compositor/intern/COM_Converter.cc @@ -460,14 +460,16 @@ NodeOperation *COM_convert_data_type(const NodeOperationOutput &from, const Node return nullptr; } -void COM_convert_resolution(NodeOperationBuilder &builder, - NodeOperationOutput *fromSocket, - NodeOperationInput *toSocket) +void COM_convert_canvas(NodeOperationBuilder &builder, + NodeOperationOutput *fromSocket, + NodeOperationInput *toSocket) { /* Data type conversions are executed before resolutions to ensure convert operations have * resolution. This method have to ensure same datatypes are linked for new operations. */ BLI_assert(fromSocket->getDataType() == toSocket->getDataType()); + ResizeMode mode = toSocket->getResizeMode(); + BLI_assert(mode != ResizeMode::None); NodeOperation *toOperation = &toSocket->getOperation(); const float toWidth = toOperation->getWidth(); @@ -477,13 +479,12 @@ void COM_convert_resolution(NodeOperationBuilder &builder, const float fromHeight = fromOperation->getHeight(); bool doCenter = false; bool doScale = false; - float addX = (toWidth - fromWidth) / 2.0f; - float addY = (toHeight - fromHeight) / 2.0f; float scaleX = 0; float scaleY = 0; switch (mode) { case ResizeMode::None: + case ResizeMode::Align: break; case ResizeMode::Center: doCenter = true; @@ -518,63 +519,74 @@ void COM_convert_resolution(NodeOperationBuilder &builder, break; } - if (doCenter) { - NodeOperation *first = nullptr; - ScaleOperation *scaleOperation = nullptr; - if (doScale) { - scaleOperation = new ScaleRelativeOperation(fromSocket->getDataType()); - scaleOperation->getInputSocket(1)->setResizeMode(ResizeMode::None); - scaleOperation->getInputSocket(2)->setResizeMode(ResizeMode::None); - first = scaleOperation; - SetValueOperation *sxop = new SetValueOperation(); - sxop->setValue(scaleX); - builder.addLink(sxop->getOutputSocket(), scaleOperation->getInputSocket(1)); - SetValueOperation *syop = new SetValueOperation(); - syop->setValue(scaleY); - builder.addLink(syop->getOutputSocket(), scaleOperation->getInputSocket(2)); - builder.addOperation(sxop); - builder.addOperation(syop); - - unsigned int resolution[2] = {fromOperation->getWidth(), fromOperation->getHeight()}; - scaleOperation->setResolution(resolution); - sxop->setResolution(resolution); - syop->setResolution(resolution); - builder.addOperation(scaleOperation); - } + float addX = doCenter ? (toWidth - fromWidth) / 2.0f : 0.0f; + float addY = doCenter ? (toHeight - fromHeight) / 2.0f : 0.0f; + NodeOperation *first = nullptr; + ScaleOperation *scaleOperation = nullptr; + if (doScale) { + scaleOperation = new ScaleRelativeOperation(fromSocket->getDataType()); + scaleOperation->getInputSocket(1)->setResizeMode(ResizeMode::None); + scaleOperation->getInputSocket(2)->setResizeMode(ResizeMode::None); + first = scaleOperation; + SetValueOperation *sxop = new SetValueOperation(); + sxop->setValue(scaleX); + builder.addLink(sxop->getOutputSocket(), scaleOperation->getInputSocket(1)); + SetValueOperation *syop = new SetValueOperation(); + syop->setValue(scaleY); + builder.addLink(syop->getOutputSocket(), scaleOperation->getInputSocket(2)); + builder.addOperation(sxop); + builder.addOperation(syop); - TranslateOperation *translateOperation = new TranslateOperation(toSocket->getDataType()); - translateOperation->getInputSocket(1)->setResizeMode(ResizeMode::None); - translateOperation->getInputSocket(2)->setResizeMode(ResizeMode::None); - if (!first) { - first = translateOperation; + rcti scale_canvas = fromOperation->get_canvas(); + if (builder.context().get_execution_model() == eExecutionModel::FullFrame) { + ScaleOperation::scale_area(scale_canvas, scaleX, scaleY); + scale_canvas.xmax = scale_canvas.xmin + toOperation->getWidth(); + scale_canvas.ymax = scale_canvas.ymin + toOperation->getHeight(); + addX = 0; + addY = 0; } - SetValueOperation *xop = new SetValueOperation(); - xop->setValue(addX); - builder.addLink(xop->getOutputSocket(), translateOperation->getInputSocket(1)); - SetValueOperation *yop = new SetValueOperation(); - yop->setValue(addY); - builder.addLink(yop->getOutputSocket(), translateOperation->getInputSocket(2)); - builder.addOperation(xop); - builder.addOperation(yop); + scaleOperation->set_canvas(scale_canvas); + sxop->set_canvas(scale_canvas); + syop->set_canvas(scale_canvas); + builder.addOperation(scaleOperation); + } - unsigned int resolution[2] = {toOperation->getWidth(), toOperation->getHeight()}; - translateOperation->setResolution(resolution); - xop->setResolution(resolution); - yop->setResolution(resolution); - builder.addOperation(translateOperation); + TranslateOperation *translateOperation = new TranslateOperation(toSocket->getDataType()); + translateOperation->getInputSocket(1)->setResizeMode(ResizeMode::None); + translateOperation->getInputSocket(2)->setResizeMode(ResizeMode::None); + if (!first) { + first = translateOperation; + } + SetValueOperation *xop = new SetValueOperation(); + xop->setValue(addX); + builder.addLink(xop->getOutputSocket(), translateOperation->getInputSocket(1)); + SetValueOperation *yop = new SetValueOperation(); + yop->setValue(addY); + builder.addLink(yop->getOutputSocket(), translateOperation->getInputSocket(2)); + builder.addOperation(xop); + builder.addOperation(yop); - if (doScale) { - translateOperation->getInputSocket(0)->setResizeMode(ResizeMode::None); - builder.addLink(scaleOperation->getOutputSocket(), translateOperation->getInputSocket(0)); - } + rcti translate_canvas = toOperation->get_canvas(); + if (mode == ResizeMode::Align) { + translate_canvas.xmax = translate_canvas.xmin + fromWidth; + translate_canvas.ymax = translate_canvas.ymin + fromHeight; + } + translateOperation->set_canvas(translate_canvas); + xop->set_canvas(translate_canvas); + yop->set_canvas(translate_canvas); + builder.addOperation(translateOperation); - /* remove previous link and replace */ - builder.removeInputLink(toSocket); - first->getInputSocket(0)->setResizeMode(ResizeMode::None); - toSocket->setResizeMode(ResizeMode::None); - builder.addLink(fromSocket, first->getInputSocket(0)); - builder.addLink(translateOperation->getOutputSocket(), toSocket); + if (doScale) { + translateOperation->getInputSocket(0)->setResizeMode(ResizeMode::None); + builder.addLink(scaleOperation->getOutputSocket(), translateOperation->getInputSocket(0)); } + + /* remove previous link and replace */ + builder.removeInputLink(toSocket); + first->getInputSocket(0)->setResizeMode(ResizeMode::None); + toSocket->setResizeMode(ResizeMode::None); + builder.addLink(fromSocket, first->getInputSocket(0)); + builder.addLink(translateOperation->getOutputSocket(), toSocket); } } // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_Converter.h b/source/blender/compositor/intern/COM_Converter.h index 28136437103..7f0402d4e70 100644 --- a/source/blender/compositor/intern/COM_Converter.h +++ b/source/blender/compositor/intern/COM_Converter.h @@ -65,8 +65,8 @@ NodeOperation *COM_convert_data_type(const NodeOperationOutput &from, * \note Conversion logic is implemented in this function. * \see InputSocketResizeMode for the possible conversions. */ -void COM_convert_resolution(NodeOperationBuilder &builder, - NodeOperationOutput *fromSocket, - NodeOperationInput *toSocket); +void COM_convert_canvas(NodeOperationBuilder &builder, + NodeOperationOutput *fromSocket, + NodeOperationInput *toSocket); } // namespace blender::compositor diff --git a/source/blender/compositor/intern/COM_Debug.cc b/source/blender/compositor/intern/COM_Debug.cc index f2dcba65b7c..9e47aa9fcb5 100644 --- a/source/blender/compositor/intern/COM_Debug.cc +++ b/source/blender/compositor/intern/COM_Debug.cc @@ -162,8 +162,10 @@ int DebugInfo::graphviz_operation(const ExecutionSystem *system, len += snprintf(str + len, maxlen > len ? maxlen - len : 0, - "#%d (%u,%u)", + "#%d (%i,%i) (%u,%u)", operation->get_id(), + operation->get_canvas().xmin, + operation->get_canvas().ymin, operation->getWidth(), operation->getHeight()); diff --git a/source/blender/compositor/intern/COM_FullFrameExecutionModel.cc b/source/blender/compositor/intern/COM_FullFrameExecutionModel.cc index bd3a481d691..c44a168390b 100644 --- a/source/blender/compositor/intern/COM_FullFrameExecutionModel.cc +++ b/source/blender/compositor/intern/COM_FullFrameExecutionModel.cc @@ -74,37 +74,61 @@ void FullFrameExecutionModel::determine_areas_to_render_and_reads() } } -Vector<MemoryBuffer *> FullFrameExecutionModel::get_input_buffers(NodeOperation *op) +/** + * Returns input buffers with an offset relative to given output coordinates. Returned memory + * buffers must be deleted. + */ +Vector<MemoryBuffer *> FullFrameExecutionModel::get_input_buffers(NodeOperation *op, + const int output_x, + const int output_y) { const int num_inputs = op->getNumberOfInputSockets(); Vector<MemoryBuffer *> inputs_buffers(num_inputs); for (int i = 0; i < num_inputs; i++) { - NodeOperation *input_op = op->get_input_operation(i); - inputs_buffers[i] = active_buffers_.get_rendered_buffer(input_op); + NodeOperation *input = op->get_input_operation(i); + const int offset_x = (input->get_canvas().xmin - op->get_canvas().xmin) + output_x; + const int offset_y = (input->get_canvas().ymin - op->get_canvas().ymin) + output_y; + MemoryBuffer *buf = active_buffers_.get_rendered_buffer(input); + + rcti rect = buf->get_rect(); + BLI_rcti_translate(&rect, offset_x, offset_y); + inputs_buffers[i] = new MemoryBuffer( + buf->getBuffer(), buf->get_num_channels(), rect, buf->is_a_single_elem()); } return inputs_buffers; } -MemoryBuffer *FullFrameExecutionModel::create_operation_buffer(NodeOperation *op) +MemoryBuffer *FullFrameExecutionModel::create_operation_buffer(NodeOperation *op, + const int output_x, + const int output_y) { - rcti op_rect; - BLI_rcti_init(&op_rect, 0, op->getWidth(), 0, op->getHeight()); + rcti rect; + BLI_rcti_init(&rect, output_x, output_x + op->getWidth(), output_y, output_y + op->getHeight()); const DataType data_type = op->getOutputSocket(0)->getDataType(); const bool is_a_single_elem = op->get_flags().is_constant_operation; - return new MemoryBuffer(data_type, op_rect, is_a_single_elem); + return new MemoryBuffer(data_type, rect, is_a_single_elem); } void FullFrameExecutionModel::render_operation(NodeOperation *op) { - Vector<MemoryBuffer *> input_bufs = get_input_buffers(op); + /* Output has no offset for easier image algorithms implementation on operations. */ + constexpr int output_x = 0; + constexpr int output_y = 0; const bool has_outputs = op->getNumberOfOutputSockets() > 0; - MemoryBuffer *op_buf = has_outputs ? create_operation_buffer(op) : nullptr; + MemoryBuffer *op_buf = has_outputs ? create_operation_buffer(op, output_x, output_y) : nullptr; if (op->getWidth() > 0 && op->getHeight() > 0) { - Span<rcti> areas = active_buffers_.get_areas_to_render(op); + Vector<MemoryBuffer *> input_bufs = get_input_buffers(op, output_x, output_y); + const int op_offset_x = output_x - op->get_canvas().xmin; + const int op_offset_y = output_y - op->get_canvas().ymin; + Span<rcti> areas = active_buffers_.get_areas_to_render(op, op_offset_x, op_offset_y); op->render(op_buf, areas, input_bufs); DebugInfo::operation_rendered(op, op_buf); + + for (MemoryBuffer *buf : input_bufs) { + delete buf; + } } /* Even if operation has no resolution set the empty buffer. It will be clipped with a * TranslateOperation from convert resolutions if linked to an operation with resolution. */ @@ -190,7 +214,8 @@ void FullFrameExecutionModel::determine_areas_to_render(NodeOperation *output_op std::pair<NodeOperation *, rcti> pair = stack.pop_last(); NodeOperation *operation = pair.first; const rcti &render_area = pair.second; - if (active_buffers_.is_area_registered(operation, render_area)) { + if (BLI_rcti_is_empty(&render_area) || + active_buffers_.is_area_registered(operation, render_area)) { continue; } @@ -199,12 +224,11 @@ void FullFrameExecutionModel::determine_areas_to_render(NodeOperation *output_op const int num_inputs = operation->getNumberOfInputSockets(); for (int i = 0; i < num_inputs; i++) { NodeOperation *input_op = operation->get_input_operation(i); - rcti input_op_rect, input_area; - BLI_rcti_init(&input_op_rect, 0, input_op->getWidth(), 0, input_op->getHeight()); + rcti input_area; operation->get_area_of_interest(input_op, render_area, input_area); /* Ensure area of interest is within operation bounds, cropping areas outside. */ - BLI_rcti_isect(&input_area, &input_op_rect, &input_area); + BLI_rcti_isect(&input_area, &input_op->get_canvas(), &input_area); stack.append({input_op, input_area}); } @@ -243,9 +267,8 @@ void FullFrameExecutionModel::get_output_render_area(NodeOperation *output_op, r BLI_assert(output_op->isOutputOperation(context_.isRendering())); /* By default return operation bounds (no border). */ - const int op_width = output_op->getWidth(); - const int op_height = output_op->getHeight(); - BLI_rcti_init(&r_area, 0, op_width, 0, op_height); + rcti canvas = output_op->get_canvas(); + r_area = canvas; const bool has_viewer_border = border_.use_viewer_border && (output_op->get_flags().is_viewer_operation || @@ -255,12 +278,13 @@ void FullFrameExecutionModel::get_output_render_area(NodeOperation *output_op, r /* Get border with normalized coordinates. */ const rctf *norm_border = has_viewer_border ? border_.viewer_border : border_.render_border; - /* Return de-normalized border. */ - BLI_rcti_init(&r_area, - norm_border->xmin * op_width, - norm_border->xmax * op_width, - norm_border->ymin * op_height, - norm_border->ymax * op_height); + /* Return de-normalized border within canvas. */ + const int w = output_op->getWidth(); + const int h = output_op->getHeight(); + r_area.xmin = canvas.xmin + norm_border->xmin * w; + r_area.xmax = canvas.xmin + norm_border->xmax * w; + r_area.ymin = canvas.ymin + norm_border->ymin * h; + r_area.ymax = canvas.ymin + norm_border->ymax * h; } } diff --git a/source/blender/compositor/intern/COM_FullFrameExecutionModel.h b/source/blender/compositor/intern/COM_FullFrameExecutionModel.h index f75d4f1afdc..66dfb8f052c 100644 --- a/source/blender/compositor/intern/COM_FullFrameExecutionModel.h +++ b/source/blender/compositor/intern/COM_FullFrameExecutionModel.h @@ -61,8 +61,10 @@ class FullFrameExecutionModel : public ExecutionModel { void determine_areas_to_render_and_reads(); void render_operations(); void render_output_dependencies(NodeOperation *output_op); - Vector<MemoryBuffer *> get_input_buffers(NodeOperation *op); - MemoryBuffer *create_operation_buffer(NodeOperation *op); + Vector<MemoryBuffer *> get_input_buffers(NodeOperation *op, + const int output_x, + const int output_y); + MemoryBuffer *create_operation_buffer(NodeOperation *op, const int output_x, const int output_y); void render_operation(NodeOperation *op); void operation_finished(NodeOperation *operation); diff --git a/source/blender/compositor/intern/COM_MemoryBuffer.cc b/source/blender/compositor/intern/COM_MemoryBuffer.cc index 5327be50b53..f57f0f055bf 100644 --- a/source/blender/compositor/intern/COM_MemoryBuffer.cc +++ b/source/blender/compositor/intern/COM_MemoryBuffer.cc @@ -122,6 +122,8 @@ void MemoryBuffer::set_strides() this->elem_stride = m_num_channels; this->row_stride = getWidth() * m_num_channels; } + to_positive_x_stride_ = m_rect.xmin < 0 ? -m_rect.xmin + 1 : (m_rect.xmin == 0 ? 1 : 0); + to_positive_y_stride_ = m_rect.ymin < 0 ? -m_rect.ymin + 1 : (m_rect.ymin == 0 ? 1 : 0); } void MemoryBuffer::clear() diff --git a/source/blender/compositor/intern/COM_MemoryBuffer.h b/source/blender/compositor/intern/COM_MemoryBuffer.h index f730d53acec..9e173f73f63 100644 --- a/source/blender/compositor/intern/COM_MemoryBuffer.h +++ b/source/blender/compositor/intern/COM_MemoryBuffer.h @@ -114,6 +114,12 @@ class MemoryBuffer { */ bool owns_data_; + /** Stride to make any x coordinate within buffer positive (non-zero). */ + int to_positive_x_stride_; + + /** Stride to make any y coordinate within buffer positive (non-zero). */ + int to_positive_y_stride_; + public: /** * \brief construct new temporarily MemoryBuffer for an area @@ -166,9 +172,9 @@ class MemoryBuffer { /** * Get offset needed to jump from buffer start to given coordinates. */ - int get_coords_offset(int x, int y) const + intptr_t get_coords_offset(int x, int y) const { - return (y - m_rect.ymin) * row_stride + (x - m_rect.xmin) * elem_stride; + return ((intptr_t)y - m_rect.ymin) * row_stride + ((intptr_t)x - m_rect.xmin) * elem_stride; } /** @@ -176,7 +182,7 @@ class MemoryBuffer { */ 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); + BLI_assert(has_coords(x, y)); return m_buffer + get_coords_offset(x, y); } @@ -185,7 +191,7 @@ class MemoryBuffer { */ 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); + BLI_assert(has_coords(x, y)); return m_buffer + get_coords_offset(x, y); } @@ -196,7 +202,7 @@ class MemoryBuffer { void read_elem_checked(int x, int y, float *out) const { - if (x < m_rect.xmin || x >= m_rect.xmax || y < m_rect.ymin || y >= m_rect.ymax) { + if (!has_coords(x, y)) { clear_elem(out); } else { @@ -206,12 +212,7 @@ class MemoryBuffer { void read_elem_checked(float x, float y, float *out) const { - if (x < m_rect.xmin || x >= m_rect.xmax || y < m_rect.ymin || y >= m_rect.ymax) { - clear_elem(out); - } - else { - read_elem(x, y, out); - } + read_elem_checked(floor_x(x), floor_y(y), out); } void read_elem_bilinear(float x, float y, float *out) const @@ -286,8 +287,7 @@ class MemoryBuffer { */ 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); + BLI_assert(has_coords(x, y) && channel >= 0 && channel < m_num_channels); return m_buffer[get_coords_offset(x, y) + channel]; } @@ -296,8 +296,7 @@ class MemoryBuffer { */ 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); + BLI_assert(has_coords(x, y) && channel >= 0 && channel < m_num_channels); return m_buffer[get_coords_offset(x, y) + channel]; } @@ -306,7 +305,7 @@ class MemoryBuffer { */ const float *get_row_end(int y) const { - BLI_assert(y >= 0 && y < getHeight()); + BLI_assert(has_y(y)); return m_buffer + (is_a_single_elem() ? m_num_channels : get_coords_offset(getWidth(), y)); } @@ -681,6 +680,34 @@ class MemoryBuffer { return y - m_rect.ymin; } + template<typename T> bool has_coords(T x, T y) const + { + return has_x(x) && has_y(y); + } + + template<typename T> bool has_x(T x) const + { + return x >= m_rect.xmin && x < m_rect.xmax; + } + + template<typename T> bool has_y(T y) const + { + return y >= m_rect.ymin && y < m_rect.ymax; + } + + /* Fast `floor(..)` functions. The caller should check result is within buffer bounds. + * It `ceil(..)` in near cases and when given coordinate + * is negative and less than buffer rect `min - 1`. */ + int floor_x(float x) const + { + return (int)(x + to_positive_x_stride_) - to_positive_x_stride_; + } + + int floor_y(float y) const + { + return (int)(y + to_positive_y_stride_) - to_positive_y_stride_; + } + void copy_single_elem_from(const MemoryBuffer *src, int channel_offset, int elem_size, diff --git a/source/blender/compositor/intern/COM_NodeOperation.cc b/source/blender/compositor/intern/COM_NodeOperation.cc index 3bbd1b22d60..a6a395261f2 100644 --- a/source/blender/compositor/intern/COM_NodeOperation.cc +++ b/source/blender/compositor/intern/COM_NodeOperation.cc @@ -35,12 +35,29 @@ namespace blender::compositor { NodeOperation::NodeOperation() { - this->m_resolutionInputSocketIndex = 0; - this->m_width = 0; - this->m_height = 0; + canvas_input_index_ = 0; + canvas_ = COM_AREA_NONE; this->m_btree = nullptr; } +/** Get constant value when operation is constant, otherwise return default_value. */ +float NodeOperation::get_constant_value_default(float default_value) +{ + BLI_assert(m_outputs.size() > 0 && getOutputSocket()->getDataType() == DataType::Value); + return *get_constant_elem_default(&default_value); +} + +/** Get constant elem when operation is constant, otherwise return default_elem. */ +const float *NodeOperation::get_constant_elem_default(const float *default_elem) +{ + BLI_assert(m_outputs.size() > 0); + if (get_flags().is_constant_operation) { + return static_cast<ConstantOperation *>(this)->get_constant_elem(); + } + + return default_elem; +} + /** * Generate a hash that identifies the operation result in the current execution. * Requires `hash_output_params` to be implemented, otherwise `std::nullopt` is returned. @@ -48,7 +65,7 @@ NodeOperation::NodeOperation() */ std::optional<NodeOperationHash> NodeOperation::generate_hash() { - params_hash_ = get_default_hash_2(m_width, m_height); + params_hash_ = get_default_hash_2(canvas_.xmin, canvas_.xmax); /* Hash subclasses params. */ is_hash_output_params_implemented_ = true; @@ -57,7 +74,11 @@ std::optional<NodeOperationHash> NodeOperation::generate_hash() return std::nullopt; } - hash_param(getOutputSocket()->getDataType()); + hash_params(canvas_.ymin, canvas_.ymax); + if (m_outputs.size() > 0) { + BLI_assert(m_outputs.size() == 1); + hash_param(this->getOutputSocket()->getDataType()); + } NodeOperationHash hash; hash.params_hash_ = params_hash_; @@ -108,48 +129,46 @@ void NodeOperation::addOutputSocket(DataType datatype) m_outputs.append(NodeOperationOutput(this, datatype)); } -void NodeOperation::determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) +void NodeOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) { - unsigned int used_resolution_index = 0; - if (m_resolutionInputSocketIndex == RESOLUTION_INPUT_ANY) { + unsigned int used_canvas_index = 0; + if (canvas_input_index_ == RESOLUTION_INPUT_ANY) { for (NodeOperationInput &input : m_inputs) { - unsigned int any_resolution[2] = {0, 0}; - input.determineResolution(any_resolution, preferredResolution); - if (any_resolution[0] * any_resolution[1] > 0) { - resolution[0] = any_resolution[0]; - resolution[1] = any_resolution[1]; + rcti any_area = COM_AREA_NONE; + const bool determined = input.determine_canvas(preferred_area, any_area); + if (determined) { + r_area = any_area; break; } - used_resolution_index += 1; + used_canvas_index += 1; } } - else if (m_resolutionInputSocketIndex < m_inputs.size()) { - NodeOperationInput &input = m_inputs[m_resolutionInputSocketIndex]; - input.determineResolution(resolution, preferredResolution); - used_resolution_index = m_resolutionInputSocketIndex; + else if (canvas_input_index_ < m_inputs.size()) { + NodeOperationInput &input = m_inputs[canvas_input_index_]; + input.determine_canvas(preferred_area, r_area); + used_canvas_index = canvas_input_index_; } - if (modify_determined_resolution_fn_) { - modify_determined_resolution_fn_(resolution); + if (modify_determined_canvas_fn_) { + modify_determined_canvas_fn_(r_area); } - unsigned int temp2[2] = {resolution[0], resolution[1]}; - unsigned int temp[2]; + rcti unused_area; + const rcti &local_preferred_area = r_area; for (unsigned int index = 0; index < m_inputs.size(); index++) { - if (index == used_resolution_index) { + if (index == used_canvas_index) { continue; } NodeOperationInput &input = m_inputs[index]; if (input.isConnected()) { - input.determineResolution(temp, temp2); + input.determine_canvas(local_preferred_area, unused_area); } } } -void NodeOperation::setResolutionInputSocketIndex(unsigned int index) +void NodeOperation::set_canvas_input_index(unsigned int index) { - this->m_resolutionInputSocketIndex = index; + this->canvas_input_index_ = index; } void NodeOperation::init_data() @@ -185,6 +204,28 @@ void NodeOperation::deinitExecution() { /* pass */ } + +void NodeOperation::set_canvas(const rcti &canvas_area) +{ + canvas_ = canvas_area; + flags.is_canvas_set = true; +} + +const rcti &NodeOperation::get_canvas() const +{ + return canvas_; +} + +/** + * Mainly used for re-determining canvas of constant operations in cases where preferred canvas + * depends on the constant element. + */ +void NodeOperation::unset_canvas() +{ + BLI_assert(m_inputs.size() == 0); + flags.is_canvas_set = false; +} + SocketReader *NodeOperation::getInputSocketReader(unsigned int inputSocketIndex) { return this->getInputSocket(inputSocketIndex)->getReader(); @@ -260,7 +301,7 @@ void NodeOperation::get_area_of_interest(const int input_idx, /* Non full-frame operations never implement this method. To ensure correctness assume * whole area is used. */ NodeOperation *input_op = getInputOperation(input_idx); - BLI_rcti_init(&r_input_area, 0, input_op->getWidth(), 0, input_op->getHeight()); + r_input_area = input_op->get_canvas(); } } @@ -420,12 +461,16 @@ SocketReader *NodeOperationInput::getReader() return nullptr; } -void NodeOperationInput::determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) +/** + * \return Whether canvas area could be determined. + */ +bool NodeOperationInput::determine_canvas(const rcti &preferred_area, rcti &r_area) { if (m_link) { - m_link->determineResolution(resolution, preferredResolution); + m_link->determine_canvas(preferred_area, r_area); + return !BLI_rcti_is_empty(&r_area); } + return false; } /****************** @@ -437,18 +482,16 @@ NodeOperationOutput::NodeOperationOutput(NodeOperation *op, DataType datatype) { } -void NodeOperationOutput::determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) +void NodeOperationOutput::determine_canvas(const rcti &preferred_area, rcti &r_area) { NodeOperation &operation = getOperation(); - if (operation.get_flags().is_resolution_set) { - resolution[0] = operation.getWidth(); - resolution[1] = operation.getHeight(); + if (operation.get_flags().is_canvas_set) { + r_area = operation.get_canvas(); } else { - operation.determineResolution(resolution, preferredResolution); - if (resolution[0] > 0 && resolution[1] > 0) { - operation.setResolution(resolution); + operation.determine_canvas(preferred_area, r_area); + if (!BLI_rcti_is_empty(&r_area)) { + operation.set_canvas(r_area); } } } @@ -470,8 +513,8 @@ std::ostream &operator<<(std::ostream &os, const NodeOperationFlags &node_operat if (node_operation_flags.use_viewer_border) { os << "view_border,"; } - if (node_operation_flags.is_resolution_set) { - os << "resolution_set,"; + if (node_operation_flags.is_canvas_set) { + os << "canvas_set,"; } if (node_operation_flags.is_set_operation) { os << "set_operation,"; diff --git a/source/blender/compositor/intern/COM_NodeOperation.h b/source/blender/compositor/intern/COM_NodeOperation.h index ef7cf319222..f507665bee3 100644 --- a/source/blender/compositor/intern/COM_NodeOperation.h +++ b/source/blender/compositor/intern/COM_NodeOperation.h @@ -62,9 +62,13 @@ enum class ResizeMode { /** \brief Center the input image to the center of the working area of the node, no resizing * occurs */ Center = NS_CR_CENTER, - /** \brief The bottom left of the input image is the bottom left of the working area of the node, - * no resizing occurs */ + /** No resizing or translation. */ None = NS_CR_NONE, + /** + * Input image is translated so that its bottom left matches the bottom left of the working area + * of the node, no resizing occurs. + */ + Align = 100, /** \brief Fit the width of the input image to the width of the working area of the node */ FitWidth = NS_CR_FIT_WIDTH, /** \brief Fit the height of the input image to the height of the working area of the node */ @@ -130,7 +134,7 @@ class NodeOperationInput { SocketReader *getReader(); - void determineResolution(unsigned int resolution[2], unsigned int preferredResolution[2]); + bool determine_canvas(const rcti &preferred_area, rcti &r_area); #ifdef WITH_CXX_GUARDEDALLOC MEM_CXX_CLASS_ALLOC_FUNCS("COM:NodeOperation") @@ -158,12 +162,7 @@ class NodeOperationOutput { return m_datatype; } - /** - * \brief determine the resolution of this data going through this socket - * \param resolution: the result of this operation - * \param preferredResolution: the preferable resolution as no resolution could be determined - */ - void determineResolution(unsigned int resolution[2], unsigned int preferredResolution[2]); + void determine_canvas(const rcti &preferred_area, rcti &r_area); #ifdef WITH_CXX_GUARDEDALLOC MEM_CXX_CLASS_ALLOC_FUNCS("COM:NodeOperation") @@ -211,9 +210,9 @@ struct NodeOperationFlags { bool use_viewer_border : 1; /** - * Is the resolution of the operation set. + * Is the canvas of the operation set. */ - bool is_resolution_set : 1; + bool is_canvas_set : 1; /** * Is this a set operation (value, color, vector). @@ -257,7 +256,7 @@ struct NodeOperationFlags { open_cl = false; use_render_border = false; use_viewer_border = false; - is_resolution_set = false; + is_canvas_set = false; is_set_operation = false; is_read_buffer_operation = false; is_write_buffer_operation = false; @@ -324,11 +323,11 @@ class NodeOperation { bool is_hash_output_params_implemented_; /** - * \brief the index of the input socket that will be used to determine the resolution + * \brief the index of the input socket that will be used to determine the canvas */ - unsigned int m_resolutionInputSocketIndex; + unsigned int canvas_input_index_; - std::function<void(unsigned int resolution[2])> modify_determined_resolution_fn_; + std::function<void(rcti &canvas)> modify_determined_canvas_fn_; /** * \brief mutex reference for very special node initializations @@ -352,15 +351,7 @@ class NodeOperation { */ eExecutionModel execution_model_; - /** - * Width of the output of this operation. - */ - unsigned int m_width; - - /** - * Height of the output of this operation. - */ - unsigned int m_height; + rcti canvas_; /** * Flags how to evaluate this operation. @@ -374,11 +365,6 @@ class NodeOperation { { } - void set_execution_model(const eExecutionModel model) - { - execution_model_ = model; - } - void set_name(const std::string name) { m_name = name; @@ -399,6 +385,9 @@ class NodeOperation { return m_id; } + float get_constant_value_default(float default_value); + const float *get_constant_elem_default(const float *default_elem); + const NodeOperationFlags get_flags() const { return flags; @@ -424,14 +413,7 @@ class NodeOperation { return getInputOperation(index); } - /** - * \brief determine the resolution of this node - * \note this method will not set the resolution, this is the responsibility of the caller - * \param resolution: the result of this operation - * \param preferredResolution: the preferable resolution as no resolution could be determined - */ - virtual void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]); + virtual void determine_canvas(const rcti &preferred_area, rcti &r_area); /** * \brief isOutputOperation determines whether this operation is an output of the @@ -453,6 +435,11 @@ class NodeOperation { return false; } + void set_execution_model(const eExecutionModel model) + { + execution_model_ = model; + } + void setbNodeTree(const bNodeTree *tree) { this->m_btree = tree; @@ -527,18 +514,9 @@ class NodeOperation { } virtual void deinitExecution(); - /** - * \brief set the resolution - * \param resolution: the resolution to set - */ - void setResolution(unsigned int resolution[2]) - { - if (!this->flags.is_resolution_set) { - this->m_width = resolution[0]; - this->m_height = resolution[1]; - this->flags.is_resolution_set = true; - } - } + void set_canvas(const rcti &canvas_area); + const rcti &get_canvas() const; + void unset_canvas(); /** * \brief is this operation the active viewer output @@ -557,18 +535,18 @@ class NodeOperation { rcti *output); /** - * \brief set the index of the input socket that will determine the resolution of this + * \brief set the index of the input socket that will determine the canvas of this * operation \param index: the index to set */ - void setResolutionInputSocketIndex(unsigned int index); + void set_canvas_input_index(unsigned int index); /** - * Set a custom function to modify determined resolution from main input just before setting it - * as preferred resolution for the other inputs. + * Set a custom function to modify determined canvas from main input just before setting it + * as preferred for the other inputs. */ - void set_determined_resolution_modifier(std::function<void(unsigned int resolution[2])> fn) + void set_determined_canvas_modifier(std::function<void(rcti &canvas)> fn) { - modify_determined_resolution_fn_ = fn; + modify_determined_canvas_fn_ = fn; } /** @@ -595,12 +573,12 @@ class NodeOperation { unsigned int getWidth() const { - return m_width; + return BLI_rcti_size_x(&get_canvas()); } unsigned int getHeight() const { - return m_height; + return BLI_rcti_size_y(&get_canvas()); } inline void readSampled(float result[4], float x, float y, PixelSampler sampler) @@ -697,16 +675,18 @@ class NodeOperation { void addInputSocket(DataType datatype, ResizeMode resize_mode = ResizeMode::Center); void addOutputSocket(DataType datatype); + /* TODO(manzanilla): to be removed with tiled implementation. */ void setWidth(unsigned int width) { - this->m_width = width; - this->flags.is_resolution_set = true; + canvas_.xmax = canvas_.xmin + width; + this->flags.is_canvas_set = true; } void setHeight(unsigned int height) { - this->m_height = height; - this->flags.is_resolution_set = true; + canvas_.ymax = canvas_.ymin + height; + this->flags.is_canvas_set = true; } + SocketReader *getInputSocketReader(unsigned int inputSocketindex); NodeOperation *getInputOperation(unsigned int inputSocketindex); diff --git a/source/blender/compositor/intern/COM_NodeOperationBuilder.cc b/source/blender/compositor/intern/COM_NodeOperationBuilder.cc index b2cd76be2c3..acb7f61f6dd 100644 --- a/source/blender/compositor/intern/COM_NodeOperationBuilder.cc +++ b/source/blender/compositor/intern/COM_NodeOperationBuilder.cc @@ -33,6 +33,7 @@ #include "COM_SetValueOperation.h" #include "COM_SetVectorOperation.h" #include "COM_SocketProxyOperation.h" +#include "COM_TranslateOperation.h" #include "COM_ViewerOperation.h" #include "COM_WriteBufferOperation.h" @@ -106,7 +107,7 @@ void NodeOperationBuilder::convertToOperations(ExecutionSystem *system) folder.fold_operations(); } - determineResolutions(); + determine_canvases(); save_graphviz("compositor_prior_merging"); merge_equal_operations(); @@ -423,41 +424,50 @@ void NodeOperationBuilder::resolve_proxies() } } -void NodeOperationBuilder::determineResolutions() +void NodeOperationBuilder::determine_canvases() { - /* determine all resolutions of the operations (Width/Height) */ + /* Determine all canvas areas of the operations. */ + const rcti &preferred_area = COM_AREA_NONE; for (NodeOperation *op : m_operations) { if (op->isOutputOperation(m_context->isRendering()) && !op->get_flags().is_preview_operation) { - unsigned int resolution[2] = {0, 0}; - unsigned int preferredResolution[2] = {0, 0}; - op->determineResolution(resolution, preferredResolution); - op->setResolution(resolution); + rcti canvas = COM_AREA_NONE; + op->determine_canvas(preferred_area, canvas); + op->set_canvas(canvas); } } for (NodeOperation *op : m_operations) { if (op->isOutputOperation(m_context->isRendering()) && op->get_flags().is_preview_operation) { - unsigned int resolution[2] = {0, 0}; - unsigned int preferredResolution[2] = {0, 0}; - op->determineResolution(resolution, preferredResolution); - op->setResolution(resolution); + rcti canvas = COM_AREA_NONE; + op->determine_canvas(preferred_area, canvas); + op->set_canvas(canvas); } } - /* add convert resolution operations when needed */ + /* Convert operation canvases when needed. */ { Vector<Link> convert_links; for (const Link &link : m_links) { if (link.to()->getResizeMode() != ResizeMode::None) { - NodeOperation &from_op = link.from()->getOperation(); - NodeOperation &to_op = link.to()->getOperation(); - if (from_op.getWidth() != to_op.getWidth() || from_op.getHeight() != to_op.getHeight()) { + const rcti &from_canvas = link.from()->getOperation().get_canvas(); + const rcti &to_canvas = link.to()->getOperation().get_canvas(); + + bool needs_conversion; + if (link.to()->getResizeMode() == ResizeMode::Align) { + needs_conversion = from_canvas.xmin != to_canvas.xmin || + from_canvas.ymin != to_canvas.ymin; + } + else { + needs_conversion = !BLI_rcti_compare(&from_canvas, &to_canvas); + } + + if (needs_conversion) { convert_links.append(link); } } } for (const Link &link : convert_links) { - COM_convert_resolution(*this, link.from(), link.to()); + COM_convert_canvas(*this, link.from(), link.to()); } } } diff --git a/source/blender/compositor/intern/COM_NodeOperationBuilder.h b/source/blender/compositor/intern/COM_NodeOperationBuilder.h index aca4d043d41..1f9c86b51cd 100644 --- a/source/blender/compositor/intern/COM_NodeOperationBuilder.h +++ b/source/blender/compositor/intern/COM_NodeOperationBuilder.h @@ -145,8 +145,8 @@ class NodeOperationBuilder { /** Replace proxy operations with direct links */ void resolve_proxies(); - /** Calculate resolution for each operation */ - void determineResolutions(); + /** Calculate canvas area for each operation. */ + void determine_canvases(); /** Helper function to store connected inputs for replacement */ Vector<NodeOperationInput *> cache_output_links(NodeOperationOutput *output) const; diff --git a/source/blender/compositor/intern/COM_SharedOperationBuffers.cc b/source/blender/compositor/intern/COM_SharedOperationBuffers.cc index 7e0486b0f54..55153bd4f0a 100644 --- a/source/blender/compositor/intern/COM_SharedOperationBuffers.cc +++ b/source/blender/compositor/intern/COM_SharedOperationBuffers.cc @@ -76,9 +76,17 @@ void SharedOperationBuffers::register_read(NodeOperation *read_op) /** * Get registered areas given operation needs to render. */ -blender::Span<rcti> SharedOperationBuffers::get_areas_to_render(NodeOperation *op) +Vector<rcti> SharedOperationBuffers::get_areas_to_render(NodeOperation *op, + const int offset_x, + const int offset_y) { - return get_buffer_data(op).render_areas.as_span(); + Span<rcti> render_areas = get_buffer_data(op).render_areas.as_span(); + Vector<rcti> dst_areas; + for (rcti dst : render_areas) { + BLI_rcti_translate(&dst, offset_x, offset_y); + dst_areas.append(std::move(dst)); + } + return dst_areas; } /** diff --git a/source/blender/compositor/intern/COM_SharedOperationBuffers.h b/source/blender/compositor/intern/COM_SharedOperationBuffers.h index f7763cd8ae4..4461ba75cbe 100644 --- a/source/blender/compositor/intern/COM_SharedOperationBuffers.h +++ b/source/blender/compositor/intern/COM_SharedOperationBuffers.h @@ -53,7 +53,7 @@ class SharedOperationBuffers { bool has_registered_reads(NodeOperation *op); void register_read(NodeOperation *read_op); - blender::Span<rcti> get_areas_to_render(NodeOperation *op); + Vector<rcti> get_areas_to_render(NodeOperation *op, int offset_x, int offset_y); bool is_operation_rendered(NodeOperation *op); void set_rendered_buffer(NodeOperation *op, std::unique_ptr<MemoryBuffer> buffer); MemoryBuffer *get_rendered_buffer(NodeOperation *op); diff --git a/source/blender/compositor/nodes/COM_AlphaOverNode.cc b/source/blender/compositor/nodes/COM_AlphaOverNode.cc index 5e09902aee2..c9038886b0d 100644 --- a/source/blender/compositor/nodes/COM_AlphaOverNode.cc +++ b/source/blender/compositor/nodes/COM_AlphaOverNode.cc @@ -51,13 +51,13 @@ void AlphaOverNode::convertToOperations(NodeConverter &converter, convertProg->setUseValueAlphaMultiply(false); if (color1Socket->isLinked()) { - convertProg->setResolutionInputSocketIndex(1); + convertProg->set_canvas_input_index(1); } else if (color2Socket->isLinked()) { - convertProg->setResolutionInputSocketIndex(2); + convertProg->set_canvas_input_index(2); } else { - convertProg->setResolutionInputSocketIndex(0); + convertProg->set_canvas_input_index(0); } converter.addOperation(convertProg); diff --git a/source/blender/compositor/nodes/COM_BoxMaskNode.cc b/source/blender/compositor/nodes/COM_BoxMaskNode.cc index 14f42cc42f7..8017e063a69 100644 --- a/source/blender/compositor/nodes/COM_BoxMaskNode.cc +++ b/source/blender/compositor/nodes/COM_BoxMaskNode.cc @@ -62,7 +62,7 @@ void BoxMaskNode::convertToOperations(NodeConverter &converter, scaleOperation->setOffset(0.0f, 0.0f); scaleOperation->setNewWidth(rd->xsch * render_size_factor); scaleOperation->setNewHeight(rd->ysch * render_size_factor); - scaleOperation->getInputSocket(0)->setResizeMode(ResizeMode::None); + scaleOperation->getInputSocket(0)->setResizeMode(ResizeMode::Align); converter.addOperation(scaleOperation); converter.addLink(valueOperation->getOutputSocket(0), scaleOperation->getInputSocket(0)); diff --git a/source/blender/compositor/nodes/COM_CombineColorNode.cc b/source/blender/compositor/nodes/COM_CombineColorNode.cc index 8a2bbba1c1e..dd68780dc19 100644 --- a/source/blender/compositor/nodes/COM_CombineColorNode.cc +++ b/source/blender/compositor/nodes/COM_CombineColorNode.cc @@ -37,16 +37,16 @@ void CombineColorNode::convertToOperations(NodeConverter &converter, CombineChannelsOperation *operation = new CombineChannelsOperation(); if (inputRSocket->isLinked()) { - operation->setResolutionInputSocketIndex(0); + operation->set_canvas_input_index(0); } else if (inputGSocket->isLinked()) { - operation->setResolutionInputSocketIndex(1); + operation->set_canvas_input_index(1); } else if (inputBSocket->isLinked()) { - operation->setResolutionInputSocketIndex(2); + operation->set_canvas_input_index(2); } else { - operation->setResolutionInputSocketIndex(3); + operation->set_canvas_input_index(3); } converter.addOperation(operation); diff --git a/source/blender/compositor/nodes/COM_EllipseMaskNode.cc b/source/blender/compositor/nodes/COM_EllipseMaskNode.cc index 3b4f5ca8c94..752597ef937 100644 --- a/source/blender/compositor/nodes/COM_EllipseMaskNode.cc +++ b/source/blender/compositor/nodes/COM_EllipseMaskNode.cc @@ -62,7 +62,7 @@ void EllipseMaskNode::convertToOperations(NodeConverter &converter, scaleOperation->setOffset(0.0f, 0.0f); scaleOperation->setNewWidth(rd->xsch * render_size_factor); scaleOperation->setNewHeight(rd->ysch * render_size_factor); - scaleOperation->getInputSocket(0)->setResizeMode(ResizeMode::None); + scaleOperation->getInputSocket(0)->setResizeMode(ResizeMode::Align); converter.addOperation(scaleOperation); converter.addLink(valueOperation->getOutputSocket(0), scaleOperation->getInputSocket(0)); diff --git a/source/blender/compositor/nodes/COM_GlareNode.cc b/source/blender/compositor/nodes/COM_GlareNode.cc index cd0b5306be1..9c26d7c86a9 100644 --- a/source/blender/compositor/nodes/COM_GlareNode.cc +++ b/source/blender/compositor/nodes/COM_GlareNode.cc @@ -66,7 +66,7 @@ void GlareNode::convertToOperations(NodeConverter &converter, mixvalueoperation->setValue(glare->mix); MixGlareOperation *mixoperation = new MixGlareOperation(); - mixoperation->setResolutionInputSocketIndex(1); + mixoperation->set_canvas_input_index(1); mixoperation->getInputSocket(2)->setResizeMode(ResizeMode::FitAny); converter.addOperation(glareoperation); diff --git a/source/blender/compositor/nodes/COM_HueSaturationValueCorrectNode.cc b/source/blender/compositor/nodes/COM_HueSaturationValueCorrectNode.cc index 5042d217f9a..e7b1664c354 100644 --- a/source/blender/compositor/nodes/COM_HueSaturationValueCorrectNode.cc +++ b/source/blender/compositor/nodes/COM_HueSaturationValueCorrectNode.cc @@ -53,7 +53,7 @@ void HueSaturationValueCorrectNode::convertToOperations( converter.addOperation(changeHSV); MixBlendOperation *blend = new MixBlendOperation(); - blend->setResolutionInputSocketIndex(1); + blend->set_canvas_input_index(1); converter.addOperation(blend); converter.mapInputSocket(colorSocket, rgbToHSV->getInputSocket(0)); diff --git a/source/blender/compositor/nodes/COM_HueSaturationValueNode.cc b/source/blender/compositor/nodes/COM_HueSaturationValueNode.cc index 54d2caa75af..29e5f39a144 100644 --- a/source/blender/compositor/nodes/COM_HueSaturationValueNode.cc +++ b/source/blender/compositor/nodes/COM_HueSaturationValueNode.cc @@ -56,7 +56,7 @@ void HueSaturationValueNode::convertToOperations(NodeConverter &converter, converter.addOperation(changeHSV); MixBlendOperation *blend = new MixBlendOperation(); - blend->setResolutionInputSocketIndex(1); + blend->set_canvas_input_index(1); converter.addOperation(blend); converter.mapInputSocket(colorSocket, rgbToHSV->getInputSocket(0)); diff --git a/source/blender/compositor/nodes/COM_MapUVNode.cc b/source/blender/compositor/nodes/COM_MapUVNode.cc index 4b7a9e8af0f..bbf9e8f3aeb 100644 --- a/source/blender/compositor/nodes/COM_MapUVNode.cc +++ b/source/blender/compositor/nodes/COM_MapUVNode.cc @@ -34,7 +34,7 @@ void MapUVNode::convertToOperations(NodeConverter &converter, MapUVOperation *operation = new MapUVOperation(); operation->setAlpha((float)node->custom1); - operation->setResolutionInputSocketIndex(1); + operation->set_canvas_input_index(1); converter.addOperation(operation); converter.mapInputSocket(getInputSocket(0), operation->getInputSocket(0)); diff --git a/source/blender/compositor/nodes/COM_ScaleNode.cc b/source/blender/compositor/nodes/COM_ScaleNode.cc index 819d2e72f30..f1f41375eba 100644 --- a/source/blender/compositor/nodes/COM_ScaleNode.cc +++ b/source/blender/compositor/nodes/COM_ScaleNode.cc @@ -52,6 +52,8 @@ void ScaleNode::convertToOperations(NodeConverter &converter, converter.mapOutputSocket(outputSocket, operation->getOutputSocket(0)); operation->setVariableSize(inputXSocket->isLinked() || inputYSocket->isLinked()); + operation->set_scale_canvas_max_size(context.get_render_size() * 1.5f); + break; } case CMP_SCALE_SCENEPERCENT: { @@ -68,6 +70,7 @@ void ScaleNode::convertToOperations(NodeConverter &converter, converter.mapOutputSocket(outputSocket, operation->getOutputSocket(0)); operation->setVariableSize(inputXSocket->isLinked() || inputYSocket->isLinked()); + operation->set_scale_canvas_max_size(context.get_render_size() * 1.5f); break; } @@ -81,13 +84,13 @@ void ScaleNode::convertToOperations(NodeConverter &converter, operation->setOffset(bnode->custom3, bnode->custom4); operation->setNewWidth(rd->xsch * render_size_factor); operation->setNewHeight(rd->ysch * render_size_factor); - operation->getInputSocket(0)->setResizeMode(ResizeMode::None); converter.addOperation(operation); converter.mapInputSocket(inputSocket, operation->getInputSocket(0)); converter.mapOutputSocket(outputSocket, operation->getOutputSocket(0)); operation->setVariableSize(inputXSocket->isLinked() || inputYSocket->isLinked()); + operation->set_scale_canvas_max_size(context.get_render_size() * 3.0f); break; } @@ -102,6 +105,7 @@ void ScaleNode::convertToOperations(NodeConverter &converter, converter.mapOutputSocket(outputSocket, operation->getOutputSocket(0)); operation->setVariableSize(inputXSocket->isLinked() || inputYSocket->isLinked()); + operation->set_scale_canvas_max_size(context.get_render_size() * 1.5f); break; } diff --git a/source/blender/compositor/nodes/COM_SetAlphaNode.cc b/source/blender/compositor/nodes/COM_SetAlphaNode.cc index dc41c126ba8..c7365b09f71 100644 --- a/source/blender/compositor/nodes/COM_SetAlphaNode.cc +++ b/source/blender/compositor/nodes/COM_SetAlphaNode.cc @@ -39,7 +39,7 @@ void SetAlphaNode::convertToOperations(NodeConverter &converter, } if (!this->getInputSocket(0)->isLinked() && this->getInputSocket(1)->isLinked()) { - operation->setResolutionInputSocketIndex(1); + operation->set_canvas_input_index(1); } converter.addOperation(operation); diff --git a/source/blender/compositor/nodes/COM_Stabilize2dNode.cc b/source/blender/compositor/nodes/COM_Stabilize2dNode.cc index 90f62c6d562..3d8f0bbda7e 100644 --- a/source/blender/compositor/nodes/COM_Stabilize2dNode.cc +++ b/source/blender/compositor/nodes/COM_Stabilize2dNode.cc @@ -123,17 +123,54 @@ void Stabilize2dNode::convertToOperations(NodeConverter &converter, break; } case eExecutionModel::FullFrame: { - TransformOperation *transform_op = new TransformOperation(); - transform_op->set_sampler(sampler); - transform_op->set_convert_rotate_degree_to_rad(false); - transform_op->set_invert(invert); - converter.addOperation(transform_op); - converter.mapInputSocket(imageInput, transform_op->getInputSocket(0)); - converter.addLink(xAttribute->getOutputSocket(), transform_op->getInputSocket(1)); - converter.addLink(yAttribute->getOutputSocket(), transform_op->getInputSocket(2)); - converter.addLink(angleAttribute->getOutputSocket(), transform_op->getInputSocket(3)); - converter.addLink(scaleAttribute->getOutputSocket(), transform_op->getInputSocket(4)); - converter.mapOutputSocket(getOutputSocket(), transform_op->getOutputSocket()); + ScaleRelativeOperation *scaleOperation = new ScaleRelativeOperation(); + scaleOperation->setSampler(sampler); + RotateOperation *rotateOperation = new RotateOperation(); + rotateOperation->setDoDegree2RadConversion(false); + rotateOperation->set_sampler(sampler); + TranslateOperation *translateOperation = new TranslateCanvasOperation(); + + converter.addOperation(scaleOperation); + converter.addOperation(translateOperation); + converter.addOperation(rotateOperation); + + converter.addLink(scaleAttribute->getOutputSocket(), scaleOperation->getInputSocket(1)); + converter.addLink(scaleAttribute->getOutputSocket(), scaleOperation->getInputSocket(2)); + + converter.addLink(angleAttribute->getOutputSocket(), rotateOperation->getInputSocket(1)); + + converter.addLink(xAttribute->getOutputSocket(), translateOperation->getInputSocket(1)); + converter.addLink(yAttribute->getOutputSocket(), translateOperation->getInputSocket(2)); + + NodeOperationInput *stabilization_socket = nullptr; + if (invert) { + /* Translate -> Rotate -> Scale. */ + stabilization_socket = translateOperation->getInputSocket(0); + converter.mapInputSocket(imageInput, translateOperation->getInputSocket(0)); + + converter.addLink(translateOperation->getOutputSocket(), + rotateOperation->getInputSocket(0)); + converter.addLink(rotateOperation->getOutputSocket(), scaleOperation->getInputSocket(0)); + + converter.mapOutputSocket(getOutputSocket(), scaleOperation->getOutputSocket()); + } + else { + /* Scale -> Rotate -> Translate. */ + stabilization_socket = scaleOperation->getInputSocket(0); + converter.mapInputSocket(imageInput, scaleOperation->getInputSocket(0)); + + converter.addLink(scaleOperation->getOutputSocket(), rotateOperation->getInputSocket(0)); + converter.addLink(rotateOperation->getOutputSocket(), + translateOperation->getInputSocket(0)); + + converter.mapOutputSocket(getOutputSocket(), translateOperation->getOutputSocket()); + } + + xAttribute->set_socket_input_resolution_for_stabilization(stabilization_socket); + yAttribute->set_socket_input_resolution_for_stabilization(stabilization_socket); + scaleAttribute->set_socket_input_resolution_for_stabilization(stabilization_socket); + angleAttribute->set_socket_input_resolution_for_stabilization(stabilization_socket); + break; } } } diff --git a/source/blender/compositor/nodes/COM_TransformNode.cc b/source/blender/compositor/nodes/COM_TransformNode.cc index d2fb7b54633..b38aad78d90 100644 --- a/source/blender/compositor/nodes/COM_TransformNode.cc +++ b/source/blender/compositor/nodes/COM_TransformNode.cc @@ -73,16 +73,33 @@ void TransformNode::convertToOperations(NodeConverter &converter, break; } case eExecutionModel::FullFrame: { - TransformOperation *op = new TransformOperation(); - op->set_sampler((PixelSampler)this->getbNode()->custom1); - converter.addOperation(op); - - converter.mapInputSocket(imageInput, op->getInputSocket(0)); - converter.mapInputSocket(xInput, op->getInputSocket(1)); - converter.mapInputSocket(yInput, op->getInputSocket(2)); - converter.mapInputSocket(angleInput, op->getInputSocket(3)); - converter.mapInputSocket(scaleInput, op->getInputSocket(4)); - converter.mapOutputSocket(getOutputSocket(), op->getOutputSocket()); + ScaleRelativeOperation *scaleOperation = new ScaleRelativeOperation(); + converter.addOperation(scaleOperation); + + RotateOperation *rotateOperation = new RotateOperation(); + rotateOperation->setDoDegree2RadConversion(false); + converter.addOperation(rotateOperation); + + TranslateOperation *translateOperation = new TranslateCanvasOperation(); + converter.addOperation(translateOperation); + + PixelSampler sampler = (PixelSampler)this->getbNode()->custom1; + scaleOperation->setSampler(sampler); + rotateOperation->set_sampler(sampler); + scaleOperation->set_scale_canvas_max_size(context.get_render_size()); + + converter.mapInputSocket(imageInput, scaleOperation->getInputSocket(0)); + converter.mapInputSocket(scaleInput, scaleOperation->getInputSocket(1)); + converter.mapInputSocket(scaleInput, scaleOperation->getInputSocket(2)); // xscale = yscale + + converter.addLink(scaleOperation->getOutputSocket(), rotateOperation->getInputSocket(0)); + converter.mapInputSocket(angleInput, rotateOperation->getInputSocket(1)); + + converter.addLink(rotateOperation->getOutputSocket(), translateOperation->getInputSocket(0)); + converter.mapInputSocket(xInput, translateOperation->getInputSocket(1)); + converter.mapInputSocket(yInput, translateOperation->getInputSocket(2)); + + converter.mapOutputSocket(getOutputSocket(), translateOperation->getOutputSocket()); break; } } diff --git a/source/blender/compositor/nodes/COM_TranslateNode.cc b/source/blender/compositor/nodes/COM_TranslateNode.cc index 3a3e98c3472..165a03baf41 100644 --- a/source/blender/compositor/nodes/COM_TranslateNode.cc +++ b/source/blender/compositor/nodes/COM_TranslateNode.cc @@ -41,7 +41,9 @@ void TranslateNode::convertToOperations(NodeConverter &converter, NodeInput *inputYSocket = this->getInputSocket(2); NodeOutput *outputSocket = this->getOutputSocket(0); - TranslateOperation *operation = new TranslateOperation(); + TranslateOperation *operation = context.get_execution_model() == eExecutionModel::Tiled ? + new TranslateOperation() : + new TranslateCanvasOperation(); operation->set_wrapping(data->wrap_axis); if (data->relative) { const RenderData *rd = context.getRenderData(); diff --git a/source/blender/compositor/nodes/COM_ViewerNode.cc b/source/blender/compositor/nodes/COM_ViewerNode.cc index 3833a8d7ca8..4dbcdbe9e40 100644 --- a/source/blender/compositor/nodes/COM_ViewerNode.cc +++ b/source/blender/compositor/nodes/COM_ViewerNode.cc @@ -60,10 +60,10 @@ void ViewerNode::convertToOperations(NodeConverter &converter, viewerOperation->setViewSettings(context.getViewSettings()); viewerOperation->setDisplaySettings(context.getDisplaySettings()); - viewerOperation->setResolutionInputSocketIndex(0); + viewerOperation->set_canvas_input_index(0); if (!imageSocket->isLinked()) { if (alphaSocket->isLinked()) { - viewerOperation->setResolutionInputSocketIndex(1); + viewerOperation->set_canvas_input_index(1); } } diff --git a/source/blender/compositor/nodes/COM_ZCombineNode.cc b/source/blender/compositor/nodes/COM_ZCombineNode.cc index e29748dc317..9753e812a8b 100644 --- a/source/blender/compositor/nodes/COM_ZCombineNode.cc +++ b/source/blender/compositor/nodes/COM_ZCombineNode.cc @@ -64,18 +64,14 @@ void ZCombineNode::convertToOperations(NodeConverter &converter, NodeOperation *maskoperation; if (this->getbNode()->custom1) { maskoperation = new MathGreaterThanOperation(); - converter.addOperation(maskoperation); - - converter.mapInputSocket(getInputSocket(1), maskoperation->getInputSocket(0)); - converter.mapInputSocket(getInputSocket(3), maskoperation->getInputSocket(1)); } else { maskoperation = new MathLessThanOperation(); - converter.addOperation(maskoperation); - - converter.mapInputSocket(getInputSocket(1), maskoperation->getInputSocket(0)); - converter.mapInputSocket(getInputSocket(3), maskoperation->getInputSocket(1)); } + converter.addOperation(maskoperation); + + converter.mapInputSocket(getInputSocket(1), maskoperation->getInputSocket(0)); + converter.mapInputSocket(getInputSocket(3), maskoperation->getInputSocket(1)); /* Step 2 anti alias mask bit of an expensive operation, but does the trick. */ AntiAliasOperation *antialiasoperation = new AntiAliasOperation(); diff --git a/source/blender/compositor/operations/COM_BlurBaseOperation.cc b/source/blender/compositor/operations/COM_BlurBaseOperation.cc index 058b422c4a5..412632e2e22 100644 --- a/source/blender/compositor/operations/COM_BlurBaseOperation.cc +++ b/source/blender/compositor/operations/COM_BlurBaseOperation.cc @@ -212,31 +212,30 @@ void BlurBaseOperation::updateSize() this->m_sizeavailable = true; } -void BlurBaseOperation::determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) +void BlurBaseOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) { if (!m_extend_bounds) { - NodeOperation::determineResolution(resolution, preferredResolution); + NodeOperation::determine_canvas(preferred_area, r_area); return; } switch (execution_model_) { case eExecutionModel::Tiled: { - NodeOperation::determineResolution(resolution, preferredResolution); - resolution[0] += 2 * m_size * m_data.sizex; - resolution[1] += 2 * m_size * m_data.sizey; + NodeOperation::determine_canvas(preferred_area, r_area); + r_area.xmax += 2 * m_size * m_data.sizex; + r_area.ymax += 2 * m_size * m_data.sizey; break; } case eExecutionModel::FullFrame: { /* Setting a modifier ensures all non main inputs have extended bounds as preferred - * resolution, avoiding unnecessary resolution conversions that would hide constant + * canvas, avoiding unnecessary canvas conversions that would hide constant * operations. */ - set_determined_resolution_modifier([=](unsigned int res[2]) { + set_determined_canvas_modifier([=](rcti &canvas) { /* Rounding to even prevents jiggling in backdrop while switching size values. */ - res[0] += round_to_even(2 * m_size * m_data.sizex); - res[1] += round_to_even(2 * m_size * m_data.sizey); + canvas.xmax += round_to_even(2 * m_size * m_data.sizex); + canvas.ymax += round_to_even(2 * m_size * m_data.sizey); }); - NodeOperation::determineResolution(resolution, preferredResolution); + NodeOperation::determine_canvas(preferred_area, r_area); break; } } @@ -251,7 +250,7 @@ void BlurBaseOperation::get_area_of_interest(const int input_idx, r_input_area = output_area; break; case 1: - r_input_area = use_variable_size_ ? output_area : COM_SINGLE_ELEM_AREA; + r_input_area = use_variable_size_ ? output_area : COM_CONSTANT_INPUT_AREA_OF_INTEREST; break; } } diff --git a/source/blender/compositor/operations/COM_BlurBaseOperation.h b/source/blender/compositor/operations/COM_BlurBaseOperation.h index 78b1e919aa6..9ab9bf5a173 100644 --- a/source/blender/compositor/operations/COM_BlurBaseOperation.h +++ b/source/blender/compositor/operations/COM_BlurBaseOperation.h @@ -85,8 +85,7 @@ class BlurBaseOperation : public MultiThreadedOperation, public QualityStepHelpe int get_blur_size(eDimension dim) const; - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; virtual void get_area_of_interest(int input_idx, const rcti &output_area, diff --git a/source/blender/compositor/operations/COM_BokehBlurOperation.cc b/source/blender/compositor/operations/COM_BokehBlurOperation.cc index f2a43b7dbca..93482dd2a54 100644 --- a/source/blender/compositor/operations/COM_BokehBlurOperation.cc +++ b/source/blender/compositor/operations/COM_BokehBlurOperation.cc @@ -34,7 +34,7 @@ constexpr int SIZE_INPUT_INDEX = 3; BokehBlurOperation::BokehBlurOperation() { this->addInputSocket(DataType::Color); - this->addInputSocket(DataType::Color, ResizeMode::None); + this->addInputSocket(DataType::Color, ResizeMode::Align); this->addInputSocket(DataType::Value); this->addInputSocket(DataType::Value); this->addOutputSocket(DataType::Color); @@ -266,31 +266,30 @@ void BokehBlurOperation::updateSize() this->m_sizeavailable = true; } -void BokehBlurOperation::determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) +void BokehBlurOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) { if (!m_extend_bounds) { - NodeOperation::determineResolution(resolution, preferredResolution); + NodeOperation::determine_canvas(preferred_area, r_area); return; } switch (execution_model_) { case eExecutionModel::Tiled: { - NodeOperation::determineResolution(resolution, preferredResolution); - const float max_dim = MAX2(resolution[0], resolution[1]); - resolution[0] += 2 * this->m_size * max_dim / 100.0f; - resolution[1] += 2 * this->m_size * max_dim / 100.0f; + NodeOperation::determine_canvas(preferred_area, r_area); + const float max_dim = MAX2(BLI_rcti_size_x(&r_area), BLI_rcti_size_y(&r_area)); + r_area.xmax += 2 * this->m_size * max_dim / 100.0f; + r_area.ymax += 2 * this->m_size * max_dim / 100.0f; break; } case eExecutionModel::FullFrame: { - set_determined_resolution_modifier([=](unsigned int res[2]) { - const float max_dim = MAX2(res[0], res[1]); + set_determined_canvas_modifier([=](rcti &canvas) { + const float max_dim = MAX2(BLI_rcti_size_x(&canvas), BLI_rcti_size_y(&canvas)); /* Rounding to even prevents image jiggling in backdrop while switching size values. */ float add_size = round_to_even(2 * this->m_size * max_dim / 100.0f); - res[0] += add_size; - res[1] += add_size; + canvas.xmax += add_size; + canvas.ymax += add_size; }); - NodeOperation::determineResolution(resolution, preferredResolution); + NodeOperation::determine_canvas(preferred_area, r_area); break; } } @@ -312,17 +311,14 @@ void BokehBlurOperation::get_area_of_interest(const int input_idx, } case BOKEH_INPUT_INDEX: { NodeOperation *bokeh_input = getInputOperation(BOKEH_INPUT_INDEX); - r_input_area.xmin = 0; - r_input_area.xmax = bokeh_input->getWidth(); - r_input_area.ymin = 0; - r_input_area.ymax = bokeh_input->getHeight(); + r_input_area = bokeh_input->get_canvas(); break; } case BOUNDING_BOX_INPUT_INDEX: r_input_area = output_area; break; case SIZE_INPUT_INDEX: { - r_input_area = COM_SINGLE_ELEM_AREA; + r_input_area = COM_CONSTANT_INPUT_AREA_OF_INTEREST; break; } } diff --git a/source/blender/compositor/operations/COM_BokehBlurOperation.h b/source/blender/compositor/operations/COM_BokehBlurOperation.h index 59c14305393..84f5a8293ba 100644 --- a/source/blender/compositor/operations/COM_BokehBlurOperation.h +++ b/source/blender/compositor/operations/COM_BokehBlurOperation.h @@ -80,8 +80,7 @@ class BokehBlurOperation : public MultiThreadedOperation, public QualityStepHelp this->m_extend_bounds = extend_bounds; } - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) override; void update_memory_buffer_partial(MemoryBuffer *output, diff --git a/source/blender/compositor/operations/COM_BokehImageOperation.cc b/source/blender/compositor/operations/COM_BokehImageOperation.cc index bd5b25b5af8..5c9c8b36ee0 100644 --- a/source/blender/compositor/operations/COM_BokehImageOperation.cc +++ b/source/blender/compositor/operations/COM_BokehImageOperation.cc @@ -145,11 +145,13 @@ void BokehImageOperation::deinitExecution() } } -void BokehImageOperation::determineResolution(unsigned int resolution[2], - unsigned int /*preferredResolution*/[2]) +void BokehImageOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) { - resolution[0] = COM_BLUR_BOKEH_PIXELS; - resolution[1] = COM_BLUR_BOKEH_PIXELS; + BLI_rcti_init(&r_area, + preferred_area.xmin, + preferred_area.xmin + COM_BLUR_BOKEH_PIXELS, + preferred_area.ymin, + preferred_area.ymin + COM_BLUR_BOKEH_PIXELS); } } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_BokehImageOperation.h b/source/blender/compositor/operations/COM_BokehImageOperation.h index 2527233fabd..f7fec5d71a5 100644 --- a/source/blender/compositor/operations/COM_BokehImageOperation.h +++ b/source/blender/compositor/operations/COM_BokehImageOperation.h @@ -128,8 +128,7 @@ class BokehImageOperation : public MultiThreadedOperation { * \brief determine the resolution of this operation. currently fixed at [COM_BLUR_BOKEH_PIXELS, * COM_BLUR_BOKEH_PIXELS] \param resolution: \param preferredResolution: */ - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; /** * \brief set the node data diff --git a/source/blender/compositor/operations/COM_CalculateMeanOperation.cc b/source/blender/compositor/operations/COM_CalculateMeanOperation.cc index 7457ac9e227..a573a9d7eed 100644 --- a/source/blender/compositor/operations/COM_CalculateMeanOperation.cc +++ b/source/blender/compositor/operations/COM_CalculateMeanOperation.cc @@ -27,7 +27,7 @@ namespace blender::compositor { CalculateMeanOperation::CalculateMeanOperation() { - this->addInputSocket(DataType::Color, ResizeMode::None); + this->addInputSocket(DataType::Color, ResizeMode::Align); this->addOutputSocket(DataType::Value); this->m_imageReader = nullptr; this->m_iscalculated = false; @@ -165,11 +165,7 @@ void CalculateMeanOperation::get_area_of_interest(int input_idx, rcti &r_input_area) { BLI_assert(input_idx == 0); - NodeOperation *operation = getInputOperation(input_idx); - r_input_area.xmin = 0; - r_input_area.ymin = 0; - r_input_area.xmax = operation->getWidth(); - r_input_area.ymax = operation->getHeight(); + r_input_area = get_input_operation(input_idx)->get_canvas(); } void CalculateMeanOperation::update_memory_buffer_started(MemoryBuffer *UNUSED(output), diff --git a/source/blender/compositor/operations/COM_ColorBalanceASCCDLOperation.cc b/source/blender/compositor/operations/COM_ColorBalanceASCCDLOperation.cc index aee8c0d52e8..0b6590ae4c7 100644 --- a/source/blender/compositor/operations/COM_ColorBalanceASCCDLOperation.cc +++ b/source/blender/compositor/operations/COM_ColorBalanceASCCDLOperation.cc @@ -40,7 +40,7 @@ ColorBalanceASCCDLOperation::ColorBalanceASCCDLOperation() this->addOutputSocket(DataType::Color); this->m_inputValueOperation = nullptr; this->m_inputColorOperation = nullptr; - this->setResolutionInputSocketIndex(1); + this->set_canvas_input_index(1); flags.can_be_constant = true; } diff --git a/source/blender/compositor/operations/COM_ColorBalanceLGGOperation.cc b/source/blender/compositor/operations/COM_ColorBalanceLGGOperation.cc index 674cb79a238..c658ecd6394 100644 --- a/source/blender/compositor/operations/COM_ColorBalanceLGGOperation.cc +++ b/source/blender/compositor/operations/COM_ColorBalanceLGGOperation.cc @@ -45,7 +45,7 @@ ColorBalanceLGGOperation::ColorBalanceLGGOperation() this->addOutputSocket(DataType::Color); this->m_inputValueOperation = nullptr; this->m_inputColorOperation = nullptr; - this->setResolutionInputSocketIndex(1); + this->set_canvas_input_index(1); flags.can_be_constant = true; } diff --git a/source/blender/compositor/operations/COM_ColorCurveOperation.cc b/source/blender/compositor/operations/COM_ColorCurveOperation.cc index 646238460ba..364b310945e 100644 --- a/source/blender/compositor/operations/COM_ColorCurveOperation.cc +++ b/source/blender/compositor/operations/COM_ColorCurveOperation.cc @@ -37,7 +37,7 @@ ColorCurveOperation::ColorCurveOperation() this->m_inputBlackProgram = nullptr; this->m_inputWhiteProgram = nullptr; - this->setResolutionInputSocketIndex(1); + this->set_canvas_input_index(1); } void ColorCurveOperation::initExecution() { @@ -139,7 +139,7 @@ ConstantLevelColorCurveOperation::ConstantLevelColorCurveOperation() this->m_inputFacProgram = nullptr; this->m_inputImageProgram = nullptr; - this->setResolutionInputSocketIndex(1); + this->set_canvas_input_index(1); } void ConstantLevelColorCurveOperation::initExecution() { diff --git a/source/blender/compositor/operations/COM_CompositorOperation.cc b/source/blender/compositor/operations/COM_CompositorOperation.cc index fb9e2e43c60..f7466b5db34 100644 --- a/source/blender/compositor/operations/COM_CompositorOperation.cc +++ b/source/blender/compositor/operations/COM_CompositorOperation.cc @@ -236,8 +236,7 @@ void CompositorOperation::update_memory_buffer_partial(MemoryBuffer *UNUSED(outp depth_buf.copy_from(inputs[2], area); } -void CompositorOperation::determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) +void CompositorOperation::determine_canvas(const rcti &UNUSED(preferred_area), rcti &r_area) { int width = this->m_rd->xsch * this->m_rd->size / 100; int height = this->m_rd->ysch * this->m_rd->size / 100; @@ -254,13 +253,19 @@ void CompositorOperation::determineResolution(unsigned int resolution[2], RE_ReleaseResult(re); } - preferredResolution[0] = width; - preferredResolution[1] = height; - - NodeOperation::determineResolution(resolution, preferredResolution); - - resolution[0] = width; - resolution[1] = height; + rcti local_preferred; + BLI_rcti_init(&local_preferred, 0, width, 0, height); + + switch (execution_model_) { + case eExecutionModel::Tiled: + NodeOperation::determine_canvas(local_preferred, r_area); + r_area = local_preferred; + break; + case eExecutionModel::FullFrame: + set_determined_canvas_modifier([&](rcti &canvas) { canvas = local_preferred; }); + NodeOperation::determine_canvas(local_preferred, r_area); + break; + } } } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_CompositorOperation.h b/source/blender/compositor/operations/COM_CompositorOperation.h index 66367ec8bae..6eb96e01b47 100644 --- a/source/blender/compositor/operations/COM_CompositorOperation.h +++ b/source/blender/compositor/operations/COM_CompositorOperation.h @@ -115,8 +115,7 @@ class CompositorOperation : public MultiThreadedOperation { { return eCompositorPriority::Medium; } - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; void setUseAlphaInput(bool value) { this->m_useAlphaInput = value; diff --git a/source/blender/compositor/operations/COM_ConstantOperation.cc b/source/blender/compositor/operations/COM_ConstantOperation.cc index 33d51cca432..c127860a89c 100644 --- a/source/blender/compositor/operations/COM_ConstantOperation.cc +++ b/source/blender/compositor/operations/COM_ConstantOperation.cc @@ -22,14 +22,14 @@ namespace blender::compositor { ConstantOperation::ConstantOperation() { - needs_resolution_to_get_constant_ = false; + needs_canvas_to_get_constant_ = false; flags.is_constant_operation = true; flags.is_fullframe_operation = true; } bool ConstantOperation::can_get_constant_elem() const { - return !needs_resolution_to_get_constant_ || this->flags.is_resolution_set; + return !needs_canvas_to_get_constant_ || this->flags.is_canvas_set; } void ConstantOperation::update_memory_buffer(MemoryBuffer *output, diff --git a/source/blender/compositor/operations/COM_ConstantOperation.h b/source/blender/compositor/operations/COM_ConstantOperation.h index 31b8d30254b..d44d1939424 100644 --- a/source/blender/compositor/operations/COM_ConstantOperation.h +++ b/source/blender/compositor/operations/COM_ConstantOperation.h @@ -31,7 +31,7 @@ namespace blender::compositor { */ class ConstantOperation : public NodeOperation { protected: - bool needs_resolution_to_get_constant_; + bool needs_canvas_to_get_constant_; public: ConstantOperation(); diff --git a/source/blender/compositor/operations/COM_ConvertOperation.cc b/source/blender/compositor/operations/COM_ConvertOperation.cc index 9a3733dda5b..7b2721ebbb2 100644 --- a/source/blender/compositor/operations/COM_ConvertOperation.cc +++ b/source/blender/compositor/operations/COM_ConvertOperation.cc @@ -587,7 +587,7 @@ CombineChannelsOperation::CombineChannelsOperation() this->addInputSocket(DataType::Value); this->addInputSocket(DataType::Value); this->addOutputSocket(DataType::Color); - this->setResolutionInputSocketIndex(0); + this->set_canvas_input_index(0); this->m_inputChannel1Operation = nullptr; this->m_inputChannel2Operation = nullptr; this->m_inputChannel3Operation = nullptr; diff --git a/source/blender/compositor/operations/COM_ConvolutionFilterOperation.cc b/source/blender/compositor/operations/COM_ConvolutionFilterOperation.cc index 11a077229fd..807223fd45f 100644 --- a/source/blender/compositor/operations/COM_ConvolutionFilterOperation.cc +++ b/source/blender/compositor/operations/COM_ConvolutionFilterOperation.cc @@ -29,7 +29,7 @@ ConvolutionFilterOperation::ConvolutionFilterOperation() this->addInputSocket(DataType::Color); this->addInputSocket(DataType::Value); this->addOutputSocket(DataType::Color); - this->setResolutionInputSocketIndex(0); + this->set_canvas_input_index(0); this->m_inputOperation = nullptr; this->flags.complex = true; } diff --git a/source/blender/compositor/operations/COM_CropOperation.cc b/source/blender/compositor/operations/COM_CropOperation.cc index 12833660fcb..6ac30c22ad1 100644 --- a/source/blender/compositor/operations/COM_CropOperation.cc +++ b/source/blender/compositor/operations/COM_CropOperation.cc @@ -23,7 +23,7 @@ namespace blender::compositor { CropBaseOperation::CropBaseOperation() { - this->addInputSocket(DataType::Color, ResizeMode::None); + this->addInputSocket(DataType::Color, ResizeMode::Align); this->addOutputSocket(DataType::Color); this->m_inputOperation = nullptr; this->m_settings = nullptr; @@ -142,13 +142,12 @@ void CropImageOperation::get_area_of_interest(const int input_idx, r_input_area.ymin = output_area.ymin + this->m_ymin; } -void CropImageOperation::determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) +void CropImageOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) { - NodeOperation::determineResolution(resolution, preferredResolution); + NodeOperation::determine_canvas(preferred_area, r_area); updateArea(); - resolution[0] = this->m_xmax - this->m_xmin; - resolution[1] = this->m_ymax - this->m_ymin; + r_area.xmax = r_area.xmin + (m_xmax - m_xmin); + r_area.ymax = r_area.ymin + (m_ymax - m_ymin); } void CropImageOperation::executePixelSampled(float output[4], diff --git a/source/blender/compositor/operations/COM_CropOperation.h b/source/blender/compositor/operations/COM_CropOperation.h index 57caa4e5834..a156727402b 100644 --- a/source/blender/compositor/operations/COM_CropOperation.h +++ b/source/blender/compositor/operations/COM_CropOperation.h @@ -66,8 +66,7 @@ class CropImageOperation : public CropBaseOperation { bool determineDependingAreaOfInterest(rcti *input, ReadBufferOperation *readOperation, rcti *output) override; - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; void executePixelSampled(float output[4], float x, float y, PixelSampler sampler) override; void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) override; diff --git a/source/blender/compositor/operations/COM_DenoiseOperation.cc b/source/blender/compositor/operations/COM_DenoiseOperation.cc index 0c660e0b723..f8a575acc3a 100644 --- a/source/blender/compositor/operations/COM_DenoiseOperation.cc +++ b/source/blender/compositor/operations/COM_DenoiseOperation.cc @@ -153,10 +153,7 @@ void DenoiseBaseOperation::get_area_of_interest(const int UNUSED(input_idx), const rcti &UNUSED(output_area), rcti &r_input_area) { - r_input_area.xmin = 0; - r_input_area.xmax = this->getWidth(); - r_input_area.ymin = 0; - r_input_area.ymax = this->getHeight(); + r_input_area = this->get_canvas(); } DenoiseOperation::DenoiseOperation() diff --git a/source/blender/compositor/operations/COM_DespeckleOperation.cc b/source/blender/compositor/operations/COM_DespeckleOperation.cc index 19bd7b2af6f..df637ee6709 100644 --- a/source/blender/compositor/operations/COM_DespeckleOperation.cc +++ b/source/blender/compositor/operations/COM_DespeckleOperation.cc @@ -29,7 +29,7 @@ DespeckleOperation::DespeckleOperation() this->addInputSocket(DataType::Color); this->addInputSocket(DataType::Value); this->addOutputSocket(DataType::Color); - this->setResolutionInputSocketIndex(0); + this->set_canvas_input_index(0); this->m_inputOperation = nullptr; this->flags.complex = true; } diff --git a/source/blender/compositor/operations/COM_DilateErodeOperation.cc b/source/blender/compositor/operations/COM_DilateErodeOperation.cc index 28b40021cd9..b7fd714ba5b 100644 --- a/source/blender/compositor/operations/COM_DilateErodeOperation.cc +++ b/source/blender/compositor/operations/COM_DilateErodeOperation.cc @@ -783,7 +783,8 @@ static void step_update_memory_buffer(MemoryBuffer *output, start = half_window + (i - 1) * window + 1; for (int y = -MIN2(0, start); y < window - MAX2(0, start + window - bheight); y++) { - result.get_value(x, y + start + area.ymin, 0) = selector(temp[y], temp[y + window - 1]); + result.get_value(x + area.xmin, y + start + area.ymin, 0) = selector(temp[y], + temp[y + window - 1]); } } } diff --git a/source/blender/compositor/operations/COM_DirectionalBlurOperation.cc b/source/blender/compositor/operations/COM_DirectionalBlurOperation.cc index 102025ed915..e69124205d0 100644 --- a/source/blender/compositor/operations/COM_DirectionalBlurOperation.cc +++ b/source/blender/compositor/operations/COM_DirectionalBlurOperation.cc @@ -152,10 +152,7 @@ void DirectionalBlurOperation::get_area_of_interest(const int input_idx, { BLI_assert(input_idx == 0); UNUSED_VARS_NDEBUG(input_idx); - r_input_area.xmin = 0; - r_input_area.xmax = this->getWidth(); - r_input_area.ymin = 0; - r_input_area.ymax = this->getHeight(); + r_input_area = this->get_canvas(); } void DirectionalBlurOperation::update_memory_buffer_partial(MemoryBuffer *output, diff --git a/source/blender/compositor/operations/COM_DisplaceOperation.cc b/source/blender/compositor/operations/COM_DisplaceOperation.cc index a4c01fda7ca..d08ff60d5d0 100644 --- a/source/blender/compositor/operations/COM_DisplaceOperation.cc +++ b/source/blender/compositor/operations/COM_DisplaceOperation.cc @@ -211,10 +211,7 @@ void DisplaceOperation::get_area_of_interest(const int input_idx, { switch (input_idx) { case 0: { - r_input_area.xmin = 0; - r_input_area.ymin = 0; - r_input_area.xmax = getInputOperation(input_idx)->getWidth(); - r_input_area.ymax = getInputOperation(input_idx)->getHeight(); + r_input_area = getInputOperation(input_idx)->get_canvas(); break; } case 1: { diff --git a/source/blender/compositor/operations/COM_DisplaceSimpleOperation.cc b/source/blender/compositor/operations/COM_DisplaceSimpleOperation.cc index e1c531bd49e..712b61be805 100644 --- a/source/blender/compositor/operations/COM_DisplaceSimpleOperation.cc +++ b/source/blender/compositor/operations/COM_DisplaceSimpleOperation.cc @@ -138,10 +138,7 @@ void DisplaceSimpleOperation::get_area_of_interest(const int input_idx, { switch (input_idx) { case 0: { - r_input_area.xmin = 0; - r_input_area.ymin = 0; - r_input_area.xmax = getInputOperation(input_idx)->getWidth(); - r_input_area.ymax = getInputOperation(input_idx)->getHeight(); + r_input_area = get_input_operation(input_idx)->get_canvas(); break; } default: { diff --git a/source/blender/compositor/operations/COM_DotproductOperation.cc b/source/blender/compositor/operations/COM_DotproductOperation.cc index 875b161e208..aa18ff1e827 100644 --- a/source/blender/compositor/operations/COM_DotproductOperation.cc +++ b/source/blender/compositor/operations/COM_DotproductOperation.cc @@ -25,7 +25,7 @@ DotproductOperation::DotproductOperation() this->addInputSocket(DataType::Vector); this->addInputSocket(DataType::Vector); this->addOutputSocket(DataType::Value); - this->setResolutionInputSocketIndex(0); + this->set_canvas_input_index(0); this->m_input1Operation = nullptr; this->m_input2Operation = nullptr; flags.can_be_constant = true; diff --git a/source/blender/compositor/operations/COM_DoubleEdgeMaskOperation.cc b/source/blender/compositor/operations/COM_DoubleEdgeMaskOperation.cc index 55ae19ad194..d112334b749 100644 --- a/source/blender/compositor/operations/COM_DoubleEdgeMaskOperation.cc +++ b/source/blender/compositor/operations/COM_DoubleEdgeMaskOperation.cc @@ -1399,10 +1399,7 @@ void DoubleEdgeMaskOperation::get_area_of_interest(int UNUSED(input_idx), const rcti &UNUSED(output_area), rcti &r_input_area) { - r_input_area.xmax = this->getWidth(); - r_input_area.xmin = 0; - r_input_area.ymax = this->getHeight(); - r_input_area.ymin = 0; + r_input_area = this->get_canvas(); } void DoubleEdgeMaskOperation::update_memory_buffer(MemoryBuffer *output, diff --git a/source/blender/compositor/operations/COM_EllipseMaskOperation.cc b/source/blender/compositor/operations/COM_EllipseMaskOperation.cc index eb1fd98a590..bf6eee6d3f9 100644 --- a/source/blender/compositor/operations/COM_EllipseMaskOperation.cc +++ b/source/blender/compositor/operations/COM_EllipseMaskOperation.cc @@ -162,13 +162,13 @@ void EllipseMaskOperation::apply_mask(MemoryBuffer *output, const float half_h = this->m_data->height / 2.0f; const float tx = half_w * half_w; const float ty = half_h * half_h; - for (const int y : YRange(area)) { + for (int y = area.ymin; y < area.ymax; y++) { const float op_ry = y / op_h; const float dy = (op_ry - this->m_data->y) / m_aspectRatio; float *out = output->get_elem(area.xmin, y); const float *mask = input_mask->get_elem(area.xmin, y); const float *value = input_value->get_elem(area.xmin, y); - for (const int x : XRange(area)) { + for (int x = area.xmin; x < area.xmax; x++) { const float op_rx = x / op_w; const float dx = op_rx - this->m_data->x; const float rx = this->m_data->x + (m_cosine * dx + m_sine * dy); diff --git a/source/blender/compositor/operations/COM_FastGaussianBlurOperation.cc b/source/blender/compositor/operations/COM_FastGaussianBlurOperation.cc index e0fc45811cb..f45b77c6ebc 100644 --- a/source/blender/compositor/operations/COM_FastGaussianBlurOperation.cc +++ b/source/blender/compositor/operations/COM_FastGaussianBlurOperation.cc @@ -271,10 +271,7 @@ void FastGaussianBlurOperation::get_area_of_interest(const int input_idx, { switch (input_idx) { case IMAGE_INPUT_INDEX: - r_input_area.xmin = 0; - r_input_area.xmax = getWidth(); - r_input_area.ymin = 0; - r_input_area.ymax = getHeight(); + r_input_area = this->get_canvas(); break; default: BlurBaseOperation::get_area_of_interest(input_idx, output_area, r_input_area); @@ -411,10 +408,7 @@ void FastGaussianBlurValueOperation::get_area_of_interest(const int UNUSED(input const rcti &UNUSED(output_area), rcti &r_input_area) { - r_input_area.xmin = 0; - r_input_area.xmax = getWidth(); - r_input_area.ymin = 0; - r_input_area.ymax = getHeight(); + r_input_area = this->get_canvas(); } void FastGaussianBlurValueOperation::update_memory_buffer_started(MemoryBuffer *UNUSED(output), diff --git a/source/blender/compositor/operations/COM_FlipOperation.cc b/source/blender/compositor/operations/COM_FlipOperation.cc index d0dc6c0b570..2d8865e41e0 100644 --- a/source/blender/compositor/operations/COM_FlipOperation.cc +++ b/source/blender/compositor/operations/COM_FlipOperation.cc @@ -22,9 +22,9 @@ namespace blender::compositor { FlipOperation::FlipOperation() { - this->addInputSocket(DataType::Color); + this->addInputSocket(DataType::Color, ResizeMode::None); this->addOutputSocket(DataType::Color); - this->setResolutionInputSocketIndex(0); + this->set_canvas_input_index(0); this->m_inputOperation = nullptr; this->m_flipX = true; this->m_flipY = false; @@ -75,6 +75,24 @@ bool FlipOperation::determineDependingAreaOfInterest(rcti *input, return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output); } +void FlipOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) +{ + NodeOperation::determine_canvas(preferred_area, r_area); + if (execution_model_ == eExecutionModel::FullFrame) { + rcti input_area = r_area; + if (m_flipX) { + const int width = BLI_rcti_size_x(&input_area) - 1; + r_area.xmax = (width - input_area.xmin) + 1; + r_area.xmin = (width - input_area.xmax) + 1; + } + if (m_flipY) { + const int height = BLI_rcti_size_y(&input_area) - 1; + r_area.ymax = (height - input_area.ymin) + 1; + r_area.ymin = (height - input_area.ymax) + 1; + } + } +} + void FlipOperation::get_area_of_interest(const int input_idx, const rcti &output_area, rcti &r_input_area) @@ -84,7 +102,7 @@ void FlipOperation::get_area_of_interest(const int input_idx, if (this->m_flipX) { const int w = (int)this->getWidth() - 1; r_input_area.xmax = (w - output_area.xmin) + 1; - r_input_area.xmin = (w - output_area.xmax) - 1; + r_input_area.xmin = (w - output_area.xmax) + 1; } else { r_input_area.xmin = output_area.xmin; @@ -93,7 +111,7 @@ void FlipOperation::get_area_of_interest(const int input_idx, if (this->m_flipY) { const int h = (int)this->getHeight() - 1; r_input_area.ymax = (h - output_area.ymin) + 1; - r_input_area.ymin = (h - output_area.ymax) - 1; + r_input_area.ymin = (h - output_area.ymax) + 1; } else { r_input_area.ymin = output_area.ymin; @@ -106,10 +124,12 @@ void FlipOperation::update_memory_buffer_partial(MemoryBuffer *output, Span<MemoryBuffer *> inputs) { const MemoryBuffer *input_img = inputs[0]; + const int input_offset_x = input_img->get_rect().xmin; + const int input_offset_y = input_img->get_rect().ymin; for (BuffersIterator<float> it = output->iterate_with({}, area); !it.is_end(); ++it) { const int nx = this->m_flipX ? ((int)this->getWidth() - 1) - it.x : it.x; const int ny = this->m_flipY ? ((int)this->getHeight() - 1) - it.y : it.y; - input_img->read_elem(nx, ny, it.out); + input_img->read_elem(input_offset_x + nx, input_offset_y + ny, it.out); } } diff --git a/source/blender/compositor/operations/COM_FlipOperation.h b/source/blender/compositor/operations/COM_FlipOperation.h index dba7f82c341..963996a5b0d 100644 --- a/source/blender/compositor/operations/COM_FlipOperation.h +++ b/source/blender/compositor/operations/COM_FlipOperation.h @@ -46,6 +46,7 @@ class FlipOperation : public MultiThreadedOperation { this->m_flipY = flipY; } + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) override; void update_memory_buffer_partial(MemoryBuffer *output, const rcti &area, diff --git a/source/blender/compositor/operations/COM_GlareBaseOperation.cc b/source/blender/compositor/operations/COM_GlareBaseOperation.cc index 90755d9f27a..cd4607b1dde 100644 --- a/source/blender/compositor/operations/COM_GlareBaseOperation.cc +++ b/source/blender/compositor/operations/COM_GlareBaseOperation.cc @@ -26,6 +26,8 @@ GlareBaseOperation::GlareBaseOperation() this->addInputSocket(DataType::Color); this->addOutputSocket(DataType::Color); this->m_settings = nullptr; + flags.is_fullframe_operation = true; + is_output_rendered_ = false; } void GlareBaseOperation::initExecution() { @@ -69,4 +71,36 @@ bool GlareBaseOperation::determineDependingAreaOfInterest(rcti * /*input*/, return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output); } +void GlareBaseOperation::get_area_of_interest(const int input_idx, + const rcti &UNUSED(output_area), + rcti &r_input_area) +{ + BLI_assert(input_idx == 0); + UNUSED_VARS_NDEBUG(input_idx); + r_input_area.xmin = 0; + r_input_area.xmax = this->getWidth(); + r_input_area.ymin = 0; + r_input_area.ymax = this->getHeight(); +} + +void GlareBaseOperation::update_memory_buffer(MemoryBuffer *output, + const rcti &UNUSED(area), + Span<MemoryBuffer *> inputs) +{ + if (!is_output_rendered_) { + MemoryBuffer *input = inputs[0]; + const bool is_input_inflated = input->is_a_single_elem(); + if (is_input_inflated) { + input = input->inflate(); + } + + this->generateGlare(output->getBuffer(), input, m_settings); + is_output_rendered_ = true; + + if (is_input_inflated) { + delete input; + } + } +} + } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_GlareBaseOperation.h b/source/blender/compositor/operations/COM_GlareBaseOperation.h index 6dac6f5ecc7..5ca240a9e66 100644 --- a/source/blender/compositor/operations/COM_GlareBaseOperation.h +++ b/source/blender/compositor/operations/COM_GlareBaseOperation.h @@ -49,6 +49,8 @@ class GlareBaseOperation : public SingleThreadedOperation { */ NodeGlare *m_settings; + bool is_output_rendered_; + public: /** * Initialize the execution @@ -68,6 +70,14 @@ class GlareBaseOperation : public SingleThreadedOperation { ReadBufferOperation *readOperation, rcti *output) override; + void get_area_of_interest(const int input_idx, + const rcti &output_area, + rcti &r_input_area) final; + + void update_memory_buffer(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) final; + protected: GlareBaseOperation(); diff --git a/source/blender/compositor/operations/COM_GlareThresholdOperation.cc b/source/blender/compositor/operations/COM_GlareThresholdOperation.cc index 1d3402f5b7b..1bf7cf5ae07 100644 --- a/source/blender/compositor/operations/COM_GlareThresholdOperation.cc +++ b/source/blender/compositor/operations/COM_GlareThresholdOperation.cc @@ -30,12 +30,13 @@ GlareThresholdOperation::GlareThresholdOperation() this->m_inputProgram = nullptr; } -void GlareThresholdOperation::determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) +void GlareThresholdOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) { - NodeOperation::determineResolution(resolution, preferredResolution); - resolution[0] = resolution[0] / (1 << this->m_settings->quality); - resolution[1] = resolution[1] / (1 << this->m_settings->quality); + NodeOperation::determine_canvas(preferred_area, r_area); + const int width = BLI_rcti_size_x(&r_area) / (1 << this->m_settings->quality); + const int height = BLI_rcti_size_y(&r_area) / (1 << this->m_settings->quality); + r_area.xmax = r_area.xmin + width; + r_area.ymax = r_area.ymin + height; } void GlareThresholdOperation::initExecution() @@ -70,4 +71,24 @@ void GlareThresholdOperation::deinitExecution() this->m_inputProgram = nullptr; } +void GlareThresholdOperation::update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) +{ + const float threshold = this->m_settings->threshold; + for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) { + const float *color = it.in(0); + if (IMB_colormanagement_get_luminance(color) >= threshold) { + it.out[0] = color[0] - threshold; + it.out[1] = color[1] - threshold; + it.out[2] = color[2] - threshold; + + CLAMP3_MIN(it.out, 0.0f); + } + else { + zero_v3(it.out); + } + } +} + } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_GlareThresholdOperation.h b/source/blender/compositor/operations/COM_GlareThresholdOperation.h index a6e971dada7..44f2d717c0e 100644 --- a/source/blender/compositor/operations/COM_GlareThresholdOperation.h +++ b/source/blender/compositor/operations/COM_GlareThresholdOperation.h @@ -18,12 +18,12 @@ #pragma once -#include "COM_NodeOperation.h" +#include "COM_MultiThreadedOperation.h" #include "DNA_light_types.h" namespace blender::compositor { -class GlareThresholdOperation : public NodeOperation { +class GlareThresholdOperation : public MultiThreadedOperation { private: /** * \brief Cached reference to the inputProgram @@ -58,8 +58,10 @@ class GlareThresholdOperation : public NodeOperation { this->m_settings = settings; } - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; + void update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span<MemoryBuffer *> inputs) override; }; } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_ImageOperation.cc b/source/blender/compositor/operations/COM_ImageOperation.cc index e78d389410f..ff389093f5a 100644 --- a/source/blender/compositor/operations/COM_ImageOperation.cc +++ b/source/blender/compositor/operations/COM_ImageOperation.cc @@ -112,17 +112,14 @@ void BaseImageOperation::deinitExecution() } } -void BaseImageOperation::determineResolution(unsigned int resolution[2], - unsigned int /*preferredResolution*/[2]) +void BaseImageOperation::determine_canvas(const rcti &UNUSED(preferred_area), rcti &r_area) { ImBuf *stackbuf = getImBuf(); - resolution[0] = 0; - resolution[1] = 0; + r_area = COM_AREA_NONE; if (stackbuf) { - resolution[0] = stackbuf->x; - resolution[1] = stackbuf->y; + BLI_rcti_init(&r_area, 0, stackbuf->x, 0, stackbuf->y); } BKE_image_release_ibuf(this->m_image, stackbuf, nullptr); @@ -222,7 +219,7 @@ void ImageDepthOperation::executePixelSampled(float output[4], output[0] = 0.0f; } else { - int offset = y * this->m_width + x; + int offset = y * getWidth() + x; output[0] = this->m_depthBuffer[offset]; } } diff --git a/source/blender/compositor/operations/COM_ImageOperation.h b/source/blender/compositor/operations/COM_ImageOperation.h index f8b4239c9f8..e2fdfbf6f81 100644 --- a/source/blender/compositor/operations/COM_ImageOperation.h +++ b/source/blender/compositor/operations/COM_ImageOperation.h @@ -54,8 +54,7 @@ class BaseImageOperation : public MultiThreadedOperation { /** * Determine the output resolution. The resolution is retrieved from the Renderer */ - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; virtual ImBuf *getImBuf(); diff --git a/source/blender/compositor/operations/COM_InpaintOperation.cc b/source/blender/compositor/operations/COM_InpaintOperation.cc index 5e76c41752c..5d440dd7d76 100644 --- a/source/blender/compositor/operations/COM_InpaintOperation.cc +++ b/source/blender/compositor/operations/COM_InpaintOperation.cc @@ -293,10 +293,7 @@ void InpaintSimpleOperation::get_area_of_interest(const int input_idx, { BLI_assert(input_idx == 0); UNUSED_VARS_NDEBUG(input_idx); - r_input_area.xmin = 0; - r_input_area.xmax = this->getWidth(); - r_input_area.ymin = 0; - r_input_area.ymax = this->getHeight(); + r_input_area = this->get_canvas(); } void InpaintSimpleOperation::update_memory_buffer(MemoryBuffer *output, diff --git a/source/blender/compositor/operations/COM_InvertOperation.cc b/source/blender/compositor/operations/COM_InvertOperation.cc index 4f71a1d0d1d..d406b809c7a 100644 --- a/source/blender/compositor/operations/COM_InvertOperation.cc +++ b/source/blender/compositor/operations/COM_InvertOperation.cc @@ -29,7 +29,7 @@ InvertOperation::InvertOperation() this->m_inputColorProgram = nullptr; this->m_color = true; this->m_alpha = false; - setResolutionInputSocketIndex(1); + set_canvas_input_index(1); this->flags.can_be_constant = true; } void InvertOperation::initExecution() diff --git a/source/blender/compositor/operations/COM_KeyingScreenOperation.cc b/source/blender/compositor/operations/COM_KeyingScreenOperation.cc index c00aafc19a2..b4840926d55 100644 --- a/source/blender/compositor/operations/COM_KeyingScreenOperation.cc +++ b/source/blender/compositor/operations/COM_KeyingScreenOperation.cc @@ -303,11 +303,9 @@ void KeyingScreenOperation::deinitializeTileData(rcti * /*rect*/, void *data) MEM_freeN(tile_data); } -void KeyingScreenOperation::determineResolution(unsigned int resolution[2], - unsigned int /*preferredResolution*/[2]) +void KeyingScreenOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) { - resolution[0] = 0; - resolution[1] = 0; + r_area = COM_AREA_NONE; if (this->m_movieClip) { MovieClipUser user = {0}; @@ -317,9 +315,9 @@ void KeyingScreenOperation::determineResolution(unsigned int resolution[2], BKE_movieclip_user_set_frame(&user, clip_frame); BKE_movieclip_get_size(this->m_movieClip, &user, &width, &height); - - resolution[0] = width; - resolution[1] = height; + r_area = preferred_area; + r_area.xmax = r_area.xmin + width; + r_area.ymax = r_area.ymin + height; } } diff --git a/source/blender/compositor/operations/COM_KeyingScreenOperation.h b/source/blender/compositor/operations/COM_KeyingScreenOperation.h index 0bc47dbea30..7a7dda27710 100644 --- a/source/blender/compositor/operations/COM_KeyingScreenOperation.h +++ b/source/blender/compositor/operations/COM_KeyingScreenOperation.h @@ -57,8 +57,7 @@ class KeyingScreenOperation : public MultiThreadedOperation { /** * Determine the output resolution. The resolution is retrieved from the Renderer */ - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; TriangulationData *buildVoronoiTriangulation(); diff --git a/source/blender/compositor/operations/COM_MapUVOperation.cc b/source/blender/compositor/operations/COM_MapUVOperation.cc index ad047c619f8..ba38e583b30 100644 --- a/source/blender/compositor/operations/COM_MapUVOperation.cc +++ b/source/blender/compositor/operations/COM_MapUVOperation.cc @@ -23,12 +23,12 @@ namespace blender::compositor { MapUVOperation::MapUVOperation() { - this->addInputSocket(DataType::Color, ResizeMode::None); + this->addInputSocket(DataType::Color, ResizeMode::Align); this->addInputSocket(DataType::Vector); this->addOutputSocket(DataType::Color); this->m_alpha = 0.0f; this->flags.complex = true; - setResolutionInputSocketIndex(1); + set_canvas_input_index(UV_INPUT_INDEX); this->m_inputUVProgram = nullptr; this->m_inputColorProgram = nullptr; @@ -36,11 +36,11 @@ MapUVOperation::MapUVOperation() void MapUVOperation::init_data() { - NodeOperation *image_input = get_input_operation(0); + NodeOperation *image_input = get_input_operation(IMAGE_INPUT_INDEX); image_width_ = image_input->getWidth(); image_height_ = image_input->getHeight(); - NodeOperation *uv_input = get_input_operation(1); + NodeOperation *uv_input = get_input_operation(UV_INPUT_INDEX); uv_width_ = uv_input->getWidth(); uv_height_ = uv_input->getHeight(); } @@ -205,14 +205,11 @@ void MapUVOperation::get_area_of_interest(const int input_idx, rcti &r_input_area) { switch (input_idx) { - case 0: { - r_input_area.xmin = 0; - r_input_area.xmax = image_width_; - r_input_area.ymin = 0; - r_input_area.ymax = image_height_; + case IMAGE_INPUT_INDEX: { + r_input_area = get_input_operation(IMAGE_INPUT_INDEX)->get_canvas(); break; } - case 1: { + case UV_INPUT_INDEX: { r_input_area = output_area; expand_area_for_sampler(r_input_area, PixelSampler::Bilinear); break; @@ -224,7 +221,7 @@ void MapUVOperation::update_memory_buffer_started(MemoryBuffer *UNUSED(output), const rcti &UNUSED(area), Span<MemoryBuffer *> inputs) { - const MemoryBuffer *uv_input = inputs[1]; + const MemoryBuffer *uv_input = inputs[UV_INPUT_INDEX]; uv_input_read_fn_ = [=](float x, float y, float *out) { uv_input->read_elem_bilinear(x, y, out); }; @@ -234,7 +231,7 @@ void MapUVOperation::update_memory_buffer_partial(MemoryBuffer *output, const rcti &area, Span<MemoryBuffer *> inputs) { - const MemoryBuffer *input_image = inputs[0]; + const MemoryBuffer *input_image = inputs[IMAGE_INPUT_INDEX]; for (BuffersIterator<float> it = output->iterate_with({}, area); !it.is_end(); ++it) { float xy[2] = {(float)it.x, (float)it.y}; float uv[2]; diff --git a/source/blender/compositor/operations/COM_MapUVOperation.h b/source/blender/compositor/operations/COM_MapUVOperation.h index 65fbcb461c9..49c6689f700 100644 --- a/source/blender/compositor/operations/COM_MapUVOperation.h +++ b/source/blender/compositor/operations/COM_MapUVOperation.h @@ -24,6 +24,8 @@ namespace blender::compositor { class MapUVOperation : public MultiThreadedOperation { private: + static constexpr int IMAGE_INPUT_INDEX = 0; + static constexpr int UV_INPUT_INDEX = 1; /** * Cached reference to the inputProgram */ diff --git a/source/blender/compositor/operations/COM_MaskOperation.cc b/source/blender/compositor/operations/COM_MaskOperation.cc index 84992f23924..65b89a8c79a 100644 --- a/source/blender/compositor/operations/COM_MaskOperation.cc +++ b/source/blender/compositor/operations/COM_MaskOperation.cc @@ -109,22 +109,15 @@ void MaskOperation::deinitExecution() } } -void MaskOperation::determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) +void MaskOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) { if (this->m_maskWidth == 0 || this->m_maskHeight == 0) { - NodeOperation::determineResolution(resolution, preferredResolution); + r_area = COM_AREA_NONE; } else { - unsigned int nr[2]; - - nr[0] = this->m_maskWidth; - nr[1] = this->m_maskHeight; - - NodeOperation::determineResolution(resolution, nr); - - resolution[0] = this->m_maskWidth; - resolution[1] = this->m_maskHeight; + r_area = preferred_area; + r_area.xmax = r_area.xmin + m_maskWidth; + r_area.ymax = r_area.ymin + m_maskHeight; } } diff --git a/source/blender/compositor/operations/COM_MaskOperation.h b/source/blender/compositor/operations/COM_MaskOperation.h index 81e344c0451..cc7eb0c022c 100644 --- a/source/blender/compositor/operations/COM_MaskOperation.h +++ b/source/blender/compositor/operations/COM_MaskOperation.h @@ -54,8 +54,7 @@ class MaskOperation : public MultiThreadedOperation { /** * Determine the output resolution. The resolution is retrieved from the Renderer */ - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; public: MaskOperation(); diff --git a/source/blender/compositor/operations/COM_MathBaseOperation.cc b/source/blender/compositor/operations/COM_MathBaseOperation.cc index 2256dce011b..d3fb83caf7c 100644 --- a/source/blender/compositor/operations/COM_MathBaseOperation.cc +++ b/source/blender/compositor/operations/COM_MathBaseOperation.cc @@ -51,22 +51,19 @@ void MathBaseOperation::deinitExecution() this->m_inputValue3Operation = nullptr; } -void MathBaseOperation::determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) +void MathBaseOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) { NodeOperationInput *socket; - unsigned int tempPreferredResolution[2] = {0, 0}; - unsigned int tempResolution[2]; - + rcti temp_area; socket = this->getInputSocket(0); - socket->determineResolution(tempResolution, tempPreferredResolution); - if ((tempResolution[0] != 0) && (tempResolution[1] != 0)) { - this->setResolutionInputSocketIndex(0); + const bool determined = socket->determine_canvas(COM_AREA_NONE, temp_area); + if (determined) { + this->set_canvas_input_index(0); } else { - this->setResolutionInputSocketIndex(1); + this->set_canvas_input_index(1); } - NodeOperation::determineResolution(resolution, preferredResolution); + NodeOperation::determine_canvas(preferred_area, r_area); } void MathBaseOperation::clampIfNeeded(float *color) diff --git a/source/blender/compositor/operations/COM_MathBaseOperation.h b/source/blender/compositor/operations/COM_MathBaseOperation.h index d2da05db68e..0188eb50fa8 100644 --- a/source/blender/compositor/operations/COM_MathBaseOperation.h +++ b/source/blender/compositor/operations/COM_MathBaseOperation.h @@ -75,8 +75,7 @@ class MathBaseOperation : public MultiThreadedOperation { /** * Determine resolution */ - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; void setUseClamp(bool value) { diff --git a/source/blender/compositor/operations/COM_MixOperation.cc b/source/blender/compositor/operations/COM_MixOperation.cc index 77ecbf60356..895d32e6fee 100644 --- a/source/blender/compositor/operations/COM_MixOperation.cc +++ b/source/blender/compositor/operations/COM_MixOperation.cc @@ -66,29 +66,27 @@ void MixBaseOperation::executePixelSampled(float output[4], float x, float y, Pi output[3] = inputColor1[3]; } -void MixBaseOperation::determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) +void MixBaseOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) { NodeOperationInput *socket; - unsigned int tempPreferredResolution[2] = {0, 0}; - unsigned int tempResolution[2]; + rcti temp_area; socket = this->getInputSocket(1); - socket->determineResolution(tempResolution, tempPreferredResolution); - if ((tempResolution[0] != 0) && (tempResolution[1] != 0)) { - this->setResolutionInputSocketIndex(1); + bool determined = socket->determine_canvas(COM_AREA_NONE, temp_area); + if (determined) { + this->set_canvas_input_index(1); } else { socket = this->getInputSocket(2); - socket->determineResolution(tempResolution, tempPreferredResolution); - if ((tempResolution[0] != 0) && (tempResolution[1] != 0)) { - this->setResolutionInputSocketIndex(2); + determined = socket->determine_canvas(COM_AREA_NONE, temp_area); + if (determined) { + this->set_canvas_input_index(2); } else { - this->setResolutionInputSocketIndex(0); + this->set_canvas_input_index(0); } } - NodeOperation::determineResolution(resolution, preferredResolution); + NodeOperation::determine_canvas(preferred_area, r_area); } void MixBaseOperation::deinitExecution() @@ -111,7 +109,7 @@ void MixBaseOperation::update_memory_buffer_partial(MemoryBuffer *output, p.value_stride = input_value->elem_stride; p.color1_stride = input_color1->elem_stride; p.color2_stride = input_color2->elem_stride; - for (const int y : YRange(area)) { + for (int y = area.ymin; y < area.ymax; y++) { p.out = output->get_elem(area.xmin, y); p.row_end = p.out + width * output->elem_stride; p.value = input_value->get_elem(area.xmin, y); diff --git a/source/blender/compositor/operations/COM_MixOperation.h b/source/blender/compositor/operations/COM_MixOperation.h index 7ef9d78d58f..fabbea422eb 100644 --- a/source/blender/compositor/operations/COM_MixOperation.h +++ b/source/blender/compositor/operations/COM_MixOperation.h @@ -87,8 +87,7 @@ class MixBaseOperation : public MultiThreadedOperation { */ void deinitExecution() override; - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; void setUseValueAlphaMultiply(const bool value) { diff --git a/source/blender/compositor/operations/COM_MovieClipAttributeOperation.cc b/source/blender/compositor/operations/COM_MovieClipAttributeOperation.cc index e36e93984fb..aed91d4d46e 100644 --- a/source/blender/compositor/operations/COM_MovieClipAttributeOperation.cc +++ b/source/blender/compositor/operations/COM_MovieClipAttributeOperation.cc @@ -29,8 +29,9 @@ MovieClipAttributeOperation::MovieClipAttributeOperation() this->m_framenumber = 0; this->m_attribute = MCA_X; this->m_invert = false; - needs_resolution_to_get_constant_ = true; + needs_canvas_to_get_constant_ = true; is_value_calculated_ = false; + stabilization_resolution_socket_ = nullptr; } void MovieClipAttributeOperation::initExecution() @@ -42,7 +43,7 @@ void MovieClipAttributeOperation::initExecution() void MovieClipAttributeOperation::calc_value() { - BLI_assert(this->get_flags().is_resolution_set); + BLI_assert(this->get_flags().is_canvas_set); is_value_calculated_ = true; if (this->m_clip == nullptr) { return; @@ -53,8 +54,17 @@ void MovieClipAttributeOperation::calc_value() scale = 1.0f; angle = 0.0f; int clip_framenr = BKE_movieclip_remap_scene_to_clip_frame(this->m_clip, this->m_framenumber); - BKE_tracking_stabilization_data_get( - this->m_clip, clip_framenr, getWidth(), getHeight(), loc, &scale, &angle); + NodeOperation &stabilization_operation = + stabilization_resolution_socket_ ? + stabilization_resolution_socket_->getLink()->getOperation() : + *this; + BKE_tracking_stabilization_data_get(this->m_clip, + clip_framenr, + stabilization_operation.getWidth(), + stabilization_operation.getHeight(), + loc, + &scale, + &angle); switch (this->m_attribute) { case MCA_SCALE: this->m_value = scale; @@ -87,11 +97,9 @@ void MovieClipAttributeOperation::executePixelSampled(float output[4], output[0] = this->m_value; } -void MovieClipAttributeOperation::determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) +void MovieClipAttributeOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) { - resolution[0] = preferredResolution[0]; - resolution[1] = preferredResolution[1]; + r_area = preferred_area; } const float *MovieClipAttributeOperation::get_constant_elem() diff --git a/source/blender/compositor/operations/COM_MovieClipAttributeOperation.h b/source/blender/compositor/operations/COM_MovieClipAttributeOperation.h index 28c39d4dad3..50680653aea 100644 --- a/source/blender/compositor/operations/COM_MovieClipAttributeOperation.h +++ b/source/blender/compositor/operations/COM_MovieClipAttributeOperation.h @@ -42,6 +42,7 @@ class MovieClipAttributeOperation : public ConstantOperation { bool m_invert; MovieClipAttribute m_attribute; bool is_value_calculated_; + NodeOperationInput *stabilization_resolution_socket_; public: /** @@ -55,8 +56,7 @@ class MovieClipAttributeOperation : public ConstantOperation { * The inner loop of this operation. */ void executePixelSampled(float output[4], float x, float y, PixelSampler sampler) override; - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; const float *get_constant_elem() override; @@ -77,6 +77,14 @@ class MovieClipAttributeOperation : public ConstantOperation { this->m_invert = invert; } + /** + * Set an operation socket which input will be used to get the resolution for stabilization. + */ + void set_socket_input_resolution_for_stabilization(NodeOperationInput *input_socket) + { + stabilization_resolution_socket_ = input_socket; + } + private: void calc_value(); }; diff --git a/source/blender/compositor/operations/COM_MovieClipOperation.cc b/source/blender/compositor/operations/COM_MovieClipOperation.cc index e520b928edf..1ca33b12432 100644 --- a/source/blender/compositor/operations/COM_MovieClipOperation.cc +++ b/source/blender/compositor/operations/COM_MovieClipOperation.cc @@ -71,19 +71,13 @@ void MovieClipBaseOperation::deinitExecution() } } -void MovieClipBaseOperation::determineResolution(unsigned int resolution[2], - unsigned int /*preferredResolution*/[2]) +void MovieClipBaseOperation::determine_canvas(const rcti &UNUSED(preferred_area), rcti &r_area) { - resolution[0] = 0; - resolution[1] = 0; - + r_area = COM_AREA_NONE; if (this->m_movieClip) { int width, height; - BKE_movieclip_get_size(this->m_movieClip, this->m_movieClipUser, &width, &height); - - resolution[0] = width; - resolution[1] = height; + BLI_rcti_init(&r_area, 0, width, 0, height); } } diff --git a/source/blender/compositor/operations/COM_MovieClipOperation.h b/source/blender/compositor/operations/COM_MovieClipOperation.h index 0a0c4c00f81..465ccd4fc00 100644 --- a/source/blender/compositor/operations/COM_MovieClipOperation.h +++ b/source/blender/compositor/operations/COM_MovieClipOperation.h @@ -41,8 +41,7 @@ class MovieClipBaseOperation : public MultiThreadedOperation { /** * Determine the output resolution. The resolution is retrieved from the Renderer */ - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; public: MovieClipBaseOperation(); diff --git a/source/blender/compositor/operations/COM_MovieDistortionOperation.cc b/source/blender/compositor/operations/COM_MovieDistortionOperation.cc index d3424959061..49f43d2c1a7 100644 --- a/source/blender/compositor/operations/COM_MovieDistortionOperation.cc +++ b/source/blender/compositor/operations/COM_MovieDistortionOperation.cc @@ -29,15 +29,14 @@ MovieDistortionOperation::MovieDistortionOperation(bool distortion) { this->addInputSocket(DataType::Color); this->addOutputSocket(DataType::Color); - this->setResolutionInputSocketIndex(0); + this->set_canvas_input_index(0); this->m_inputOperation = nullptr; this->m_movieClip = nullptr; this->m_apply = distortion; } -void MovieDistortionOperation::initExecution() +void MovieDistortionOperation::init_data() { - this->m_inputOperation = this->getInputSocketReader(0); if (this->m_movieClip) { MovieTracking *tracking = &this->m_movieClip->tracking; MovieClipUser clipUser = {0}; @@ -49,10 +48,10 @@ void MovieDistortionOperation::initExecution() float delta[2]; rcti full_frame; full_frame.xmin = full_frame.ymin = 0; - full_frame.xmax = this->m_width; - full_frame.ymax = this->m_height; + full_frame.xmax = this->getWidth(); + full_frame.ymax = this->getHeight(); BKE_tracking_max_distortion_delta_across_bound( - tracking, this->m_width, this->m_height, &full_frame, !this->m_apply, delta); + tracking, this->getWidth(), this->getHeight(), &full_frame, !this->m_apply, delta); /* 5 is just in case we didn't hit real max of distortion in * BKE_tracking_max_undistortion_delta_across_bound @@ -60,15 +59,25 @@ void MovieDistortionOperation::initExecution() m_margin[0] = delta[0] + 5; m_margin[1] = delta[1] + 5; - this->m_distortion = BKE_tracking_distortion_new( - tracking, calibration_width, calibration_height); this->m_calibration_width = calibration_width; this->m_calibration_height = calibration_height; this->m_pixel_aspect = tracking->camera.pixel_aspect; } else { m_margin[0] = m_margin[1] = 0; - this->m_distortion = nullptr; + } +} + +void MovieDistortionOperation::initExecution() +{ + m_inputOperation = this->getInputSocketReader(0); + if (m_movieClip) { + MovieTracking *tracking = &m_movieClip->tracking; + m_distortion = BKE_tracking_distortion_new( + tracking, m_calibration_width, m_calibration_height); + } + else { + m_distortion = nullptr; } } @@ -89,8 +98,8 @@ void MovieDistortionOperation::executePixelSampled(float output[4], if (this->m_distortion != nullptr) { /* float overscan = 0.0f; */ const float pixel_aspect = this->m_pixel_aspect; - const float w = (float)this->m_width /* / (1 + overscan) */; - const float h = (float)this->m_height /* / (1 + overscan) */; + const float w = (float)this->getWidth() /* / (1 + overscan) */; + const float h = (float)this->getHeight() /* / (1 + overscan) */; const float aspx = w / (float)this->m_calibration_width; const float aspy = h / (float)this->m_calibration_height; float in[2]; @@ -152,8 +161,8 @@ void MovieDistortionOperation::update_memory_buffer_partial(MemoryBuffer *output /* `float overscan = 0.0f;` */ const float pixel_aspect = this->m_pixel_aspect; - const float w = (float)this->m_width /* `/ (1 + overscan)` */; - const float h = (float)this->m_height /* `/ (1 + overscan)` */; + const float w = (float)this->getWidth() /* `/ (1 + overscan)` */; + const float h = (float)this->getHeight() /* `/ (1 + overscan)` */; const float aspx = w / (float)this->m_calibration_width; const float aspy = h / (float)this->m_calibration_height; float xy[2]; diff --git a/source/blender/compositor/operations/COM_MovieDistortionOperation.h b/source/blender/compositor/operations/COM_MovieDistortionOperation.h index 69c2f9c269c..abf0553b75b 100644 --- a/source/blender/compositor/operations/COM_MovieDistortionOperation.h +++ b/source/blender/compositor/operations/COM_MovieDistortionOperation.h @@ -44,6 +44,7 @@ class MovieDistortionOperation : public MultiThreadedOperation { MovieDistortionOperation(bool distortion); void executePixelSampled(float output[4], float x, float y, PixelSampler sampler) override; + void init_data() override; void initExecution() override; void deinitExecution() override; diff --git a/source/blender/compositor/operations/COM_NormalizeOperation.cc b/source/blender/compositor/operations/COM_NormalizeOperation.cc index c3e72d2575f..7d79b375b45 100644 --- a/source/blender/compositor/operations/COM_NormalizeOperation.cc +++ b/source/blender/compositor/operations/COM_NormalizeOperation.cc @@ -133,11 +133,7 @@ void NormalizeOperation::get_area_of_interest(const int UNUSED(input_idx), const rcti &UNUSED(output_area), rcti &r_input_area) { - NodeOperation *input = get_input_operation(0); - r_input_area.xmin = 0; - r_input_area.xmax = input->getWidth(); - r_input_area.ymin = 0; - r_input_area.ymax = input->getHeight(); + r_input_area = get_input_operation(0)->get_canvas(); } void NormalizeOperation::update_memory_buffer_started(MemoryBuffer *UNUSED(output), diff --git a/source/blender/compositor/operations/COM_OutputFileMultiViewOperation.cc b/source/blender/compositor/operations/COM_OutputFileMultiViewOperation.cc index 5b6f650d40e..d436b00a6e3 100644 --- a/source/blender/compositor/operations/COM_OutputFileMultiViewOperation.cc +++ b/source/blender/compositor/operations/COM_OutputFileMultiViewOperation.cc @@ -86,8 +86,8 @@ void *OutputOpenExrSingleLayerMultiViewOperation::get_handle(const char *filenam /* prepare the file with all the channels */ - if (IMB_exr_begin_write( - exrhandle, filename, width, height, this->m_format->exr_codec, nullptr) == 0) { + if (!IMB_exr_begin_write( + exrhandle, filename, width, height, this->m_format->exr_codec, nullptr)) { printf("Error Writing Singlelayer Multiview Openexr\n"); IMB_exr_close(exrhandle); } @@ -200,8 +200,7 @@ void *OutputOpenExrMultiLayerMultiViewOperation::get_handle(const char *filename /* prepare the file with all the channels for the header */ StampData *stamp_data = createStampData(); - if (IMB_exr_begin_write(exrhandle, filename, width, height, this->m_exr_codec, stamp_data) == - 0) { + if (!IMB_exr_begin_write(exrhandle, filename, width, height, this->m_exr_codec, stamp_data)) { printf("Error Writing Multilayer Multiview Openexr\n"); IMB_exr_close(exrhandle); BKE_stamp_data_free(stamp_data); diff --git a/source/blender/compositor/operations/COM_OutputFileOperation.cc b/source/blender/compositor/operations/COM_OutputFileOperation.cc index 402d29893a4..79be95bb686 100644 --- a/source/blender/compositor/operations/COM_OutputFileOperation.cc +++ b/source/blender/compositor/operations/COM_OutputFileOperation.cc @@ -339,7 +339,7 @@ OutputOpenExrMultiLayerOperation::OutputOpenExrMultiLayerOperation(const Scene * this->m_exr_codec = exr_codec; this->m_exr_half_float = exr_half_float; this->m_viewName = viewName; - this->setResolutionInputSocketIndex(RESOLUTION_INPUT_ANY); + this->set_canvas_input_index(RESOLUTION_INPUT_ANY); } void OutputOpenExrMultiLayerOperation::add_layer(const char *name, diff --git a/source/blender/compositor/operations/COM_PixelateOperation.cc b/source/blender/compositor/operations/COM_PixelateOperation.cc index 94827cd1b02..4fc238bc094 100644 --- a/source/blender/compositor/operations/COM_PixelateOperation.cc +++ b/source/blender/compositor/operations/COM_PixelateOperation.cc @@ -24,7 +24,7 @@ PixelateOperation::PixelateOperation(DataType datatype) { this->addInputSocket(datatype); this->addOutputSocket(datatype); - this->setResolutionInputSocketIndex(0); + this->set_canvas_input_index(0); this->m_inputOperation = nullptr; } diff --git a/source/blender/compositor/operations/COM_PlaneCornerPinOperation.cc b/source/blender/compositor/operations/COM_PlaneCornerPinOperation.cc index d2a06ddd7c4..65cd08456ef 100644 --- a/source/blender/compositor/operations/COM_PlaneCornerPinOperation.cc +++ b/source/blender/compositor/operations/COM_PlaneCornerPinOperation.cc @@ -209,15 +209,13 @@ void *PlaneCornerPinMaskOperation::initializeTileData(rcti *rect) return data; } -void PlaneCornerPinMaskOperation::determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) +void PlaneCornerPinMaskOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) { if (execution_model_ == eExecutionModel::FullFrame) { - /* Determine inputs resolution. */ - PlaneDistortMaskOperation::determineResolution(resolution, preferredResolution); + /* Determine input canvases. */ + PlaneDistortMaskOperation::determine_canvas(preferred_area, r_area); } - resolution[0] = preferredResolution[0]; - resolution[1] = preferredResolution[1]; + r_area = preferred_area; } void PlaneCornerPinMaskOperation::get_area_of_interest(const int UNUSED(input_idx), @@ -225,7 +223,7 @@ void PlaneCornerPinMaskOperation::get_area_of_interest(const int UNUSED(input_id rcti &r_input_area) { /* All corner inputs are used as constants. */ - r_input_area = COM_SINGLE_ELEM_AREA; + r_input_area = COM_CONSTANT_INPUT_AREA_OF_INTEREST; } /* ******** PlaneCornerPinWarpImageOperation ******** */ @@ -322,7 +320,7 @@ void PlaneCornerPinWarpImageOperation::get_area_of_interest(const int input_idx, } else { /* Corner inputs are used as constants. */ - r_input_area = COM_SINGLE_ELEM_AREA; + r_input_area = COM_CONSTANT_INPUT_AREA_OF_INTEREST; } } diff --git a/source/blender/compositor/operations/COM_PlaneCornerPinOperation.h b/source/blender/compositor/operations/COM_PlaneCornerPinOperation.h index 2831e937147..7b3917923d2 100644 --- a/source/blender/compositor/operations/COM_PlaneCornerPinOperation.h +++ b/source/blender/compositor/operations/COM_PlaneCornerPinOperation.h @@ -43,8 +43,7 @@ class PlaneCornerPinMaskOperation : public PlaneDistortMaskOperation { void *initializeTileData(rcti *rect) override; - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) override; }; diff --git a/source/blender/compositor/operations/COM_PlaneDistortCommonOperation.cc b/source/blender/compositor/operations/COM_PlaneDistortCommonOperation.cc index ccabb3cf11c..31ef41789fd 100644 --- a/source/blender/compositor/operations/COM_PlaneDistortCommonOperation.cc +++ b/source/blender/compositor/operations/COM_PlaneDistortCommonOperation.cc @@ -73,7 +73,7 @@ BLI_INLINE void warpCoord(float x, float y, float matrix[3][3], float uv[2], flo PlaneDistortWarpImageOperation::PlaneDistortWarpImageOperation() : PlaneDistortBaseOperation() { - this->addInputSocket(DataType::Color, ResizeMode::None); + this->addInputSocket(DataType::Color, ResizeMode::Align); this->addOutputSocket(DataType::Color); this->m_pixelReader = nullptr; this->flags.complex = true; @@ -196,10 +196,7 @@ void PlaneDistortWarpImageOperation::get_area_of_interest(const int input_idx, } /* TODO: figure out the area needed for warping and EWA filtering. */ - r_input_area.xmin = 0; - r_input_area.ymin = 0; - r_input_area.xmax = get_input_operation(0)->getWidth(); - r_input_area.ymax = get_input_operation(0)->getHeight(); + r_input_area = get_input_operation(0)->get_canvas(); /* Old implementation but resulting coordinates are way out of input operation bounds and in some * cases the area result may incorrectly cause cropping. */ diff --git a/source/blender/compositor/operations/COM_PlaneTrackOperation.cc b/source/blender/compositor/operations/COM_PlaneTrackOperation.cc index bf24f843ca2..7226a133a52 100644 --- a/source/blender/compositor/operations/COM_PlaneTrackOperation.cc +++ b/source/blender/compositor/operations/COM_PlaneTrackOperation.cc @@ -83,19 +83,17 @@ void PlaneTrackCommon::readCornersFromTrack(float corners[4][2], float frame) } } -void PlaneTrackCommon::determineResolution(unsigned int resolution[2], - unsigned int /*preferredResolution*/[2]) +void PlaneTrackCommon::determine_canvas(const rcti &preferred_area, rcti &r_area) { - resolution[0] = 0; - resolution[1] = 0; - + r_area = COM_AREA_NONE; if (this->m_movieClip) { int width, height; MovieClipUser user = {0}; BKE_movieclip_user_set_frame(&user, this->m_framenumber); BKE_movieclip_get_size(this->m_movieClip, &user, &width, &height); - resolution[0] = width; - resolution[1] = height; + r_area = preferred_area; + r_area.xmax = r_area.xmin + width; + r_area.ymax = r_area.ymin + height; } } diff --git a/source/blender/compositor/operations/COM_PlaneTrackOperation.h b/source/blender/compositor/operations/COM_PlaneTrackOperation.h index d2027755162..60845aac514 100644 --- a/source/blender/compositor/operations/COM_PlaneTrackOperation.h +++ b/source/blender/compositor/operations/COM_PlaneTrackOperation.h @@ -41,7 +41,7 @@ class PlaneTrackCommon { * implementation classes must make wrappers to use these methods, see below. */ void read_and_calculate_corners(PlaneDistortBaseOperation *distort_op); - void determineResolution(unsigned int resolution[2], unsigned int preferredResolution[2]); + void determine_canvas(const rcti &preferred_area, rcti &r_area); public: PlaneTrackCommon(); @@ -77,13 +77,13 @@ class PlaneTrackMaskOperation : public PlaneDistortMaskOperation, public PlaneTr void initExecution() override; - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override + void determine_canvas(const rcti &preferred_area, rcti &r_area) override { - PlaneTrackCommon::determineResolution(resolution, preferredResolution); + PlaneTrackCommon::determine_canvas(preferred_area, r_area); - unsigned int temp[2]; - NodeOperation::determineResolution(temp, resolution); + rcti unused; + rcti &preferred = r_area; + NodeOperation::determine_canvas(preferred, unused); } }; @@ -98,12 +98,13 @@ class PlaneTrackWarpImageOperation : public PlaneDistortWarpImageOperation, void initExecution() override; - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override + void determine_canvas(const rcti &preferred_area, rcti &r_area) override { - PlaneTrackCommon::determineResolution(resolution, preferredResolution); - unsigned int temp[2]; - NodeOperation::determineResolution(temp, resolution); + PlaneTrackCommon::determine_canvas(preferred_area, r_area); + + rcti unused; + rcti &preferred = r_area; + NodeOperation::determine_canvas(preferred, unused); } }; diff --git a/source/blender/compositor/operations/COM_PreviewOperation.cc b/source/blender/compositor/operations/COM_PreviewOperation.cc index fa8b5ffcabf..34520264d54 100644 --- a/source/blender/compositor/operations/COM_PreviewOperation.cc +++ b/source/blender/compositor/operations/COM_PreviewOperation.cc @@ -41,7 +41,7 @@ PreviewOperation::PreviewOperation(const ColorManagedViewSettings *viewSettings, const unsigned int defaultHeight) { - this->addInputSocket(DataType::Color, ResizeMode::None); + this->addInputSocket(DataType::Color, ResizeMode::Align); this->m_preview = nullptr; this->m_outputBuffer = nullptr; this->m_input = nullptr; @@ -130,14 +130,14 @@ bool PreviewOperation::determineDependingAreaOfInterest(rcti *input, return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output); } -void PreviewOperation::determineResolution(unsigned int resolution[2], - unsigned int /*preferredResolution*/[2]) +void PreviewOperation::determine_canvas(const rcti &UNUSED(preferred_area), rcti &r_area) { /* Use default preview resolution as preferred ensuring it has size so that * generated inputs (which don't have resolution on their own) are displayed */ BLI_assert(this->m_defaultWidth > 0 && this->m_defaultHeight > 0); - unsigned int previewPreferredRes[2] = {this->m_defaultWidth, this->m_defaultHeight}; - NodeOperation::determineResolution(resolution, previewPreferredRes); + rcti local_preferred; + BLI_rcti_init(&local_preferred, 0, m_defaultWidth, 0, m_defaultHeight); + NodeOperation::determine_canvas(local_preferred, r_area); /* If resolution is 0 there are two possible scenarios: * - Either node is not connected at all @@ -148,8 +148,8 @@ void PreviewOperation::determineResolution(unsigned int resolution[2], * The latter case would only happen if an input doesn't set any resolution ignoring output * preferred resolution. In such case preview size will be 0 too. */ - int width = resolution[0]; - int height = resolution[1]; + int width = BLI_rcti_size_x(&r_area); + int height = BLI_rcti_size_y(&r_area); this->m_divider = 0.0f; if (width > 0 && height > 0) { if (width > height) { @@ -162,8 +162,7 @@ void PreviewOperation::determineResolution(unsigned int resolution[2], width = width * this->m_divider; height = height * this->m_divider; - resolution[0] = width; - resolution[1] = height; + BLI_rcti_init(&r_area, r_area.xmin, r_area.xmin + width, r_area.ymin, r_area.ymin + height); } eCompositorPriority PreviewOperation::getRenderPriority() const diff --git a/source/blender/compositor/operations/COM_PreviewOperation.h b/source/blender/compositor/operations/COM_PreviewOperation.h index 05dae9c4dd8..4bd0f07d882 100644 --- a/source/blender/compositor/operations/COM_PreviewOperation.h +++ b/source/blender/compositor/operations/COM_PreviewOperation.h @@ -58,8 +58,7 @@ class PreviewOperation : public MultiThreadedOperation { eCompositorPriority getRenderPriority() const override; void executeRegion(rcti *rect, unsigned int tileNumber) override; - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; bool determineDependingAreaOfInterest(rcti *input, ReadBufferOperation *readOperation, rcti *output) override; diff --git a/source/blender/compositor/operations/COM_ProjectorLensDistortionOperation.cc b/source/blender/compositor/operations/COM_ProjectorLensDistortionOperation.cc index fcab5dd5751..2982f59a019 100644 --- a/source/blender/compositor/operations/COM_ProjectorLensDistortionOperation.cc +++ b/source/blender/compositor/operations/COM_ProjectorLensDistortionOperation.cc @@ -131,13 +131,30 @@ void ProjectorLensDistortionOperation::updateDispersion() this->unlockMutex(); } +void ProjectorLensDistortionOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) +{ + switch (execution_model_) { + case eExecutionModel::FullFrame: { + set_determined_canvas_modifier([=](rcti &canvas) { + /* Ensure screen space. */ + BLI_rcti_translate(&canvas, -canvas.xmin, -canvas.ymin); + }); + break; + } + default: + break; + } + + NodeOperation::determine_canvas(preferred_area, r_area); +} + void ProjectorLensDistortionOperation::get_area_of_interest(const int input_idx, const rcti &output_area, rcti &r_input_area) { if (input_idx == 1) { /* Dispersion input is used as constant only. */ - r_input_area = COM_SINGLE_ELEM_AREA; + r_input_area = COM_CONSTANT_INPUT_AREA_OF_INTEREST; return; } diff --git a/source/blender/compositor/operations/COM_ProjectorLensDistortionOperation.h b/source/blender/compositor/operations/COM_ProjectorLensDistortionOperation.h index 7c7626bf271..b42fa3a361c 100644 --- a/source/blender/compositor/operations/COM_ProjectorLensDistortionOperation.h +++ b/source/blender/compositor/operations/COM_ProjectorLensDistortionOperation.h @@ -62,6 +62,7 @@ class ProjectorLensDistortionOperation : public MultiThreadedOperation { void updateDispersion(); + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) override; void update_memory_buffer_partial(MemoryBuffer *output, const rcti &area, diff --git a/source/blender/compositor/operations/COM_ReadBufferOperation.cc b/source/blender/compositor/operations/COM_ReadBufferOperation.cc index d35d2516f16..599370751bb 100644 --- a/source/blender/compositor/operations/COM_ReadBufferOperation.cc +++ b/source/blender/compositor/operations/COM_ReadBufferOperation.cc @@ -36,16 +36,17 @@ void *ReadBufferOperation::initializeTileData(rcti * /*rect*/) return m_buffer; } -void ReadBufferOperation::determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) +void ReadBufferOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) { if (this->m_memoryProxy != nullptr) { WriteBufferOperation *operation = this->m_memoryProxy->getWriteBufferOperation(); - operation->determineResolution(resolution, preferredResolution); - operation->setResolution(resolution); + operation->determine_canvas(preferred_area, r_area); + operation->set_canvas(r_area); /** \todo may not occur! But does with blur node. */ if (this->m_memoryProxy->getExecutor()) { + uint resolution[2] = {static_cast<uint>(BLI_rcti_size_x(&r_area)), + static_cast<uint>(BLI_rcti_size_y(&r_area))}; this->m_memoryProxy->getExecutor()->setResolution(resolution); } diff --git a/source/blender/compositor/operations/COM_ReadBufferOperation.h b/source/blender/compositor/operations/COM_ReadBufferOperation.h index 8b96b961a43..02f6eccd246 100644 --- a/source/blender/compositor/operations/COM_ReadBufferOperation.h +++ b/source/blender/compositor/operations/COM_ReadBufferOperation.h @@ -43,8 +43,7 @@ class ReadBufferOperation : public NodeOperation { return this->m_memoryProxy; } - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; void *initializeTileData(rcti *rect) override; void executePixelSampled(float output[4], float x, float y, PixelSampler sampler) override; diff --git a/source/blender/compositor/operations/COM_RenderLayersProg.cc b/source/blender/compositor/operations/COM_RenderLayersProg.cc index 72e2c92c9cf..2ac551ffe6f 100644 --- a/source/blender/compositor/operations/COM_RenderLayersProg.cc +++ b/source/blender/compositor/operations/COM_RenderLayersProg.cc @@ -196,15 +196,13 @@ void RenderLayersProg::deinitExecution() } } -void RenderLayersProg::determineResolution(unsigned int resolution[2], - unsigned int /*preferredResolution*/[2]) +void RenderLayersProg::determine_canvas(const rcti &UNUSED(preferred_area), rcti &r_area) { Scene *sce = this->getScene(); Render *re = (sce) ? RE_GetSceneRender(sce) : nullptr; RenderResult *rr = nullptr; - resolution[0] = 0; - resolution[1] = 0; + r_area = COM_AREA_NONE; if (re) { rr = RE_AcquireResultRead(re); @@ -215,8 +213,7 @@ void RenderLayersProg::determineResolution(unsigned int resolution[2], if (view_layer) { RenderLayer *rl = RE_GetRenderLayer(rr, view_layer->name); if (rl) { - resolution[0] = rl->rectx; - resolution[1] = rl->recty; + BLI_rcti_init(&r_area, 0, rl->rectx, 0, rl->recty); } } } diff --git a/source/blender/compositor/operations/COM_RenderLayersProg.h b/source/blender/compositor/operations/COM_RenderLayersProg.h index dd76a56d645..b499afd45df 100644 --- a/source/blender/compositor/operations/COM_RenderLayersProg.h +++ b/source/blender/compositor/operations/COM_RenderLayersProg.h @@ -73,8 +73,7 @@ class RenderLayersProg : public MultiThreadedOperation { /** * Determine the output resolution. The resolution is retrieved from the Renderer */ - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; /** * retrieve the reference to the float buffer of the renderer. diff --git a/source/blender/compositor/operations/COM_RotateOperation.cc b/source/blender/compositor/operations/COM_RotateOperation.cc index e3c482c15cb..9e26c93feac 100644 --- a/source/blender/compositor/operations/COM_RotateOperation.cc +++ b/source/blender/compositor/operations/COM_RotateOperation.cc @@ -25,10 +25,10 @@ namespace blender::compositor { RotateOperation::RotateOperation() { - this->addInputSocket(DataType::Color); - this->addInputSocket(DataType::Value); + this->addInputSocket(DataType::Color, ResizeMode::None); + this->addInputSocket(DataType::Value, ResizeMode::None); this->addOutputSocket(DataType::Color); - this->setResolutionInputSocketIndex(0); + this->set_canvas_input_index(0); this->m_imageSocket = nullptr; this->m_degreeSocket = nullptr; this->m_doDegree2RadConversion = false; @@ -36,6 +36,21 @@ RotateOperation::RotateOperation() sampler_ = PixelSampler::Bilinear; } +void RotateOperation::get_rotation_center(const rcti &area, float &r_x, float &r_y) +{ + r_x = (BLI_rcti_size_x(&area) - 1) / 2.0; + r_y = (BLI_rcti_size_y(&area) - 1) / 2.0; +} + +void RotateOperation::get_rotation_offset(const rcti &input_canvas, + const rcti &rotate_canvas, + float &r_offset_x, + float &r_offset_y) +{ + r_offset_x = (BLI_rcti_size_x(&input_canvas) - BLI_rcti_size_x(&rotate_canvas)) / 2.0f; + r_offset_y = (BLI_rcti_size_y(&input_canvas) - BLI_rcti_size_y(&rotate_canvas)) / 2.0f; +} + void RotateOperation::get_area_rotation_bounds(const rcti &area, const float center_x, const float center_y, @@ -48,14 +63,14 @@ void RotateOperation::get_area_rotation_bounds(const rcti &area, const float dxmax = area.xmax - center_x; const float dymax = area.ymax - center_y; - const float x1 = center_x + (cosine * dxmin + sine * dymin); - const float x2 = center_x + (cosine * dxmax + sine * dymin); - const float x3 = center_x + (cosine * dxmin + sine * dymax); - const float x4 = center_x + (cosine * dxmax + sine * dymax); - const float y1 = center_y + (-sine * dxmin + cosine * dymin); - const float y2 = center_y + (-sine * dxmax + cosine * dymin); - const float y3 = center_y + (-sine * dxmin + cosine * dymax); - const float y4 = center_y + (-sine * dxmax + cosine * dymax); + const float x1 = center_x + (cosine * dxmin + (-sine) * dymin); + const float x2 = center_x + (cosine * dxmax + (-sine) * dymin); + const float x3 = center_x + (cosine * dxmin + (-sine) * dymax); + const float x4 = center_x + (cosine * dxmax + (-sine) * dymax); + const float y1 = center_y + (sine * dxmin + cosine * dymin); + const float y2 = center_y + (sine * dxmax + cosine * dymin); + const float y3 = center_y + (sine * dxmin + cosine * dymax); + const float y4 = center_y + (sine * dxmax + cosine * dymax); const float minx = MIN2(x1, MIN2(x2, MIN2(x3, x4))); const float maxx = MAX2(x1, MAX2(x2, MAX2(x3, x4))); const float miny = MIN2(y1, MIN2(y2, MIN2(y3, y4))); @@ -67,10 +82,56 @@ void RotateOperation::get_area_rotation_bounds(const rcti &area, r_bounds.ymax = ceil(maxy); } +void RotateOperation::get_area_rotation_bounds_inverted(const rcti &area, + const float center_x, + const float center_y, + const float sine, + const float cosine, + rcti &r_bounds) +{ + get_area_rotation_bounds(area, center_x, center_y, -sine, cosine, r_bounds); +} + +void RotateOperation::get_rotation_area_of_interest(const rcti &input_canvas, + const rcti &rotate_canvas, + const float sine, + const float cosine, + const rcti &output_area, + rcti &r_input_area) +{ + float center_x, center_y; + get_rotation_center(input_canvas, center_x, center_y); + + float rotate_offset_x, rotate_offset_y; + get_rotation_offset(input_canvas, rotate_canvas, rotate_offset_x, rotate_offset_y); + + r_input_area = output_area; + BLI_rcti_translate(&r_input_area, rotate_offset_x, rotate_offset_y); + get_area_rotation_bounds_inverted(r_input_area, center_x, center_y, sine, cosine, r_input_area); +} + +void RotateOperation::get_rotation_canvas(const rcti &input_canvas, + const float sine, + const float cosine, + rcti &r_canvas) +{ + float center_x, center_y; + get_rotation_center(input_canvas, center_x, center_y); + + rcti rot_bounds; + get_area_rotation_bounds(input_canvas, center_x, center_y, sine, cosine, rot_bounds); + + float offset_x, offset_y; + get_rotation_offset(input_canvas, rot_bounds, offset_x, offset_y); + r_canvas = rot_bounds; + BLI_rcti_translate(&r_canvas, -offset_x, -offset_y); +} + void RotateOperation::init_data() { - this->m_centerX = (getWidth() - 1) / 2.0; - this->m_centerY = (getHeight() - 1) / 2.0; + if (execution_model_ == eExecutionModel::Tiled) { + get_rotation_center(get_canvas(), m_centerX, m_centerY); + } } void RotateOperation::initExecution() @@ -94,11 +155,7 @@ inline void RotateOperation::ensureDegree() this->m_degreeSocket->readSampled(degree, 0, 0, PixelSampler::Nearest); break; case eExecutionModel::FullFrame: - NodeOperation *degree_op = getInputOperation(DEGREE_INPUT_INDEX); - const bool is_constant_degree = degree_op->get_flags().is_constant_operation; - degree[0] = is_constant_degree ? - static_cast<ConstantOperation *>(degree_op)->get_constant_elem()[0] : - 0.0f; + degree[0] = get_input_operation(DEGREE_INPUT_INDEX)->get_constant_value_default(0.0f); break; } @@ -159,18 +216,40 @@ bool RotateOperation::determineDependingAreaOfInterest(rcti *input, return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output); } +void RotateOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) +{ + if (execution_model_ == eExecutionModel::Tiled) { + NodeOperation::determine_canvas(preferred_area, r_area); + return; + } + + const bool image_determined = + getInputSocket(IMAGE_INPUT_INDEX)->determine_canvas(preferred_area, r_area); + if (image_determined) { + rcti input_canvas = r_area; + rcti unused; + getInputSocket(DEGREE_INPUT_INDEX)->determine_canvas(input_canvas, unused); + + ensureDegree(); + + get_rotation_canvas(input_canvas, m_sine, m_cosine, r_area); + } +} + void RotateOperation::get_area_of_interest(const int input_idx, const rcti &output_area, rcti &r_input_area) { if (input_idx == DEGREE_INPUT_INDEX) { - /* Degrees input is always used as constant. */ - r_input_area = COM_SINGLE_ELEM_AREA; + r_input_area = COM_CONSTANT_INPUT_AREA_OF_INTEREST; return; } ensureDegree(); - get_area_rotation_bounds(output_area, m_centerX, m_centerY, m_sine, m_cosine, r_input_area); + + const rcti &input_image_canvas = get_input_operation(IMAGE_INPUT_INDEX)->get_canvas(); + get_rotation_area_of_interest( + input_image_canvas, this->get_canvas(), m_sine, m_cosine, output_area, r_input_area); expand_area_for_sampler(r_input_area, sampler_); } @@ -178,13 +257,20 @@ void RotateOperation::update_memory_buffer_partial(MemoryBuffer *output, const rcti &area, Span<MemoryBuffer *> inputs) { - ensureDegree(); const MemoryBuffer *input_img = inputs[IMAGE_INPUT_INDEX]; + + NodeOperation *image_op = get_input_operation(IMAGE_INPUT_INDEX); + float center_x, center_y; + get_rotation_center(image_op->get_canvas(), center_x, center_y); + float rotate_offset_x, rotate_offset_y; + get_rotation_offset( + image_op->get_canvas(), this->get_canvas(), rotate_offset_x, rotate_offset_y); + for (BuffersIterator<float> it = output->iterate_with({}, area); !it.is_end(); ++it) { - float x = it.x; - float y = it.y; - rotate_coords(x, y, m_centerX, m_centerY, m_sine, m_cosine); - input_img->read_elem_sampled(x, y, sampler_, it.out); + float x = rotate_offset_x + it.x + canvas_.xmin; + float y = rotate_offset_y + it.y + canvas_.ymin; + rotate_coords(x, y, center_x, center_y, m_sine, m_cosine); + input_img->read_elem_sampled(x - canvas_.xmin, y - canvas_.ymin, sampler_, it.out); } } diff --git a/source/blender/compositor/operations/COM_RotateOperation.h b/source/blender/compositor/operations/COM_RotateOperation.h index f0de699f9ce..76f203cfbc0 100644 --- a/source/blender/compositor/operations/COM_RotateOperation.h +++ b/source/blender/compositor/operations/COM_RotateOperation.h @@ -29,8 +29,10 @@ class RotateOperation : public MultiThreadedOperation { SocketReader *m_imageSocket; SocketReader *m_degreeSocket; + /* TODO(manzanilla): to be removed with tiled implementation. */ float m_centerX; float m_centerY; + float m_cosine; float m_sine; bool m_doDegree2RadConversion; @@ -49,12 +51,33 @@ class RotateOperation : public MultiThreadedOperation { y = center_y + (-sine * dx + cosine * dy); } + static void get_rotation_center(const rcti &area, float &r_x, float &r_y); + static void get_rotation_offset(const rcti &input_canvas, + const rcti &rotate_canvas, + float &r_offset_x, + float &r_offset_y); static void get_area_rotation_bounds(const rcti &area, const float center_x, const float center_y, const float sine, const float cosine, rcti &r_bounds); + static void get_area_rotation_bounds_inverted(const rcti &area, + const float center_x, + const float center_y, + const float sine, + const float cosine, + rcti &r_bounds); + static void get_rotation_area_of_interest(const rcti &input_canvas, + const rcti &rotate_canvas, + const float sine, + const float cosine, + const rcti &output_area, + rcti &r_input_area); + static void get_rotation_canvas(const rcti &input_canvas, + const float sine, + const float cosine, + rcti &r_canvas); bool determineDependingAreaOfInterest(rcti *input, ReadBufferOperation *readOperation, @@ -80,6 +103,8 @@ class RotateOperation : public MultiThreadedOperation { void update_memory_buffer_partial(MemoryBuffer *output, const rcti &area, Span<MemoryBuffer *> inputs) override; + + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; }; } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_ScaleOperation.cc b/source/blender/compositor/operations/COM_ScaleOperation.cc index 0161b837915..dbd8faf0f1d 100644 --- a/source/blender/compositor/operations/COM_ScaleOperation.cc +++ b/source/blender/compositor/operations/COM_ScaleOperation.cc @@ -38,17 +38,21 @@ BaseScaleOperation::BaseScaleOperation() m_variable_size = false; } +void BaseScaleOperation::set_scale_canvas_max_size(Size2f size) +{ + max_scale_canvas_size_ = size; +} + ScaleOperation::ScaleOperation() : ScaleOperation(DataType::Color) { } ScaleOperation::ScaleOperation(DataType data_type) : BaseScaleOperation() { - this->addInputSocket(data_type); + this->addInputSocket(data_type, ResizeMode::None); this->addInputSocket(DataType::Value); this->addInputSocket(DataType::Value); this->addOutputSocket(data_type); - this->setResolutionInputSocketIndex(0); this->m_inputOperation = nullptr; this->m_inputXOperation = nullptr; this->m_inputYOperation = nullptr; @@ -64,34 +68,52 @@ float ScaleOperation::get_constant_scale(const int input_op_idx, const float fac return 1.0f; } -float ScaleOperation::get_constant_scale_x() +float ScaleOperation::get_constant_scale_x(const float width) +{ + return get_constant_scale(X_INPUT_INDEX, get_relative_scale_x_factor(width)); +} + +float ScaleOperation::get_constant_scale_y(const float height) { - return get_constant_scale(1, get_relative_scale_x_factor()); + return get_constant_scale(Y_INPUT_INDEX, get_relative_scale_y_factor(height)); } -float ScaleOperation::get_constant_scale_y() +bool ScaleOperation::is_scaling_variable() { - return get_constant_scale(2, get_relative_scale_y_factor()); + return !get_input_operation(X_INPUT_INDEX)->get_flags().is_constant_operation || + !get_input_operation(Y_INPUT_INDEX)->get_flags().is_constant_operation; } -void ScaleOperation::scale_area( - rcti &rect, float center_x, float center_y, float scale_x, float scale_y) +void ScaleOperation::scale_area(rcti &area, float relative_scale_x, float relative_scale_y) { - rect.xmin = scale_coord(rect.xmin, center_x, scale_x); - rect.xmax = scale_coord(rect.xmax, center_x, scale_x); - rect.ymin = scale_coord(rect.ymin, center_y, scale_y); - rect.ymax = scale_coord(rect.ymax, center_y, scale_y); + const rcti src_area = area; + const float center_x = BLI_rcti_size_x(&area) / 2.0f; + const float center_y = BLI_rcti_size_y(&area) / 2.0f; + area.xmin = floorf(scale_coord(area.xmin, center_x, relative_scale_x)); + area.xmax = ceilf(scale_coord(area.xmax, center_x, relative_scale_x)); + area.ymin = floorf(scale_coord(area.ymin, center_y, relative_scale_y)); + area.ymax = ceilf(scale_coord(area.ymax, center_y, relative_scale_y)); + + float scale_offset_x, scale_offset_y; + ScaleOperation::get_scale_offset(src_area, area, scale_offset_x, scale_offset_y); + BLI_rcti_translate(&area, -scale_offset_x, -scale_offset_y); } -void ScaleOperation::scale_area(rcti &rect, float scale_x, float scale_y) +void ScaleOperation::clamp_area_size_max(rcti &area, Size2f max_size) { - scale_area(rect, m_centerX, m_centerY, scale_x, scale_y); + + if (BLI_rcti_size_x(&area) > max_size.x) { + area.xmax = area.xmin + max_size.x; + } + if (BLI_rcti_size_y(&area) > max_size.y) { + area.ymax = area.ymin + max_size.y; + } } void ScaleOperation::init_data() { - m_centerX = getWidth() / 2.0f; - m_centerY = getHeight() / 2.0f; + canvas_center_x_ = canvas_.xmin + getWidth() / 2.0f; + canvas_center_y_ = canvas_.ymin + getHeight() / 2.0f; } void ScaleOperation::initExecution() @@ -108,18 +130,52 @@ void ScaleOperation::deinitExecution() this->m_inputYOperation = nullptr; } +void ScaleOperation::get_scale_offset(const rcti &input_canvas, + const rcti &scale_canvas, + float &r_scale_offset_x, + float &r_scale_offset_y) +{ + r_scale_offset_x = (BLI_rcti_size_x(&input_canvas) - BLI_rcti_size_x(&scale_canvas)) / 2.0f; + r_scale_offset_y = (BLI_rcti_size_y(&input_canvas) - BLI_rcti_size_y(&scale_canvas)) / 2.0f; +} + +void ScaleOperation::get_scale_area_of_interest(const rcti &input_canvas, + const rcti &scale_canvas, + const float relative_scale_x, + const float relative_scale_y, + const rcti &output_area, + rcti &r_input_area) +{ + const float scale_center_x = BLI_rcti_size_x(&input_canvas) / 2.0f; + const float scale_center_y = BLI_rcti_size_y(&input_canvas) / 2.0f; + float scale_offset_x, scale_offset_y; + ScaleOperation::get_scale_offset(input_canvas, scale_canvas, scale_offset_x, scale_offset_y); + + r_input_area.xmin = floorf( + scale_coord_inverted(output_area.xmin + scale_offset_x, scale_center_x, relative_scale_x)); + r_input_area.xmax = ceilf( + scale_coord_inverted(output_area.xmax + scale_offset_x, scale_center_x, relative_scale_x)); + r_input_area.ymin = floorf( + scale_coord_inverted(output_area.ymin + scale_offset_y, scale_center_y, relative_scale_y)); + r_input_area.ymax = ceilf( + scale_coord_inverted(output_area.ymax + scale_offset_y, scale_center_y, relative_scale_y)); +} + void ScaleOperation::get_area_of_interest(const int input_idx, const rcti &output_area, rcti &r_input_area) { r_input_area = output_area; - if (input_idx != 0 || m_variable_size) { + if (input_idx != 0 || is_scaling_variable()) { return; } - float scale_x = get_constant_scale_x(); - float scale_y = get_constant_scale_y(); - scale_area(r_input_area, scale_x, scale_y); + NodeOperation *image_op = get_input_operation(IMAGE_INPUT_INDEX); + const float scale_x = get_constant_scale_x(image_op->getWidth()); + const float scale_y = get_constant_scale_y(image_op->getHeight()); + + get_scale_area_of_interest( + image_op->get_canvas(), this->get_canvas(), scale_x, scale_y, output_area, r_input_area); expand_area_for_sampler(r_input_area, (PixelSampler)m_sampler); } @@ -127,18 +183,70 @@ void ScaleOperation::update_memory_buffer_partial(MemoryBuffer *output, const rcti &area, Span<MemoryBuffer *> inputs) { - const MemoryBuffer *input_img = inputs[0]; - MemoryBuffer *input_x = inputs[1]; - MemoryBuffer *input_y = inputs[2]; - const float scale_x_factor = get_relative_scale_x_factor(); - const float scale_y_factor = get_relative_scale_y_factor(); + NodeOperation *input_image_op = get_input_operation(IMAGE_INPUT_INDEX); + const int input_image_width = input_image_op->getWidth(); + const int input_image_height = input_image_op->getHeight(); + const float scale_x_factor = get_relative_scale_x_factor(input_image_width); + const float scale_y_factor = get_relative_scale_y_factor(input_image_height); + const float scale_center_x = input_image_width / 2.0f; + const float scale_center_y = input_image_height / 2.0f; + float from_scale_offset_x, from_scale_offset_y; + ScaleOperation::get_scale_offset( + input_image_op->get_canvas(), this->get_canvas(), from_scale_offset_x, from_scale_offset_y); + + const MemoryBuffer *input_image = inputs[IMAGE_INPUT_INDEX]; + MemoryBuffer *input_x = inputs[X_INPUT_INDEX]; + MemoryBuffer *input_y = inputs[Y_INPUT_INDEX]; BuffersIterator<float> it = output->iterate_with({input_x, input_y}, area); for (; !it.is_end(); ++it) { const float rel_scale_x = *it.in(0) * scale_x_factor; const float rel_scale_y = *it.in(1) * scale_y_factor; - const float scaled_x = scale_coord(it.x, m_centerX, rel_scale_x); - const float scaled_y = scale_coord(it.y, m_centerY, rel_scale_y); - input_img->read_elem_sampled(scaled_x, scaled_y, (PixelSampler)m_sampler, it.out); + const float scaled_x = scale_coord_inverted( + from_scale_offset_x + canvas_.xmin + it.x, scale_center_x, rel_scale_x); + const float scaled_y = scale_coord_inverted( + from_scale_offset_y + canvas_.ymin + it.y, scale_center_y, rel_scale_y); + + input_image->read_elem_sampled( + scaled_x - canvas_.xmin, scaled_y - canvas_.ymin, (PixelSampler)m_sampler, it.out); + } +} + +void ScaleOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) +{ + if (execution_model_ == eExecutionModel::Tiled) { + NodeOperation::determine_canvas(preferred_area, r_area); + return; + } + + const bool image_determined = + getInputSocket(IMAGE_INPUT_INDEX)->determine_canvas(preferred_area, r_area); + if (image_determined) { + rcti image_canvas = r_area; + rcti unused; + NodeOperationInput *x_socket = getInputSocket(X_INPUT_INDEX); + NodeOperationInput *y_socket = getInputSocket(Y_INPUT_INDEX); + x_socket->determine_canvas(image_canvas, unused); + y_socket->determine_canvas(image_canvas, unused); + if (is_scaling_variable()) { + /* Do not scale canvas. */ + return; + } + + /* Determine scaled canvas. */ + const float input_width = BLI_rcti_size_x(&r_area); + const float input_height = BLI_rcti_size_y(&r_area); + const float scale_x = get_constant_scale_x(input_width); + const float scale_y = get_constant_scale_y(input_height); + scale_area(r_area, scale_x, scale_y); + const Size2f max_scale_size = {MAX2(input_width, max_scale_canvas_size_.x), + MAX2(input_height, max_scale_canvas_size_.y)}; + clamp_area_size_max(r_area, max_scale_size); + + /* Re-determine canvases of x and y constant inputs with scaled canvas as preferred. */ + get_input_operation(X_INPUT_INDEX)->unset_canvas(); + get_input_operation(Y_INPUT_INDEX)->unset_canvas(); + x_socket->determine_canvas(r_area, unused); + y_socket->determine_canvas(r_area, unused); } } @@ -166,8 +274,8 @@ void ScaleRelativeOperation::executePixelSampled(float output[4], const float scx = scaleX[0]; const float scy = scaleY[0]; - float nx = this->m_centerX + (x - this->m_centerX) / scx; - float ny = this->m_centerY + (y - this->m_centerY) / scy; + float nx = this->canvas_center_x_ + (x - this->canvas_center_x_) / scx; + float ny = this->canvas_center_y_ + (y - this->canvas_center_y_) / scy; this->m_inputOperation->readSampled(output, nx, ny, effective_sampler); } @@ -186,10 +294,10 @@ bool ScaleRelativeOperation::determineDependingAreaOfInterest(rcti *input, const float scx = scaleX[0]; const float scy = scaleY[0]; - newInput.xmax = this->m_centerX + (input->xmax - this->m_centerX) / scx + 1; - newInput.xmin = this->m_centerX + (input->xmin - this->m_centerX) / scx - 1; - newInput.ymax = this->m_centerY + (input->ymax - this->m_centerY) / scy + 1; - newInput.ymin = this->m_centerY + (input->ymin - this->m_centerY) / scy - 1; + newInput.xmax = this->canvas_center_x_ + (input->xmax - this->canvas_center_x_) / scx + 1; + newInput.xmin = this->canvas_center_x_ + (input->xmin - this->canvas_center_x_) / scx - 1; + newInput.ymax = this->canvas_center_y_ + (input->ymax - this->canvas_center_y_) / scy + 1; + newInput.ymin = this->canvas_center_y_ + (input->ymin - this->canvas_center_y_) / scy - 1; } else { newInput.xmax = this->getWidth(); @@ -222,8 +330,8 @@ void ScaleAbsoluteOperation::executePixelSampled(float output[4], float relativeXScale = scx / width; float relativeYScale = scy / height; - float nx = this->m_centerX + (x - this->m_centerX) / relativeXScale; - float ny = this->m_centerY + (y - this->m_centerY) / relativeYScale; + float nx = this->canvas_center_x_ + (x - this->canvas_center_x_) / relativeXScale; + float ny = this->canvas_center_y_ + (y - this->canvas_center_y_) / relativeYScale; this->m_inputOperation->readSampled(output, nx, ny, effective_sampler); } @@ -248,10 +356,14 @@ bool ScaleAbsoluteOperation::determineDependingAreaOfInterest(rcti *input, float relateveXScale = scx / width; float relateveYScale = scy / height; - newInput.xmax = this->m_centerX + (input->xmax - this->m_centerX) / relateveXScale; - newInput.xmin = this->m_centerX + (input->xmin - this->m_centerX) / relateveXScale; - newInput.ymax = this->m_centerY + (input->ymax - this->m_centerY) / relateveYScale; - newInput.ymin = this->m_centerY + (input->ymin - this->m_centerY) / relateveYScale; + newInput.xmax = this->canvas_center_x_ + + (input->xmax - this->canvas_center_x_) / relateveXScale; + newInput.xmin = this->canvas_center_x_ + + (input->xmin - this->canvas_center_x_) / relateveXScale; + newInput.ymax = this->canvas_center_y_ + + (input->ymax - this->canvas_center_y_) / relateveYScale; + newInput.ymin = this->canvas_center_y_ + + (input->ymin - this->canvas_center_y_) / relateveYScale; } else { newInput.xmax = this->getWidth(); @@ -267,16 +379,17 @@ ScaleFixedSizeOperation::ScaleFixedSizeOperation() : BaseScaleOperation() { this->addInputSocket(DataType::Color, ResizeMode::None); this->addOutputSocket(DataType::Color); - this->setResolutionInputSocketIndex(0); + this->set_canvas_input_index(0); this->m_inputOperation = nullptr; this->m_is_offset = false; } -void ScaleFixedSizeOperation::init_data() +void ScaleFixedSizeOperation::init_data(const rcti &input_canvas) { - const NodeOperation *input_op = getInputOperation(0); - this->m_relX = input_op->getWidth() / (float)this->m_newWidth; - this->m_relY = input_op->getHeight() / (float)this->m_newHeight; + const int input_width = BLI_rcti_size_x(&input_canvas); + const int input_height = BLI_rcti_size_y(&input_canvas); + this->m_relX = input_width / (float)this->m_newWidth; + this->m_relY = input_height / (float)this->m_newHeight; /* *** all the options below are for a fairly special case - camera framing *** */ if (this->m_offsetX != 0.0f || this->m_offsetY != 0.0f) { @@ -294,8 +407,8 @@ void ScaleFixedSizeOperation::init_data() if (this->m_is_aspect) { /* apply aspect from clip */ - const float w_src = input_op->getWidth(); - const float h_src = input_op->getHeight(); + const float w_src = input_width; + const float h_src = input_height; /* destination aspect is already applied from the camera frame */ const float w_dst = this->m_newWidth; @@ -310,12 +423,32 @@ void ScaleFixedSizeOperation::init_data() const float div = asp_src / asp_dst; this->m_relX /= div; this->m_offsetX += ((w_src - (w_src * div)) / (w_src / w_dst)) / 2.0f; + if (m_is_crop && execution_model_ == eExecutionModel::FullFrame) { + int fit_width = m_newWidth * div; + if (fit_width > max_scale_canvas_size_.x) { + fit_width = max_scale_canvas_size_.x; + } + + const int added_width = fit_width - m_newWidth; + m_newWidth += added_width; + m_offsetX += added_width / 2.0f; + } } else { /* fit Y */ const float div = asp_dst / asp_src; this->m_relY /= div; this->m_offsetY += ((h_src - (h_src * div)) / (h_src / h_dst)) / 2.0f; + if (m_is_crop && execution_model_ == eExecutionModel::FullFrame) { + int fit_height = m_newHeight * div; + if (fit_height > max_scale_canvas_size_.y) { + fit_height = max_scale_canvas_size_.y; + } + + const int added_height = fit_height - m_newHeight; + m_newHeight += added_height; + m_offsetY += added_height / 2.0f; + } } this->m_is_offset = true; @@ -366,15 +499,26 @@ bool ScaleFixedSizeOperation::determineDependingAreaOfInterest(rcti *input, return BaseScaleOperation::determineDependingAreaOfInterest(&newInput, readOperation, output); } -void ScaleFixedSizeOperation::determineResolution(unsigned int resolution[2], - unsigned int /*preferredResolution*/[2]) +void ScaleFixedSizeOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) { - unsigned int nr[2]; - nr[0] = this->m_newWidth; - nr[1] = this->m_newHeight; - BaseScaleOperation::determineResolution(resolution, nr); - resolution[0] = this->m_newWidth; - resolution[1] = this->m_newHeight; + rcti local_preferred = preferred_area; + local_preferred.xmax = local_preferred.xmin + m_newWidth; + local_preferred.ymax = local_preferred.ymin + m_newHeight; + rcti input_canvas; + const bool input_determined = getInputSocket(0)->determine_canvas(local_preferred, input_canvas); + if (input_determined) { + init_data(input_canvas); + r_area = input_canvas; + if (execution_model_ == eExecutionModel::FullFrame) { + r_area.xmin /= m_relX; + r_area.ymin /= m_relY; + r_area.xmin += m_offsetX; + r_area.ymin += m_offsetY; + } + + r_area.xmax = r_area.xmin + m_newWidth; + r_area.ymax = r_area.ymin + m_newHeight; + } } void ScaleFixedSizeOperation::get_area_of_interest(const int input_idx, @@ -383,10 +527,11 @@ void ScaleFixedSizeOperation::get_area_of_interest(const int input_idx, { BLI_assert(input_idx == 0); UNUSED_VARS_NDEBUG(input_idx); - r_input_area.xmax = (output_area.xmax - m_offsetX) * this->m_relX; - r_input_area.xmin = (output_area.xmin - m_offsetX) * this->m_relX; - r_input_area.ymax = (output_area.ymax - m_offsetY) * this->m_relY; - r_input_area.ymin = (output_area.ymin - m_offsetY) * this->m_relY; + + r_input_area.xmax = ceilf((output_area.xmax - m_offsetX) * this->m_relX); + r_input_area.xmin = floorf((output_area.xmin - m_offsetX) * this->m_relX); + r_input_area.ymax = ceilf((output_area.ymax - m_offsetY) * this->m_relY); + r_input_area.ymin = floorf((output_area.ymin - m_offsetY) * this->m_relY); expand_area_for_sampler(r_input_area, (PixelSampler)m_sampler); } @@ -399,14 +544,17 @@ void ScaleFixedSizeOperation::update_memory_buffer_partial(MemoryBuffer *output, BuffersIterator<float> it = output->iterate_with({}, area); if (this->m_is_offset) { for (; !it.is_end(); ++it) { - const float nx = (it.x - this->m_offsetX) * this->m_relX; - const float ny = (it.y - this->m_offsetY) * this->m_relY; - input_img->read_elem_sampled(nx, ny, sampler, it.out); + const float nx = (canvas_.xmin + it.x - this->m_offsetX) * this->m_relX; + const float ny = (canvas_.ymin + it.y - this->m_offsetY) * this->m_relY; + input_img->read_elem_sampled(nx - canvas_.xmin, ny - canvas_.ymin, sampler, it.out); } } else { for (; !it.is_end(); ++it) { - input_img->read_elem_sampled(it.x * this->m_relX, it.y * this->m_relY, sampler, it.out); + input_img->read_elem_sampled((canvas_.xmin + it.x) * this->m_relX - canvas_.xmin, + (canvas_.ymin + it.y) * this->m_relY - canvas_.ymin, + sampler, + it.out); } } } diff --git a/source/blender/compositor/operations/COM_ScaleOperation.h b/source/blender/compositor/operations/COM_ScaleOperation.h index 65762d1ce62..746e490d900 100644 --- a/source/blender/compositor/operations/COM_ScaleOperation.h +++ b/source/blender/compositor/operations/COM_ScaleOperation.h @@ -24,6 +24,9 @@ namespace blender::compositor { class BaseScaleOperation : public MultiThreadedOperation { public: + static constexpr float DEFAULT_MAX_SCALE_CANVAS_SIZE = 12000; + + public: void setSampler(PixelSampler sampler) { this->m_sampler = (int)sampler; @@ -33,6 +36,8 @@ class BaseScaleOperation : public MultiThreadedOperation { m_variable_size = variable_size; }; + void set_scale_canvas_max_size(Size2f size); + protected: BaseScaleOperation(); @@ -41,20 +46,26 @@ class BaseScaleOperation : public MultiThreadedOperation { return (m_sampler == -1) ? sampler : (PixelSampler)m_sampler; } + Size2f max_scale_canvas_size_ = {DEFAULT_MAX_SCALE_CANVAS_SIZE, DEFAULT_MAX_SCALE_CANVAS_SIZE}; int m_sampler; + /* TODO(manzanilla): to be removed with tiled implementation. */ bool m_variable_size; }; class ScaleOperation : public BaseScaleOperation { public: - static constexpr float MIN_SCALE = 0.0001f; + static constexpr float MIN_RELATIVE_SCALE = 0.0001f; protected: + static constexpr int IMAGE_INPUT_INDEX = 0; + static constexpr int X_INPUT_INDEX = 1; + static constexpr int Y_INPUT_INDEX = 2; + SocketReader *m_inputOperation; SocketReader *m_inputXOperation; SocketReader *m_inputYOperation; - float m_centerX; - float m_centerY; + float canvas_center_x_; + float canvas_center_y_; public: ScaleOperation(); @@ -62,9 +73,28 @@ class ScaleOperation : public BaseScaleOperation { static float scale_coord(const float coord, const float center, const float relative_scale) { - return center + (coord - center) / MAX2(relative_scale, MIN_SCALE); + return center + (coord - center) * MAX2(relative_scale, MIN_RELATIVE_SCALE); } - static void scale_area(rcti &rect, float center_x, float center_y, float scale_x, float scale_y); + + static float scale_coord_inverted(const float coord, + const float center, + const float relative_scale) + { + return center + (coord - center) / MAX2(relative_scale, MIN_RELATIVE_SCALE); + } + + static void get_scale_offset(const rcti &input_canvas, + const rcti &scale_canvas, + float &r_scale_offset_x, + float &r_scale_offset_y); + static void scale_area(rcti &area, float relative_scale_x, float relative_scale_y); + static void get_scale_area_of_interest(const rcti &input_canvas, + const rcti &scale_canvas, + const float relative_scale_x, + const float relative_scale_y, + const rcti &output_area, + rcti &r_input_area); + static void clamp_area_size_max(rcti &area, Size2f max_size); void init_data() override; void initExecution() override; @@ -75,15 +105,17 @@ class ScaleOperation : public BaseScaleOperation { const rcti &area, Span<MemoryBuffer *> inputs) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; + protected: - virtual float get_relative_scale_x_factor() = 0; - virtual float get_relative_scale_y_factor() = 0; + virtual float get_relative_scale_x_factor(float width) = 0; + virtual float get_relative_scale_y_factor(float height) = 0; private: + bool is_scaling_variable(); float get_constant_scale(int input_op_idx, float factor); - float get_constant_scale_x(); - float get_constant_scale_y(); - void scale_area(rcti &rect, float scale_x, float scale_y); + float get_constant_scale_x(float width); + float get_constant_scale_y(float height); }; class ScaleRelativeOperation : public ScaleOperation { @@ -94,11 +126,13 @@ class ScaleRelativeOperation : public ScaleOperation { ReadBufferOperation *readOperation, rcti *output) override; void executePixelSampled(float output[4], float x, float y, PixelSampler sampler) override; - float get_relative_scale_x_factor() override + + float get_relative_scale_x_factor(float UNUSED(width)) override { return 1.0f; } - float get_relative_scale_y_factor() override + + float get_relative_scale_y_factor(float UNUSED(height)) override { return 1.0f; } @@ -110,13 +144,15 @@ class ScaleAbsoluteOperation : public ScaleOperation { ReadBufferOperation *readOperation, rcti *output) override; void executePixelSampled(float output[4], float x, float y, PixelSampler sampler) override; - float get_relative_scale_x_factor() override + + float get_relative_scale_x_factor(float width) override { - return 1.0f / getWidth(); + return 1.0f / width; } - float get_relative_scale_y_factor() override + + float get_relative_scale_y_factor(float height) override { - return 1.0f / getHeight(); + return 1.0f / height; } }; @@ -141,11 +177,9 @@ class ScaleFixedSizeOperation : public BaseScaleOperation { bool determineDependingAreaOfInterest(rcti *input, ReadBufferOperation *readOperation, rcti *output) override; - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; void executePixelSampled(float output[4], float x, float y, PixelSampler sampler) override; - void init_data() override; void initExecution() override; void deinitExecution() override; void setNewWidth(int width) @@ -174,6 +208,9 @@ class ScaleFixedSizeOperation : public BaseScaleOperation { void update_memory_buffer_partial(MemoryBuffer *output, const rcti &area, Span<MemoryBuffer *> inputs) override; + + private: + void init_data(const rcti &input_canvas); }; } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_ScreenLensDistortionOperation.cc b/source/blender/compositor/operations/COM_ScreenLensDistortionOperation.cc index 628da686a42..21d9210bdac 100644 --- a/source/blender/compositor/operations/COM_ScreenLensDistortionOperation.cc +++ b/source/blender/compositor/operations/COM_ScreenLensDistortionOperation.cc @@ -382,13 +382,30 @@ void ScreenLensDistortionOperation::updateVariables(float distortion, float disp mul_v3_v3fl(m_k4, m_k, 4.0f); } +void ScreenLensDistortionOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) +{ + switch (execution_model_) { + case eExecutionModel::FullFrame: { + set_determined_canvas_modifier([=](rcti &canvas) { + /* Ensure screen space. */ + BLI_rcti_translate(&canvas, -canvas.xmin, -canvas.ymin); + }); + break; + } + default: + break; + } + + NodeOperation::determine_canvas(preferred_area, r_area); +} + void ScreenLensDistortionOperation::get_area_of_interest(const int input_idx, const rcti &UNUSED(output_area), rcti &r_input_area) { if (input_idx != 0) { /* Dispersion and distortion inputs are used as constants only. */ - r_input_area = COM_SINGLE_ELEM_AREA; + r_input_area = COM_CONSTANT_INPUT_AREA_OF_INTEREST; } /* XXX the original method of estimating the area-of-interest does not work @@ -398,10 +415,7 @@ void ScreenLensDistortionOperation::get_area_of_interest(const int input_idx, */ #if 1 NodeOperation *image = getInputOperation(0); - r_input_area.xmax = image->getWidth(); - r_input_area.xmin = 0; - r_input_area.ymax = image->getHeight(); - r_input_area.ymin = 0; + r_input_area = image->get_canvas(); #else /* Original method in tiled implementation. */ rcti newInput; diff --git a/source/blender/compositor/operations/COM_ScreenLensDistortionOperation.h b/source/blender/compositor/operations/COM_ScreenLensDistortionOperation.h index 616fc8883b0..93681b2f934 100644 --- a/source/blender/compositor/operations/COM_ScreenLensDistortionOperation.h +++ b/source/blender/compositor/operations/COM_ScreenLensDistortionOperation.h @@ -86,6 +86,7 @@ class ScreenLensDistortionOperation : public MultiThreadedOperation { ReadBufferOperation *readOperation, rcti *output) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) override; void update_memory_buffer_partial(MemoryBuffer *output, const rcti &area, diff --git a/source/blender/compositor/operations/COM_SetColorOperation.cc b/source/blender/compositor/operations/COM_SetColorOperation.cc index dbe45fa60db..1e7a950e727 100644 --- a/source/blender/compositor/operations/COM_SetColorOperation.cc +++ b/source/blender/compositor/operations/COM_SetColorOperation.cc @@ -34,11 +34,9 @@ void SetColorOperation::executePixelSampled(float output[4], copy_v4_v4(output, this->m_color); } -void SetColorOperation::determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) +void SetColorOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) { - resolution[0] = preferredResolution[0]; - resolution[1] = preferredResolution[1]; + r_area = preferred_area; } } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_SetColorOperation.h b/source/blender/compositor/operations/COM_SetColorOperation.h index f546d5e7668..34ea35bdcbc 100644 --- a/source/blender/compositor/operations/COM_SetColorOperation.h +++ b/source/blender/compositor/operations/COM_SetColorOperation.h @@ -83,8 +83,7 @@ class SetColorOperation : public ConstantOperation { */ void executePixelSampled(float output[4], float x, float y, PixelSampler sampler) override; - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; }; } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_SetValueOperation.cc b/source/blender/compositor/operations/COM_SetValueOperation.cc index ef43cf64653..b7c50f94887 100644 --- a/source/blender/compositor/operations/COM_SetValueOperation.cc +++ b/source/blender/compositor/operations/COM_SetValueOperation.cc @@ -34,11 +34,9 @@ void SetValueOperation::executePixelSampled(float output[4], output[0] = this->m_value; } -void SetValueOperation::determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) +void SetValueOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) { - resolution[0] = preferredResolution[0]; - resolution[1] = preferredResolution[1]; + r_area = preferred_area; } } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_SetValueOperation.h b/source/blender/compositor/operations/COM_SetValueOperation.h index 726624c1c6a..e72652450a9 100644 --- a/source/blender/compositor/operations/COM_SetValueOperation.h +++ b/source/blender/compositor/operations/COM_SetValueOperation.h @@ -54,8 +54,8 @@ class SetValueOperation : public ConstantOperation { * The inner loop of this operation. */ void executePixelSampled(float output[4], float x, float y, PixelSampler sampler) override; - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; }; } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_SetVectorOperation.cc b/source/blender/compositor/operations/COM_SetVectorOperation.cc index 7b8cf44048c..3e8514f1f59 100644 --- a/source/blender/compositor/operations/COM_SetVectorOperation.cc +++ b/source/blender/compositor/operations/COM_SetVectorOperation.cc @@ -37,11 +37,9 @@ void SetVectorOperation::executePixelSampled(float output[4], output[2] = vector_.z; } -void SetVectorOperation::determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) +void SetVectorOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) { - resolution[0] = preferredResolution[0]; - resolution[1] = preferredResolution[1]; + r_area = preferred_area; } } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_SetVectorOperation.h b/source/blender/compositor/operations/COM_SetVectorOperation.h index 41fd06659d6..920ea8051e4 100644 --- a/source/blender/compositor/operations/COM_SetVectorOperation.h +++ b/source/blender/compositor/operations/COM_SetVectorOperation.h @@ -84,8 +84,7 @@ class SetVectorOperation : public ConstantOperation { */ void executePixelSampled(float output[4], float x, float y, PixelSampler sampler) override; - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; void setVector(const float vector[3]) { diff --git a/source/blender/compositor/operations/COM_SplitOperation.cc b/source/blender/compositor/operations/COM_SplitOperation.cc index b2a50e2c740..47b2bbb7e94 100644 --- a/source/blender/compositor/operations/COM_SplitOperation.cc +++ b/source/blender/compositor/operations/COM_SplitOperation.cc @@ -67,16 +67,14 @@ void SplitOperation::executePixelSampled(float output[4], } } -void SplitOperation::determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) +void SplitOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) { - unsigned int tempPreferredResolution[2] = {0, 0}; - unsigned int tempResolution[2]; + rcti unused_area; - this->getInputSocket(0)->determineResolution(tempResolution, tempPreferredResolution); - this->setResolutionInputSocketIndex((tempResolution[0] && tempResolution[1]) ? 0 : 1); + const bool determined = this->getInputSocket(0)->determine_canvas(COM_AREA_NONE, unused_area); + this->set_canvas_input_index(determined ? 0 : 1); - NodeOperation::determineResolution(resolution, preferredResolution); + NodeOperation::determine_canvas(preferred_area, r_area); } void SplitOperation::update_memory_buffer_partial(MemoryBuffer *output, diff --git a/source/blender/compositor/operations/COM_SplitOperation.h b/source/blender/compositor/operations/COM_SplitOperation.h index 2d09d2a07dc..f923c9f8b7a 100644 --- a/source/blender/compositor/operations/COM_SplitOperation.h +++ b/source/blender/compositor/operations/COM_SplitOperation.h @@ -35,8 +35,7 @@ class SplitOperation : public MultiThreadedOperation { void initExecution() override; void deinitExecution() override; void executePixelSampled(float output[4], float x, float y, PixelSampler sampler) override; - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; void setSplitPercentage(float splitPercentage) { this->m_splitPercentage = splitPercentage; diff --git a/source/blender/compositor/operations/COM_SunBeamsOperation.cc b/source/blender/compositor/operations/COM_SunBeamsOperation.cc index ad96e3a02ba..494506389c5 100644 --- a/source/blender/compositor/operations/COM_SunBeamsOperation.cc +++ b/source/blender/compositor/operations/COM_SunBeamsOperation.cc @@ -25,7 +25,7 @@ SunBeamsOperation::SunBeamsOperation() { this->addInputSocket(DataType::Color); this->addOutputSocket(DataType::Color); - this->setResolutionInputSocketIndex(0); + this->set_canvas_input_index(0); this->flags.complex = true; } diff --git a/source/blender/compositor/operations/COM_TextureOperation.cc b/source/blender/compositor/operations/COM_TextureOperation.cc index c8e0844d35f..c06e3ac7cb0 100644 --- a/source/blender/compositor/operations/COM_TextureOperation.cc +++ b/source/blender/compositor/operations/COM_TextureOperation.cc @@ -72,34 +72,20 @@ void TextureBaseOperation::deinitExecution() NodeOperation::deinitExecution(); } -void TextureBaseOperation::determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) +void TextureBaseOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) { - switch (execution_model_) { - case eExecutionModel::Tiled: { - if (preferredResolution[0] == 0 || preferredResolution[1] == 0) { - int width = this->m_rd->xsch * this->m_rd->size / 100; - int height = this->m_rd->ysch * this->m_rd->size / 100; - resolution[0] = width; - resolution[1] = height; - } - else { - resolution[0] = preferredResolution[0]; - resolution[1] = preferredResolution[1]; - } - break; - } - case eExecutionModel::FullFrame: { - /* Determine inputs resolutions. */ - unsigned int temp[2]; - NodeOperation::determineResolution(temp, preferredResolution); - - /* We don't use inputs resolutions because they are only used as parameters, not image data. - */ - resolution[0] = preferredResolution[0]; - resolution[1] = preferredResolution[1]; - break; - } + r_area = preferred_area; + if (BLI_rcti_is_empty(&preferred_area)) { + int width = this->m_rd->xsch * this->m_rd->size / 100; + int height = this->m_rd->ysch * this->m_rd->size / 100; + r_area.xmax = preferred_area.xmin + width; + r_area.ymax = preferred_area.ymin + height; + } + + if (execution_model_ == eExecutionModel::FullFrame) { + /* Determine inputs. */ + rcti temp; + NodeOperation::determine_canvas(r_area, temp); } } diff --git a/source/blender/compositor/operations/COM_TextureOperation.h b/source/blender/compositor/operations/COM_TextureOperation.h index 1e95cb270d0..5bc21da1f96 100644 --- a/source/blender/compositor/operations/COM_TextureOperation.h +++ b/source/blender/compositor/operations/COM_TextureOperation.h @@ -46,8 +46,7 @@ class TextureBaseOperation : public MultiThreadedOperation { /** * Determine the output resolution. */ - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; /** * Constructor diff --git a/source/blender/compositor/operations/COM_TonemapOperation.cc b/source/blender/compositor/operations/COM_TonemapOperation.cc index 20da468eeb1..cb671c54abe 100644 --- a/source/blender/compositor/operations/COM_TonemapOperation.cc +++ b/source/blender/compositor/operations/COM_TonemapOperation.cc @@ -28,7 +28,7 @@ namespace blender::compositor { TonemapOperation::TonemapOperation() { - this->addInputSocket(DataType::Color, ResizeMode::None); + this->addInputSocket(DataType::Color, ResizeMode::Align); this->addOutputSocket(DataType::Color); this->m_imageReader = nullptr; this->m_data = nullptr; @@ -160,11 +160,7 @@ void TonemapOperation::get_area_of_interest(const int input_idx, rcti &r_input_area) { BLI_assert(input_idx == 0); - NodeOperation *operation = getInputOperation(input_idx); - r_input_area.xmin = 0; - r_input_area.ymin = 0; - r_input_area.xmax = operation->getWidth(); - r_input_area.ymax = operation->getHeight(); + r_input_area = get_input_operation(input_idx)->get_canvas(); } struct Luminance { diff --git a/source/blender/compositor/operations/COM_TrackPositionOperation.cc b/source/blender/compositor/operations/COM_TrackPositionOperation.cc index 0f4be16a620..1929c578177 100644 --- a/source/blender/compositor/operations/COM_TrackPositionOperation.cc +++ b/source/blender/compositor/operations/COM_TrackPositionOperation.cc @@ -157,11 +157,9 @@ const float *TrackPositionOperation::get_constant_elem() return &track_position_; } -void TrackPositionOperation::determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) +void TrackPositionOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) { - resolution[0] = preferredResolution[0]; - resolution[1] = preferredResolution[1]; + r_area = preferred_area; } } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_TrackPositionOperation.h b/source/blender/compositor/operations/COM_TrackPositionOperation.h index f716bd97737..a47d3e03ed4 100644 --- a/source/blender/compositor/operations/COM_TrackPositionOperation.h +++ b/source/blender/compositor/operations/COM_TrackPositionOperation.h @@ -53,8 +53,7 @@ class TrackPositionOperation : public ConstantOperation { /** * Determine the output resolution. The resolution is retrieved from the Renderer */ - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; public: TrackPositionOperation(); diff --git a/source/blender/compositor/operations/COM_TransformOperation.cc b/source/blender/compositor/operations/COM_TransformOperation.cc index 2feaa0ae16d..5f6e9ed4d21 100644 --- a/source/blender/compositor/operations/COM_TransformOperation.cc +++ b/source/blender/compositor/operations/COM_TransformOperation.cc @@ -27,55 +27,40 @@ namespace blender::compositor { TransformOperation::TransformOperation() { - addInputSocket(DataType::Color); - addInputSocket(DataType::Value); - addInputSocket(DataType::Value); - addInputSocket(DataType::Value); - addInputSocket(DataType::Value); + addInputSocket(DataType::Color, ResizeMode::None); + addInputSocket(DataType::Value, ResizeMode::None); + addInputSocket(DataType::Value, ResizeMode::None); + addInputSocket(DataType::Value, ResizeMode::None); + addInputSocket(DataType::Value, ResizeMode::None); addOutputSocket(DataType::Color); translate_factor_x_ = 1.0f; translate_factor_y_ = 1.0f; convert_degree_to_rad_ = false; sampler_ = PixelSampler::Bilinear; invert_ = false; + max_scale_canvas_size_ = {ScaleOperation::DEFAULT_MAX_SCALE_CANVAS_SIZE, + ScaleOperation::DEFAULT_MAX_SCALE_CANVAS_SIZE}; +} + +void TransformOperation::set_scale_canvas_max_size(Size2f size) +{ + max_scale_canvas_size_ = size; } void TransformOperation::init_data() { - /* Translation. */ - translate_x_ = 0; - NodeOperation *x_op = getInputOperation(X_INPUT_INDEX); - if (x_op->get_flags().is_constant_operation) { - translate_x_ = static_cast<ConstantOperation *>(x_op)->get_constant_elem()[0] * - translate_factor_x_; - } - translate_y_ = 0; - NodeOperation *y_op = getInputOperation(Y_INPUT_INDEX); - if (y_op->get_flags().is_constant_operation) { - translate_y_ = static_cast<ConstantOperation *>(y_op)->get_constant_elem()[0] * - translate_factor_y_; - } - /* Scaling. */ - scale_center_x_ = getWidth() / 2.0; - scale_center_y_ = getHeight() / 2.0; - constant_scale_ = 1.0f; - NodeOperation *scale_op = getInputOperation(SCALE_INPUT_INDEX); - if (scale_op->get_flags().is_constant_operation) { - constant_scale_ = static_cast<ConstantOperation *>(scale_op)->get_constant_elem()[0]; - } + translate_x_ = get_input_operation(X_INPUT_INDEX)->get_constant_value_default(0.0f) * + translate_factor_x_; + translate_y_ = get_input_operation(Y_INPUT_INDEX)->get_constant_value_default(0.0f) * + translate_factor_y_; - /* Rotation. */ - rotate_center_x_ = (getWidth() - 1.0) / 2.0; - rotate_center_y_ = (getHeight() - 1.0) / 2.0; - NodeOperation *degree_op = getInputOperation(DEGREE_INPUT_INDEX); - const bool is_constant_degree = degree_op->get_flags().is_constant_operation; - const float degree = is_constant_degree ? - static_cast<ConstantOperation *>(degree_op)->get_constant_elem()[0] : - 0.0f; + const float degree = get_input_operation(DEGREE_INPUT_INDEX)->get_constant_value_default(0.0f); const double rad = convert_degree_to_rad_ ? DEG2RAD((double)degree) : degree; rotate_cosine_ = cos(rad); rotate_sine_ = sin(rad); + + scale_ = get_input_operation(SCALE_INPUT_INDEX)->get_constant_value_default(1.0f); } void TransformOperation::get_area_of_interest(const int input_idx, @@ -84,26 +69,41 @@ void TransformOperation::get_area_of_interest(const int input_idx, { switch (input_idx) { case IMAGE_INPUT_INDEX: { - BLI_rcti_translate(&r_input_area, translate_x_, translate_y_); - ScaleOperation::scale_area( - r_input_area, scale_center_x_, scale_center_y_, constant_scale_, constant_scale_); - RotateOperation::get_area_rotation_bounds(r_input_area, - rotate_center_x_, - rotate_center_y_, - rotate_sine_, - rotate_cosine_, - r_input_area); + NodeOperation *image_op = get_input_operation(IMAGE_INPUT_INDEX); + const rcti &image_canvas = image_op->get_canvas(); + if (invert_) { + /* Scale -> Rotate -> Translate. */ + r_input_area = output_area; + BLI_rcti_translate(&r_input_area, -translate_x_, -translate_y_); + RotateOperation::get_rotation_area_of_interest(scale_canvas_, + rotate_canvas_, + rotate_sine_, + rotate_cosine_, + r_input_area, + r_input_area); + ScaleOperation::get_scale_area_of_interest( + image_canvas, scale_canvas_, scale_, scale_, r_input_area, r_input_area); + } + else { + /* Translate -> Rotate -> Scale. */ + ScaleOperation::get_scale_area_of_interest( + rotate_canvas_, scale_canvas_, scale_, scale_, output_area, r_input_area); + RotateOperation::get_rotation_area_of_interest(translate_canvas_, + rotate_canvas_, + rotate_sine_, + rotate_cosine_, + r_input_area, + r_input_area); + BLI_rcti_translate(&r_input_area, -translate_x_, -translate_y_); + } expand_area_for_sampler(r_input_area, sampler_); break; } case X_INPUT_INDEX: case Y_INPUT_INDEX: - case DEGREE_INPUT_INDEX: { - r_input_area = COM_SINGLE_ELEM_AREA; - break; - } + case DEGREE_INPUT_INDEX: case SCALE_INPUT_INDEX: { - r_input_area = output_area; + r_input_area = COM_CONSTANT_INPUT_AREA_OF_INTEREST; break; } } @@ -114,8 +114,7 @@ void TransformOperation::update_memory_buffer_partial(MemoryBuffer *output, Span<MemoryBuffer *> inputs) { const MemoryBuffer *input_img = inputs[IMAGE_INPUT_INDEX]; - MemoryBuffer *input_scale = inputs[SCALE_INPUT_INDEX]; - BuffersIterator<float> it = output->iterate_with({input_scale}, area); + BuffersIterator<float> it = output->iterate_with({}, area); if (invert_) { transform_inverted(it, input_img); } @@ -124,31 +123,111 @@ void TransformOperation::update_memory_buffer_partial(MemoryBuffer *output, } } +void TransformOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) +{ + const bool image_determined = + getInputSocket(IMAGE_INPUT_INDEX)->determine_canvas(preferred_area, r_area); + if (image_determined) { + rcti image_canvas = r_area; + rcti unused; + getInputSocket(X_INPUT_INDEX)->determine_canvas(image_canvas, unused); + getInputSocket(Y_INPUT_INDEX)->determine_canvas(image_canvas, unused); + getInputSocket(DEGREE_INPUT_INDEX)->determine_canvas(image_canvas, unused); + getInputSocket(SCALE_INPUT_INDEX)->determine_canvas(image_canvas, unused); + + init_data(); + if (invert_) { + /* Scale -> Rotate -> Translate. */ + scale_canvas_ = image_canvas; + ScaleOperation::scale_area(scale_canvas_, scale_, scale_); + const Size2f max_scale_size = { + MAX2(BLI_rcti_size_x(&image_canvas), max_scale_canvas_size_.x), + MAX2(BLI_rcti_size_y(&image_canvas), max_scale_canvas_size_.y)}; + ScaleOperation::clamp_area_size_max(scale_canvas_, max_scale_size); + + RotateOperation::get_rotation_canvas( + scale_canvas_, rotate_sine_, rotate_cosine_, rotate_canvas_); + + translate_canvas_ = rotate_canvas_; + BLI_rcti_translate(&translate_canvas_, translate_x_, translate_y_); + + r_area = translate_canvas_; + } + else { + /* Translate -> Rotate -> Scale. */ + translate_canvas_ = image_canvas; + BLI_rcti_translate(&translate_canvas_, translate_x_, translate_y_); + + RotateOperation::get_rotation_canvas( + translate_canvas_, rotate_sine_, rotate_cosine_, rotate_canvas_); + + scale_canvas_ = rotate_canvas_; + ScaleOperation::scale_area(scale_canvas_, scale_, scale_); + + const Size2f max_scale_size = { + MAX2(BLI_rcti_size_x(&rotate_canvas_), max_scale_canvas_size_.x), + MAX2(BLI_rcti_size_y(&rotate_canvas_), max_scale_canvas_size_.y)}; + ScaleOperation::clamp_area_size_max(scale_canvas_, max_scale_size); + + r_area = scale_canvas_; + } + } +} + +/** Translate -> Rotate -> Scale. */ void TransformOperation::transform(BuffersIterator<float> &it, const MemoryBuffer *input_img) { + float rotate_center_x, rotate_center_y; + RotateOperation::get_rotation_center(translate_canvas_, rotate_center_x, rotate_center_y); + float rotate_offset_x, rotate_offset_y; + RotateOperation::get_rotation_offset( + translate_canvas_, rotate_canvas_, rotate_offset_x, rotate_offset_y); + + const float scale_center_x = BLI_rcti_size_x(&rotate_canvas_) / 2.0f; + const float scale_center_y = BLI_rcti_size_y(&rotate_canvas_) / 2.0f; + float scale_offset_x, scale_offset_y; + ScaleOperation::get_scale_offset(rotate_canvas_, scale_canvas_, scale_offset_x, scale_offset_y); + for (; !it.is_end(); ++it) { - const float scale = *it.in(0); - float x = it.x - translate_x_; - float y = it.y - translate_y_; + float x = ScaleOperation::scale_coord_inverted(it.x + scale_offset_x, scale_center_x, scale_); + float y = ScaleOperation::scale_coord_inverted(it.y + scale_offset_y, scale_center_y, scale_); + + x = rotate_offset_x + x; + y = rotate_offset_y + y; RotateOperation::rotate_coords( - x, y, rotate_center_x_, rotate_center_y_, rotate_sine_, rotate_cosine_); - x = ScaleOperation::scale_coord(x, scale_center_x_, scale); - y = ScaleOperation::scale_coord(y, scale_center_y_, scale); - input_img->read_elem_sampled(x, y, sampler_, it.out); + x, y, rotate_center_x, rotate_center_y, rotate_sine_, rotate_cosine_); + + input_img->read_elem_sampled(x - translate_x_, y - translate_y_, sampler_, it.out); } } +/** Scale -> Rotate -> Translate. */ void TransformOperation::transform_inverted(BuffersIterator<float> &it, const MemoryBuffer *input_img) { + const rcti &image_canvas = get_input_operation(IMAGE_INPUT_INDEX)->get_canvas(); + const float scale_center_x = BLI_rcti_size_x(&image_canvas) / 2.0f - translate_x_; + const float scale_center_y = BLI_rcti_size_y(&image_canvas) / 2.0f - translate_y_; + float scale_offset_x, scale_offset_y; + ScaleOperation::get_scale_offset(image_canvas, scale_canvas_, scale_offset_x, scale_offset_y); + + float rotate_center_x, rotate_center_y; + RotateOperation::get_rotation_center(translate_canvas_, rotate_center_x, rotate_center_y); + rotate_center_x -= translate_x_; + rotate_center_y -= translate_y_; + float rotate_offset_x, rotate_offset_y; + RotateOperation::get_rotation_offset( + scale_canvas_, rotate_canvas_, rotate_offset_x, rotate_offset_y); + for (; !it.is_end(); ++it) { - const float scale = *it.in(0); - float x = ScaleOperation::scale_coord(it.x, scale_center_x_, scale); - float y = ScaleOperation::scale_coord(it.y, scale_center_y_, scale); + float x = rotate_offset_x + (it.x - translate_x_); + float y = rotate_offset_y + (it.y - translate_y_); RotateOperation::rotate_coords( - x, y, rotate_center_x_, rotate_center_y_, rotate_sine_, rotate_cosine_); - x -= translate_x_; - y -= translate_y_; + x, y, rotate_center_x, rotate_center_y, rotate_sine_, rotate_cosine_); + + x = ScaleOperation::scale_coord_inverted(x + scale_offset_x, scale_center_x, scale_); + y = ScaleOperation::scale_coord_inverted(y + scale_offset_y, scale_center_y, scale_); + input_img->read_elem_sampled(x, y, sampler_, it.out); } } diff --git a/source/blender/compositor/operations/COM_TransformOperation.h b/source/blender/compositor/operations/COM_TransformOperation.h index 480998a0207..3c5584a1bea 100644 --- a/source/blender/compositor/operations/COM_TransformOperation.h +++ b/source/blender/compositor/operations/COM_TransformOperation.h @@ -30,15 +30,14 @@ class TransformOperation : public MultiThreadedOperation { constexpr static int DEGREE_INPUT_INDEX = 3; constexpr static int SCALE_INPUT_INDEX = 4; - float scale_center_x_; - float scale_center_y_; - float rotate_center_x_; - float rotate_center_y_; float rotate_cosine_; float rotate_sine_; - float translate_x_; - float translate_y_; - float constant_scale_; + int translate_x_; + int translate_y_; + float scale_; + rcti scale_canvas_; + rcti rotate_canvas_; + rcti translate_canvas_; /* Set variables. */ PixelSampler sampler_; @@ -46,6 +45,7 @@ class TransformOperation : public MultiThreadedOperation { float translate_factor_x_; float translate_factor_y_; bool invert_; + Size2f max_scale_canvas_size_; public: TransformOperation(); @@ -71,16 +71,18 @@ class TransformOperation : public MultiThreadedOperation { invert_ = value; } + void set_scale_canvas_max_size(Size2f size); + void init_data() override; void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) override; void update_memory_buffer_partial(MemoryBuffer *output, const rcti &area, Span<MemoryBuffer *> inputs) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; + private: - /** Translate -> Rotate -> Scale. */ void transform(BuffersIterator<float> &it, const MemoryBuffer *input_img); - /** Scale -> Rotate -> Translate. */ void transform_inverted(BuffersIterator<float> &it, const MemoryBuffer *input_img); }; diff --git a/source/blender/compositor/operations/COM_TranslateOperation.cc b/source/blender/compositor/operations/COM_TranslateOperation.cc index a3db086e974..9868f21654e 100644 --- a/source/blender/compositor/operations/COM_TranslateOperation.cc +++ b/source/blender/compositor/operations/COM_TranslateOperation.cc @@ -23,13 +23,13 @@ namespace blender::compositor { TranslateOperation::TranslateOperation() : TranslateOperation(DataType::Color) { } -TranslateOperation::TranslateOperation(DataType data_type) +TranslateOperation::TranslateOperation(DataType data_type, ResizeMode resize_mode) { - this->addInputSocket(data_type); - this->addInputSocket(DataType::Value); - this->addInputSocket(DataType::Value); + this->addInputSocket(data_type, resize_mode); + this->addInputSocket(DataType::Value, ResizeMode::None); + this->addInputSocket(DataType::Value, ResizeMode::None); this->addOutputSocket(data_type); - this->setResolutionInputSocketIndex(0); + this->set_canvas_input_index(0); this->m_inputOperation = nullptr; this->m_inputXOperation = nullptr; this->m_inputYOperation = nullptr; @@ -39,6 +39,7 @@ TranslateOperation::TranslateOperation(DataType data_type) this->x_extend_mode_ = MemoryBufferExtend::Clip; this->y_extend_mode_ = MemoryBufferExtend::Clip; } + void TranslateOperation::initExecution() { this->m_inputOperation = this->getInputSocketReader(0); @@ -122,6 +123,9 @@ void TranslateOperation::get_area_of_interest(const int input_idx, BLI_rcti_translate(&r_input_area, 0, -delta_y); } } + else { + r_input_area = output_area; + } } void TranslateOperation::update_memory_buffer_partial(MemoryBuffer *output, @@ -142,4 +146,27 @@ void TranslateOperation::update_memory_buffer_partial(MemoryBuffer *output, } } +TranslateCanvasOperation::TranslateCanvasOperation() + : TranslateOperation(DataType::Color, ResizeMode::None) +{ +} + +void TranslateCanvasOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) +{ + const bool determined = + getInputSocket(IMAGE_INPUT_INDEX)->determine_canvas(preferred_area, r_area); + if (determined) { + NodeOperationInput *x_socket = getInputSocket(X_INPUT_INDEX); + NodeOperationInput *y_socket = getInputSocket(Y_INPUT_INDEX); + rcti unused; + x_socket->determine_canvas(r_area, unused); + y_socket->determine_canvas(r_area, unused); + + ensureDelta(); + const float delta_x = x_extend_mode_ == MemoryBufferExtend::Clip ? getDeltaX() : 0.0f; + const float delta_y = y_extend_mode_ == MemoryBufferExtend::Clip ? getDeltaY() : 0.0f; + BLI_rcti_translate(&r_area, delta_x, delta_y); + } +} + } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_TranslateOperation.h b/source/blender/compositor/operations/COM_TranslateOperation.h index ce1965cecef..c5a3d1ffd99 100644 --- a/source/blender/compositor/operations/COM_TranslateOperation.h +++ b/source/blender/compositor/operations/COM_TranslateOperation.h @@ -24,6 +24,11 @@ namespace blender::compositor { class TranslateOperation : public MultiThreadedOperation { + protected: + static constexpr int IMAGE_INPUT_INDEX = 0; + static constexpr int X_INPUT_INDEX = 1; + static constexpr int Y_INPUT_INDEX = 2; + private: SocketReader *m_inputOperation; SocketReader *m_inputXOperation; @@ -33,12 +38,14 @@ class TranslateOperation : public MultiThreadedOperation { bool m_isDeltaSet; float m_factorX; float m_factorY; + + protected: MemoryBufferExtend x_extend_mode_; MemoryBufferExtend y_extend_mode_; public: TranslateOperation(); - TranslateOperation(DataType data_type); + TranslateOperation(DataType data_type, ResizeMode mode = ResizeMode::Center); bool determineDependingAreaOfInterest(rcti *input, ReadBufferOperation *readOperation, rcti *output) override; @@ -67,16 +74,8 @@ class TranslateOperation : public MultiThreadedOperation { this->m_deltaY = tempDelta[0]; } else { - this->m_deltaX = 0; - NodeOperation *x_op = getInputOperation(1); - if (x_op->get_flags().is_constant_operation) { - this->m_deltaX = ((ConstantOperation *)x_op)->get_constant_elem()[0]; - } - this->m_deltaY = 0; - NodeOperation *y_op = getInputOperation(2); - if (y_op->get_flags().is_constant_operation) { - this->m_deltaY = ((ConstantOperation *)y_op)->get_constant_elem()[0]; - } + m_deltaX = get_input_operation(X_INPUT_INDEX)->get_constant_value_default(0.0f); + m_deltaY = get_input_operation(Y_INPUT_INDEX)->get_constant_value_default(0.0f); } this->m_isDeltaSet = true; @@ -93,4 +92,10 @@ class TranslateOperation : public MultiThreadedOperation { Span<MemoryBuffer *> inputs) override; }; +class TranslateCanvasOperation : public TranslateOperation { + public: + TranslateCanvasOperation(); + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; +}; + } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_VariableSizeBokehBlurOperation.cc b/source/blender/compositor/operations/COM_VariableSizeBokehBlurOperation.cc index b8274576cb5..c524447a4fa 100644 --- a/source/blender/compositor/operations/COM_VariableSizeBokehBlurOperation.cc +++ b/source/blender/compositor/operations/COM_VariableSizeBokehBlurOperation.cc @@ -28,8 +28,8 @@ namespace blender::compositor { VariableSizeBokehBlurOperation::VariableSizeBokehBlurOperation() { this->addInputSocket(DataType::Color); - this->addInputSocket(DataType::Color, ResizeMode::None); /* Do not resize the bokeh image. */ - this->addInputSocket(DataType::Value); /* Radius. */ + this->addInputSocket(DataType::Color, ResizeMode::Align); /* Do not resize the bokeh image. */ + this->addInputSocket(DataType::Value); /* Radius. */ #ifdef COM_DEFOCUS_SEARCH /* Inverse search radius optimization structure. */ this->addInputSocket(DataType::Color, ResizeMode::None); @@ -77,7 +77,7 @@ void *VariableSizeBokehBlurOperation::initializeTileData(rcti *rect) this->determineDependingAreaOfInterest( rect, (ReadBufferOperation *)this->m_inputSizeProgram, &rect2); - const float max_dim = MAX2(m_width, m_height); + const float max_dim = MAX2(this->getWidth(), this->getHeight()); const float scalar = this->m_do_size_scale ? (max_dim / 100.0f) : 1.0f; data->maxBlurScalar = (int)(data->size->get_max_value(rect2) * scalar); @@ -105,7 +105,7 @@ void VariableSizeBokehBlurOperation::executePixel(float output[4], int x, int y, float multiplier_accum[4]; float color_accum[4]; - const float max_dim = MAX2(m_width, m_height); + const float max_dim = MAX2(getWidth(), getHeight()); const float scalar = this->m_do_size_scale ? (max_dim / 100.0f) : 1.0f; int maxBlurScalar = tileData->maxBlurScalar; @@ -125,8 +125,8 @@ void VariableSizeBokehBlurOperation::executePixel(float output[4], int x, int y, #else int minx = MAX2(x - maxBlurScalar, 0); int miny = MAX2(y - maxBlurScalar, 0); - int maxx = MIN2(x + maxBlurScalar, (int)m_width); - int maxy = MIN2(y + maxBlurScalar, (int)m_height); + int maxx = MIN2(x + maxBlurScalar, (int)getWidth()); + int maxy = MIN2(y + maxBlurScalar, (int)getHeight()); #endif { inputSizeBuffer->readNoCheck(tempSize, x, y); @@ -200,7 +200,7 @@ void VariableSizeBokehBlurOperation::executeOpenCL(OpenCLDevice *device, MemoryBuffer *sizeMemoryBuffer = this->m_inputSizeProgram->getInputMemoryBuffer( inputMemoryBuffers); - const float max_dim = MAX2(m_width, m_height); + const float max_dim = MAX2(getWidth(), getHeight()); cl_float scalar = this->m_do_size_scale ? (max_dim / 100.0f) : 1.0f; maxBlur = (cl_int)min_ff(sizeMemoryBuffer->get_max_value() * scalar, (float)this->m_maxBlur); @@ -238,7 +238,7 @@ bool VariableSizeBokehBlurOperation::determineDependingAreaOfInterest( rcti newInput; rcti bokehInput; - const float max_dim = MAX2(m_width, m_height); + const float max_dim = MAX2(getWidth(), getHeight()); const float scalar = this->m_do_size_scale ? (max_dim / 100.0f) : 1.0f; int maxBlurScalar = this->m_maxBlur * scalar; @@ -294,10 +294,9 @@ void VariableSizeBokehBlurOperation::get_area_of_interest(const int input_idx, break; } case BOKEH_INPUT_INDEX: { - r_input_area.xmax = COM_BLUR_BOKEH_PIXELS; - r_input_area.xmin = 0; - r_input_area.ymax = COM_BLUR_BOKEH_PIXELS; - r_input_area.ymin = 0; + r_input_area = output_area; + r_input_area.xmax = r_input_area.xmin + COM_BLUR_BOKEH_PIXELS; + r_input_area.ymax = r_input_area.ymin + COM_BLUR_BOKEH_PIXELS; break; } #ifdef COM_DEFOCUS_SEARCH @@ -441,7 +440,7 @@ void VariableSizeBokehBlurOperation::update_memory_buffer_partial(MemoryBuffer * /* #InverseSearchRadiusOperation. */ InverseSearchRadiusOperation::InverseSearchRadiusOperation() { - this->addInputSocket(DataType::Value, ResizeMode::None); /* Radius. */ + this->addInputSocket(DataType::Value, ResizeMode::Align); /* Radius. */ this->addOutputSocket(DataType::Color); this->flags.complex = true; this->m_inputRadius = nullptr; diff --git a/source/blender/compositor/operations/COM_VariableSizeBokehBlurOperation.h b/source/blender/compositor/operations/COM_VariableSizeBokehBlurOperation.h index 56b4677087b..3634bc2f1d7 100644 --- a/source/blender/compositor/operations/COM_VariableSizeBokehBlurOperation.h +++ b/source/blender/compositor/operations/COM_VariableSizeBokehBlurOperation.h @@ -130,8 +130,7 @@ class InverseSearchRadiusOperation : public NodeOperation { bool determineDependingAreaOfInterest(rcti *input, ReadBufferOperation *readOperation, rcti *output) override; - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; void setMaxBlur(int maxRadius) { diff --git a/source/blender/compositor/operations/COM_VectorBlurOperation.cc b/source/blender/compositor/operations/COM_VectorBlurOperation.cc index 5405e6d424a..63956410b60 100644 --- a/source/blender/compositor/operations/COM_VectorBlurOperation.cc +++ b/source/blender/compositor/operations/COM_VectorBlurOperation.cc @@ -126,10 +126,7 @@ void VectorBlurOperation::get_area_of_interest(const int UNUSED(input_idx), const rcti &UNUSED(output_area), rcti &r_input_area) { - r_input_area.xmin = 0; - r_input_area.xmax = this->getWidth(); - r_input_area.ymin = 0; - r_input_area.ymax = this->getHeight(); + r_input_area = this->get_canvas(); } void VectorBlurOperation::update_memory_buffer(MemoryBuffer *output, diff --git a/source/blender/compositor/operations/COM_ViewerOperation.cc b/source/blender/compositor/operations/COM_ViewerOperation.cc index a038e8994d8..1faff0fd07f 100644 --- a/source/blender/compositor/operations/COM_ViewerOperation.cc +++ b/source/blender/compositor/operations/COM_ViewerOperation.cc @@ -23,6 +23,7 @@ #include "BLI_math_color.h" #include "BLI_math_vector.h" #include "BLI_utildefines.h" +#include "COM_ExecutionSystem.h" #include "MEM_guardedalloc.h" #include "PIL_time.h" #include "WM_api.h" @@ -34,6 +35,8 @@ namespace blender::compositor { +static int MAX_VIEWER_TRANSLATION_PADDING = 12000; + ViewerOperation::ViewerOperation() { this->setImage(nullptr); @@ -67,7 +70,7 @@ void ViewerOperation::initExecution() this->m_depthInput = getInputSocketReader(2); this->m_doDepthBuffer = (this->m_depthInput != nullptr); - if (isActiveViewerOutput()) { + if (isActiveViewerOutput() && !exec_system_->is_breaked()) { initImage(); } } @@ -122,15 +125,16 @@ void ViewerOperation::executeRegion(rcti *rect, unsigned int /*tileNumber*/) updateImage(rect); } -void ViewerOperation::determineResolution(unsigned int resolution[2], - unsigned int /*preferredResolution*/[2]) +void ViewerOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) { const int sceneRenderWidth = this->m_rd->xsch * this->m_rd->size / 100; const int sceneRenderHeight = this->m_rd->ysch * this->m_rd->size / 100; - unsigned int localPrefRes[2] = {static_cast<unsigned int>(sceneRenderWidth), - static_cast<unsigned int>(sceneRenderHeight)}; - NodeOperation::determineResolution(resolution, localPrefRes); + rcti local_preferred = preferred_area; + local_preferred.xmax = local_preferred.xmin + sceneRenderWidth; + local_preferred.ymax = local_preferred.ymin + sceneRenderHeight; + + NodeOperation::determine_canvas(local_preferred, r_area); } void ViewerOperation::initImage() @@ -155,13 +159,24 @@ void ViewerOperation::initImage() BLI_thread_unlock(LOCK_DRAW_IMAGE); return; } - if (ibuf->x != (int)getWidth() || ibuf->y != (int)getHeight()) { + int padding_x = abs(canvas_.xmin) * 2; + int padding_y = abs(canvas_.ymin) * 2; + if (padding_x > MAX_VIEWER_TRANSLATION_PADDING) { + padding_x = MAX_VIEWER_TRANSLATION_PADDING; + } + if (padding_y > MAX_VIEWER_TRANSLATION_PADDING) { + padding_y = MAX_VIEWER_TRANSLATION_PADDING; + } + + display_width_ = getWidth() + padding_x; + display_height_ = getHeight() + padding_y; + if (ibuf->x != display_width_ || ibuf->y != display_height_) { imb_freerectImBuf(ibuf); imb_freerectfloatImBuf(ibuf); IMB_freezbuffloatImBuf(ibuf); - ibuf->x = getWidth(); - ibuf->y = getHeight(); + ibuf->x = display_width_; + ibuf->y = display_height_; /* zero size can happen if no image buffers exist to define a sensible resolution */ if (ibuf->x > 0 && ibuf->y > 0) { imb_addrectfloatImBuf(ibuf); @@ -193,11 +208,15 @@ void ViewerOperation::initImage() void ViewerOperation::updateImage(const rcti *rect) { + if (exec_system_->is_breaked()) { + return; + } + float *buffer = m_outputBuffer; IMB_partial_display_buffer_update(this->m_ibuf, buffer, nullptr, - getWidth(), + display_width_, 0, 0, this->m_viewSettings, @@ -227,29 +246,46 @@ void ViewerOperation::update_memory_buffer_partial(MemoryBuffer *UNUSED(output), return; } + const int offset_x = area.xmin + (canvas_.xmin > 0 ? canvas_.xmin * 2 : 0); + const int offset_y = area.ymin + (canvas_.ymin > 0 ? canvas_.ymin * 2 : 0); MemoryBuffer output_buffer( - m_outputBuffer, COM_DATA_TYPE_COLOR_CHANNELS, getWidth(), getHeight()); + m_outputBuffer, COM_DATA_TYPE_COLOR_CHANNELS, display_width_, display_height_); const MemoryBuffer *input_image = inputs[0]; - output_buffer.copy_from(input_image, area); + output_buffer.copy_from(input_image, area, offset_x, offset_y); if (this->m_useAlphaInput) { const MemoryBuffer *input_alpha = inputs[1]; - output_buffer.copy_from(input_alpha, area, 0, COM_DATA_TYPE_VALUE_CHANNELS, 3); + output_buffer.copy_from( + input_alpha, area, 0, COM_DATA_TYPE_VALUE_CHANNELS, offset_x, offset_y, 3); } if (m_depthBuffer) { MemoryBuffer depth_buffer( - m_depthBuffer, COM_DATA_TYPE_VALUE_CHANNELS, getWidth(), getHeight()); + m_depthBuffer, COM_DATA_TYPE_VALUE_CHANNELS, display_width_, display_height_); const MemoryBuffer *input_depth = inputs[2]; - depth_buffer.copy_from(input_depth, area); + depth_buffer.copy_from(input_depth, area, offset_x, offset_y); } - updateImage(&area); + rcti display_area; + BLI_rcti_init(&display_area, + offset_x, + offset_x + BLI_rcti_size_x(&area), + offset_y, + offset_y + BLI_rcti_size_y(&area)); + updateImage(&display_area); } void ViewerOperation::clear_display_buffer() { BLI_assert(isActiveViewerOutput()); + if (exec_system_->is_breaked()) { + return; + } + initImage(); + if (m_outputBuffer == nullptr) { + return; + } + size_t buf_bytes = (size_t)m_ibuf->y * m_ibuf->x * COM_DATA_TYPE_COLOR_CHANNELS * sizeof(float); if (buf_bytes > 0) { memset(m_outputBuffer, 0, buf_bytes); diff --git a/source/blender/compositor/operations/COM_ViewerOperation.h b/source/blender/compositor/operations/COM_ViewerOperation.h index 06ac501a535..95ee982f692 100644 --- a/source/blender/compositor/operations/COM_ViewerOperation.h +++ b/source/blender/compositor/operations/COM_ViewerOperation.h @@ -50,13 +50,15 @@ class ViewerOperation : public MultiThreadedOperation { SocketReader *m_alphaInput; SocketReader *m_depthInput; + int display_width_; + int display_height_; + public: ViewerOperation(); void initExecution() override; void deinitExecution() override; void executeRegion(rcti *rect, unsigned int tileNumber) override; - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; bool isOutputOperation(bool /*rendering*/) const override { if (G.background) { diff --git a/source/blender/compositor/operations/COM_WrapOperation.cc b/source/blender/compositor/operations/COM_WrapOperation.cc index 888602114cc..393128ded41 100644 --- a/source/blender/compositor/operations/COM_WrapOperation.cc +++ b/source/blender/compositor/operations/COM_WrapOperation.cc @@ -33,7 +33,7 @@ inline float WrapOperation::getWrappedOriginalXPos(float x) return 0; } while (x < 0) { - x += this->m_width; + x += this->getWidth(); } return fmodf(x, this->getWidth()); } @@ -44,7 +44,7 @@ inline float WrapOperation::getWrappedOriginalYPos(float y) return 0; } while (y < 0) { - y += this->m_height; + y += this->getHeight(); } return fmodf(y, this->getHeight()); } diff --git a/source/blender/compositor/operations/COM_WriteBufferOperation.cc b/source/blender/compositor/operations/COM_WriteBufferOperation.cc index 6380f6bf3df..a1c1e514eb7 100644 --- a/source/blender/compositor/operations/COM_WriteBufferOperation.cc +++ b/source/blender/compositor/operations/COM_WriteBufferOperation.cc @@ -50,7 +50,7 @@ void WriteBufferOperation::executePixelSampled(float output[4], void WriteBufferOperation::initExecution() { this->m_input = this->getInputOperation(0); - this->m_memoryProxy->allocate(this->m_width, this->m_height); + this->m_memoryProxy->allocate(this->getWidth(), this->getHeight()); } void WriteBufferOperation::deinitExecution() @@ -206,18 +206,17 @@ void WriteBufferOperation::executeOpenCLRegion(OpenCLDevice *device, delete clKernelsToCleanUp; } -void WriteBufferOperation::determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) +void WriteBufferOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) { - NodeOperation::determineResolution(resolution, preferredResolution); + NodeOperation::determine_canvas(preferred_area, r_area); /* make sure there is at least one pixel stored in case the input is a single value */ m_single_value = false; - if (resolution[0] == 0) { - resolution[0] = 1; + if (BLI_rcti_size_x(&r_area) == 0) { + r_area.xmax += 1; m_single_value = true; } - if (resolution[1] == 0) { - resolution[1] = 1; + if (BLI_rcti_size_y(&r_area) == 0) { + r_area.ymax += 1; m_single_value = true; } } diff --git a/source/blender/compositor/operations/COM_WriteBufferOperation.h b/source/blender/compositor/operations/COM_WriteBufferOperation.h index 2817fbe24b9..b7dededc69f 100644 --- a/source/blender/compositor/operations/COM_WriteBufferOperation.h +++ b/source/blender/compositor/operations/COM_WriteBufferOperation.h @@ -56,8 +56,7 @@ class WriteBufferOperation : public NodeOperation { unsigned int chunkNumber, MemoryBuffer **memoryBuffers, MemoryBuffer *outputBuffer) override; - void determineResolution(unsigned int resolution[2], - unsigned int preferredResolution[2]) override; + void determine_canvas(const rcti &preferred_area, rcti &r_area) override; void readResolutionFromInputSocket(); inline NodeOperation *getInput() { diff --git a/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc b/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc index 36c6b56caae..a09f79ffa39 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc +++ b/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc @@ -63,6 +63,7 @@ #include "DNA_sound_types.h" #include "DNA_speaker_types.h" #include "DNA_texture_types.h" +#include "DNA_vfont_types.h" #include "DNA_world_types.h" #include "BKE_action.h" @@ -1466,7 +1467,7 @@ void DepsgraphNodeBuilder::build_shapekeys(Key *key) } /* ObData Geometry Evaluation */ -// XXX: what happens if the datablock is shared! +/* XXX: what happens if the datablock is shared! */ void DepsgraphNodeBuilder::build_object_data_geometry(Object *object, bool is_object_visible) { OperationNode *op_node; @@ -1764,6 +1765,9 @@ void DepsgraphNodeBuilder::build_nodetree(bNodeTree *ntree) else if (id_type == ID_MC) { build_movieclip((MovieClip *)id); } + else if (id_type == ID_VF) { + build_vfont((VFont *)id); + } else if (ELEM(bnode->type, NODE_GROUP, NODE_CUSTOM_GROUP)) { bNodeTree *group_ntree = (bNodeTree *)id; build_nodetree(group_ntree); @@ -1780,7 +1784,7 @@ void DepsgraphNodeBuilder::build_nodetree(bNodeTree *ntree) build_idproperties(socket->prop); } - // TODO: link from nodetree to owner_component? + /* TODO: link from nodetree to owner_component? */ } /* Recursively build graph for material */ @@ -2015,6 +2019,17 @@ void DepsgraphNodeBuilder::build_simulation(Simulation *simulation) }); } +void DepsgraphNodeBuilder::build_vfont(VFont *vfont) +{ + if (built_map_.checkIsBuiltAndTag(vfont)) { + return; + } + build_parameters(&vfont->id); + build_idproperties(vfont->id.properties); + add_operation_node( + &vfont->id, NodeType::GENERIC_DATABLOCK, OperationCode::GENERIC_DATABLOCK_UPDATE); +} + static bool seq_node_build_cb(Sequence *seq, void *user_data) { DepsgraphNodeBuilder *nb = (DepsgraphNodeBuilder *)user_data; diff --git a/source/blender/depsgraph/intern/builder/deg_builder_nodes.h b/source/blender/depsgraph/intern/builder/deg_builder_nodes.h index 2378f3fc100..d31290ecbff 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_nodes.h +++ b/source/blender/depsgraph/intern/builder/deg_builder_nodes.h @@ -55,6 +55,7 @@ struct Scene; struct Simulation; struct Speaker; struct Tex; +struct VFont; struct World; struct bAction; struct bArmature; @@ -235,6 +236,7 @@ class DepsgraphNodeBuilder : public DepsgraphBuilder { virtual void build_scene_sequencer(Scene *scene); virtual void build_scene_audio(Scene *scene); virtual void build_scene_speakers(Scene *scene, ViewLayer *view_layer); + virtual void build_vfont(VFont *vfont); /* Per-ID information about what was already in the dependency graph. * Allows to re-use certain values, to speed up following evaluation. */ diff --git a/source/blender/depsgraph/intern/builder/deg_builder_relations.cc b/source/blender/depsgraph/intern/builder/deg_builder_relations.cc index 28cfc5a9e1a..55e8c5ed033 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_relations.cc +++ b/source/blender/depsgraph/intern/builder/deg_builder_relations.cc @@ -65,6 +65,7 @@ #include "DNA_sound_types.h" #include "DNA_speaker_types.h" #include "DNA_texture_types.h" +#include "DNA_vfont_types.h" #include "DNA_volume_types.h" #include "DNA_world_types.h" @@ -2496,6 +2497,11 @@ void DepsgraphRelationBuilder::build_nodetree(bNodeTree *ntree) OperationKey clip_key(id, NodeType::PARAMETERS, OperationCode::MOVIECLIP_EVAL); add_relation(clip_key, shading_key, "Clip -> Node"); } + else if (id_type == ID_VF) { + build_vfont((VFont *)id); + ComponentKey vfont_key(id, NodeType::GENERIC_DATABLOCK); + add_relation(vfont_key, shading_key, "VFont -> Node"); + } else if (ELEM(bnode->type, NODE_GROUP, NODE_CUSTOM_GROUP)) { bNodeTree *group_ntree = (bNodeTree *)id; build_nodetree(group_ntree); @@ -2842,6 +2848,15 @@ void DepsgraphRelationBuilder::build_scene_speakers(Scene * /*scene*/, ViewLayer } } +void DepsgraphRelationBuilder::build_vfont(VFont *vfont) +{ + if (built_map_.checkIsBuiltAndTag(vfont)) { + return; + } + build_parameters(&vfont->id); + build_idproperties(vfont->id.properties); +} + void DepsgraphRelationBuilder::build_copy_on_write_relations() { for (IDNode *id_node : graph_->id_nodes) { diff --git a/source/blender/depsgraph/intern/builder/deg_builder_relations.h b/source/blender/depsgraph/intern/builder/deg_builder_relations.h index 1ad61c25305..f0393544511 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_relations.h +++ b/source/blender/depsgraph/intern/builder/deg_builder_relations.h @@ -72,6 +72,7 @@ struct Simulation; struct Speaker; struct Tex; struct ViewLayer; +struct VFont; struct World; struct bAction; struct bArmature; @@ -296,6 +297,7 @@ class DepsgraphRelationBuilder : public DepsgraphBuilder { virtual void build_scene_sequencer(Scene *scene); virtual void build_scene_audio(Scene *scene); virtual void build_scene_speakers(Scene *scene, ViewLayer *view_layer); + virtual void build_vfont(VFont *vfont); virtual void build_nested_datablock(ID *owner, ID *id); virtual void build_nested_nodetree(ID *owner, bNodeTree *ntree); diff --git a/source/blender/depsgraph/intern/node/deg_node.cc b/source/blender/depsgraph/intern/node/deg_node.cc index fee5342df59..16089ba27dd 100644 --- a/source/blender/depsgraph/intern/node/deg_node.cc +++ b/source/blender/depsgraph/intern/node/deg_node.cc @@ -177,7 +177,7 @@ eDepsSceneComponentType nodeTypeToSceneComponent(NodeType type) case NodeType::SIMULATION: return DEG_SCENE_COMP_PARAMETERS; } - BLI_assert_msg(0, "Unhandled node type, not suppsed to happen."); + BLI_assert_msg(0, "Unhandled node type, not supposed to happen."); return DEG_SCENE_COMP_PARAMETERS; } diff --git a/source/blender/depsgraph/intern/node/deg_node_component.cc b/source/blender/depsgraph/intern/node/deg_node_component.cc index a29618cefa8..0947fad7670 100644 --- a/source/blender/depsgraph/intern/node/deg_node_component.cc +++ b/source/blender/depsgraph/intern/node/deg_node_component.cc @@ -90,7 +90,7 @@ ComponentNode::ComponentNode() void ComponentNode::init(const ID * /*id*/, const char * /*subdata*/) { /* hook up eval context? */ - // XXX: maybe this needs a special API? + /* XXX: maybe this needs a special API? */ } /* Free 'component' node */ diff --git a/source/blender/draw/engines/eevee/eevee_cryptomatte.c b/source/blender/draw/engines/eevee/eevee_cryptomatte.c index 49780abc6f4..ea60dd0753e 100644 --- a/source/blender/draw/engines/eevee/eevee_cryptomatte.c +++ b/source/blender/draw/engines/eevee/eevee_cryptomatte.c @@ -255,7 +255,7 @@ static void eevee_cryptomatte_hair_cache_populate(EEVEE_Data *vedata, { DRWShadingGroup *grp = eevee_cryptomatte_shading_group_create( vedata, sldata, ob, material, true); - DRW_shgroup_hair_create_sub(ob, psys, md, grp); + DRW_shgroup_hair_create_sub(ob, psys, md, grp, NULL); } void EEVEE_cryptomatte_object_hair_cache_populate(EEVEE_Data *vedata, diff --git a/source/blender/draw/engines/eevee/eevee_materials.c b/source/blender/draw/engines/eevee/eevee_materials.c index 9ecb737192e..a627bcd9488 100644 --- a/source/blender/draw/engines/eevee/eevee_materials.c +++ b/source/blender/draw/engines/eevee/eevee_materials.c @@ -769,15 +769,16 @@ static void eevee_hair_cache_populate(EEVEE_Data *vedata, EeveeMaterialCache matcache = eevee_material_cache_get(vedata, sldata, ob, matnr - 1, true); if (matcache.depth_grp) { - *matcache.depth_grp_p = DRW_shgroup_hair_create_sub(ob, psys, md, matcache.depth_grp); + *matcache.depth_grp_p = DRW_shgroup_hair_create_sub(ob, psys, md, matcache.depth_grp, NULL); DRW_shgroup_add_material_resources(*matcache.depth_grp_p, matcache.shading_gpumat); } if (matcache.shading_grp) { - *matcache.shading_grp_p = DRW_shgroup_hair_create_sub(ob, psys, md, matcache.shading_grp); + *matcache.shading_grp_p = DRW_shgroup_hair_create_sub( + ob, psys, md, matcache.shading_grp, matcache.shading_gpumat); DRW_shgroup_add_material_resources(*matcache.shading_grp_p, matcache.shading_gpumat); } if (matcache.shadow_grp) { - *matcache.shadow_grp_p = DRW_shgroup_hair_create_sub(ob, psys, md, matcache.shadow_grp); + *matcache.shadow_grp_p = DRW_shgroup_hair_create_sub(ob, psys, md, matcache.shadow_grp, NULL); DRW_shgroup_add_material_resources(*matcache.shadow_grp_p, matcache.shading_gpumat); *cast_shadow = true; } diff --git a/source/blender/draw/engines/eevee/eevee_motion_blur.c b/source/blender/draw/engines/eevee/eevee_motion_blur.c index 2e200c8e053..e8c2514d908 100644 --- a/source/blender/draw/engines/eevee/eevee_motion_blur.c +++ b/source/blender/draw/engines/eevee/eevee_motion_blur.c @@ -269,7 +269,7 @@ void EEVEE_motion_blur_hair_cache_populate(EEVEE_ViewLayerData *UNUSED(sldata), GPUTexture *tex_prev = mb_hair->psys[psys_id].hair_pos_tx[MB_PREV]; GPUTexture *tex_next = mb_hair->psys[psys_id].hair_pos_tx[MB_NEXT]; - grp = DRW_shgroup_hair_create_sub(ob, psys, md, effects->motion_blur.hair_grp); + grp = DRW_shgroup_hair_create_sub(ob, psys, md, effects->motion_blur.hair_grp, NULL); DRW_shgroup_uniform_mat4(grp, "prevModelMatrix", mb_data->obmat[MB_PREV]); DRW_shgroup_uniform_mat4(grp, "currModelMatrix", mb_data->obmat[MB_CURR]); DRW_shgroup_uniform_mat4(grp, "nextModelMatrix", mb_data->obmat[MB_NEXT]); diff --git a/source/blender/draw/engines/gpencil/gpencil_engine.h b/source/blender/draw/engines/gpencil/gpencil_engine.h index 34fe29055d6..674aca29662 100644 --- a/source/blender/draw/engines/gpencil/gpencil_engine.h +++ b/source/blender/draw/engines/gpencil/gpencil_engine.h @@ -298,14 +298,14 @@ typedef struct GPENCIL_PrivateData { /* Current frame */ int cfra; /* If we are rendering for final render (F12). - * NOTE: set to false for viewport and opengl rendering (including VSE scene rendering), but set - * to true when rendering in `OB_RENDER` shading mode (viewport or opengl rendering) */ + * NOTE: set to false for viewport and opengl rendering (including sequencer scene rendering), + * but set to true when rendering in #OB_RENDER shading mode (viewport or opengl rendering). */ bool is_render; /* If we are in viewport display (used for VFX). */ bool is_viewport; /* True in selection and auto_depth drawing */ bool draw_depth_only; - /* Is shading set to wireframe. */ + /* Is shading set to wire-frame. */ bool draw_wireframe; /* Used by the depth merge step. */ int is_stroke_order_3d; diff --git a/source/blender/draw/engines/overlay/overlay_edit_uv.c b/source/blender/draw/engines/overlay/overlay_edit_uv.c index 985f8a6785c..983df1ceac8 100644 --- a/source/blender/draw/engines/overlay/overlay_edit_uv.c +++ b/source/blender/draw/engines/overlay/overlay_edit_uv.c @@ -340,34 +340,42 @@ void OVERLAY_edit_uv_cache_init(OVERLAY_Data *vedata) if (pd->edit_uv.do_stencil_overlay) { const Brush *brush = BKE_paint_brush(&ts->imapaint.paint); - - DRW_PASS_CREATE(psl->edit_uv_stencil_ps, - DRW_STATE_WRITE_COLOR | DRW_STATE_DEPTH_ALWAYS | DRW_STATE_BLEND_ALPHA_PREMUL); - GPUShader *sh = OVERLAY_shader_edit_uv_stencil_image(); - GPUBatch *geom = DRW_cache_quad_get(); - DRWShadingGroup *grp = DRW_shgroup_create(sh, psl->edit_uv_stencil_ps); Image *stencil_image = brush->clone.image; ImBuf *stencil_ibuf = BKE_image_acquire_ibuf(stencil_image, NULL, &pd->edit_uv.stencil_lock); - pd->edit_uv.stencil_ibuf = stencil_ibuf; - pd->edit_uv.stencil_image = stencil_image; - GPUTexture *stencil_texture = BKE_image_get_gpu_texture(stencil_image, NULL, stencil_ibuf); - DRW_shgroup_uniform_texture(grp, "imgTexture", stencil_texture); - DRW_shgroup_uniform_bool_copy(grp, "imgPremultiplied", true); - DRW_shgroup_uniform_bool_copy(grp, "imgAlphaBlend", true); - DRW_shgroup_uniform_vec4_copy(grp, "color", (float[4]){1.0f, 1.0f, 1.0f, brush->clone.alpha}); - - float size_image[2]; - BKE_image_get_size_fl(image, NULL, size_image); - float size_stencil_image[2] = {stencil_ibuf->x, stencil_ibuf->y}; - float obmat[4][4]; - unit_m4(obmat); - obmat[3][1] = brush->clone.offset[1]; - obmat[3][0] = brush->clone.offset[0]; - obmat[0][0] = size_stencil_image[0] / size_image[0]; - obmat[1][1] = size_stencil_image[1] / size_image[1]; + if (stencil_ibuf == NULL) { + pd->edit_uv.stencil_ibuf = NULL; + pd->edit_uv.stencil_image = NULL; + } + else { + DRW_PASS_CREATE(psl->edit_uv_stencil_ps, + DRW_STATE_WRITE_COLOR | DRW_STATE_DEPTH_ALWAYS | + DRW_STATE_BLEND_ALPHA_PREMUL); + GPUShader *sh = OVERLAY_shader_edit_uv_stencil_image(); + GPUBatch *geom = DRW_cache_quad_get(); + DRWShadingGroup *grp = DRW_shgroup_create(sh, psl->edit_uv_stencil_ps); + pd->edit_uv.stencil_ibuf = stencil_ibuf; + pd->edit_uv.stencil_image = stencil_image; + GPUTexture *stencil_texture = BKE_image_get_gpu_texture(stencil_image, NULL, stencil_ibuf); + DRW_shgroup_uniform_texture(grp, "imgTexture", stencil_texture); + DRW_shgroup_uniform_bool_copy(grp, "imgPremultiplied", true); + DRW_shgroup_uniform_bool_copy(grp, "imgAlphaBlend", true); + DRW_shgroup_uniform_vec4_copy( + grp, "color", (float[4]){1.0f, 1.0f, 1.0f, brush->clone.alpha}); + + float size_image[2]; + BKE_image_get_size_fl(image, NULL, size_image); + float size_stencil_image[2] = {stencil_ibuf->x, stencil_ibuf->y}; + + float obmat[4][4]; + unit_m4(obmat); + obmat[3][1] = brush->clone.offset[1]; + obmat[3][0] = brush->clone.offset[0]; + obmat[0][0] = size_stencil_image[0] / size_image[0]; + obmat[1][1] = size_stencil_image[1] / size_image[1]; - DRW_shgroup_call_obmat(grp, geom, obmat); + DRW_shgroup_call_obmat(grp, geom, obmat); + } } else { pd->edit_uv.stencil_ibuf = NULL; diff --git a/source/blender/draw/engines/overlay/overlay_extra.c b/source/blender/draw/engines/overlay/overlay_extra.c index 8f9db7f6f13..98db7136398 100644 --- a/source/blender/draw/engines/overlay/overlay_extra.c +++ b/source/blender/draw/engines/overlay/overlay_extra.c @@ -696,7 +696,7 @@ void OVERLAY_light_cache_populate(OVERLAY_Data *vedata, Object *ob) /** \} */ /* -------------------------------------------------------------------- */ -/** \name Lightprobe +/** \name Light-probe * \{ */ void OVERLAY_lightprobe_cache_populate(OVERLAY_Data *vedata, Object *ob) diff --git a/source/blender/draw/engines/overlay/overlay_grid.c b/source/blender/draw/engines/overlay/overlay_grid.c index ea8735419f9..cc8ab00baef 100644 --- a/source/blender/draw/engines/overlay/overlay_grid.c +++ b/source/blender/draw/engines/overlay/overlay_grid.c @@ -23,6 +23,7 @@ #include "DRW_render.h" #include "DNA_camera_types.h" +#include "DNA_screen_types.h" #include "DEG_depsgraph_query.h" @@ -46,6 +47,7 @@ enum { GRID_BACK = (1 << 9), GRID_CAMERA = (1 << 10), PLANE_IMAGE = (1 << 11), + CUSTOM_GRID = (1 << 12), }; void OVERLAY_grid_init(OVERLAY_Data *vedata) @@ -61,6 +63,7 @@ void OVERLAY_grid_init(OVERLAY_Data *vedata) if (pd->space_type == SPACE_IMAGE) { SpaceImage *sima = (SpaceImage *)draw_ctx->space_data; + View2D *v2d = &draw_ctx->region->v2d; if (sima->mode == SI_MODE_UV || !ED_space_image_has_buffer(sima)) { shd->grid_flag = GRID_BACK | PLANE_IMAGE | SHOW_GRID; } @@ -68,15 +71,21 @@ void OVERLAY_grid_init(OVERLAY_Data *vedata) shd->grid_flag = 0; } + if (sima->flag & SI_CUSTOM_GRID) { + shd->grid_flag |= CUSTOM_GRID; + } + shd->grid_distance = 1.0f; copy_v3_fl3(shd->grid_size, 1.0f, 1.0f, 1.0f); if (sima->mode == SI_MODE_UV) { shd->grid_size[0] = (float)sima->tile_grid_shape[0]; shd->grid_size[1] = (float)sima->tile_grid_shape[1]; } - for (int step = 0; step < 8; step++) { - shd->grid_steps[step] = powf(4, step) * (1.0f / 16.0f); - } + + const int grid_size = SI_GRID_STEPS_LEN; + shd->zoom_factor = ED_space_image_zoom_level(v2d, grid_size); + ED_space_image_grid_steps(sima, shd->grid_steps, grid_size); + return; } @@ -257,6 +266,7 @@ void OVERLAY_grid_cache_init(OVERLAY_Data *vedata) grp = DRW_shgroup_create(sh, psl->grid_ps); DRW_shgroup_uniform_int(grp, "gridFlag", &shd->grid_flag, 1); + DRW_shgroup_uniform_float_copy(grp, "zoomFactor", shd->zoom_factor); DRW_shgroup_uniform_vec3(grp, "planeAxes", shd->grid_axes, 1); DRW_shgroup_uniform_block(grp, "globalsBlock", G_draw.block_ubo); DRW_shgroup_uniform_texture_ref(grp, "depthBuffer", &dtxl->depth); diff --git a/source/blender/draw/engines/overlay/overlay_private.h b/source/blender/draw/engines/overlay/overlay_private.h index 23df571e8de..def278f98df 100644 --- a/source/blender/draw/engines/overlay/overlay_private.h +++ b/source/blender/draw/engines/overlay/overlay_private.h @@ -139,9 +139,10 @@ typedef struct OVERLAY_ShadingData { /** Grid */ float grid_axes[3], grid_distance; float zplane_axes[3], grid_size[3]; - float grid_steps[8]; + float grid_steps[SI_GRID_STEPS_LEN]; float inv_viewport_size[2]; float grid_line_size; + float zoom_factor; /* Only for UV editor */ int grid_flag; int zpos_flag; int zneg_flag; diff --git a/source/blender/draw/engines/overlay/shaders/grid_frag.glsl b/source/blender/draw/engines/overlay/shaders/grid_frag.glsl index 3220adbff36..9feca644bd3 100644 --- a/source/blender/draw/engines/overlay/shaders/grid_frag.glsl +++ b/source/blender/draw/engines/overlay/shaders/grid_frag.glsl @@ -15,8 +15,9 @@ uniform float lineKernel = 0.0; uniform sampler2D depthBuffer; uniform int gridFlag; +uniform float zoomFactor; -#define STEPS_LEN 8 +#define STEPS_LEN 8 /* Match: #SI_GRID_STEPS_LEN */ uniform float gridSteps[STEPS_LEN] = float[](0.001, 0.01, 0.1, 1.0, 10.0, 100.0, 1000.0, 10000.0); #define AXIS_X (1 << 0) @@ -28,6 +29,8 @@ uniform float gridSteps[STEPS_LEN] = float[](0.001, 0.01, 0.1, 1.0, 10.0, 100.0, #define PLANE_YZ (1 << 6) #define GRID_BACK (1 << 9) /* grid is behind objects */ #define GRID_CAMERA (1 << 10) /* In camera view */ +#define PLANE_IMAGE (1 << 11) /* UV/Image Image editor */ +#define CUSTOM_GRID (1 << 12) /* UV Editor only */ #define M_1_SQRTPI 0.5641895835477563 /* 1/sqrt(pi) */ @@ -122,9 +125,17 @@ void main() * would be more accurate, but not really necessary. */ float grid_res = dot(dFdxPos, screenVecs[0].xyz); - /* The gride begins to appear when it comprises 4 pixels */ + /* The grid begins to appear when it comprises 4 pixels */ grid_res *= 4; + /* For UV/Image editor use zoomFactor */ + if ((gridFlag & PLANE_IMAGE) != 0 && + /* Grid begins to appear when the length of one grid unit is at least + * (256/grid_size) pixels Value of grid_size defined in `overlay_grid.c`. */ + (gridFlag & CUSTOM_GRID) == 0) { + grid_res = zoomFactor; + } + /* from biggest to smallest */ vec4 scale; #if 0 diff --git a/source/blender/draw/engines/workbench/workbench_engine.c b/source/blender/draw/engines/workbench/workbench_engine.c index 635aa7cef25..a5281427fa8 100644 --- a/source/blender/draw/engines/workbench/workbench_engine.c +++ b/source/blender/draw/engines/workbench/workbench_engine.c @@ -238,7 +238,7 @@ static void workbench_cache_hair_populate(WORKBENCH_PrivateData *wpd, workbench_image_hair_setup(wpd, ob, matnr, ima, NULL, state) : workbench_material_hair_setup(wpd, ob, matnr, color_type); - DRW_shgroup_hair_create_sub(ob, psys, md, grp); + DRW_shgroup_hair_create_sub(ob, psys, md, grp, NULL); } /** diff --git a/source/blender/draw/intern/draw_cache_impl_curve.cc b/source/blender/draw/intern/draw_cache_impl_curve.cc index 0804745fab5..dc8f382b7f8 100644 --- a/source/blender/draw/intern/draw_cache_impl_curve.cc +++ b/source/blender/draw/intern/draw_cache_impl_curve.cc @@ -342,6 +342,9 @@ static void curve_cd_calc_used_gpu_layers(CustomDataMask *cd_layers, case CD_ORCO: *cd_layers |= CD_MASK_ORCO; break; + case CD_HAIRLENGTH: + *cd_layers |= CD_MASK_HAIRLENGTH; + break; } } } diff --git a/source/blender/draw/intern/draw_cache_impl_hair.c b/source/blender/draw/intern/draw_cache_impl_hair.c index 6424b21666d..41a0cca8a8f 100644 --- a/source/blender/draw/intern/draw_cache_impl_hair.c +++ b/source/blender/draw/intern/draw_cache_impl_hair.c @@ -27,6 +27,7 @@ #include "MEM_guardedalloc.h" +#include "BLI_listbase.h" #include "BLI_math_base.h" #include "BLI_math_vector.h" #include "BLI_utildefines.h" @@ -37,6 +38,7 @@ #include "BKE_hair.h" #include "GPU_batch.h" +#include "GPU_material.h" #include "GPU_texture.h" #include "draw_cache_impl.h" /* own include */ @@ -141,7 +143,9 @@ static void ensure_seg_pt_count(Hair *hair, ParticleHairCache *hair_cache) } } -static void hair_batch_cache_fill_segments_proc_pos(Hair *hair, GPUVertBufRaw *attr_step) +static void hair_batch_cache_fill_segments_proc_pos(Hair *hair, + GPUVertBufRaw *attr_step, + GPUVertBufRaw *length_step) { /* TODO: use hair radius layer if available. */ HairCurve *curve = hair->curves; @@ -162,6 +166,8 @@ static void hair_batch_cache_fill_segments_proc_pos(Hair *hair, GPUVertBufRaw *a seg_data[3] = total_len; co_prev = curve_co[j]; } + /* Assign length value*/ + *(float *)GPU_vertbuf_raw_step(length_step) = total_len; if (total_len > 0.0f) { /* Divide by total length to have a [0-1] number. */ for (int j = 0; j < curve->numpoints; j++, seg_data_first += 4) { @@ -171,28 +177,48 @@ static void hair_batch_cache_fill_segments_proc_pos(Hair *hair, GPUVertBufRaw *a } } -static void hair_batch_cache_ensure_procedural_pos(Hair *hair, ParticleHairCache *cache) +static void hair_batch_cache_ensure_procedural_pos(Hair *hair, + ParticleHairCache *cache, + GPUMaterial *gpu_material) { - if (cache->proc_point_buf != NULL) { - return; - } + if (cache->proc_point_buf == NULL) { + /* initialize vertex format */ + GPUVertFormat format = {0}; + uint pos_id = GPU_vertformat_attr_add(&format, "posTime", GPU_COMP_F32, 4, GPU_FETCH_FLOAT); - /* initialize vertex format */ - GPUVertFormat format = {0}; - uint pos_id = GPU_vertformat_attr_add(&format, "posTime", GPU_COMP_F32, 4, GPU_FETCH_FLOAT); + cache->proc_point_buf = GPU_vertbuf_create_with_format(&format); + GPU_vertbuf_data_alloc(cache->proc_point_buf, cache->point_len); - cache->proc_point_buf = GPU_vertbuf_create_with_format(&format); - GPU_vertbuf_data_alloc(cache->proc_point_buf, cache->point_len); + GPUVertBufRaw point_step; + GPU_vertbuf_attr_get_raw_data(cache->proc_point_buf, pos_id, &point_step); - GPUVertBufRaw pos_step; - GPU_vertbuf_attr_get_raw_data(cache->proc_point_buf, pos_id, &pos_step); + GPUVertFormat length_format = {0}; + uint length_id = GPU_vertformat_attr_add( + &length_format, "hairLength", GPU_COMP_F32, 1, GPU_FETCH_FLOAT); - hair_batch_cache_fill_segments_proc_pos(hair, &pos_step); + cache->proc_length_buf = GPU_vertbuf_create_with_format(&length_format); + GPU_vertbuf_data_alloc(cache->proc_length_buf, cache->strands_len); - /* Create vbo immediately to bind to texture buffer. */ - GPU_vertbuf_use(cache->proc_point_buf); + GPUVertBufRaw length_step; + GPU_vertbuf_attr_get_raw_data(cache->proc_length_buf, length_id, &length_step); + + hair_batch_cache_fill_segments_proc_pos(hair, &point_step, &length_step); - cache->point_tex = GPU_texture_create_from_vertbuf("hair_point", cache->proc_point_buf); + /* Create vbo immediately to bind to texture buffer. */ + GPU_vertbuf_use(cache->proc_point_buf); + cache->point_tex = GPU_texture_create_from_vertbuf("hair_point", cache->proc_point_buf); + } + + if (gpu_material && cache->proc_length_buf != NULL && cache->length_tex) { + ListBase gpu_attrs = GPU_material_attributes(gpu_material); + LISTBASE_FOREACH (GPUMaterialAttribute *, attr, &gpu_attrs) { + if (attr->type == CD_HAIRLENGTH) { + GPU_vertbuf_use(cache->proc_length_buf); + cache->length_tex = GPU_texture_create_from_vertbuf("hair_length", cache->proc_length_buf); + break; + } + } + } } static void hair_batch_cache_fill_strands_data(Hair *hair, @@ -310,6 +336,7 @@ static void hair_batch_cache_ensure_procedural_indices(Hair *hair, /* Ensure all textures and buffers needed for GPU accelerated drawing. */ bool hair_ensure_procedural_data(Object *object, ParticleHairCache **r_hair_cache, + GPUMaterial *gpu_material, int subdiv, int thickness_res) { @@ -325,7 +352,7 @@ bool hair_ensure_procedural_data(Object *object, /* Refreshed on combing and simulation. */ if ((*r_hair_cache)->proc_point_buf == NULL) { ensure_seg_pt_count(hair, &cache->hair); - hair_batch_cache_ensure_procedural_pos(hair, &cache->hair); + hair_batch_cache_ensure_procedural_pos(hair, &cache->hair, gpu_material); need_ft_update = true; } diff --git a/source/blender/draw/intern/draw_cache_impl_particles.c b/source/blender/draw/intern/draw_cache_impl_particles.c index 5c51f24a435..96bdca7d935 100644 --- a/source/blender/draw/intern/draw_cache_impl_particles.c +++ b/source/blender/draw/intern/draw_cache_impl_particles.c @@ -46,6 +46,7 @@ #include "ED_particle.h" #include "GPU_batch.h" +#include "GPU_material.h" #include "DEG_depsgraph_query.h" @@ -183,7 +184,9 @@ void particle_batch_cache_clear_hair(ParticleHairCache *hair_cache) { /* TODO: more granular update tagging. */ GPU_VERTBUF_DISCARD_SAFE(hair_cache->proc_point_buf); + GPU_VERTBUF_DISCARD_SAFE(hair_cache->proc_length_buf); DRW_TEXTURE_FREE_SAFE(hair_cache->point_tex); + DRW_TEXTURE_FREE_SAFE(hair_cache->length_tex); GPU_VERTBUF_DISCARD_SAFE(hair_cache->proc_strand_buf); GPU_VERTBUF_DISCARD_SAFE(hair_cache->proc_strand_seg_buf); @@ -609,7 +612,8 @@ static int particle_batch_cache_fill_segments(ParticleSystem *psys, static void particle_batch_cache_fill_segments_proc_pos(ParticleCacheKey **path_cache, const int num_path_keys, - GPUVertBufRaw *attr_step) + GPUVertBufRaw *attr_step, + GPUVertBufRaw *length_step) { for (int i = 0; i < num_path_keys; i++) { ParticleCacheKey *path = path_cache[i]; @@ -630,6 +634,8 @@ static void particle_batch_cache_fill_segments_proc_pos(ParticleCacheKey **path_ seg_data[3] = total_len; co_prev = path[j].co; } + /* Assign length value*/ + *(float *)GPU_vertbuf_raw_step(length_step) = total_len; if (total_len > 0.0f) { /* Divide by total length to have a [0-1] number. */ for (int j = 0; j <= path->segments; j++, seg_data_first += 4) { @@ -1079,40 +1085,64 @@ static void particle_batch_cache_ensure_procedural_indices(PTCacheEdit *edit, static void particle_batch_cache_ensure_procedural_pos(PTCacheEdit *edit, ParticleSystem *psys, - ParticleHairCache *cache) + ParticleHairCache *cache, + GPUMaterial *gpu_material) { - if (cache->proc_point_buf != NULL) { - return; - } + if (cache->proc_point_buf == NULL) { + /* initialize vertex format */ + GPUVertFormat pos_format = {0}; + uint pos_id = GPU_vertformat_attr_add( + &pos_format, "posTime", GPU_COMP_F32, 4, GPU_FETCH_FLOAT); - /* initialize vertex format */ - GPUVertFormat format = {0}; - uint pos_id = GPU_vertformat_attr_add(&format, "posTime", GPU_COMP_F32, 4, GPU_FETCH_FLOAT); + cache->proc_point_buf = GPU_vertbuf_create_with_format(&pos_format); + GPU_vertbuf_data_alloc(cache->proc_point_buf, cache->point_len); - cache->proc_point_buf = GPU_vertbuf_create_with_format(&format); - GPU_vertbuf_data_alloc(cache->proc_point_buf, cache->point_len); + GPUVertBufRaw pos_step; + GPU_vertbuf_attr_get_raw_data(cache->proc_point_buf, pos_id, &pos_step); - GPUVertBufRaw pos_step; - GPU_vertbuf_attr_get_raw_data(cache->proc_point_buf, pos_id, &pos_step); + GPUVertFormat length_format = {0}; + uint length_id = GPU_vertformat_attr_add( + &length_format, "hairLength", GPU_COMP_F32, 1, GPU_FETCH_FLOAT); - if (edit != NULL && edit->pathcache != NULL) { - particle_batch_cache_fill_segments_proc_pos(edit->pathcache, edit->totcached, &pos_step); - } - else { - if ((psys->pathcache != NULL) && - (!psys->childcache || (psys->part->draw & PART_DRAW_PARENT))) { - particle_batch_cache_fill_segments_proc_pos(psys->pathcache, psys->totpart, &pos_step); + cache->proc_length_buf = GPU_vertbuf_create_with_format(&length_format); + GPU_vertbuf_data_alloc(cache->proc_length_buf, cache->strands_len); + + GPUVertBufRaw length_step; + GPU_vertbuf_attr_get_raw_data(cache->proc_length_buf, length_id, &length_step); + + if (edit != NULL && edit->pathcache != NULL) { + particle_batch_cache_fill_segments_proc_pos( + edit->pathcache, edit->totcached, &pos_step, &length_step); } - if (psys->childcache) { - const int child_count = psys->totchild * psys->part->disp / 100; - particle_batch_cache_fill_segments_proc_pos(psys->childcache, child_count, &pos_step); + else { + if ((psys->pathcache != NULL) && + (!psys->childcache || (psys->part->draw & PART_DRAW_PARENT))) { + particle_batch_cache_fill_segments_proc_pos( + psys->pathcache, psys->totpart, &pos_step, &length_step); + } + if (psys->childcache) { + const int child_count = psys->totchild * psys->part->disp / 100; + particle_batch_cache_fill_segments_proc_pos( + psys->childcache, child_count, &pos_step, &length_step); + } } - } - /* Create vbo immediately to bind to texture buffer. */ - GPU_vertbuf_use(cache->proc_point_buf); + /* Create vbo immediately to bind to texture buffer. */ + GPU_vertbuf_use(cache->proc_point_buf); + cache->point_tex = GPU_texture_create_from_vertbuf("part_point", cache->proc_point_buf); + } - cache->point_tex = GPU_texture_create_from_vertbuf("part_point", cache->proc_point_buf); + /* Checking hair length separately, only allocating gpu memory when needed. */ + if (gpu_material && cache->proc_length_buf != NULL && cache->length_tex == NULL) { + ListBase gpu_attrs = GPU_material_attributes(gpu_material); + LISTBASE_FOREACH (GPUMaterialAttribute *, attr, &gpu_attrs) { + if (attr->type == CD_HAIRLENGTH) { + GPU_vertbuf_use(cache->proc_length_buf); + cache->length_tex = GPU_texture_create_from_vertbuf("hair_length", cache->proc_length_buf); + break; + } + } + } } static void particle_batch_cache_ensure_pos_and_seg(PTCacheEdit *edit, @@ -1649,6 +1679,7 @@ bool particles_ensure_procedural_data(Object *object, ParticleSystem *psys, ModifierData *md, ParticleHairCache **r_hair_cache, + GPUMaterial *gpu_material, int subdiv, int thickness_res) { @@ -1666,9 +1697,11 @@ bool particles_ensure_procedural_data(Object *object, (*r_hair_cache)->final[subdiv].strands_res = 1 << (part->draw_step + subdiv); /* Refreshed on combing and simulation. */ - if ((*r_hair_cache)->proc_point_buf == NULL) { + if ((*r_hair_cache)->proc_point_buf == NULL || + (gpu_material && (*r_hair_cache)->length_tex == NULL)) { ensure_seg_pt_count(source.edit, source.psys, &cache->hair); - particle_batch_cache_ensure_procedural_pos(source.edit, source.psys, &cache->hair); + particle_batch_cache_ensure_procedural_pos( + source.edit, source.psys, &cache->hair, gpu_material); need_ft_update = true; } diff --git a/source/blender/draw/intern/draw_common.h b/source/blender/draw/intern/draw_common.h index 1eaf2bee236..2913877c9c1 100644 --- a/source/blender/draw/intern/draw_common.h +++ b/source/blender/draw/intern/draw_common.h @@ -29,6 +29,7 @@ struct Object; struct ParticleSystem; struct RegionView3D; struct ViewLayer; +struct GPUMaterial; #define UBO_FIRST_COLOR colorWire #define UBO_LAST_COLOR colorUVShadow @@ -175,7 +176,8 @@ bool DRW_object_axis_orthogonal_to_view(struct Object *ob, int axis); struct DRWShadingGroup *DRW_shgroup_hair_create_sub(struct Object *object, struct ParticleSystem *psys, struct ModifierData *md, - struct DRWShadingGroup *shgrp); + struct DRWShadingGroup *shgrp, + struct GPUMaterial *gpu_material); struct GPUVertBuf *DRW_hair_pos_buffer_get(struct Object *object, struct ParticleSystem *psys, struct ModifierData *md); diff --git a/source/blender/draw/intern/draw_hair.c b/source/blender/draw/intern/draw_hair.c index c2e25389091..5c7eb083fc9 100644 --- a/source/blender/draw/intern/draw_hair.c +++ b/source/blender/draw/intern/draw_hair.c @@ -38,6 +38,7 @@ #include "GPU_batch.h" #include "GPU_capabilities.h" #include "GPU_compute.h" +#include "GPU_material.h" #include "GPU_shader.h" #include "GPU_texture.h" #include "GPU_vertex_buffer.h" @@ -172,18 +173,23 @@ static void drw_hair_particle_cache_update_transform_feedback(ParticleHairCache } } -static ParticleHairCache *drw_hair_particle_cache_get( - Object *object, ParticleSystem *psys, ModifierData *md, int subdiv, int thickness_res) +static ParticleHairCache *drw_hair_particle_cache_get(Object *object, + ParticleSystem *psys, + ModifierData *md, + GPUMaterial *gpu_material, + int subdiv, + int thickness_res) { bool update; ParticleHairCache *cache; if (psys) { /* Old particle hair. */ - update = particles_ensure_procedural_data(object, psys, md, &cache, subdiv, thickness_res); + update = particles_ensure_procedural_data( + object, psys, md, &cache, gpu_material, subdiv, thickness_res); } else { /* New hair object. */ - update = hair_ensure_procedural_data(object, &cache, subdiv, thickness_res); + update = hair_ensure_procedural_data(object, &cache, gpu_material, subdiv, thickness_res); } if (update) { @@ -206,7 +212,8 @@ GPUVertBuf *DRW_hair_pos_buffer_get(Object *object, ParticleSystem *psys, Modifi int subdiv = scene->r.hair_subdiv; int thickness_res = (scene->r.hair_type == SCE_HAIR_SHAPE_STRAND) ? 1 : 2; - ParticleHairCache *cache = drw_hair_particle_cache_get(object, psys, md, subdiv, thickness_res); + ParticleHairCache *cache = drw_hair_particle_cache_get( + object, psys, md, NULL, subdiv, thickness_res); return cache->final[subdiv].proc_buf; } @@ -248,7 +255,8 @@ void DRW_hair_duplimat_get(Object *object, DRWShadingGroup *DRW_shgroup_hair_create_sub(Object *object, ParticleSystem *psys, ModifierData *md, - DRWShadingGroup *shgrp_parent) + DRWShadingGroup *shgrp_parent, + GPUMaterial *gpu_material) { const DRWContextState *draw_ctx = DRW_context_state_get(); Scene *scene = draw_ctx->scene; @@ -258,7 +266,7 @@ DRWShadingGroup *DRW_shgroup_hair_create_sub(Object *object, int thickness_res = (scene->r.hair_type == SCE_HAIR_SHAPE_STRAND) ? 1 : 2; ParticleHairCache *hair_cache = drw_hair_particle_cache_get( - object, psys, md, subdiv, thickness_res); + object, psys, md, gpu_material, subdiv, thickness_res); DRWShadingGroup *shgrp = DRW_shgroup_create_sub(shgrp_parent); @@ -308,6 +316,9 @@ DRWShadingGroup *DRW_shgroup_hair_create_sub(Object *object, } DRW_shgroup_uniform_texture(shgrp, "hairPointBuffer", hair_cache->final[subdiv].proc_tex); + if (hair_cache->length_tex) { + DRW_shgroup_uniform_texture(shgrp, "hairLen", hair_cache->length_tex); + } DRW_shgroup_uniform_int(shgrp, "hairStrandsRes", &hair_cache->final[subdiv].strands_res, 1); DRW_shgroup_uniform_int_copy(shgrp, "hairThicknessRes", thickness_res); DRW_shgroup_uniform_float_copy(shgrp, "hairRadShape", hair_rad_shape); diff --git a/source/blender/draw/intern/draw_hair_private.h b/source/blender/draw/intern/draw_hair_private.h index 1f58d8d0ead..289a1690fc6 100644 --- a/source/blender/draw/intern/draw_hair_private.h +++ b/source/blender/draw/intern/draw_hair_private.h @@ -66,6 +66,10 @@ typedef struct ParticleHairCache { GPUVertBuf *proc_strand_buf; GPUTexture *strand_tex; + /* Hair Length */ + GPUVertBuf *proc_length_buf; + GPUTexture *length_tex; + GPUVertBuf *proc_strand_seg_buf; GPUTexture *strand_seg_tex; @@ -93,11 +97,13 @@ bool particles_ensure_procedural_data(struct Object *object, struct ParticleSystem *psys, struct ModifierData *md, struct ParticleHairCache **r_hair_cache, + struct GPUMaterial *gpu_material, int subdiv, int thickness_res); bool hair_ensure_procedural_data(struct Object *object, struct ParticleHairCache **r_hair_cache, + struct GPUMaterial *gpu_material, int subdiv, int thickness_res); diff --git a/source/blender/draw/intern/shaders/common_hair_lib.glsl b/source/blender/draw/intern/shaders/common_hair_lib.glsl index 02c335ddae2..6cc7f09a852 100644 --- a/source/blender/draw/intern/shaders/common_hair_lib.glsl +++ b/source/blender/draw/intern/shaders/common_hair_lib.glsl @@ -210,6 +210,12 @@ void hair_get_pos_tan_binor_time(bool is_persp, } } +float hair_get_customdata_float(const samplerBuffer cd_buf) +{ + int id = hair_get_strand_id(); + return texelFetch(cd_buf, id).r; +} + vec2 hair_get_customdata_vec2(const samplerBuffer cd_buf) { int id = hair_get_strand_id(); diff --git a/source/blender/editors/animation/anim_filter.c b/source/blender/editors/animation/anim_filter.c index b12e0ae5cab..e1d046428a8 100644 --- a/source/blender/editors/animation/anim_filter.c +++ b/source/blender/editors/animation/anim_filter.c @@ -3087,7 +3087,10 @@ static size_t animdata_filter_dopesheet_movieclips(bAnimContext *ac, } /* Helper for animdata_filter_dopesheet() - For checking if an object should be included or not */ -static bool animdata_filter_base_is_ok(bDopeSheet *ads, Base *base, int filter_mode) +static bool animdata_filter_base_is_ok(bDopeSheet *ads, + Base *base, + const eObjectMode object_mode, + int filter_mode) { Object *ob = base->object; @@ -3144,10 +3147,21 @@ static bool animdata_filter_base_is_ok(bDopeSheet *ads, Base *base, int filter_m } /* check selection and object type filters */ - if ((ads->filterflag & ADS_FILTER_ONLYSEL) && - !((base->flag & BASE_SELECTED) /*|| (base == sce->basact) */)) { - /* only selected should be shown */ - return false; + if (ads->filterflag & ADS_FILTER_ONLYSEL) { + if (object_mode & OB_MODE_POSE) { + /* When in pose-mode handle all pose-mode objects. + * This avoids problems with pose-mode where objects may be unselected, + * where a selected bone of an unselected object would be hidden. see: T81922. */ + if (!(base->object->mode & object_mode)) { + return false; + } + } + else { + /* only selected should be shown (ignore active) */ + if (!(base->flag & BASE_SELECTED)) { + return false; + } + } } /* check if object belongs to the filtering group if option to filter @@ -3185,7 +3199,7 @@ static Base **animdata_filter_ds_sorted_bases(bDopeSheet *ads, Base **sorted_bases = MEM_mallocN(sizeof(Base *) * tot_bases, "Dopesheet Usable Sorted Bases"); LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { - if (animdata_filter_base_is_ok(ads, base, filter_mode)) { + if (animdata_filter_base_is_ok(ads, base, OB_MODE_OBJECT, filter_mode)) { sorted_bases[num_bases++] = base; } } @@ -3278,8 +3292,10 @@ static size_t animdata_filter_dopesheet(bAnimContext *ac, /* Filter and add contents of each base (i.e. object) without them sorting first * NOTE: This saves performance in cases where order doesn't matter */ + Object *obact = OBACT(view_layer); + const eObjectMode object_mode = obact ? obact->mode : OB_MODE_OBJECT; LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { - if (animdata_filter_base_is_ok(ads, base, filter_mode)) { + if (animdata_filter_base_is_ok(ads, base, object_mode, filter_mode)) { /* since we're still here, this object should be usable */ items += animdata_filter_dopesheet_ob(ac, anim_data, ads, base, filter_mode); } diff --git a/source/blender/editors/animation/anim_ipo_utils.c b/source/blender/editors/animation/anim_ipo_utils.c index 33b4882927a..6fe32699907 100644 --- a/source/blender/editors/animation/anim_ipo_utils.c +++ b/source/blender/editors/animation/anim_ipo_utils.c @@ -126,9 +126,10 @@ int getname_anim_fcurve(char *name, ID *id, FCurve *fcu) structname = RNA_struct_ui_name(ptr.type); } - /* For the VSE, a strip's 'Transform' or 'Crop' is a nested (under Sequence) struct, but - * displaying the struct name alone is no meaningful information (and also cannot be - * filtered well), same for modifiers. So display strip name alongside as well. */ + /* For the sequencer, a strip's 'Transform' or 'Crop' is a nested (under Sequence) + * struct, but displaying the struct name alone is no meaningful information + * (and also cannot be filtered well), same for modifiers. + * So display strip name alongside as well. */ if (GS(ptr.owner_id->name) == ID_SCE) { char stripname[256]; if (BLI_str_quoted_substr( diff --git a/source/blender/editors/animation/anim_ops.c b/source/blender/editors/animation/anim_ops.c index b4ea33920b2..3958c7f9e34 100644 --- a/source/blender/editors/animation/anim_ops.c +++ b/source/blender/editors/animation/anim_ops.c @@ -74,9 +74,16 @@ static bool change_frame_poll(bContext *C) * this shouldn't show up in 3D editor (or others without 2D timeline view) via search */ if (area) { - if (ELEM(area->spacetype, SPACE_ACTION, SPACE_NLA, SPACE_SEQ, SPACE_CLIP, SPACE_GRAPH)) { + if (ELEM(area->spacetype, SPACE_ACTION, SPACE_NLA, SPACE_SEQ, SPACE_CLIP)) { return true; } + if (area->spacetype == SPACE_GRAPH) { + const SpaceGraph *sipo = area->spacedata.first; + /* Driver Editor's X axis is not time. */ + if (sipo->mode != SIPO_MODE_DRIVERS) { + return true; + } + } } CTX_wm_operator_poll_msg_set(C, "Expected an animation area to be active"); diff --git a/source/blender/editors/animation/drivers.c b/source/blender/editors/animation/drivers.c index bfaa76b3bf9..dbf379971fa 100644 --- a/source/blender/editors/animation/drivers.c +++ b/source/blender/editors/animation/drivers.c @@ -1115,7 +1115,7 @@ static int add_driver_button_invoke(bContext *C, wmOperator *op, const wmEvent * } /* 2) Show editing panel for setting up this driver */ - /* TODO: Use a different one from the editing popever, so we can have the single/all toggle? */ + /* TODO: Use a different one from the editing popover, so we can have the single/all toggle? */ UI_popover_panel_invoke(C, "GRAPH_PT_drivers_popover", true, op->reports); } diff --git a/source/blender/editors/animation/keyframes_draw.c b/source/blender/editors/animation/keyframes_draw.c index ac7db9f4f46..e3ea8f0ab21 100644 --- a/source/blender/editors/animation/keyframes_draw.c +++ b/source/blender/editors/animation/keyframes_draw.c @@ -146,31 +146,31 @@ void draw_keyframe_shape(float x, /* Handle type to outline shape. */ switch (handle_type) { case KEYFRAME_HANDLE_AUTO_CLAMP: - flags = 0x2; + flags = GPU_KEYFRAME_SHAPE_CIRCLE; break; /* circle */ case KEYFRAME_HANDLE_AUTO: - flags = 0x12; + flags = GPU_KEYFRAME_SHAPE_CIRCLE | GPU_KEYFRAME_SHAPE_INNER_DOT; break; /* circle with dot */ case KEYFRAME_HANDLE_VECTOR: - flags = 0xC; + flags = GPU_KEYFRAME_SHAPE_SQUARE; break; /* square */ case KEYFRAME_HANDLE_ALIGNED: - flags = 0x5; + flags = GPU_KEYFRAME_SHAPE_DIAMOND | GPU_KEYFRAME_SHAPE_CLIPPED_VERTICAL; break; /* clipped diamond */ case KEYFRAME_HANDLE_FREE: default: - flags = 1; /* diamond */ + flags = GPU_KEYFRAME_SHAPE_DIAMOND; /* diamond */ } /* Extreme type to arrow-like shading. */ if (extreme_type & KEYFRAME_EXTREME_MAX) { - flags |= 0x100; + flags |= GPU_KEYFRAME_SHAPE_ARROW_END_MAX; } if (extreme_type & KEYFRAME_EXTREME_MIN) { - flags |= 0x200; + flags |= GPU_KEYFRAME_SHAPE_ARROW_END_MIN; } - if (extreme_type & KEYFRAME_EXTREME_MIXED) { + if (extreme_type & GPU_KEYFRAME_SHAPE_ARROW_END_MIXED) { flags |= 0x400; } } @@ -584,7 +584,7 @@ static void ED_keylist_draw_list_draw_keys(AnimKeylistDrawList *draw_list, View2 sh_bindings.flags_id = GPU_vertformat_attr_add(format, "flags", GPU_COMP_U32, 1, GPU_FETCH_INT); GPU_program_point_size(true); - immBindBuiltinProgram(GPU_SHADER_KEYFRAME_DIAMOND); + immBindBuiltinProgram(GPU_SHADER_KEYFRAME_SHAPE); immUniform1f("outline_scale", 1.0f); immUniform2f("ViewportSize", BLI_rcti_size_x(&v2d->mask) + 1, BLI_rcti_size_y(&v2d->mask) + 1); immBegin(GPU_PRIM_POINTS, visible_key_len); diff --git a/source/blender/editors/animation/keyframing.c b/source/blender/editors/animation/keyframing.c index 8dc4aed9f0e..1ef7ee755ea 100644 --- a/source/blender/editors/animation/keyframing.c +++ b/source/blender/editors/animation/keyframing.c @@ -2107,10 +2107,12 @@ static int delete_key_using_keying_set(bContext *C, wmOperator *op, KeyingSet *k return OPERATOR_CANCELLED; } + PropertyRNA *prop = RNA_struct_find_property(op->ptr, "confirm_success"); + bool confirm = (prop != NULL && RNA_property_boolean_get(op->ptr, prop)); + if (num_channels > 0) { /* if the appropriate properties have been set, make a note that we've inserted something */ - PropertyRNA *prop = RNA_struct_find_property(op->ptr, "confirm_success"); - if (prop != NULL && RNA_property_boolean_get(op->ptr, prop)) { + if (confirm) { BKE_reportf(op->reports, RPT_INFO, "Successfully removed %d keyframes for keying set '%s'", @@ -2121,7 +2123,7 @@ static int delete_key_using_keying_set(bContext *C, wmOperator *op, KeyingSet *k /* send notifiers that keyframes have been changed */ WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_REMOVED, NULL); } - else { + else if (confirm) { BKE_report(op->reports, RPT_WARNING, "Keying set failed to remove any keyframes"); } @@ -2289,6 +2291,8 @@ static int delete_key_v3d_without_keying_set(bContext *C, wmOperator *op) int selected_objects_success_len = 0; int success_multi = 0; + bool confirm = op->flag & OP_IS_INVOKE; + CTX_DATA_BEGIN (C, Object *, ob, selected_objects) { ID *id = &ob->id; int success = 0; @@ -2370,20 +2374,20 @@ static int delete_key_v3d_without_keying_set(bContext *C, wmOperator *op) /* report success (or failure) */ if (selected_objects_success_len) { - BKE_reportf(op->reports, - RPT_INFO, - "%d object(s) successfully had %d keyframes removed", - selected_objects_success_len, - success_multi); + if (confirm) { + BKE_reportf(op->reports, + RPT_INFO, + "%d object(s) successfully had %d keyframes removed", + selected_objects_success_len, + success_multi); + } + /* send updates */ + WM_event_add_notifier(C, NC_OBJECT | ND_KEYS, NULL); } - else { + else if (confirm) { BKE_reportf( op->reports, RPT_ERROR, "No keyframes removed from %d object(s)", selected_objects_len); } - - /* send updates */ - WM_event_add_notifier(C, NC_OBJECT | ND_KEYS, NULL); - return OPERATOR_FINISHED; } diff --git a/source/blender/editors/animation/keyingsets.c b/source/blender/editors/animation/keyingsets.c index 0206aabd359..e1fd3b07f46 100644 --- a/source/blender/editors/animation/keyingsets.c +++ b/source/blender/editors/animation/keyingsets.c @@ -610,7 +610,7 @@ void ANIM_keyingset_info_unregister(Main *bmain, KeyingSetInfo *ksi) KeyingSet *ks, *ksn; /* find relevant builtin KeyingSets which use this, and remove them */ - /* TODO: this isn't done now, since unregister is really only used atm when we + /* TODO: this isn't done now, since unregister is really only used at the moment when we * reload the scripts, which kindof defeats the purpose of "builtin"? */ for (ks = builtin_keyingsets.first; ks; ks = ksn) { ksn = ks->next; diff --git a/source/blender/editors/animation/time_scrub_ui.c b/source/blender/editors/animation/time_scrub_ui.c index 8aeb6a57124..b0eb014c5d9 100644 --- a/source/blender/editors/animation/time_scrub_ui.c +++ b/source/blender/editors/animation/time_scrub_ui.c @@ -91,8 +91,7 @@ static void draw_current_frame(const Scene *scene, bool display_seconds, const View2D *v2d, const rcti *scrub_region_rect, - int current_frame, - bool draw_line) + int current_frame) { const uiFontStyle *fstyle = UI_FSTYLE_WIDGET; int frame_x = UI_view2d_view_to_region_x(v2d, current_frame); @@ -106,21 +105,18 @@ static void draw_current_frame(const Scene *scene, float bg_color[4]; UI_GetThemeColorShade4fv(TH_CFRAME, -5, bg_color); - if (draw_line) { - /* Draw vertical line to from the bottom of the current frame box to the bottom of the screen. - */ - const float subframe_x = UI_view2d_view_to_region_x(v2d, BKE_scene_ctime_get(scene)); - GPUVertFormat *format = immVertexFormat(); - uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); - immUniformThemeColor(TH_CFRAME); - immRectf(pos, - subframe_x - U.pixelsize, - scrub_region_rect->ymax - box_padding, - subframe_x + U.pixelsize, - 0.0f); - immUnbindProgram(); - } + /* Draw vertical line from the bottom of the current frame box to the bottom of the screen. */ + const float subframe_x = UI_view2d_view_to_region_x(v2d, BKE_scene_ctime_get(scene)); + GPUVertFormat *format = immVertexFormat(); + uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); + immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immUniformThemeColor(TH_CFRAME); + immRectf(pos, + subframe_x - U.pixelsize, + scrub_region_rect->ymax - box_padding, + subframe_x + U.pixelsize, + 0.0f); + immUnbindProgram(); UI_draw_roundbox_corner_set(UI_CNR_ALL); @@ -152,8 +148,7 @@ static void draw_current_frame(const Scene *scene, void ED_time_scrub_draw_current_frame(const ARegion *region, const Scene *scene, - bool display_seconds, - bool draw_line) + bool display_seconds) { const View2D *v2d = ®ion->v2d; GPU_matrix_push_projection(); @@ -162,7 +157,7 @@ void ED_time_scrub_draw_current_frame(const ARegion *region, rcti scrub_region_rect; get_time_scrub_region_rect(region, &scrub_region_rect); - draw_current_frame(scene, display_seconds, v2d, &scrub_region_rect, scene->r.cfra, draw_line); + draw_current_frame(scene, display_seconds, v2d, &scrub_region_rect, scene->r.cfra); GPU_matrix_pop_projection(); } diff --git a/source/blender/editors/armature/pose_transform.c b/source/blender/editors/armature/pose_transform.c index 3798ca308ed..279f79ac44b 100644 --- a/source/blender/editors/armature/pose_transform.c +++ b/source/blender/editors/armature/pose_transform.c @@ -1184,7 +1184,7 @@ static int pose_clear_transform_generic_exec(bContext *C, ViewLayer *view_layer = CTX_data_view_layer(C); View3D *v3d = CTX_wm_view3d(C); FOREACH_OBJECT_IN_MODE_BEGIN (view_layer, v3d, OB_ARMATURE, OB_MODE_POSE, ob_iter) { - /* XXX: UGLY HACK (for autokey + clear transforms) */ + /* XXX: UGLY HACK (for auto-key + clear transforms). */ Object *ob_eval = DEG_get_evaluated_object(depsgraph, ob_iter); ListBase dsources = {NULL, NULL}; bool changed = false; diff --git a/source/blender/editors/asset/CMakeLists.txt b/source/blender/editors/asset/CMakeLists.txt index 31c07580570..b6657bfca63 100644 --- a/source/blender/editors/asset/CMakeLists.txt +++ b/source/blender/editors/asset/CMakeLists.txt @@ -31,6 +31,7 @@ set(INC_SYS ) set(SRC + intern/asset_catalog.cc intern/asset_filter.cc intern/asset_handle.cc intern/asset_library_reference.cc @@ -40,6 +41,7 @@ set(SRC intern/asset_ops.cc intern/asset_temp_id_consumer.cc + ED_asset_catalog.hh ED_asset_filter.h ED_asset_handle.h ED_asset_library.h diff --git a/source/blender/editors/asset/ED_asset_catalog.hh b/source/blender/editors/asset/ED_asset_catalog.hh new file mode 100644 index 00000000000..cffd7728a60 --- /dev/null +++ b/source/blender/editors/asset/ED_asset_catalog.hh @@ -0,0 +1,35 @@ +/* + * 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 edasset + */ + +#pragma once + +#include "BKE_asset_catalog.hh" + +#include "BLI_string_ref.hh" + +struct AssetLibrary; +namespace blender::bke { +class AssetCatalog; +} // namespace blender::bke + +blender::bke::AssetCatalog *ED_asset_catalog_add(AssetLibrary *library, + blender::StringRefNull name, + blender::StringRef parent_path = nullptr); +void ED_asset_catalog_remove(AssetLibrary *library, const blender::bke::CatalogID &catalog_id); diff --git a/source/blender/editors/asset/ED_asset_mark_clear.h b/source/blender/editors/asset/ED_asset_mark_clear.h index 7524ec6b02a..d8b8f15a109 100644 --- a/source/blender/editors/asset/ED_asset_mark_clear.h +++ b/source/blender/editors/asset/ED_asset_mark_clear.h @@ -27,7 +27,22 @@ extern "C" { struct ID; struct bContext; +/** + * Mark the datablock as asset. + * + * To ensure the datablock is saved, this sets Fake User. + * + * \return whether the datablock was marked as asset; false when it is not capable of becoming an + * asset, or when it already was an asset. */ bool ED_asset_mark_id(const struct bContext *C, struct ID *id); + +/** + * Remove the asset metadata, turning the ID into a "normal" ID. + * + * This clears the Fake User. If for some reason the datablock is meant to be saved anyway, the + * caller is responsible for explicitly setting the Fake User. + * + * \return whether the asset metadata was actually removed; false when the ID was not an asset. */ bool ED_asset_clear_id(struct ID *id); bool ED_asset_can_mark_single_from_context(const struct bContext *C); diff --git a/source/blender/editors/asset/intern/asset_catalog.cc b/source/blender/editors/asset/intern/asset_catalog.cc new file mode 100644 index 00000000000..6e49ca2dd5c --- /dev/null +++ b/source/blender/editors/asset/intern/asset_catalog.cc @@ -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 edasset + */ + +#include "BKE_asset_catalog.hh" +#include "BKE_asset_catalog_path.hh" +#include "BKE_asset_library.hh" + +#include "BLI_string_utils.h" + +#include "ED_asset_catalog.hh" + +using namespace blender; +using namespace blender::bke; + +struct CatalogUniqueNameFnData { + const AssetCatalogService &catalog_service; + StringRef parent_path; +}; + +static bool catalog_name_exists_fn(void *arg, const char *name) +{ + CatalogUniqueNameFnData &fn_data = *static_cast<CatalogUniqueNameFnData *>(arg); + AssetCatalogPath fullpath = AssetCatalogPath(fn_data.parent_path) / name; + return fn_data.catalog_service.find_catalog_by_path(fullpath); +} + +static std::string catalog_name_ensure_unique(AssetCatalogService &catalog_service, + StringRefNull name, + StringRef parent_path) +{ + CatalogUniqueNameFnData fn_data = {catalog_service, parent_path}; + + char unique_name[MAX_NAME] = ""; + BLI_uniquename_cb( + catalog_name_exists_fn, &fn_data, name.c_str(), '.', unique_name, sizeof(unique_name)); + + return unique_name; +} + +AssetCatalog *ED_asset_catalog_add(::AssetLibrary *library, + StringRefNull name, + StringRef parent_path) +{ + bke::AssetCatalogService *catalog_service = BKE_asset_library_get_catalog_service(library); + if (!catalog_service) { + return nullptr; + } + + std::string unique_name = catalog_name_ensure_unique(*catalog_service, name, parent_path); + AssetCatalogPath fullpath = AssetCatalogPath(parent_path) / unique_name; + + return catalog_service->create_catalog(fullpath); +} + +void ED_asset_catalog_remove(::AssetLibrary *library, const CatalogID &catalog_id) +{ + bke::AssetCatalogService *catalog_service = BKE_asset_library_get_catalog_service(library); + if (!catalog_service) { + BLI_assert_unreachable(); + return; + } + + catalog_service->delete_catalog(catalog_id); +} diff --git a/source/blender/editors/asset/intern/asset_list.cc b/source/blender/editors/asset/intern/asset_list.cc index 57c25e1614b..400b3572c9b 100644 --- a/source/blender/editors/asset/intern/asset_list.cc +++ b/source/blender/editors/asset/intern/asset_list.cc @@ -157,7 +157,7 @@ void AssetList::setup() /* Relevant bits from file_refresh(). */ /* TODO pass options properly. */ - filelist_setrecursion(files, 1); + filelist_setrecursion(files, FILE_SELECT_MAX_RECURSIONS); filelist_setsorting(files, FILE_SORT_ALPHA, false); filelist_setlibrary(files, &library_ref_); filelist_setfilter_options( @@ -390,7 +390,7 @@ std::optional<eFileSelectType> AssetListStorage::asset_library_reference_to_file { switch (library_reference.type) { case ASSET_LIBRARY_CUSTOM: - return FILE_LOADLIB; + return FILE_ASSET_LIBRARY; case ASSET_LIBRARY_LOCAL: return FILE_MAIN_ASSET; } diff --git a/source/blender/editors/asset/intern/asset_mark_clear.cc b/source/blender/editors/asset/intern/asset_mark_clear.cc index ba348e38823..8290124c209 100644 --- a/source/blender/editors/asset/intern/asset_mark_clear.cc +++ b/source/blender/editors/asset/intern/asset_mark_clear.cc @@ -67,8 +67,7 @@ bool ED_asset_clear_id(ID *id) return false; } BKE_asset_metadata_free(&id->asset_data); - /* Don't clear fake user here, there's no guarantee that it was actually set by - * #ED_asset_mark_id(), it might have been something/someone else. */ + id_fake_user_clear(id); /* Important for asset storage to update properly! */ ED_assetlist_storage_tag_main_data_dirty(); diff --git a/source/blender/editors/asset/intern/asset_ops.cc b/source/blender/editors/asset/intern/asset_ops.cc index d69a2cae94d..5424bae77b4 100644 --- a/source/blender/editors/asset/intern/asset_ops.cc +++ b/source/blender/editors/asset/intern/asset_ops.cc @@ -18,27 +18,55 @@ * \ingroup edasset */ +#include "BKE_asset_catalog.hh" #include "BKE_context.h" +#include "BKE_lib_id.h" #include "BKE_report.h" +#include "BLI_string_ref.hh" #include "BLI_vector.hh" #include "ED_asset.h" +#include "ED_asset_catalog.hh" +/* XXX needs access to the file list, should all be done via the asset system in future. */ +#include "ED_fileselect.h" #include "RNA_access.h" +#include "RNA_define.h" #include "WM_api.h" #include "WM_types.h" +using namespace blender; + /* -------------------------------------------------------------------- */ using PointerRNAVec = blender::Vector<PointerRNA>; +static PointerRNAVec asset_operation_get_ids_from_context(const bContext *C); +static PointerRNAVec asset_operation_get_nonexperimental_ids_from_context(const bContext *C); +static bool asset_type_is_nonexperimental(const ID_Type id_type); + static bool asset_operation_poll(bContext * /*C*/) { + /* At this moment only the pose library is non-experimental. Still, directly marking arbitrary + * Actions as asset is not part of the stable functionality; instead, the pose library "Create + * Pose Asset" operator should be used. Actions can still be marked as asset via + * `the_action.asset_mark()` (so a function call instead of this operator), which is what the + * pose library uses internally. */ return U.experimental.use_extended_asset_browser; } +static bool asset_clear_poll(bContext *C) +{ + if (asset_operation_poll(C)) { + return true; + } + + PointerRNAVec pointers = asset_operation_get_nonexperimental_ids_from_context(C); + return !pointers.is_empty(); +} + /** * Return the IDs to operate on as PointerRNA vector. Either a single one ("id" context member) or * multiple ones ("selected_ids" context member). @@ -64,6 +92,28 @@ static PointerRNAVec asset_operation_get_ids_from_context(const bContext *C) return ids; } +static PointerRNAVec asset_operation_get_nonexperimental_ids_from_context(const bContext *C) +{ + PointerRNAVec nonexperimental; + PointerRNAVec pointers = asset_operation_get_ids_from_context(C); + for (PointerRNA &ptr : pointers) { + BLI_assert(RNA_struct_is_ID(ptr.type)); + + ID *id = static_cast<ID *>(ptr.data); + if (asset_type_is_nonexperimental(GS(id->name))) { + nonexperimental.append(ptr); + } + } + return nonexperimental; +} + +static bool asset_type_is_nonexperimental(const ID_Type id_type) +{ + /* At this moment only the pose library is non-experimental. For simplicity, allow asset + * operations on all Action datablocks (even though pose assets are limited to single frames). */ + return ELEM(id_type, ID_AC); +} + /* -------------------------------------------------------------------- */ class AssetMarkHelper { @@ -166,7 +216,13 @@ static void ASSET_OT_mark(wmOperatorType *ot) /* -------------------------------------------------------------------- */ class AssetClearHelper { + const bool set_fake_user_; + public: + AssetClearHelper(const bool set_fake_user) : set_fake_user_(set_fake_user) + { + } + void operator()(PointerRNAVec &ids); void reportResults(const bContext *C, ReportList &reports) const; @@ -191,10 +247,16 @@ void AssetClearHelper::operator()(PointerRNAVec &ids) continue; } - if (ED_asset_clear_id(id)) { - stats.tot_cleared++; - stats.last_id = id; + if (!ED_asset_clear_id(id)) { + continue; } + + if (set_fake_user_) { + id_fake_user_set(id); + } + + stats.tot_cleared++; + stats.last_id = id; } } @@ -234,7 +296,8 @@ static int asset_clear_exec(bContext *C, wmOperator *op) { PointerRNAVec ids = asset_operation_get_ids_from_context(C); - AssetClearHelper clear_helper; + const bool set_fake_user = RNA_boolean_get(op->ptr, "set_fake_user"); + AssetClearHelper clear_helper(set_fake_user); clear_helper(ids); clear_helper.reportResults(C, *op->reports); @@ -248,18 +311,39 @@ static int asset_clear_exec(bContext *C, wmOperator *op) return OPERATOR_FINISHED; } +static char *asset_clear_get_description(struct bContext *UNUSED(C), + struct wmOperatorType *UNUSED(op), + struct PointerRNA *values) +{ + const bool set_fake_user = RNA_boolean_get(values, "set_fake_user"); + if (!set_fake_user) { + return nullptr; + } + + return BLI_strdup( + "Delete all asset metadata, turning the selected asset data-blocks back into normal " + "data-blocks, and set Fake User to ensure the data-blocks will still be saved"); +} + static void ASSET_OT_clear(wmOperatorType *ot) { ot->name = "Clear Asset"; ot->description = "Delete all asset metadata and turn the selected asset data-blocks back into normal " "data-blocks"; + ot->get_description = asset_clear_get_description; ot->idname = "ASSET_OT_clear"; ot->exec = asset_clear_exec; - ot->poll = asset_operation_poll; + ot->poll = asset_clear_poll; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_boolean(ot->srna, + "set_fake_user", + false, + "Set Fake User", + "Ensure the data-block is saved, even when it is no longer marked as asset"); } /* -------------------------------------------------------------------- */ @@ -295,10 +379,91 @@ static void ASSET_OT_list_refresh(struct wmOperatorType *ot) /* -------------------------------------------------------------------- */ +static bool asset_catalog_operator_poll(bContext *C) +{ + const SpaceFile *sfile = CTX_wm_space_file(C); + return asset_operation_poll(C) && sfile && ED_fileselect_active_asset_library_get(sfile); +} + +static int asset_catalog_new_exec(bContext *C, wmOperator *op) +{ + SpaceFile *sfile = CTX_wm_space_file(C); + struct AssetLibrary *asset_library = ED_fileselect_active_asset_library_get(sfile); + char *parent_path = RNA_string_get_alloc(op->ptr, "parent_path", nullptr, 0, nullptr); + + ED_asset_catalog_add(asset_library, "Catalog", parent_path); + + MEM_freeN(parent_path); + + WM_main_add_notifier(NC_SPACE | ND_SPACE_ASSET_PARAMS, nullptr); + + return OPERATOR_FINISHED; +} + +static void ASSET_OT_catalog_new(struct wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "New Asset Catalog"; + ot->description = "Create a new catalog to put assets in"; + ot->idname = "ASSET_OT_catalog_new"; + + /* api callbacks */ + ot->exec = asset_catalog_new_exec; + ot->poll = asset_catalog_operator_poll; + + RNA_def_string(ot->srna, + "parent_path", + nullptr, + 0, + "Parent Path", + "Optional path defining the location to put the new catalog under"); +} + +static int asset_catalog_delete_exec(bContext *C, wmOperator *op) +{ + SpaceFile *sfile = CTX_wm_space_file(C); + struct AssetLibrary *asset_library = ED_fileselect_active_asset_library_get(sfile); + char *catalog_id_str = RNA_string_get_alloc(op->ptr, "catalog_id", nullptr, 0, nullptr); + bke::CatalogID catalog_id; + if (!BLI_uuid_parse_string(&catalog_id, catalog_id_str)) { + return OPERATOR_CANCELLED; + } + + ED_asset_catalog_remove(asset_library, catalog_id); + + MEM_freeN(catalog_id_str); + + WM_main_add_notifier(NC_SPACE | ND_SPACE_ASSET_PARAMS, nullptr); + + return OPERATOR_FINISHED; +} + +static void ASSET_OT_catalog_delete(struct wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Delete Asset Catalog"; + ot->description = + "Remove an asset catalog from the asset library (contained assets will not be affected and " + "show up as unassigned)"; + ot->idname = "ASSET_OT_catalog_delete"; + + /* api callbacks */ + ot->exec = asset_catalog_delete_exec; + ot->invoke = WM_operator_confirm; + ot->poll = asset_catalog_operator_poll; + + RNA_def_string(ot->srna, "catalog_id", nullptr, 0, "Catalog ID", "ID of the catalog to delete"); +} + +/* -------------------------------------------------------------------- */ + void ED_operatortypes_asset(void) { WM_operatortype_append(ASSET_OT_mark); WM_operatortype_append(ASSET_OT_clear); + WM_operatortype_append(ASSET_OT_catalog_new); + WM_operatortype_append(ASSET_OT_catalog_delete); + WM_operatortype_append(ASSET_OT_list_refresh); } diff --git a/source/blender/editors/gpencil/annotate_paint.c b/source/blender/editors/gpencil/annotate_paint.c index 68e2aece6e2..59ea105fbbb 100644 --- a/source/blender/editors/gpencil/annotate_paint.c +++ b/source/blender/editors/gpencil/annotate_paint.c @@ -2104,7 +2104,7 @@ static void annotation_draw_apply_event( p->flags |= GP_PAINTFLAG_USE_STABILIZER_TEMP; } } - /* We are using the temporal stabilizer flag atm, + /* We are using the temporal stabilizer flag at the moment, * but shift is not pressed as well as the permanent flag is not used, * so we don't need the cursor anymore. */ else if (p->flags & GP_PAINTFLAG_USE_STABILIZER_TEMP) { diff --git a/source/blender/editors/gpencil/gpencil_edit.c b/source/blender/editors/gpencil/gpencil_edit.c index 75ddfa47c57..1f31c60367e 100644 --- a/source/blender/editors/gpencil/gpencil_edit.c +++ b/source/blender/editors/gpencil/gpencil_edit.c @@ -4729,7 +4729,7 @@ static int gpencil_stroke_separate_exec(bContext *C, wmOperator *op) /* Remove unused slots. */ int actcol = ob_dst->actcol; for (int slot = 1; slot <= ob_dst->totcol; slot++) { - while (slot <= ob_dst->totcol && !BKE_object_material_slot_used(ob_dst->data, slot)) { + while (slot <= ob_dst->totcol && !BKE_object_material_slot_used(ob_dst, slot)) { ob_dst->actcol = slot; BKE_object_material_slot_remove(bmain, ob_dst); if (actcol >= slot) { diff --git a/source/blender/editors/gpencil/gpencil_mesh.c b/source/blender/editors/gpencil/gpencil_mesh.c index 0939d53736b..079089786d0 100644 --- a/source/blender/editors/gpencil/gpencil_mesh.c +++ b/source/blender/editors/gpencil/gpencil_mesh.c @@ -345,7 +345,7 @@ static int gpencil_bake_mesh_animation_exec(bContext *C, wmOperator *op) /* Remove unused materials. */ int actcol = ob_gpencil->actcol; for (int slot = 1; slot <= ob_gpencil->totcol; slot++) { - while (slot <= ob_gpencil->totcol && !BKE_object_material_slot_used(ob_gpencil->data, slot)) { + while (slot <= ob_gpencil->totcol && !BKE_object_material_slot_used(ob_gpencil, slot)) { ob_gpencil->actcol = slot; BKE_object_material_slot_remove(CTX_data_main(C), ob_gpencil); diff --git a/source/blender/editors/gpencil/gpencil_paint.c b/source/blender/editors/gpencil/gpencil_paint.c index 9b157224178..957d8087bbd 100644 --- a/source/blender/editors/gpencil/gpencil_paint.c +++ b/source/blender/editors/gpencil/gpencil_paint.c @@ -1001,8 +1001,6 @@ static void gpencil_stroke_newfrombuffer(tGPsdata *p) gps->points = MEM_callocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points"); gps->dvert = NULL; - /* drawing batch cache is dirty now */ - gpencil_update_cache(p->gpd); /* set pointer to first non-initialized point */ pt = gps->points + (gps->totpoints - totelem); if (gps->dvert != NULL) { diff --git a/source/blender/editors/include/ED_anim_api.h b/source/blender/editors/include/ED_anim_api.h index 6a0a42ee77b..e9601220f2e 100644 --- a/source/blender/editors/include/ED_anim_api.h +++ b/source/blender/editors/include/ED_anim_api.h @@ -861,7 +861,7 @@ void ED_operatormacros_action(void); /* XXX: Should we be doing these here, or at all? */ /* Action Editor - Action Management */ -struct AnimData *ED_actedit_animdata_from_context(struct bContext *C); +struct AnimData *ED_actedit_animdata_from_context(struct bContext *C, struct ID **r_adt_id_owner); void ED_animedit_unlink_action(struct bContext *C, struct ID *id, struct AnimData *adt, diff --git a/source/blender/editors/include/ED_fileselect.h b/source/blender/editors/include/ED_fileselect.h index 82057c726a5..423d619f41a 100644 --- a/source/blender/editors/include/ED_fileselect.h +++ b/source/blender/editors/include/ED_fileselect.h @@ -142,6 +142,7 @@ void ED_fileselect_exit(struct wmWindowManager *wm, struct SpaceFile *sfile); bool ED_fileselect_is_file_browser(const struct SpaceFile *sfile); bool ED_fileselect_is_asset_browser(const struct SpaceFile *sfile); +struct AssetLibrary *ED_fileselect_active_asset_library_get(const struct SpaceFile *sfile); struct ID *ED_fileselect_active_asset_get(const struct SpaceFile *sfile); /* Activate the file that corresponds to the given ID. diff --git a/source/blender/editors/include/ED_image.h b/source/blender/editors/include/ED_image.h index 6b0b9f4a27c..9532035a1cd 100644 --- a/source/blender/editors/include/ED_image.h +++ b/source/blender/editors/include/ED_image.h @@ -41,6 +41,16 @@ struct SpaceImage; struct bContext; struct wmOperator; struct wmWindowManager; +struct View2D; + +/* image_draw.c */ +float ED_space_image_zoom_level(const struct View2D *v2d, const int grid_dimension); +void ED_space_image_grid_steps(struct SpaceImage *sima, + float grid_steps[SI_GRID_STEPS_LEN], + const int grid_dimension); +float ED_space_image_increment_snap_value(const int grid_dimesnions, + const float grid_steps[SI_GRID_STEPS_LEN], + const float zoom_factor); /* image_edit.c, exported for transform */ struct Image *ED_space_image(struct SpaceImage *sima); diff --git a/source/blender/editors/include/ED_keyframes_draw.h b/source/blender/editors/include/ED_keyframes_draw.h index 61e37f20b1b..6a7037c3eed 100644 --- a/source/blender/editors/include/ED_keyframes_draw.h +++ b/source/blender/editors/include/ED_keyframes_draw.h @@ -41,7 +41,7 @@ struct bDopeSheet; struct bGPDlayer; /* draw simple diamond-shape keyframe */ -/* caller should set up vertex format, bind GPU_SHADER_KEYFRAME_DIAMOND, +/* caller should set up vertex format, bind GPU_SHADER_KEYFRAME_SHAPE, * immBegin(GPU_PRIM_POINTS, n), then call this n times */ typedef struct KeyframeShaderBindings { uint pos_id; diff --git a/source/blender/editors/include/ED_text.h b/source/blender/editors/include/ED_text.h index 2284c82b3d5..6e012ec1a91 100644 --- a/source/blender/editors/include/ED_text.h +++ b/source/blender/editors/include/ED_text.h @@ -34,6 +34,8 @@ struct UndoStep; struct UndoType; struct bContext; +bool ED_text_activate_in_screen(struct bContext *C, struct Text *text); + void ED_text_scroll_to_cursor(struct SpaceText *st, struct ARegion *region, bool center); bool ED_text_region_location_from_cursor(struct SpaceText *st, diff --git a/source/blender/editors/include/ED_time_scrub_ui.h b/source/blender/editors/include/ED_time_scrub_ui.h index 6420aaf5ef0..812cb31c9b0 100644 --- a/source/blender/editors/include/ED_time_scrub_ui.h +++ b/source/blender/editors/include/ED_time_scrub_ui.h @@ -33,8 +33,7 @@ struct wmEvent; void ED_time_scrub_draw_current_frame(const struct ARegion *region, const struct Scene *scene, - bool display_seconds, - bool draw_line); + bool display_seconds); void ED_time_scrub_draw(const struct ARegion *region, const struct Scene *scene, diff --git a/source/blender/editors/include/ED_uvedit.h b/source/blender/editors/include/ED_uvedit.h index ea3d921f2c5..f3aba12a924 100644 --- a/source/blender/editors/include/ED_uvedit.h +++ b/source/blender/editors/include/ED_uvedit.h @@ -237,6 +237,18 @@ void ED_image_draw_cursor(struct ARegion *region, const float cursor[2]); void ED_uvedit_buttons_register(struct ARegionType *art); /* uvedit_islands.c */ + +struct UVMapUDIM_Params { + const struct Image *image; + /** Copied from #SpaceImage.tile_grid_shape */ + int grid_shape[2]; + bool use_target_udim; + int target_udim; +}; +bool ED_uvedit_udim_params_from_image_space(const struct SpaceImage *sima, + bool use_active, + struct UVMapUDIM_Params *udim_params); + struct UVPackIsland_Params { uint rotate : 1; /** -1 not to align to axis, otherwise 0,1 for X,Y. */ @@ -246,9 +258,14 @@ struct UVPackIsland_Params { uint use_seams : 1; uint correct_aspect : 1; }; + +bool uv_coords_isect_udim(const struct Image *image, + const int udim_grid[2], + const float coords[2]); void ED_uvedit_pack_islands_multi(const struct Scene *scene, Object **objects, const uint objects_len, + const struct UVMapUDIM_Params *udim_params, const struct UVPackIsland_Params *params); #ifdef __cplusplus diff --git a/source/blender/editors/include/UI_icons.h b/source/blender/editors/include/UI_icons.h index ddd9ca4a98c..8a7df5b54ff 100644 --- a/source/blender/editors/include/UI_icons.h +++ b/source/blender/editors/include/UI_icons.h @@ -989,6 +989,16 @@ DEF_ICON_VECTOR(COLLECTION_COLOR_06) DEF_ICON_VECTOR(COLLECTION_COLOR_07) DEF_ICON_VECTOR(COLLECTION_COLOR_08) +DEF_ICON_VECTOR(SEQUENCE_COLOR_01) +DEF_ICON_VECTOR(SEQUENCE_COLOR_02) +DEF_ICON_VECTOR(SEQUENCE_COLOR_03) +DEF_ICON_VECTOR(SEQUENCE_COLOR_04) +DEF_ICON_VECTOR(SEQUENCE_COLOR_05) +DEF_ICON_VECTOR(SEQUENCE_COLOR_06) +DEF_ICON_VECTOR(SEQUENCE_COLOR_07) +DEF_ICON_VECTOR(SEQUENCE_COLOR_08) +DEF_ICON_VECTOR(SEQUENCE_COLOR_09) + /* Events. */ DEF_ICON_COLOR(EVENT_A) DEF_ICON_COLOR(EVENT_B) diff --git a/source/blender/editors/include/UI_interface.h b/source/blender/editors/include/UI_interface.h index 0f74d81f7bb..59d688ad2d0 100644 --- a/source/blender/editors/include/UI_interface.h +++ b/source/blender/editors/include/UI_interface.h @@ -84,6 +84,10 @@ typedef struct uiBlock uiBlock; typedef struct uiBut uiBut; typedef struct uiLayout uiLayout; typedef struct uiPopupBlockHandle uiPopupBlockHandle; +/* C handle for C++ #ui::AbstractTreeView type. */ +typedef struct uiTreeViewHandle uiTreeViewHandle; +/* C handle for C++ #ui::AbstractTreeViewItem type. */ +typedef struct uiTreeViewItemHandle uiTreeViewItemHandle; /* Defines */ @@ -389,6 +393,8 @@ typedef enum { UI_BTYPE_GRIP = 57 << 9, UI_BTYPE_DECORATOR = 58 << 9, UI_BTYPE_DATASETROW = 59 << 9, + /* An item in a tree view. Parent items may be collapsible. */ + UI_BTYPE_TREEROW = 60 << 9, } eButType; #define BUTTYPE (63 << 9) @@ -1672,6 +1678,7 @@ void UI_but_datasetrow_component_set(uiBut *but, uint8_t geometry_component_type void UI_but_datasetrow_domain_set(uiBut *but, uint8_t attribute_domain); uint8_t UI_but_datasetrow_component_get(uiBut *but); uint8_t UI_but_datasetrow_domain_get(uiBut *but); +void UI_but_treerow_indentation_set(uiBut *but, int indentation); void UI_but_node_link_set(uiBut *but, struct bNodeSocket *socket, const float draw_color[4]); @@ -2587,6 +2594,7 @@ typedef struct uiDragColorHandle { void ED_operatortypes_ui(void); void ED_keymap_ui(struct wmKeyConfig *keyconf); +void ED_dropboxes_ui(void); void ED_uilisttypes_ui(void); void UI_drop_color_copy(struct wmDrag *drag, struct wmDropBox *drop); @@ -2755,6 +2763,17 @@ void UI_interface_tag_script_reload(void); /* Support click-drag motion which presses the button and closes a popover (like a menu). */ #define USE_UI_POPOVER_ONCE +bool UI_tree_view_item_is_active(const uiTreeViewItemHandle *item); +bool UI_tree_view_item_matches(const uiTreeViewItemHandle *a, const uiTreeViewItemHandle *b); +bool UI_tree_view_item_can_drop(const uiTreeViewItemHandle *item_, const struct wmDrag *drag); +bool UI_tree_view_item_drop_handle(uiTreeViewItemHandle *item_, const struct ListBase *drags); +char *UI_tree_view_item_drop_tooltip(const uiTreeViewItemHandle *item, + const struct bContext *C, + const struct wmDrag *drag, + const struct wmEvent *event); + +uiTreeViewItemHandle *UI_block_tree_view_find_item_at(const struct ARegion *region, int x, int y); + #ifdef __cplusplus } #endif diff --git a/source/blender/editors/include/UI_interface.hh b/source/blender/editors/include/UI_interface.hh new file mode 100644 index 00000000000..4a583d0225e --- /dev/null +++ b/source/blender/editors/include/UI_interface.hh @@ -0,0 +1,35 @@ +/* + * 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 editorui + */ + +#pragma once + +#include <memory> + +#include "BLI_string_ref.hh" + +struct uiBlock; +namespace blender::ui { +class AbstractTreeView; +} + +blender::ui::AbstractTreeView *UI_block_add_view( + uiBlock &block, + blender::StringRef idname, + std::unique_ptr<blender::ui::AbstractTreeView> tree_view); diff --git a/source/blender/editors/include/UI_tree_view.hh b/source/blender/editors/include/UI_tree_view.hh new file mode 100644 index 00000000000..d36e688dd65 --- /dev/null +++ b/source/blender/editors/include/UI_tree_view.hh @@ -0,0 +1,257 @@ +/* + * 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 editorui + */ + +#pragma once + +#include <functional> +#include <memory> +#include <string> + +#include "BLI_function_ref.hh" +#include "BLI_vector.hh" + +#include "UI_resources.h" + +struct bContext; +struct PointerRNA; +struct uiBlock; +struct uiBut; +struct uiButTreeRow; +struct uiLayout; +struct wmEvent; +struct wmDrag; + +namespace blender::ui { + +class AbstractTreeView; +class AbstractTreeViewItem; + +/* ---------------------------------------------------------------------- */ +/** \name Tree-View Item Container + * \{ */ + +/** + * Helper base class to expose common child-item data and functionality to both #AbstractTreeView + * and #AbstractTreeViewItem. + * + * That means this type can be used whenever either a #AbstractTreeView or a + * #AbstractTreeViewItem is needed. + */ +class TreeViewItemContainer { + friend class AbstractTreeView; + friend class AbstractTreeViewItem; + + /* Private constructor, so only the friends above can create this! */ + TreeViewItemContainer() = default; + + protected: + Vector<std::unique_ptr<AbstractTreeViewItem>> children_; + /** Adding the first item to the root will set this, then it's passed on to all children. */ + TreeViewItemContainer *root_ = nullptr; + /** Pointer back to the owning item. */ + AbstractTreeViewItem *parent_ = nullptr; + + public: + enum class IterOptions { + None = 0, + SkipCollapsed = 1 << 0, + + /* Keep ENUM_OPERATORS() below updated! */ + }; + using ItemIterFn = FunctionRef<void(AbstractTreeViewItem &)>; + + /** + * Convenience wrapper taking the arguments needed to construct an item of type \a ItemT. Calls + * the version just below. + */ + template<class ItemT, typename... Args> ItemT &add_tree_item(Args &&...args) + { + static_assert(std::is_base_of<AbstractTreeViewItem, ItemT>::value, + "Type must derive from and implement the AbstractTreeViewItem interface"); + + return dynamic_cast<ItemT &>( + add_tree_item(std::make_unique<ItemT>(std::forward<Args>(args)...))); + } + + AbstractTreeViewItem &add_tree_item(std::unique_ptr<AbstractTreeViewItem> item); + + protected: + void foreach_item_recursive(ItemIterFn iter_fn, IterOptions options = IterOptions::None) const; +}; + +ENUM_OPERATORS(TreeViewItemContainer::IterOptions, + TreeViewItemContainer::IterOptions::SkipCollapsed); + +/** \} */ + +/* ---------------------------------------------------------------------- */ +/** \name Tree-View Builders + * \{ */ + +class TreeViewBuilder { + uiBlock &block_; + + public: + TreeViewBuilder(uiBlock &block); + + void build_tree_view(AbstractTreeView &tree_view); +}; + +class TreeViewLayoutBuilder { + uiBlock &block_; + + friend TreeViewBuilder; + + public: + void build_row(AbstractTreeViewItem &item) const; + uiBlock &block() const; + uiLayout *current_layout() const; + + private: + /* Created through #TreeViewBuilder. */ + TreeViewLayoutBuilder(uiBlock &block); +}; + +/** \} */ + +/* ---------------------------------------------------------------------- */ +/** \name Tree-View Base Class + * \{ */ + +class AbstractTreeView : public TreeViewItemContainer { + friend TreeViewBuilder; + friend TreeViewLayoutBuilder; + + public: + virtual ~AbstractTreeView() = default; + + void foreach_item(ItemIterFn iter_fn, IterOptions options = IterOptions::None) const; + + protected: + virtual void build_tree() = 0; + + private: + /** Match the tree-view against an earlier version of itself (if any) and copy the old UI state + * (e.g. collapsed, active, selected) to the new one. See + * #AbstractTreeViewItem.update_from_old(). */ + void update_from_old(uiBlock &new_block); + static void update_children_from_old_recursive(const TreeViewItemContainer &new_items, + const TreeViewItemContainer &old_items); + static AbstractTreeViewItem *find_matching_child(const AbstractTreeViewItem &lookup_item, + const TreeViewItemContainer &items); + void build_layout_from_tree(const TreeViewLayoutBuilder &builder); +}; + +/** \} */ + +/* ---------------------------------------------------------------------- */ +/** \name Tree-View Item Type + * \{ */ + +/** \brief Abstract base class for defining a customizable tree-view item. + * + * The tree-view item defines how to build its data into a tree-row. There are implementations for + * common layouts, e.g. #BasicTreeViewItem. + * It also stores state information that needs to be persistent over redraws, like the collapsed + * state. + */ +class AbstractTreeViewItem : public TreeViewItemContainer { + friend class AbstractTreeView; + + bool is_open_ = false; + bool is_active_ = false; + + protected: + /** This label is used for identifying an item (together with its parent's labels). */ + std::string label_{}; + + public: + virtual ~AbstractTreeViewItem() = default; + + virtual void build_row(uiLayout &row) = 0; + + virtual void on_activate(); + virtual bool on_drop(const wmDrag &drag); + virtual bool can_drop(const wmDrag &drag) const; + /** Custom text to display when dragging over a tree item. Should explain what happens when + * dropping the data onto this item. Will only be used if #AbstractTreeViewItem::can_drop() + * returns true, so the implementing override doesn't have to check that again. + * The returned value must be a translated string. */ + virtual std::string drop_tooltip(const bContext &C, + const wmDrag &drag, + const wmEvent &event) const; + + /** Copy persistent state (e.g. is-collapsed flag, selection, etc.) from a matching item of + * the last redraw to this item. If sub-classes introduce more advanced state they should + * override this and make it update their state accordingly. */ + virtual void update_from_old(const AbstractTreeViewItem &old); + /** Compare this item to \a other to check if they represent the same data. This is critical for + * being able to recognize an item from a previous redraw, to be able to keep its state (e.g. + * open/closed, active, etc.). Items are only matched if their parents also match. + * By default this just matches the items names/labels (if their parents match!). If that isn't + * good enough for a sub-class, that can override it. */ + virtual bool matches(const AbstractTreeViewItem &other) const; + + const AbstractTreeView &get_tree_view() const; + int count_parents() const; + void set_active(bool value = true); + bool is_active() const; + void toggle_collapsed(); + bool is_collapsed() const; + void set_collapsed(bool collapsed); + bool is_collapsible() const; +}; + +/** \} */ + +/* ---------------------------------------------------------------------- */ +/** \name Predefined Tree-View Item Types + * + * Common, Basic Tree-View Item Types. + * \{ */ + +/** + * The most basic type, just a label with an icon. + */ +class BasicTreeViewItem : public AbstractTreeViewItem { + public: + using ActivateFn = std::function<void(BasicTreeViewItem &new_active)>; + BIFIconID icon; + + BasicTreeViewItem(StringRef label, BIFIconID icon = ICON_NONE, ActivateFn activate_fn = nullptr); + + void build_row(uiLayout &row) override; + void on_activate() override; + + protected: + /** Created in the #build() function. */ + uiButTreeRow *tree_row_but_ = nullptr; + /** Optionally passed to the #BasicTreeViewItem constructor. Called when activating this tree + * view item. This way users don't have to sub-class #BasicTreeViewItem, just to implement + * custom activation behavior (a common thing to do). */ + ActivateFn activate_fn_; + + uiBut *button(); + BIFIconID get_draw_icon() const; +}; + +/** \} */ + +} // namespace blender::ui diff --git a/source/blender/editors/include/UI_view2d.h b/source/blender/editors/include/UI_view2d.h index e3c02b4c249..fdfa07a7e02 100644 --- a/source/blender/editors/include/UI_view2d.h +++ b/source/blender/editors/include/UI_view2d.h @@ -123,6 +123,7 @@ void UI_view2d_region_reinit(struct View2D *v2d, short type, int winx, int winy) void UI_view2d_curRect_validate(struct View2D *v2d); void UI_view2d_curRect_reset(struct View2D *v2d); +bool UI_view2d_area_supports_sync(struct ScrArea *area); void UI_view2d_sync(struct bScreen *screen, struct ScrArea *area, struct View2D *v2dcur, int flag); /* Perform all required updates after `v2d->cur` as been modified. diff --git a/source/blender/editors/interface/CMakeLists.txt b/source/blender/editors/interface/CMakeLists.txt index 64c874e26a9..f27ff9694c2 100644 --- a/source/blender/editors/interface/CMakeLists.txt +++ b/source/blender/editors/interface/CMakeLists.txt @@ -42,6 +42,7 @@ set(SRC interface_button_group.c interface_context_menu.c interface_draw.c + interface_dropboxes.cc interface_eyedropper.c interface_eyedropper_color.c interface_eyedropper_colorband.c @@ -73,8 +74,10 @@ set(SRC interface_templates.c interface_undo.c interface_utils.c + interface_view.cc interface_widgets.c resources.c + tree_view.cc view2d.c view2d_draw.c view2d_edge_pan.c diff --git a/source/blender/editors/interface/interface.c b/source/blender/editors/interface/interface.c index fd75be5b847..c53bffca778 100644 --- a/source/blender/editors/interface/interface.c +++ b/source/blender/editors/interface/interface.c @@ -743,6 +743,15 @@ static bool ui_but_equals_old(const uiBut *but, const uiBut *oldbut) return false; } + if ((but->type == UI_BTYPE_TREEROW) && (oldbut->type == UI_BTYPE_TREEROW)) { + uiButTreeRow *but_treerow = (uiButTreeRow *)but; + uiButTreeRow *oldbut_treerow = (uiButTreeRow *)oldbut; + if (!but_treerow->tree_item || !oldbut_treerow->tree_item || + !UI_tree_view_item_matches(but_treerow->tree_item, oldbut_treerow->tree_item)) { + return false; + } + } + return true; } @@ -856,10 +865,21 @@ static void ui_but_update_old_active_from_new(uiBut *oldbut, uiBut *but) oldbut->hardmax = but->hardmax; } - if (oldbut->type == UI_BTYPE_PROGRESS_BAR) { - uiButProgressbar *progress_oldbut = (uiButProgressbar *)oldbut; - uiButProgressbar *progress_but = (uiButProgressbar *)but; - progress_oldbut->progress = progress_but->progress; + switch (oldbut->type) { + case UI_BTYPE_PROGRESS_BAR: { + uiButProgressbar *progress_oldbut = (uiButProgressbar *)oldbut; + uiButProgressbar *progress_but = (uiButProgressbar *)but; + progress_oldbut->progress = progress_but->progress; + break; + } + case UI_BTYPE_TREEROW: { + uiButTreeRow *treerow_oldbut = (uiButTreeRow *)oldbut; + uiButTreeRow *treerow_newbut = (uiButTreeRow *)but; + SWAP(uiTreeViewItemHandle *, treerow_newbut->tree_item, treerow_oldbut->tree_item); + break; + } + default: + break; } /* move/copy string from the new button to the old */ @@ -2203,6 +2223,15 @@ int ui_but_is_pushed_ex(uiBut *but, double *value) } } break; + case UI_BTYPE_TREEROW: { + uiButTreeRow *tree_row_but = (uiButTreeRow *)but; + + is_push = -1; + if (tree_row_but->tree_item) { + is_push = UI_tree_view_item_is_active(tree_row_but->tree_item); + } + break; + } default: is_push = -1; break; @@ -3447,6 +3476,7 @@ void UI_block_free(const bContext *C, uiBlock *block) BLI_freelistN(&block->color_pickers.list); ui_block_free_button_groups(block); + ui_block_free_views(block); MEM_freeN(block); } @@ -3942,6 +3972,10 @@ static void ui_but_alloc_info(const eButType type, alloc_size = sizeof(uiButDatasetRow); alloc_str = "uiButDatasetRow"; break; + case UI_BTYPE_TREEROW: + alloc_size = sizeof(uiButTreeRow); + alloc_str = "uiButTreeRow"; + break; default: alloc_size = sizeof(uiBut); alloc_str = "uiBut"; @@ -4141,6 +4175,7 @@ static uiBut *ui_def_but(uiBlock *block, UI_BTYPE_BUT_MENU, UI_BTYPE_SEARCH_MENU, UI_BTYPE_DATASETROW, + UI_BTYPE_TREEROW, UI_BTYPE_POPOVER)) { but->drawflag |= (UI_BUT_TEXT_LEFT | UI_BUT_ICON_LEFT); } @@ -6198,6 +6233,13 @@ void UI_but_drag_set_asset(uiBut *but, asset_drag->id_type = ED_asset_handle_get_id_type(asset); asset_drag->import_type = import_type; + /* FIXME: This is temporary evil solution to get scene/viewlayer/etc in the copy callback of the + * #wmDropBox. + * TODO: Handle link/append in operator called at the end of the drop process, and NOT in its + * copy callback. + * */ + asset_drag->evil_C = but->block->evil_C; + but->dragtype = WM_DRAG_ASSET; ui_def_but_icon(but, icon, 0); /* no flag UI_HAS_ICON, so icon doesn't draw in button */ if (but->dragflag & UI_BUT_DRAGPOIN_FREE) { @@ -6878,6 +6920,15 @@ void UI_but_datasetrow_indentation_set(uiBut *but, int indentation) BLI_assert(indentation >= 0); } +void UI_but_treerow_indentation_set(uiBut *but, int indentation) +{ + uiButTreeRow *but_row = (uiButTreeRow *)but; + BLI_assert(but->type == UI_BTYPE_TREEROW); + + but_row->indentation = indentation; + BLI_assert(indentation >= 0); +} + /** * Adds a hint to the button which draws right aligned, grayed out and never clipped. */ diff --git a/source/blender/editors/interface/interface_dropboxes.cc b/source/blender/editors/interface/interface_dropboxes.cc new file mode 100644 index 00000000000..cb33e7f736e --- /dev/null +++ b/source/blender/editors/interface/interface_dropboxes.cc @@ -0,0 +1,66 @@ +/* + * 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 edinterface + */ + +#include "BKE_context.h" + +#include "DNA_space_types.h" + +#include "WM_api.h" + +#include "UI_interface.h" + +static bool ui_tree_view_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event) +{ + const ARegion *region = CTX_wm_region(C); + const uiTreeViewItemHandle *hovered_tree_item = UI_block_tree_view_find_item_at( + region, event->x, event->y); + if (!hovered_tree_item) { + return false; + } + + return UI_tree_view_item_can_drop(hovered_tree_item, drag); +} + +static char *ui_tree_view_drop_tooltip(bContext *C, + wmDrag *drag, + const wmEvent *event, + wmDropBox *UNUSED(drop)) +{ + const ARegion *region = CTX_wm_region(C); + const uiTreeViewItemHandle *hovered_tree_item = UI_block_tree_view_find_item_at( + region, event->x, event->y); + if (!hovered_tree_item) { + return nullptr; + } + + return UI_tree_view_item_drop_tooltip(hovered_tree_item, C, drag, event); +} + +void ED_dropboxes_ui() +{ + ListBase *lb = WM_dropboxmap_find("User Interface", SPACE_EMPTY, 0); + + WM_dropbox_add(lb, + "UI_OT_tree_view_drop", + ui_tree_view_drop_poll, + nullptr, + nullptr, + ui_tree_view_drop_tooltip); +} diff --git a/source/blender/editors/interface/interface_handlers.c b/source/blender/editors/interface/interface_handlers.c index 77ae16d7cc7..aee66ec3a93 100644 --- a/source/blender/editors/interface/interface_handlers.c +++ b/source/blender/editors/interface/interface_handlers.c @@ -384,6 +384,8 @@ typedef struct uiHandleButtonData { /* booleans (could be made into flags) */ bool cancel, escapecancel; bool applied, applied_interactive; + /* Button is being applied through an extra icon. */ + bool apply_through_extra_icon; bool changed_cursor; wmTimer *flashtimer; @@ -1164,6 +1166,16 @@ static void ui_apply_but_ROW(bContext *C, uiBlock *block, uiBut *but, uiHandleBu data->applied = true; } +static void ui_apply_but_TREEROW(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data) +{ + if (data->apply_through_extra_icon) { + /* Don't apply this, it would cause unintended tree-row toggling when clicking on extra icons. + */ + return; + } + ui_apply_but_ROW(C, block, but, data); +} + /** * \note Ownership of \a properties is moved here. The #uiAfterFunc owns it now. * @@ -2307,6 +2319,9 @@ static void ui_apply_but( case UI_BTYPE_ROW: ui_apply_but_ROW(C, block, but, data); break; + case UI_BTYPE_TREEROW: + ui_apply_but_TREEROW(C, block, but, data); + break; case UI_BTYPE_LISTROW: ui_apply_but_LISTROW(C, block, but, data); break; @@ -4194,6 +4209,8 @@ static void ui_numedit_apply(bContext *C, uiBlock *block, uiBut *but, uiHandleBu static void ui_but_extra_operator_icon_apply(bContext *C, uiBut *but, uiButExtraOpIcon *op_icon) { + but->active->apply_through_extra_icon = true; + if (but->active->interactive) { ui_apply_but(C, but->block, but, but->active, true); } @@ -4737,7 +4754,7 @@ static int ui_do_but_TOG(bContext *C, uiBut *but, uiHandleButtonData *data, cons /* Behave like other menu items. */ do_activate = (event->val == KM_RELEASE); } - else { + else if (!ui_do_but_extra_operator_icon(C, but, data, event)) { /* Also use double-clicks to prevent fast clicks to leak to other handlers (T76481). */ do_activate = ELEM(event->val, KM_PRESS, KM_DBL_CLICK); } @@ -7966,6 +7983,7 @@ static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, const wmEvent * case UI_BTYPE_CHECKBOX: case UI_BTYPE_CHECKBOX_N: case UI_BTYPE_ROW: + case UI_BTYPE_TREEROW: case UI_BTYPE_DATASETROW: retval = ui_do_but_TOG(C, but, data, event); break; diff --git a/source/blender/editors/interface/interface_icons.c b/source/blender/editors/interface/interface_icons.c index f739830cfdb..c20129b4184 100644 --- a/source/blender/editors/interface/interface_icons.c +++ b/source/blender/editors/interface/interface_icons.c @@ -47,6 +47,7 @@ #include "DNA_gpencil_types.h" #include "DNA_object_types.h" #include "DNA_screen_types.h" +#include "DNA_sequence_types.h" #include "DNA_space_types.h" #include "RNA_access.h" @@ -309,7 +310,7 @@ static void vicon_keytype_draw_wrapper( sh_bindings.flags_id = GPU_vertformat_attr_add(format, "flags", GPU_COMP_U32, 1, GPU_FETCH_INT); GPU_program_point_size(true); - immBindBuiltinProgram(GPU_SHADER_KEYFRAME_DIAMOND); + immBindBuiltinProgram(GPU_SHADER_KEYFRAME_SHAPE); immUniform1f("outline_scale", 1.0f); immUniform2f("ViewportSize", -1.0f, -1.0f); immBegin(GPU_PRIM_POINTS, 1); @@ -480,6 +481,35 @@ DEF_ICON_COLLECTION_COLOR_DRAW(08, COLLECTION_COLOR_08); # undef DEF_ICON_COLLECTION_COLOR_DRAW +static void vicon_strip_color_draw( + short color_tag, int x, int y, int w, int UNUSED(h), float UNUSED(alpha)) +{ + bTheme *btheme = UI_GetTheme(); + const ThemeStripColor *strip_color = &btheme->strip_color[color_tag]; + + const float aspect = (float)ICON_DEFAULT_WIDTH / (float)w; + + UI_icon_draw_ex(x, y, ICON_SNAP_FACE, aspect, 1.0f, 0.0f, strip_color->color, true); +} + +# define DEF_ICON_STRIP_COLOR_DRAW(index, color) \ + static void vicon_strip_color_draw_##index(int x, int y, int w, int h, float alpha) \ + { \ + vicon_strip_color_draw(color, x, y, w, h, alpha); \ + } + +DEF_ICON_STRIP_COLOR_DRAW(01, SEQUENCE_COLOR_01); +DEF_ICON_STRIP_COLOR_DRAW(02, SEQUENCE_COLOR_02); +DEF_ICON_STRIP_COLOR_DRAW(03, SEQUENCE_COLOR_03); +DEF_ICON_STRIP_COLOR_DRAW(04, SEQUENCE_COLOR_04); +DEF_ICON_STRIP_COLOR_DRAW(05, SEQUENCE_COLOR_05); +DEF_ICON_STRIP_COLOR_DRAW(06, SEQUENCE_COLOR_06); +DEF_ICON_STRIP_COLOR_DRAW(07, SEQUENCE_COLOR_07); +DEF_ICON_STRIP_COLOR_DRAW(08, SEQUENCE_COLOR_08); +DEF_ICON_STRIP_COLOR_DRAW(09, SEQUENCE_COLOR_09); + +# undef DEF_ICON_STRIP_COLOR_DRAW + /* Dynamically render icon instead of rendering a plain color to a texture/buffer * This is not strictly a "vicon", as it needs access to icon->obj to get the color info, * but it works in a very similar way. @@ -995,6 +1025,16 @@ static void init_internal_icons(void) def_internal_vicon(ICON_COLLECTION_COLOR_06, vicon_collection_color_draw_06); def_internal_vicon(ICON_COLLECTION_COLOR_07, vicon_collection_color_draw_07); def_internal_vicon(ICON_COLLECTION_COLOR_08, vicon_collection_color_draw_08); + + def_internal_vicon(ICON_SEQUENCE_COLOR_01, vicon_strip_color_draw_01); + def_internal_vicon(ICON_SEQUENCE_COLOR_02, vicon_strip_color_draw_02); + def_internal_vicon(ICON_SEQUENCE_COLOR_03, vicon_strip_color_draw_03); + def_internal_vicon(ICON_SEQUENCE_COLOR_04, vicon_strip_color_draw_04); + def_internal_vicon(ICON_SEQUENCE_COLOR_05, vicon_strip_color_draw_05); + def_internal_vicon(ICON_SEQUENCE_COLOR_06, vicon_strip_color_draw_06); + def_internal_vicon(ICON_SEQUENCE_COLOR_07, vicon_strip_color_draw_07); + def_internal_vicon(ICON_SEQUENCE_COLOR_08, vicon_strip_color_draw_08); + def_internal_vicon(ICON_SEQUENCE_COLOR_09, vicon_strip_color_draw_09); } static void init_iconfile_list(struct ListBase *list) diff --git a/source/blender/editors/interface/interface_intern.h b/source/blender/editors/interface/interface_intern.h index d61104f094e..8b45d9faae6 100644 --- a/source/blender/editors/interface/interface_intern.h +++ b/source/blender/editors/interface/interface_intern.h @@ -360,6 +360,14 @@ typedef struct uiButDatasetRow { int indentation; } uiButDatasetRow; +/** Derived struct for #UI_BTYPE_TREEROW. */ +typedef struct uiButTreeRow { + uiBut but; + + uiTreeViewItemHandle *tree_item; + int indentation; +} uiButTreeRow; + /** Derived struct for #UI_BTYPE_HSVCUBE. */ typedef struct uiButHSVCube { uiBut but; @@ -488,6 +496,11 @@ struct uiBlock { ListBase contexts; + /** A block can store "views" on data-sets. Currently tree-views (#AbstractTreeView) only. + * Others are imaginable, e.g. table-views, grid-views, etc. These are stored here to support + * state that is persistent over redraws (e.g. collapsed tree-view items). */ + ListBase views; + char name[UI_MAX_NAME_STR]; float winmat[4][4]; @@ -1158,6 +1171,7 @@ uiBut *ui_list_row_find_mouse_over(const struct ARegion *region, uiBut *ui_list_row_find_from_index(const struct ARegion *region, const int index, uiBut *listbox) ATTR_WARN_UNUSED_RESULT; +uiBut *ui_tree_row_find_mouse_over(const struct ARegion *region, const int x, const int y); typedef bool (*uiButFindPollFn)(const uiBut *but, const void *customdata); uiBut *ui_but_find_mouse_over_ex(const struct ARegion *region, @@ -1274,6 +1288,11 @@ bool ui_jump_to_target_button_poll(struct bContext *C); /* interface_queries.c */ void ui_interface_tag_script_reload_queries(void); +/* interface_view.cc */ +void ui_block_free_views(struct uiBlock *block); +uiTreeViewHandle *ui_block_view_find_matching_in_old_block(const uiBlock *new_block, + const uiTreeViewHandle *new_view); + #ifdef __cplusplus } #endif diff --git a/source/blender/editors/interface/interface_layout.c b/source/blender/editors/interface/interface_layout.c index 66c75c63050..64c16e57f56 100644 --- a/source/blender/editors/interface/interface_layout.c +++ b/source/blender/editors/interface/interface_layout.c @@ -288,35 +288,85 @@ static bool ui_layout_variable_size(uiLayout *layout) return ui_layout_vary_direction(layout) == UI_ITEM_VARY_X || layout->variable_size; } -/* estimated size of text + icon */ -static int ui_text_icon_width(uiLayout *layout, const char *name, int icon, bool compact) +/** + * Factors to apply to #UI_UNIT_X when calculating button width. + * This is used when the layout is a varying size, see #ui_layout_variable_size. + */ +struct uiTextIconPadFactor { + float text; + float icon; + float icon_only; +}; + +/** + * This adds over an icons width of padding even when no icon is used, + * this is done because most buttons need additional space (drop-down chevron for example). + * menus and labels use much smaller `text` values compared to this default. + * + * \note It may seem odd that the icon only adds 0.25 + * but taking margins into account its fine, + * except for #ui_text_pad_compact where a bit more margin is required. + */ +static const struct uiTextIconPadFactor ui_text_pad_default = { + .text = 1.50f, + .icon = 0.25f, + .icon_only = 0.0f, +}; + +/** #ui_text_pad_default scaled down. */ +static const struct uiTextIconPadFactor ui_text_pad_compact = { + .text = 1.25f, + .icon = 0.35f, + .icon_only = 0.0f, +}; + +/** Least amount of padding not to clip the text or icon. */ +static const struct uiTextIconPadFactor ui_text_pad_none = { + .text = 0.25f, + .icon = 1.50f, + .icon_only = 0.0f, +}; + +/** + * Estimated size of text + icon. + */ +static int ui_text_icon_width_ex(uiLayout *layout, + const char *name, + int icon, + const struct uiTextIconPadFactor *pad_factor) { const int unit_x = UI_UNIT_X * (layout->scale[0] ? layout->scale[0] : 1.0f); + /* When there is no text, always behave as if this is an icon-only button + * since it's not useful to return empty space. */ if (icon && !name[0]) { - return unit_x; /* icon only */ + return unit_x * (1.0f + pad_factor->icon_only); } if (ui_layout_variable_size(layout)) { if (!icon && !name[0]) { - return unit_x; /* No icon or name. */ + return unit_x * (1.0f + pad_factor->icon_only); } + if (layout->alignment != UI_LAYOUT_ALIGN_EXPAND) { layout->item.flag |= UI_ITEM_FIXED_SIZE; } const uiFontStyle *fstyle = UI_FSTYLE_WIDGET; - float margin = compact ? 1.25 : 1.50; + float margin = pad_factor->text; if (icon) { - /* It may seem odd that the icon only adds (unit_x / 4) - * but taking margins into account its fine, except - * in compact mode a bit more margin is required. */ - margin += compact ? 0.35 : 0.25; + margin += pad_factor->icon; } return UI_fontstyle_string_width(fstyle, name) + (unit_x * margin); } return unit_x * 10; } +static int ui_text_icon_width(uiLayout *layout, const char *name, int icon, bool compact) +{ + return ui_text_icon_width_ex( + layout, name, icon, compact ? &ui_text_pad_compact : &ui_text_pad_default); +} + static void ui_item_size(uiItem *item, int *r_w, int *r_h) { if (item->type == ITEM_BUTTON) { @@ -2857,23 +2907,23 @@ static uiBut *ui_item_menu(uiLayout *layout, icon = ICON_BLANK1; } - int w = ui_text_icon_width(layout, name, icon, 1); - const int h = UI_UNIT_Y; - + struct uiTextIconPadFactor pad_factor = ui_text_pad_compact; if (layout->root->type == UI_LAYOUT_HEADER) { /* Ugly! */ if (icon == ICON_NONE && force_menu) { /* pass */ } else if (force_menu) { - w += 0.6f * UI_UNIT_X; + pad_factor.text = 1.85; + pad_factor.icon_only = 0.6f; } else { - if (name[0]) { - w -= UI_UNIT_X / 2; - } + pad_factor.text = 0.75f; } } + const int w = ui_text_icon_width_ex(layout, name, icon, &pad_factor); + const int h = UI_UNIT_Y; + if (heading_layout) { ui_layout_heading_label_add(layout, heading_layout, true, true); } @@ -3133,8 +3183,7 @@ static uiBut *uiItemL_(uiLayout *layout, const char *name, int icon) icon = ICON_BLANK1; } - const int w = ui_text_icon_width(layout, name, icon, 0); - + const int w = ui_text_icon_width_ex(layout, name, icon, &ui_text_pad_none); uiBut *but; if (icon && name[0]) { but = uiDefIconTextBut( diff --git a/source/blender/editors/interface/interface_ops.c b/source/blender/editors/interface/interface_ops.c index dd10d942fc9..1fc07bce341 100644 --- a/source/blender/editors/interface/interface_ops.c +++ b/source/blender/editors/interface/interface_ops.c @@ -1381,16 +1381,7 @@ static int editsource_text_edit(bContext *C, /* naughty!, find text area to set, not good behavior * but since this is a developer tool lets allow it - campbell */ - ScrArea *area = BKE_screen_find_big_area(CTX_wm_screen(C), SPACE_TEXT, 0); - if (area) { - SpaceText *st = area->spacedata.first; - ARegion *region = BKE_area_find_region_type(area, RGN_TYPE_WINDOW); - st->text = text; - if (region) { - ED_text_scroll_to_cursor(st, region, true); - } - } - else { + if (!ED_text_activate_in_screen(C, text)) { BKE_reportf(op->reports, RPT_INFO, "See '%s' in the text editor", text->id.name + 2); } @@ -1927,6 +1918,51 @@ static void UI_OT_list_start_filter(wmOperatorType *ot) /** \} */ /* -------------------------------------------------------------------- */ +/** \name UI Tree-View Drop Operator + * \{ */ + +static bool ui_tree_view_drop_poll(bContext *C) +{ + const wmWindow *win = CTX_wm_window(C); + const ARegion *region = CTX_wm_region(C); + const uiTreeViewItemHandle *hovered_tree_item = UI_block_tree_view_find_item_at( + region, win->eventstate->x, win->eventstate->y); + + return hovered_tree_item != NULL; +} + +static int ui_tree_view_drop_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event) +{ + if (event->custom != EVT_DATA_DRAGDROP) { + return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH; + } + + const ARegion *region = CTX_wm_region(C); + uiTreeViewItemHandle *hovered_tree_item = UI_block_tree_view_find_item_at( + region, event->x, event->y); + + if (!UI_tree_view_item_drop_handle(hovered_tree_item, event->customdata)) { + return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH; + } + + return OPERATOR_FINISHED; +} + +static void UI_OT_tree_view_drop(wmOperatorType *ot) +{ + ot->name = "Tree View drop"; + ot->idname = "UI_OT_tree_view_drop"; + ot->description = "Drag and drop items onto a tree item"; + + ot->invoke = ui_tree_view_drop_invoke; + ot->poll = ui_tree_view_drop_poll; + + ot->flag = OPTYPE_INTERNAL; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ /** \name Operator & Keymap Registration * \{ */ @@ -1953,6 +1989,8 @@ void ED_operatortypes_ui(void) WM_operatortype_append(UI_OT_list_start_filter); + WM_operatortype_append(UI_OT_tree_view_drop); + /* external */ WM_operatortype_append(UI_OT_eyedropper_color); WM_operatortype_append(UI_OT_eyedropper_colorramp); diff --git a/source/blender/editors/interface/interface_query.c b/source/blender/editors/interface/interface_query.c index 09429bb6df5..2f6bda3252d 100644 --- a/source/blender/editors/interface/interface_query.c +++ b/source/blender/editors/interface/interface_query.c @@ -69,7 +69,8 @@ bool ui_but_is_toggle(const uiBut *but) UI_BTYPE_CHECKBOX, UI_BTYPE_CHECKBOX_N, UI_BTYPE_ROW, - UI_BTYPE_DATASETROW); + UI_BTYPE_DATASETROW, + UI_BTYPE_TREEROW); } /** @@ -462,6 +463,16 @@ uiBut *ui_list_row_find_from_index(const ARegion *region, const int index, uiBut return ui_but_find(region, ui_but_is_listrow_at_index, &data); } +static bool ui_but_is_treerow(const uiBut *but, const void *UNUSED(customdata)) +{ + return but->type == UI_BTYPE_TREEROW; +} + +uiBut *ui_tree_row_find_mouse_over(const ARegion *region, const int x, const int y) +{ + return ui_but_find_mouse_over_ex(region, x, y, false, ui_but_is_treerow, NULL); +} + /** \} */ /* -------------------------------------------------------------------- */ diff --git a/source/blender/editors/interface/interface_view.cc b/source/blender/editors/interface/interface_view.cc new file mode 100644 index 00000000000..b199ce9562e --- /dev/null +++ b/source/blender/editors/interface/interface_view.cc @@ -0,0 +1,133 @@ +/* + * 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 edinterface + * + * This part of the UI-View API is mostly needed to support persistent state of items within the + * view. Views are stored in #uiBlock's, and kept alive with it until after the next redraw. So we + * can compare the old view items with the new view items and keep state persistent for matching + * ones. + */ + +#include <memory> +#include <variant> + +#include "DNA_screen_types.h" + +#include "BLI_listbase.h" + +#include "interface_intern.h" + +#include "UI_interface.hh" +#include "UI_tree_view.hh" + +using namespace blender; +using namespace blender::ui; + +/** + * Wrapper to store views in a #ListBase. There's no `uiView` base class, we just store views as a + * #std::variant. + */ +struct ViewLink : public Link { + using TreeViewPtr = std::unique_ptr<AbstractTreeView>; + + std::string idname; + /* Note: Can't use std::get() on this until minimum macOS deployment target is 10.14. */ + std::variant<TreeViewPtr> view; +}; + +template<class T> T *get_view_from_link(ViewLink &link) +{ + auto *t_uptr = std::get_if<std::unique_ptr<T>>(&link.view); + return t_uptr ? t_uptr->get() : nullptr; +} + +/** + * Override this for all available tree types. + */ +AbstractTreeView *UI_block_add_view(uiBlock &block, + StringRef idname, + std::unique_ptr<AbstractTreeView> tree_view) +{ + ViewLink *view_link = OBJECT_GUARDED_NEW(ViewLink); + BLI_addtail(&block.views, view_link); + + view_link->view = std::move(tree_view); + view_link->idname = idname; + + return get_view_from_link<AbstractTreeView>(*view_link); +} + +void ui_block_free_views(uiBlock *block) +{ + LISTBASE_FOREACH_MUTABLE (ViewLink *, link, &block->views) { + OBJECT_GUARDED_DELETE(link, ViewLink); + } +} + +/** + * \param x, y: Coordinate to find a tree-row item at, in window space. + */ +uiTreeViewItemHandle *UI_block_tree_view_find_item_at(const ARegion *region, + const int x, + const int y) +{ + uiButTreeRow *tree_row_but = (uiButTreeRow *)ui_tree_row_find_mouse_over(region, x, y); + if (!tree_row_but) { + return nullptr; + } + + return tree_row_but->tree_item; +} + +static StringRef ui_block_view_find_idname(const uiBlock &block, const AbstractTreeView &view) +{ + /* First get the idname the of the view we're looking for. */ + LISTBASE_FOREACH (ViewLink *, view_link, &block.views) { + if (get_view_from_link<AbstractTreeView>(*view_link) == &view) { + return view_link->idname; + } + } + + return {}; +} + +uiTreeViewHandle *ui_block_view_find_matching_in_old_block(const uiBlock *new_block, + const uiTreeViewHandle *new_view_handle) +{ + const AbstractTreeView &needle_view = reinterpret_cast<const AbstractTreeView &>( + *new_view_handle); + + uiBlock *old_block = new_block->oldblock; + if (!old_block) { + return nullptr; + } + + StringRef idname = ui_block_view_find_idname(*new_block, needle_view); + if (idname.is_empty()) { + return nullptr; + } + + LISTBASE_FOREACH (ViewLink *, old_view_link, &old_block->views) { + if (old_view_link->idname == idname) { + return reinterpret_cast<uiTreeViewHandle *>( + get_view_from_link<AbstractTreeView>(*old_view_link)); + } + } + + return nullptr; +} diff --git a/source/blender/editors/interface/interface_widgets.c b/source/blender/editors/interface/interface_widgets.c index 0dc7c2d3f9a..466deedf3bb 100644 --- a/source/blender/editors/interface/interface_widgets.c +++ b/source/blender/editors/interface/interface_widgets.c @@ -115,6 +115,7 @@ typedef enum { UI_WTYPE_PROGRESSBAR, UI_WTYPE_NODESOCKET, UI_WTYPE_DATASETROW, + UI_WTYPE_TREEROW, } uiWidgetTypeEnum; /* Button state argument shares bits with 'uiBut.flag'. @@ -3679,10 +3680,9 @@ static void widget_progressbar( widgetbase_draw(&wtb_bar, wcol); } -static void widget_datasetrow( - uiBut *but, uiWidgetColors *wcol, rcti *rect, int state, int UNUSED(roundboxalign)) +static void widget_treerow_exec( + uiWidgetColors *wcol, rcti *rect, int state, int UNUSED(roundboxalign), int indentation) { - uiButDatasetRow *but_componentrow = (uiButDatasetRow *)but; uiWidgetBase wtb; widget_init(&wtb); @@ -3695,10 +3695,24 @@ static void widget_datasetrow( widgetbase_draw(&wtb, wcol); } - BLI_rcti_resize(rect, - BLI_rcti_size_x(rect) - UI_UNIT_X * but_componentrow->indentation, - BLI_rcti_size_y(rect)); - BLI_rcti_translate(rect, 0.5f * UI_UNIT_X * but_componentrow->indentation, 0); + BLI_rcti_resize(rect, BLI_rcti_size_x(rect) - UI_UNIT_X * indentation, BLI_rcti_size_y(rect)); + BLI_rcti_translate(rect, 0.5f * UI_UNIT_X * indentation, 0); +} + +static void widget_treerow( + uiBut *but, uiWidgetColors *wcol, rcti *rect, int state, int roundboxalign) +{ + uiButTreeRow *tree_row = (uiButTreeRow *)but; + BLI_assert(but->type == UI_BTYPE_TREEROW); + widget_treerow_exec(wcol, rect, state, roundboxalign, tree_row->indentation); +} + +static void widget_datasetrow( + uiBut *but, uiWidgetColors *wcol, rcti *rect, int state, int roundboxalign) +{ + uiButDatasetRow *dataset_row = (uiButDatasetRow *)but; + BLI_assert(but->type == UI_BTYPE_DATASETROW); + widget_treerow_exec(wcol, rect, state, roundboxalign, dataset_row->indentation); } static void widget_nodesocket( @@ -4492,6 +4506,10 @@ static uiWidgetType *widget_type(uiWidgetTypeEnum type) wt.custom = widget_datasetrow; break; + case UI_WTYPE_TREEROW: + wt.custom = widget_treerow; + break; + case UI_WTYPE_NODESOCKET: wt.custom = widget_nodesocket; break; @@ -4824,6 +4842,11 @@ void ui_draw_but(const bContext *C, struct ARegion *region, uiStyle *style, uiBu fstyle = &style->widgetlabel; break; + case UI_BTYPE_TREEROW: + wt = widget_type(UI_WTYPE_TREEROW); + fstyle = &style->widgetlabel; + break; + case UI_BTYPE_SCROLL: wt = widget_type(UI_WTYPE_SCROLL); break; @@ -5348,7 +5371,7 @@ void ui_draw_menu_item(const uiFontStyle *fstyle, } } else { - BLI_assert_msg(0, "Unknwon menu item separator type"); + BLI_assert_msg(0, "Unknown menu item separator type"); } } } diff --git a/source/blender/editors/interface/tree_view.cc b/source/blender/editors/interface/tree_view.cc new file mode 100644 index 00000000000..0ea15a2a5bb --- /dev/null +++ b/source/blender/editors/interface/tree_view.cc @@ -0,0 +1,381 @@ +/* + * 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 edinterface + */ + +#include "DNA_userdef_types.h" + +#include "BLT_translation.h" + +#include "interface_intern.h" + +#include "UI_interface.h" + +#include "UI_tree_view.hh" + +namespace blender::ui { + +/* ---------------------------------------------------------------------- */ + +/** + * Add a tree-item to the container. This is the only place where items should be added, it handles + * important invariants! + */ +AbstractTreeViewItem &TreeViewItemContainer::add_tree_item( + std::unique_ptr<AbstractTreeViewItem> item) +{ + children_.append(std::move(item)); + + /* The first item that will be added to the root sets this. */ + if (root_ == nullptr) { + root_ = this; + } + + AbstractTreeViewItem &added_item = *children_.last(); + added_item.root_ = root_; + if (root_ != this) { + /* Any item that isn't the root can be assumed to the a #AbstractTreeViewItem. Not entirely + * nice to static_cast this, but well... */ + added_item.parent_ = static_cast<AbstractTreeViewItem *>(this); + } + + return added_item; +} + +void TreeViewItemContainer::foreach_item_recursive(ItemIterFn iter_fn, IterOptions options) const +{ + for (const auto &child : children_) { + iter_fn(*child); + if (bool(options & IterOptions::SkipCollapsed) && child->is_collapsed()) { + continue; + } + + child->foreach_item_recursive(iter_fn, options); + } +} + +/* ---------------------------------------------------------------------- */ + +void AbstractTreeView::foreach_item(ItemIterFn iter_fn, IterOptions options) const +{ + foreach_item_recursive(iter_fn, options); +} + +void AbstractTreeView::build_layout_from_tree(const TreeViewLayoutBuilder &builder) +{ + uiLayout *prev_layout = builder.current_layout(); + + uiLayoutColumn(prev_layout, true); + + foreach_item([&builder](AbstractTreeViewItem &item) { builder.build_row(item); }, + IterOptions::SkipCollapsed); + + UI_block_layout_set_current(&builder.block(), prev_layout); +} + +void AbstractTreeView::update_from_old(uiBlock &new_block) +{ + uiBlock *old_block = new_block.oldblock; + if (!old_block) { + return; + } + + uiTreeViewHandle *old_view_handle = ui_block_view_find_matching_in_old_block( + &new_block, reinterpret_cast<uiTreeViewHandle *>(this)); + if (!old_view_handle) { + return; + } + + AbstractTreeView &old_view = reinterpret_cast<AbstractTreeView &>(*old_view_handle); + update_children_from_old_recursive(*this, old_view); +} + +void AbstractTreeView::update_children_from_old_recursive(const TreeViewItemContainer &new_items, + const TreeViewItemContainer &old_items) +{ + for (const auto &new_item : new_items.children_) { + AbstractTreeViewItem *matching_old_item = find_matching_child(*new_item, old_items); + if (!matching_old_item) { + continue; + } + + new_item->update_from_old(*matching_old_item); + + /* Recurse into children of the matched item. */ + update_children_from_old_recursive(*new_item, *matching_old_item); + } +} + +AbstractTreeViewItem *AbstractTreeView::find_matching_child( + const AbstractTreeViewItem &lookup_item, const TreeViewItemContainer &items) +{ + for (const auto &iter_item : items.children_) { + if (lookup_item.matches(*iter_item)) { + /* We have a matching item! */ + return iter_item.get(); + } + } + + return nullptr; +} + +/* ---------------------------------------------------------------------- */ + +void AbstractTreeViewItem::on_activate() +{ + /* Do nothing by default. */ +} + +bool AbstractTreeViewItem::on_drop(const wmDrag & /*drag*/) +{ + /* Do nothing by default. */ + return false; +} + +bool AbstractTreeViewItem::can_drop(const wmDrag & /*drag*/) const +{ + return false; +} + +std::string AbstractTreeViewItem::drop_tooltip(const bContext & /*C*/, + const wmDrag & /*drag*/, + const wmEvent & /*event*/) const +{ + return TIP_("Drop into/onto tree item"); +} + +void AbstractTreeViewItem::update_from_old(const AbstractTreeViewItem &old) +{ + is_open_ = old.is_open_; + is_active_ = old.is_active_; +} + +bool AbstractTreeViewItem::matches(const AbstractTreeViewItem &other) const +{ + return label_ == other.label_; +} + +const AbstractTreeView &AbstractTreeViewItem::get_tree_view() const +{ + return static_cast<AbstractTreeView &>(*root_); +} + +int AbstractTreeViewItem::count_parents() const +{ + int i = 0; + for (TreeViewItemContainer *parent = parent_; parent; parent = parent->parent_) { + i++; + } + return i; +} + +void AbstractTreeViewItem::set_active(bool value) +{ + if (value && !is_active()) { + /* Deactivate other items in the tree. */ + get_tree_view().foreach_item([](auto &item) { item.set_active(false); }); + on_activate(); + } + is_active_ = value; +} + +bool AbstractTreeViewItem::is_active() const +{ + return is_active_; +} + +bool AbstractTreeViewItem::is_collapsed() const +{ + return is_collapsible() && !is_open_; +} + +void AbstractTreeViewItem::toggle_collapsed() +{ + is_open_ = !is_open_; +} + +void AbstractTreeViewItem::set_collapsed(bool collapsed) +{ + is_open_ = !collapsed; +} + +bool AbstractTreeViewItem::is_collapsible() const +{ + return !children_.is_empty(); +} + +/* ---------------------------------------------------------------------- */ + +TreeViewBuilder::TreeViewBuilder(uiBlock &block) : block_(block) +{ +} + +void TreeViewBuilder::build_tree_view(AbstractTreeView &tree_view) +{ + tree_view.build_tree(); + tree_view.update_from_old(block_); + tree_view.build_layout_from_tree(TreeViewLayoutBuilder(block_)); +} + +/* ---------------------------------------------------------------------- */ + +TreeViewLayoutBuilder::TreeViewLayoutBuilder(uiBlock &block) : block_(block) +{ +} + +void TreeViewLayoutBuilder::build_row(AbstractTreeViewItem &item) const +{ + uiLayout *prev_layout = current_layout(); + uiLayout *row = uiLayoutRow(prev_layout, false); + + item.build_row(*row); + + UI_block_layout_set_current(&block(), prev_layout); +} + +uiBlock &TreeViewLayoutBuilder::block() const +{ + return block_; +} + +uiLayout *TreeViewLayoutBuilder::current_layout() const +{ + return block().curlayout; +} + +/* ---------------------------------------------------------------------- */ + +BasicTreeViewItem::BasicTreeViewItem(StringRef label, BIFIconID icon_, ActivateFn activate_fn) + : icon(icon_), activate_fn_(activate_fn) +{ + label_ = label; +} + +static void tree_row_click_fn(struct bContext *UNUSED(C), void *but_arg1, void *UNUSED(arg2)) +{ + uiButTreeRow *tree_row_but = (uiButTreeRow *)but_arg1; + AbstractTreeViewItem &tree_item = reinterpret_cast<AbstractTreeViewItem &>( + *tree_row_but->tree_item); + + /* Let a click on an opened item activate it, a second click will close it then. + * TODO Should this be for asset catalogs only? */ + if (tree_item.is_collapsed() || tree_item.is_active()) { + tree_item.toggle_collapsed(); + } + tree_item.set_active(); +} + +void BasicTreeViewItem::build_row(uiLayout &row) +{ + uiBlock *block = uiLayoutGetBlock(&row); + tree_row_but_ = (uiButTreeRow *)uiDefIconTextBut(block, + UI_BTYPE_TREEROW, + 0, + /* TODO allow icon besides the chevron icon? */ + get_draw_icon(), + label_.data(), + 0, + 0, + UI_UNIT_X, + UI_UNIT_Y, + nullptr, + 0, + 0, + 0, + 0, + nullptr); + + tree_row_but_->tree_item = reinterpret_cast<uiTreeViewItemHandle *>(this); + UI_but_func_set(&tree_row_but_->but, tree_row_click_fn, tree_row_but_, nullptr); + UI_but_treerow_indentation_set(&tree_row_but_->but, count_parents()); +} + +void BasicTreeViewItem::on_activate() +{ + if (activate_fn_) { + activate_fn_(*this); + } +} + +BIFIconID BasicTreeViewItem::get_draw_icon() const +{ + if (icon) { + return icon; + } + + if (is_collapsible()) { + return is_collapsed() ? ICON_TRIA_RIGHT : ICON_TRIA_DOWN; + } + + return ICON_NONE; +} + +uiBut *BasicTreeViewItem::button() +{ + return &tree_row_but_->but; +} + +} // namespace blender::ui + +using namespace blender::ui; + +bool UI_tree_view_item_is_active(const uiTreeViewItemHandle *item_handle) +{ + const AbstractTreeViewItem &item = reinterpret_cast<const AbstractTreeViewItem &>(*item_handle); + return item.is_active(); +} + +bool UI_tree_view_item_matches(const uiTreeViewItemHandle *a_handle, + const uiTreeViewItemHandle *b_handle) +{ + const AbstractTreeViewItem &a = reinterpret_cast<const AbstractTreeViewItem &>(*a_handle); + const AbstractTreeViewItem &b = reinterpret_cast<const AbstractTreeViewItem &>(*b_handle); + return a.matches(b); +} + +bool UI_tree_view_item_can_drop(const uiTreeViewItemHandle *item_, const wmDrag *drag) +{ + const AbstractTreeViewItem &item = reinterpret_cast<const AbstractTreeViewItem &>(*item_); + return item.can_drop(*drag); +} + +char *UI_tree_view_item_drop_tooltip(const uiTreeViewItemHandle *item_, + const bContext *C, + const wmDrag *drag, + const wmEvent *event) +{ + const AbstractTreeViewItem &item = reinterpret_cast<const AbstractTreeViewItem &>(*item_); + return BLI_strdup(item.drop_tooltip(*C, *drag, *event).c_str()); +} + +/** + * Let a tree-view item handle a drop event. + * \return True if the drop was handled by the tree-view item. + */ +bool UI_tree_view_item_drop_handle(uiTreeViewItemHandle *item_, const ListBase *drags) +{ + AbstractTreeViewItem &item = reinterpret_cast<AbstractTreeViewItem &>(*item_); + + LISTBASE_FOREACH (const wmDrag *, drag, drags) { + if (item.can_drop(*drag)) { + return item.on_drop(*drag); + } + } + + return false; +} diff --git a/source/blender/editors/interface/view2d.c b/source/blender/editors/interface/view2d.c index 23c8a0d35bf..0036a812a87 100644 --- a/source/blender/editors/interface/view2d.c +++ b/source/blender/editors/interface/view2d.c @@ -866,6 +866,11 @@ void UI_view2d_curRect_changed(const bContext *C, View2D *v2d) /* ------------------ */ +bool UI_view2d_area_supports_sync(ScrArea *area) +{ + return ELEM(area->spacetype, SPACE_ACTION, SPACE_NLA, SPACE_SEQ, SPACE_CLIP, SPACE_GRAPH); +} + /* Called by menus to activate it, or by view2d operators * to make sure 'related' views stay in synchrony */ void UI_view2d_sync(bScreen *screen, ScrArea *area, View2D *v2dcur, int flag) @@ -903,6 +908,9 @@ void UI_view2d_sync(bScreen *screen, ScrArea *area, View2D *v2dcur, int flag) /* check if doing whole screen syncing (i.e. time/horizontal) */ if ((v2dcur->flag & V2D_VIEWSYNC_SCREEN_TIME) && (screen)) { LISTBASE_FOREACH (ScrArea *, area_iter, &screen->areabase) { + if (!UI_view2d_area_supports_sync(area_iter)) { + continue; + } LISTBASE_FOREACH (ARegion *, region, &area_iter->regionbase) { /* don't operate on self */ if (v2dcur != ®ion->v2d) { diff --git a/source/blender/editors/mesh/editmesh_knife.c b/source/blender/editors/mesh/editmesh_knife.c index eee4aec7459..64c008acf8e 100644 --- a/source/blender/editors/mesh/editmesh_knife.c +++ b/source/blender/editors/mesh/editmesh_knife.c @@ -113,7 +113,7 @@ typedef struct KnifeColors { uchar axis_extra[3]; } KnifeColors; -/* Knifetool Operator. */ +/* Knife-tool Operator. */ typedef struct KnifeVert { Object *ob; uint base_index; @@ -333,6 +333,9 @@ enum { KNF_MODAL_SHOW_DISTANCE_ANGLE_TOGGLE, KNF_MODAL_DEPTH_TEST_TOGGLE, KNF_MODAL_PANNING, + KNF_MODAL_X_AXIS, + KNF_MODAL_Y_AXIS, + KNF_MODAL_Z_AXIS, KNF_MODAL_ADD_CUT_CLOSED, }; @@ -366,7 +369,6 @@ enum { /** \name Drawing * \{ */ -#if 1 static void knifetool_raycast_planes(const KnifeTool_OpData *kcd, float r_v1[3], float r_v2[3]) { float planes[4][4]; @@ -380,10 +382,6 @@ static void knifetool_raycast_planes(const KnifeTool_OpData *kcd, float r_v1[3], float lambda_best[2] = {-FLT_MAX, FLT_MAX}; int i; - /* We (sometimes) need the lines to be at the same depth before projecting. */ -# if 0 - sub_v3_v3v3(ray_dir, kcd->curr.cage, kcd->prev.cage); -# else { float curr_cage_adjust[3]; float co_depth[3]; @@ -393,7 +391,6 @@ static void knifetool_raycast_planes(const KnifeTool_OpData *kcd, float r_v1[3], sub_v3_v3v3(ray_dir, curr_cage_adjust, kcd->prev.cage); } -# endif for (i = 0; i < 4; i++) { float ray_hit[3]; @@ -445,7 +442,7 @@ static void knifetool_draw_orientation_locking(const KnifeTool_OpData *kcd) if (!compare_v3v3(kcd->prev.cage, kcd->curr.cage, KNIFE_FLT_EPSBIG)) { float v1[3], v2[3]; - /* This is causing buggyness when prev.cage and curr.cage are too close together. */ + /* This is causing buggy behavior when `prev.cage` and `curr.cage` are too close together. */ knifetool_raycast_planes(kcd, v1, v2); uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT); @@ -480,7 +477,6 @@ static void knifetool_draw_orientation_locking(const KnifeTool_OpData *kcd) immUnbindProgram(); } } -#endif static void knifetool_draw_visible_distances(const KnifeTool_OpData *kcd) { @@ -1112,7 +1108,7 @@ static void knife_update_header(bContext *C, wmOperator *op, KnifeTool_OpData *k "%s: start/define cut, %s: close cut, %s: new cut, " "%s: midpoint snap (%s), %s: ignore snap (%s), " "%s: angle constraint %.2f(%.2f) (%s%s%s%s), %s: cut through (%s), " - "%s: panning, XYZ: orientation lock (%s), " + "%s: panning, %s%s%s: orientation lock (%s), " "%s: distance/angle measurements (%s), " "%s: x-ray (%s)"), WM_MODALKEY(KNF_MODAL_CONFIRM), @@ -1144,6 +1140,9 @@ static void knife_update_header(bContext *C, wmOperator *op, KnifeTool_OpData *k WM_MODALKEY(KNF_MODAL_CUT_THROUGH_TOGGLE), WM_bool_as_string(kcd->cut_through), WM_MODALKEY(KNF_MODAL_PANNING), + WM_MODALKEY(KNF_MODAL_X_AXIS), + WM_MODALKEY(KNF_MODAL_Y_AXIS), + WM_MODALKEY(KNF_MODAL_Z_AXIS), (kcd->axis_constrained ? kcd->axis_string : WM_bool_as_string(kcd->axis_constrained)), WM_MODALKEY(KNF_MODAL_SHOW_DISTANCE_ANGLE_TOGGLE), WM_bool_as_string(kcd->show_dist_angle), @@ -3900,71 +3899,69 @@ static void knifetool_undo(KnifeTool_OpData *kcd) KnifeUndoFrame *undo; BLI_mempool_iter iterkfe; - if (!BLI_stack_is_empty(kcd->undostack)) { - undo = BLI_stack_peek(kcd->undostack); + undo = BLI_stack_peek(kcd->undostack); - /* Undo edge splitting. */ - for (int i = 0; i < undo->splits; i++) { - BLI_stack_pop(kcd->splitstack, &newkfe); - BLI_stack_pop(kcd->splitstack, &kfe); - knife_join_edge(newkfe, kfe); - } + /* Undo edge splitting. */ + for (int i = 0; i < undo->splits; i++) { + BLI_stack_pop(kcd->splitstack, &newkfe); + BLI_stack_pop(kcd->splitstack, &kfe); + knife_join_edge(newkfe, kfe); + } - for (int i = 0; i < undo->cuts; i++) { + for (int i = 0; i < undo->cuts; i++) { - BLI_mempool_iternew(kcd->kedges, &iterkfe); - for (kfe = BLI_mempool_iterstep(&iterkfe); kfe; kfe = BLI_mempool_iterstep(&iterkfe)) { - if (!kfe->is_cut || kfe->is_invalid || kfe->splits) { - continue; - } - lastkfe = kfe; + BLI_mempool_iternew(kcd->kedges, &iterkfe); + for (kfe = BLI_mempool_iterstep(&iterkfe); kfe; kfe = BLI_mempool_iterstep(&iterkfe)) { + if (!kfe->is_cut || kfe->is_invalid || kfe->splits) { + continue; } + lastkfe = kfe; + } - if (lastkfe) { - lastkfe->is_invalid = true; - - /* TODO: Are they always guaranteed to be in this order? */ - v1 = lastkfe->v1; - v2 = lastkfe->v2; - - /* Only remove first vertex if it is the start segment of the cut. */ - if (!v1->is_invalid && !v1->is_splitting) { - v1->is_invalid = true; - /* If the first vertex is touching any other cut edges don't remove it. */ - for (ref = v1->edges.first; ref; ref = ref->next) { - kfe = ref->ref; - if (kfe->is_cut && !kfe->is_invalid) { - v1->is_invalid = false; - break; - } + if (lastkfe) { + lastkfe->is_invalid = true; + + /* TODO: Are they always guaranteed to be in this order? */ + v1 = lastkfe->v1; + v2 = lastkfe->v2; + + /* Only remove first vertex if it is the start segment of the cut. */ + if (!v1->is_invalid && !v1->is_splitting) { + v1->is_invalid = true; + /* If the first vertex is touching any other cut edges don't remove it. */ + for (ref = v1->edges.first; ref; ref = ref->next) { + kfe = ref->ref; + if (kfe->is_cut && !kfe->is_invalid) { + v1->is_invalid = false; + break; } } + } - /* Only remove second vertex if it is the end segment of the cut. */ - if (!v2->is_invalid && !v2->is_splitting) { - v2->is_invalid = true; - /* If the second vertex is touching any other cut edges don't remove it. */ - for (ref = v2->edges.first; ref; ref = ref->next) { - kfe = ref->ref; - if (kfe->is_cut && !kfe->is_invalid) { - v2->is_invalid = false; - break; - } + /* Only remove second vertex if it is the end segment of the cut. */ + if (!v2->is_invalid && !v2->is_splitting) { + v2->is_invalid = true; + /* If the second vertex is touching any other cut edges don't remove it. */ + for (ref = v2->edges.first; ref; ref = ref->next) { + kfe = ref->ref; + if (kfe->is_cut && !kfe->is_invalid) { + v2->is_invalid = false; + break; } } } } + } - if (kcd->mode == MODE_DRAGGING) { - /* Restore kcd->prev. */ - kcd->prev = undo->pos; - } + if (kcd->mode == MODE_DRAGGING) { + /* Restore kcd->prev. */ + kcd->prev = undo->pos; + } - /* Restore data for distance and angle measurements. */ - kcd->mdata = undo->mdata; + /* Restore data for distance and angle measurements. */ + kcd->mdata = undo->mdata; - BLI_stack_discard(kcd->undostack); - } + BLI_stack_discard(kcd->undostack); } /** \} */ @@ -4100,7 +4097,7 @@ static void knifetool_init(bContext *C, kcd->axis_string[0] = ' '; kcd->axis_string[1] = '\0'; - /* Initialise num input handling for angle snapping. */ + /* Initialize number input handling for angle snapping. */ initNumInput(&kcd->num); kcd->num.idx_max = 0; kcd->num.val_flag[0] |= NUM_NO_NEGATIVE; @@ -4151,7 +4148,7 @@ static void knifetool_exit_ex(KnifeTool_OpData *kcd) MEM_freeN(kcd->cagecos); knife_bvh_free(kcd); - /* Linehits cleanup. */ + /* Line-hits cleanup. */ if (kcd->linehits) { MEM_freeN(kcd->linehits); } @@ -4307,6 +4304,9 @@ wmKeyMap *knifetool_modal_keymap(wmKeyConfig *keyconf) {KNF_MODAL_ADD_CUT, "ADD_CUT", 0, "Add Cut", ""}, {KNF_MODAL_ADD_CUT_CLOSED, "ADD_CUT_CLOSED", 0, "Add Cut Closed", ""}, {KNF_MODAL_PANNING, "PANNING", 0, "Panning", ""}, + {KNF_MODAL_X_AXIS, "X_AXIS", 0, "X Axis Locking", ""}, + {KNF_MODAL_Y_AXIS, "Y_AXIS", 0, "Y Axis Locking", ""}, + {KNF_MODAL_Z_AXIS, "Z_AXIS", 0, "Z Axis Locking", ""}, {0, NULL, 0, NULL, NULL}, }; @@ -4404,6 +4404,12 @@ static int knifetool_modal(bContext *C, wmOperator *op, const wmEvent *event) return OPERATOR_FINISHED; case KNF_MODAL_UNDO: + if (BLI_stack_is_empty(kcd->undostack)) { + ED_region_tag_redraw(kcd->region); + knifetool_exit(op); + ED_workspace_status_text(C, NULL); + return OPERATOR_CANCELLED; + } knifetool_undo(kcd); knife_update_active(C, kcd); ED_region_tag_redraw(kcd->region); @@ -4614,52 +4620,58 @@ static int knifetool_modal(bContext *C, wmOperator *op, const wmEvent *event) if (kcd->num.str_cur >= 2) { knife_reset_snap_angle_input(kcd); } - /* Modal numinput inactive, try to handle numeric inputs last... */ - if (!handled && event->val == KM_PRESS && handleNumInput(C, &kcd->num, event)) { - applyNumInput(&kcd->num, &snapping_increment_temp); - /* Restrict number key input to 0 - 90 degree range. */ - if (snapping_increment_temp > KNIFE_MIN_ANGLE_SNAPPING_INCREMENT && - snapping_increment_temp < KNIFE_MAX_ANGLE_SNAPPING_INCREMENT) { - kcd->angle_snapping_increment = snapping_increment_temp; + if (event->type != EVT_MODAL_MAP) { + /* Modal number-input inactive, try to handle numeric inputs last. */ + if (!handled && event->val == KM_PRESS && handleNumInput(C, &kcd->num, event)) { + applyNumInput(&kcd->num, &snapping_increment_temp); + /* Restrict number key input to 0 - 90 degree range. */ + if (snapping_increment_temp > KNIFE_MIN_ANGLE_SNAPPING_INCREMENT && + snapping_increment_temp < KNIFE_MAX_ANGLE_SNAPPING_INCREMENT) { + kcd->angle_snapping_increment = snapping_increment_temp; + } + knife_update_active(C, kcd); + knife_update_header(C, op, kcd); + ED_region_tag_redraw(kcd->region); + return OPERATOR_RUNNING_MODAL; } - knife_update_active(C, kcd); - knife_update_header(C, op, kcd); - ED_region_tag_redraw(kcd->region); - return OPERATOR_RUNNING_MODAL; } } /* Constrain axes with X,Y,Z keys. */ - if (event->val == KM_PRESS && ELEM(event->type, EVT_XKEY, EVT_YKEY, EVT_ZKEY)) { - if (event->type == EVT_XKEY && kcd->constrain_axis != KNF_CONSTRAIN_AXIS_X) { - kcd->constrain_axis = KNF_CONSTRAIN_AXIS_X; - kcd->constrain_axis_mode = KNF_CONSTRAIN_AXIS_MODE_GLOBAL; - kcd->axis_string[0] = 'X'; - } - else if (event->type == EVT_YKEY && kcd->constrain_axis != KNF_CONSTRAIN_AXIS_Y) { - kcd->constrain_axis = KNF_CONSTRAIN_AXIS_Y; - kcd->constrain_axis_mode = KNF_CONSTRAIN_AXIS_MODE_GLOBAL; - kcd->axis_string[0] = 'Y'; - } - else if (event->type == EVT_ZKEY && kcd->constrain_axis != KNF_CONSTRAIN_AXIS_Z) { - kcd->constrain_axis = KNF_CONSTRAIN_AXIS_Z; - kcd->constrain_axis_mode = KNF_CONSTRAIN_AXIS_MODE_GLOBAL; - kcd->axis_string[0] = 'Z'; - } - else { - /* Cycle through modes with repeated key presses. */ - if (kcd->constrain_axis_mode != KNF_CONSTRAIN_AXIS_MODE_LOCAL) { - kcd->constrain_axis_mode++; - kcd->axis_string[0] += 32; /* Lower case. */ + if (event->type == EVT_MODAL_MAP) { + if (ELEM(event->val, KNF_MODAL_X_AXIS, KNF_MODAL_Y_AXIS, KNF_MODAL_Z_AXIS)) { + if (event->val == KNF_MODAL_X_AXIS && kcd->constrain_axis != KNF_CONSTRAIN_AXIS_X) { + kcd->constrain_axis = KNF_CONSTRAIN_AXIS_X; + kcd->constrain_axis_mode = KNF_CONSTRAIN_AXIS_MODE_GLOBAL; + kcd->axis_string[0] = 'X'; + } + else if (event->val == KNF_MODAL_Y_AXIS && kcd->constrain_axis != KNF_CONSTRAIN_AXIS_Y) { + kcd->constrain_axis = KNF_CONSTRAIN_AXIS_Y; + kcd->constrain_axis_mode = KNF_CONSTRAIN_AXIS_MODE_GLOBAL; + kcd->axis_string[0] = 'Y'; + } + else if (event->val == KNF_MODAL_Z_AXIS && kcd->constrain_axis != KNF_CONSTRAIN_AXIS_Z) { + kcd->constrain_axis = KNF_CONSTRAIN_AXIS_Z; + kcd->constrain_axis_mode = KNF_CONSTRAIN_AXIS_MODE_GLOBAL; + kcd->axis_string[0] = 'Z'; } else { - kcd->constrain_axis = KNF_CONSTRAIN_AXIS_NONE; - kcd->constrain_axis_mode = KNF_CONSTRAIN_AXIS_MODE_NONE; + /* Cycle through modes with repeated key presses. */ + if (kcd->constrain_axis_mode != KNF_CONSTRAIN_AXIS_MODE_LOCAL) { + kcd->constrain_axis_mode++; + kcd->axis_string[0] += 32; /* Lower case. */ + } + else { + kcd->constrain_axis = KNF_CONSTRAIN_AXIS_NONE; + kcd->constrain_axis_mode = KNF_CONSTRAIN_AXIS_MODE_NONE; + } } + kcd->axis_constrained = (kcd->constrain_axis != KNF_CONSTRAIN_AXIS_NONE); + knifetool_disable_angle_snapping(kcd); + knife_update_header(C, op, kcd); + ED_region_tag_redraw(kcd->region); + do_refresh = true; } - kcd->axis_constrained = (kcd->constrain_axis != KNF_CONSTRAIN_AXIS_NONE); - knifetool_disable_angle_snapping(kcd); - knife_update_header(C, op, kcd); } if (kcd->mode == MODE_DRAGGING) { diff --git a/source/blender/editors/object/object_add.c b/source/blender/editors/object/object_add.c index beadbf2689e..cc4f2acc346 100644 --- a/source/blender/editors/object/object_add.c +++ b/source/blender/editors/object/object_add.c @@ -75,6 +75,7 @@ #include "BKE_lattice.h" #include "BKE_layer.h" #include "BKE_lib_id.h" +#include "BKE_lib_override.h" #include "BKE_lib_query.h" #include "BKE_lib_remap.h" #include "BKE_light.h" @@ -2015,6 +2016,15 @@ static int object_delete_exec(bContext *C, wmOperator *op) continue; } + if (!BKE_lib_override_library_id_is_user_deletable(bmain, &ob->id)) { + /* Can this case ever happen? */ + BKE_reportf(op->reports, + RPT_WARNING, + "Cannot delete object '%s' as it used by override collections", + ob->id.name + 2); + continue; + } + if (ID_REAL_USERS(ob) <= 1 && ID_EXTRA_USERS(ob) == 0 && BKE_library_ID_is_indirectly_used(bmain, ob)) { BKE_reportf(op->reports, @@ -2828,8 +2838,7 @@ static int object_convert_exec(bContext *C, wmOperator *op) /* Remove unused materials. */ int actcol = ob_gpencil->actcol; for (int slot = 1; slot <= ob_gpencil->totcol; slot++) { - while (slot <= ob_gpencil->totcol && - !BKE_object_material_slot_used(ob_gpencil->data, slot)) { + while (slot <= ob_gpencil->totcol && !BKE_object_material_slot_used(ob_gpencil, slot)) { ob_gpencil->actcol = slot; BKE_object_material_slot_remove(CTX_data_main(C), ob_gpencil); diff --git a/source/blender/editors/object/object_edit.c b/source/blender/editors/object/object_edit.c index 5697c2c973d..2bd0ae5f121 100644 --- a/source/blender/editors/object/object_edit.c +++ b/source/blender/editors/object/object_edit.c @@ -1739,7 +1739,7 @@ void OBJECT_OT_mode_set_with_submode(wmOperatorType *ot) OBJECT_OT_mode_set(ot); /* identifiers */ - ot->name = "Set Object Mode with Submode"; + ot->name = "Set Object Mode with Sub-mode"; ot->idname = "OBJECT_OT_mode_set_with_submode"; /* properties */ diff --git a/source/blender/editors/object/object_intern.h b/source/blender/editors/object/object_intern.h index 50dd9322c5c..d00e6efeb29 100644 --- a/source/blender/editors/object/object_intern.h +++ b/source/blender/editors/object/object_intern.h @@ -78,7 +78,6 @@ void OBJECT_OT_mode_set(struct wmOperatorType *ot); void OBJECT_OT_mode_set_with_submode(struct wmOperatorType *ot); void OBJECT_OT_editmode_toggle(struct wmOperatorType *ot); void OBJECT_OT_posemode_toggle(struct wmOperatorType *ot); -void OBJECT_OT_proxy_make(struct wmOperatorType *ot); void OBJECT_OT_shade_smooth(struct wmOperatorType *ot); void OBJECT_OT_shade_flat(struct wmOperatorType *ot); void OBJECT_OT_paths_calculate(struct wmOperatorType *ot); diff --git a/source/blender/editors/object/object_modifier.c b/source/blender/editors/object/object_modifier.c index b9942bc563a..efe19785f31 100644 --- a/source/blender/editors/object/object_modifier.c +++ b/source/blender/editors/object/object_modifier.c @@ -1044,6 +1044,10 @@ bool edit_modifier_poll_generic(bContext *C, Object *ob = (ptr.owner_id) ? (Object *)ptr.owner_id : ED_object_active_context(C); ModifierData *mod = ptr.data; /* May be NULL. */ + if (mod == NULL && ob != NULL) { + mod = BKE_object_active_modifier(ob); + } + if (!ob || ID_IS_LINKED(ob)) { return false; } diff --git a/source/blender/editors/object/object_ops.c b/source/blender/editors/object/object_ops.c index aa9ae082317..fa0208a7022 100644 --- a/source/blender/editors/object/object_ops.c +++ b/source/blender/editors/object/object_ops.c @@ -56,7 +56,6 @@ void ED_operatortypes_object(void) WM_operatortype_append(OBJECT_OT_mode_set_with_submode); WM_operatortype_append(OBJECT_OT_editmode_toggle); WM_operatortype_append(OBJECT_OT_posemode_toggle); - WM_operatortype_append(OBJECT_OT_proxy_make); WM_operatortype_append(OBJECT_OT_shade_smooth); WM_operatortype_append(OBJECT_OT_shade_flat); WM_operatortype_append(OBJECT_OT_paths_calculate); diff --git a/source/blender/editors/object/object_relations.c b/source/blender/editors/object/object_relations.c index 75269dffec8..5c7e1e1fa01 100644 --- a/source/blender/editors/object/object_relations.c +++ b/source/blender/editors/object/object_relations.c @@ -331,167 +331,6 @@ void OBJECT_OT_vertex_parent_set(wmOperatorType *ot) /** \} */ /* ------------------------------------------------------------------- */ -/** \name Make Proxy Operator - * \{ */ - -/* set the object to proxify */ -static int make_proxy_invoke(bContext *C, wmOperator *op, const wmEvent *event) -{ - Scene *scene = CTX_data_scene(C); - Object *ob = ED_object_active_context(C); - - /* sanity checks */ - if (!scene || ID_IS_LINKED(scene) || !ob) { - return OPERATOR_CANCELLED; - } - - /* Get object to work on - use a menu if we need to... */ - if (ob->instance_collection && ID_IS_LINKED(ob->instance_collection)) { - /* gives menu with list of objects in group */ - /* proxy_group_objects_menu(C, op, ob, ob->instance_collection); */ - WM_enum_search_invoke(C, op, event); - return OPERATOR_CANCELLED; - } - if (ID_IS_LINKED(ob)) { - uiPopupMenu *pup = UI_popup_menu_begin(C, IFACE_("OK?"), ICON_QUESTION); - uiLayout *layout = UI_popup_menu_layout(pup); - - /* create operator menu item with relevant properties filled in */ - PointerRNA opptr_dummy; - uiItemFullO_ptr( - layout, op->type, op->type->name, ICON_NONE, NULL, WM_OP_EXEC_REGION_WIN, 0, &opptr_dummy); - - /* present the menu and be done... */ - UI_popup_menu_end(C, pup); - - /* this invoke just calls another instance of this operator... */ - return OPERATOR_INTERFACE; - } - - /* error.. cannot continue */ - BKE_report(op->reports, RPT_ERROR, "Can only make proxy for a referenced object or collection"); - return OPERATOR_CANCELLED; -} - -static int make_proxy_exec(bContext *C, wmOperator *op) -{ - Main *bmain = CTX_data_main(C); - Object *ob, *gob = ED_object_active_context(C); - Scene *scene = CTX_data_scene(C); - ViewLayer *view_layer = CTX_data_view_layer(C); - - if (gob->instance_collection != NULL) { - const ListBase instance_collection_objects = BKE_collection_object_cache_get( - gob->instance_collection); - Base *base = BLI_findlink(&instance_collection_objects, RNA_enum_get(op->ptr, "object")); - ob = base->object; - } - else { - ob = gob; - gob = NULL; - } - - if (ob) { - Object *newob; - char name[MAX_ID_NAME + 4]; - - BLI_snprintf(name, sizeof(name), "%s_proxy", ((ID *)(gob ? gob : ob))->name + 2); - - /* Add new object for the proxy */ - newob = BKE_object_add_from(bmain, scene, view_layer, OB_EMPTY, name, gob ? gob : ob); - - /* set layers OK */ - BKE_object_make_proxy(bmain, newob, ob, gob); - - /* Set back pointer immediately so dependency graph knows that this is - * is a proxy and will act accordingly. Otherwise correctness of graph - * will depend on order of bases. - * - * TODO(sergey): We really need to get rid of this bi-directional links - * in proxies with something like library overrides. - */ - if (newob->proxy != NULL) { - newob->proxy->proxy_from = newob; - } - else { - BKE_report(op->reports, RPT_ERROR, "Unable to assign proxy"); - } - - /* depsgraph flushes are needed for the new data */ - DEG_relations_tag_update(bmain); - DEG_id_tag_update(&newob->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_ANIMATION); - WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, newob); - } - else { - BKE_report(op->reports, RPT_ERROR, "No object to make proxy for"); - return OPERATOR_CANCELLED; - } - - return OPERATOR_FINISHED; -} - -/* Generic itemf's for operators that take library args */ -static const EnumPropertyItem *proxy_collection_object_itemf(bContext *C, - PointerRNA *UNUSED(ptr), - PropertyRNA *UNUSED(prop), - bool *r_free) -{ - EnumPropertyItem item_tmp = {0}, *item = NULL; - int totitem = 0; - int i = 0; - Object *ob = ED_object_active_context(C); - - if (!ob || !ob->instance_collection) { - return DummyRNA_DEFAULT_items; - } - - /* find the object to affect */ - FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN (ob->instance_collection, object) { - item_tmp.identifier = item_tmp.name = object->id.name + 2; - item_tmp.value = i++; - RNA_enum_item_add(&item, &totitem, &item_tmp); - } - FOREACH_COLLECTION_OBJECT_RECURSIVE_END; - - RNA_enum_item_end(&item, &totitem); - *r_free = true; - - return item; -} - -void OBJECT_OT_proxy_make(wmOperatorType *ot) -{ - PropertyRNA *prop; - - /* identifiers */ - ot->name = "Make Proxy"; - ot->idname = "OBJECT_OT_proxy_make"; - ot->description = "Add empty object to become local replacement data of a library-linked object"; - - /* callbacks */ - ot->invoke = make_proxy_invoke; - ot->exec = make_proxy_exec; - ot->poll = ED_operator_object_active; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - /* properties */ - /* XXX, relies on hard coded ID at the moment */ - prop = RNA_def_enum(ot->srna, - "object", - DummyRNA_DEFAULT_items, - 0, - "Proxy Object", - "Name of library-linked/collection object to make a proxy for"); - RNA_def_enum_funcs(prop, proxy_collection_object_itemf); - RNA_def_property_flag(prop, PROP_ENUM_NO_TRANSLATE); - ot->prop = prop; -} - -/** \} */ - -/* ------------------------------------------------------------------- */ /** \name Clear Parent Operator * \{ */ diff --git a/source/blender/editors/render/render_shading.c b/source/blender/editors/render/render_shading.c index 8a3d8f9636b..7b2667905ff 100644 --- a/source/blender/editors/render/render_shading.c +++ b/source/blender/editors/render/render_shading.c @@ -690,7 +690,7 @@ static int material_slot_remove_unused_exec(bContext *C, wmOperator *op) Object *ob = objects[ob_index]; int actcol = ob->actcol; for (int slot = 1; slot <= ob->totcol; slot++) { - while (slot <= ob->totcol && !BKE_object_material_slot_used(ob->data, slot)) { + while (slot <= ob->totcol && !BKE_object_material_slot_used(ob, slot)) { ob->actcol = slot; BKE_object_material_slot_remove(bmain, ob); diff --git a/source/blender/editors/screen/area.c b/source/blender/editors/screen/area.c index c71e68df2fd..833c9accf95 100644 --- a/source/blender/editors/screen/area.c +++ b/source/blender/editors/screen/area.c @@ -1284,8 +1284,8 @@ bool ED_region_is_overlap(int spacetype, int regiontype) RGN_TYPE_TOOLS, RGN_TYPE_UI, RGN_TYPE_TOOL_PROPS, - RGN_TYPE_HEADER, - RGN_TYPE_FOOTER)) { + RGN_TYPE_FOOTER, + RGN_TYPE_TOOL_HEADER)) { return true; } } @@ -1699,6 +1699,9 @@ static void ed_default_handlers( wmKeyMap *keymap = WM_keymap_ensure(wm->defaultconf, "User Interface", 0, 0); WM_event_add_keymap_handler(handlers, keymap); + ListBase *dropboxes = WM_dropboxmap_find("User Interface", 0, 0); + WM_event_add_dropbox_handler(handlers, dropboxes); + /* user interface widgets */ UI_region_handlers_add(handlers); } @@ -3000,7 +3003,7 @@ void ED_region_panels_layout_ex(const bContext *C, /* before setting the view */ if (region_layout_based) { - /* XXX, only single panel support atm. + /* XXX, only single panel support at the moment. * Can't use x/y values calculated above because they're not using the real height of panels, * instead they calculate offsets for the next panel to start drawing. */ Panel *panel = region->panels.last; diff --git a/source/blender/editors/screen/screen_context.c b/source/blender/editors/screen/screen_context.c index 2ccefb993c7..3d447d90626 100644 --- a/source/blender/editors/screen/screen_context.c +++ b/source/blender/editors/screen/screen_context.c @@ -1073,9 +1073,14 @@ static eContextResult screen_ctx_ui_list(const bContext *C, bContextDataResult * { wmWindow *win = CTX_wm_window(C); ARegion *region = CTX_wm_region(C); - uiList *list = UI_list_find_mouse_over(region, win->eventstate); - CTX_data_pointer_set(result, NULL, &RNA_UIList, list); - return CTX_RESULT_OK; + if (region) { + uiList *list = UI_list_find_mouse_over(region, win->eventstate); + if (list) { + CTX_data_pointer_set(result, NULL, &RNA_UIList, list); + return CTX_RESULT_OK; + } + } + return CTX_RESULT_NO_DATA; } /* Registry of context callback functions. */ diff --git a/source/blender/editors/screen/screen_intern.h b/source/blender/editors/screen/screen_intern.h index 4016ef84bfd..04ee62b1631 100644 --- a/source/blender/editors/screen/screen_intern.h +++ b/source/blender/editors/screen/screen_intern.h @@ -62,7 +62,7 @@ typedef enum eScreenAxis { #define AREAJOINTOLERANCEY (HEADERY * U.dpi_fac) /* Expanded interaction influence of area borders. */ -#define BORDERPADDING (U.dpi_fac + U.pixelsize) +#define BORDERPADDING ((2.0f * U.dpi_fac) + U.pixelsize) /* area.c */ void ED_area_data_copy(ScrArea *area_dst, ScrArea *area_src, const bool do_free); diff --git a/source/blender/editors/screen/workspace_edit.c b/source/blender/editors/screen/workspace_edit.c index b99cb831bee..4b81e713080 100644 --- a/source/blender/editors/screen/workspace_edit.c +++ b/source/blender/editors/screen/workspace_edit.c @@ -310,7 +310,14 @@ static int workspace_append_activate_exec(bContext *C, wmOperator *op) RNA_string_get(op->ptr, "filepath", filepath); WorkSpace *appended_workspace = (WorkSpace *)WM_file_append_datablock( - bmain, CTX_data_scene(C), CTX_data_view_layer(C), CTX_wm_view3d(C), filepath, ID_WS, idname); + bmain, + CTX_data_scene(C), + CTX_data_view_layer(C), + CTX_wm_view3d(C), + filepath, + ID_WS, + idname, + BLO_LIBLINK_APPEND_RECURSIVE); if (appended_workspace) { /* Set defaults. */ diff --git a/source/blender/editors/sculpt_paint/paint_image_proj.c b/source/blender/editors/sculpt_paint/paint_image_proj.c index a58b1947b0c..0176b4a1b13 100644 --- a/source/blender/editors/sculpt_paint/paint_image_proj.c +++ b/source/blender/editors/sculpt_paint/paint_image_proj.c @@ -1659,7 +1659,9 @@ static float project_paint_uvpixel_mask(const ProjPaintState *ps, if (other_tpage && (ibuf_other = BKE_image_acquire_ibuf(other_tpage, NULL, NULL))) { const MLoopTri *lt_other = &ps->mlooptri_eval[tri_index]; - const float *lt_other_tri_uv[3] = {PS_LOOPTRI_AS_UV_3(ps->poly_to_loop_uv, lt_other)}; + const float *lt_other_tri_uv[3] = {ps->mloopuv_stencil_eval[lt_other->tri[0]].uv, + ps->mloopuv_stencil_eval[lt_other->tri[1]].uv, + ps->mloopuv_stencil_eval[lt_other->tri[2]].uv}; /* #BKE_image_acquire_ibuf - TODO: this may be slow. */ uchar rgba_ub[4]; diff --git a/source/blender/editors/sculpt_paint/sculpt_smooth.c b/source/blender/editors/sculpt_paint/sculpt_smooth.c index 38165b7622f..1bfe8e1cbf1 100644 --- a/source/blender/editors/sculpt_paint/sculpt_smooth.c +++ b/source/blender/editors/sculpt_paint/sculpt_smooth.c @@ -395,7 +395,12 @@ void SCULPT_smooth(Sculpt *sd, void SCULPT_do_smooth_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) { SculptSession *ss = ob->sculpt; - if (ss->cache->bstrength <= 0.0f) { + + /* NOTE: The enhance brush needs to initialize its state on the first brush step. The stroke + * strength can become 0 during the stroke, but it can not change sign (the sign is determined + * in the beginning of the stroke. So here it is important to not switch to enhance brush in the + * middle of the stroke. */ + if (ss->cache->bstrength < 0.0f) { /* Invert mode, intensify details. */ SCULPT_enhance_details_brush(sd, ob, nodes, totnode); } diff --git a/source/blender/editors/space_action/action_data.c b/source/blender/editors/space_action/action_data.c index 717d87c4972..a4fd2d81778 100644 --- a/source/blender/editors/space_action/action_data.c +++ b/source/blender/editors/space_action/action_data.c @@ -71,7 +71,7 @@ /* ACTION CREATION */ /* Helper function to find the active AnimData block from the Action Editor context */ -AnimData *ED_actedit_animdata_from_context(bContext *C) +AnimData *ED_actedit_animdata_from_context(bContext *C, ID **r_adt_id_owner) { SpaceAction *saction = (SpaceAction *)CTX_wm_space_data(C); Object *ob = CTX_data_active_object(C); @@ -82,12 +82,18 @@ AnimData *ED_actedit_animdata_from_context(bContext *C) /* Currently, "Action Editor" means object-level only... */ if (ob) { adt = ob->adt; + if (r_adt_id_owner) { + *r_adt_id_owner = &ob->id; + } } } else if (saction->mode == SACTCONT_SHAPEKEY) { Key *key = BKE_key_from_object(ob); if (key) { adt = key->adt; + if (r_adt_id_owner) { + *r_adt_id_owner = &key->id; + } } } @@ -212,6 +218,7 @@ static int action_new_exec(bContext *C, wmOperator *UNUSED(op)) bAction *oldact = NULL; AnimData *adt = NULL; + ID *adt_id_owner = NULL; /* hook into UI */ UI_context_active_but_prop_get_templateID(C, &ptr, &prop); @@ -225,13 +232,14 @@ static int action_new_exec(bContext *C, wmOperator *UNUSED(op)) /* stash the old action to prevent it from being lost */ if (ptr.type == &RNA_AnimData) { adt = ptr.data; + adt_id_owner = ptr.owner_id; } else if (ptr.type == &RNA_SpaceDopeSheetEditor) { - adt = ED_actedit_animdata_from_context(C); + adt = ED_actedit_animdata_from_context(C, &adt_id_owner); } } else { - adt = ED_actedit_animdata_from_context(C); + adt = ED_actedit_animdata_from_context(C, &adt_id_owner); oldact = adt->action; } { @@ -239,8 +247,9 @@ static int action_new_exec(bContext *C, wmOperator *UNUSED(op)) /* Perform stashing operation - But only if there is an action */ if (adt && oldact) { + BLI_assert(adt_id_owner != NULL); /* stash the action */ - if (BKE_nla_action_stash(adt, ID_IS_OVERRIDE_LIBRARY(ptr.owner_id))) { + if (BKE_nla_action_stash(adt, ID_IS_OVERRIDE_LIBRARY(adt_id_owner))) { /* The stash operation will remove the user already * (and unlink the action from the AnimData action slot). * Hence, we must unset the ref to the action in the @@ -306,7 +315,7 @@ static bool action_pushdown_poll(bContext *C) { if (ED_operator_action_active(C)) { SpaceAction *saction = (SpaceAction *)CTX_wm_space_data(C); - AnimData *adt = ED_actedit_animdata_from_context(C); + AnimData *adt = ED_actedit_animdata_from_context(C, NULL); /* Check for AnimData, Actions, and that tweak-mode is off. */ if (adt && saction->action) { @@ -326,7 +335,8 @@ static bool action_pushdown_poll(bContext *C) static int action_pushdown_exec(bContext *C, wmOperator *op) { SpaceAction *saction = (SpaceAction *)CTX_wm_space_data(C); - AnimData *adt = ED_actedit_animdata_from_context(C); + ID *adt_id_owner = NULL; + AnimData *adt = ED_actedit_animdata_from_context(C, &adt_id_owner); /* Do the deed... */ if (adt) { @@ -339,8 +349,7 @@ static int action_pushdown_exec(bContext *C, wmOperator *op) } /* action can be safely added */ - const Object *ob = CTX_data_active_object(C); - BKE_nla_action_pushdown(adt, ID_IS_OVERRIDE_LIBRARY(ob)); + BKE_nla_action_pushdown(adt, ID_IS_OVERRIDE_LIBRARY(adt_id_owner)); /* Stop displaying this action in this editor * NOTE: The editor itself doesn't set a user... @@ -373,7 +382,8 @@ void ACTION_OT_push_down(wmOperatorType *ot) static int action_stash_exec(bContext *C, wmOperator *op) { SpaceAction *saction = (SpaceAction *)CTX_wm_space_data(C); - AnimData *adt = ED_actedit_animdata_from_context(C); + ID *adt_id_owner = NULL; + AnimData *adt = ED_actedit_animdata_from_context(C, &adt_id_owner); /* Perform stashing operation */ if (adt) { @@ -385,8 +395,7 @@ static int action_stash_exec(bContext *C, wmOperator *op) } /* stash the action */ - Object *ob = CTX_data_active_object(C); - if (BKE_nla_action_stash(adt, ID_IS_OVERRIDE_LIBRARY(ob))) { + if (BKE_nla_action_stash(adt, ID_IS_OVERRIDE_LIBRARY(adt_id_owner))) { /* The stash operation will remove the user already, * so the flushing step later shouldn't double up * the user-count fixes. Hence, we must unset this ref @@ -439,7 +448,7 @@ void ACTION_OT_stash(wmOperatorType *ot) static bool action_stash_create_poll(bContext *C) { if (ED_operator_action_active(C)) { - AnimData *adt = ED_actedit_animdata_from_context(C); + AnimData *adt = ED_actedit_animdata_from_context(C, NULL); /* Check tweak-mode is off (as you don't want to be tampering with the action in that case) */ /* NOTE: unlike for pushdown, @@ -471,7 +480,8 @@ static bool action_stash_create_poll(bContext *C) static int action_stash_create_exec(bContext *C, wmOperator *op) { SpaceAction *saction = (SpaceAction *)CTX_wm_space_data(C); - AnimData *adt = ED_actedit_animdata_from_context(C); + ID *adt_id_owner = NULL; + AnimData *adt = ED_actedit_animdata_from_context(C, &adt_id_owner); /* Check for no action... */ if (saction->action == NULL) { @@ -488,8 +498,7 @@ static int action_stash_create_exec(bContext *C, wmOperator *op) } /* stash the action */ - Object *ob = CTX_data_active_object(C); - if (BKE_nla_action_stash(adt, ID_IS_OVERRIDE_LIBRARY(ob))) { + if (BKE_nla_action_stash(adt, ID_IS_OVERRIDE_LIBRARY(adt_id_owner))) { bAction *new_action = NULL; /* Create new action not based on the old one @@ -636,7 +645,7 @@ static bool action_unlink_poll(bContext *C) { if (ED_operator_action_active(C)) { SpaceAction *saction = (SpaceAction *)CTX_wm_space_data(C); - AnimData *adt = ED_actedit_animdata_from_context(C); + AnimData *adt = ED_actedit_animdata_from_context(C, NULL); /* Only when there's an active action, in the right modes... */ if (saction->action && adt) { @@ -650,7 +659,7 @@ static bool action_unlink_poll(bContext *C) static int action_unlink_exec(bContext *C, wmOperator *op) { - AnimData *adt = ED_actedit_animdata_from_context(C); + AnimData *adt = ED_actedit_animdata_from_context(C, NULL); bool force_delete = RNA_boolean_get(op->ptr, "force_delete"); if (adt && adt->action) { @@ -775,7 +784,7 @@ static bool action_layer_next_poll(bContext *C) { /* Action Editor's action editing modes only */ if (ED_operator_action_active(C)) { - AnimData *adt = ED_actedit_animdata_from_context(C); + AnimData *adt = ED_actedit_animdata_from_context(C, NULL); if (adt) { /* only allow if we're in tweak-mode, and there's something above us... */ if (adt->flag & ADT_NLA_EDIT_ON) { @@ -809,7 +818,7 @@ static bool action_layer_next_poll(bContext *C) static int action_layer_next_exec(bContext *C, wmOperator *op) { - AnimData *adt = ED_actedit_animdata_from_context(C); + AnimData *adt = ED_actedit_animdata_from_context(C, NULL); NlaTrack *act_track; Scene *scene = CTX_data_scene(C); @@ -886,7 +895,7 @@ static bool action_layer_prev_poll(bContext *C) { /* Action Editor's action editing modes only */ if (ED_operator_action_active(C)) { - AnimData *adt = ED_actedit_animdata_from_context(C); + AnimData *adt = ED_actedit_animdata_from_context(C, NULL); if (adt) { if (adt->flag & ADT_NLA_EDIT_ON) { /* Tweak Mode: We need to check if there are any tracks below the active one @@ -920,7 +929,7 @@ static bool action_layer_prev_poll(bContext *C) static int action_layer_prev_exec(bContext *C, wmOperator *op) { - AnimData *adt = ED_actedit_animdata_from_context(C); + AnimData *adt = ED_actedit_animdata_from_context(C, NULL); NlaTrack *act_track; NlaTrack *nlt; diff --git a/source/blender/editors/space_action/space_action.c b/source/blender/editors/space_action/space_action.c index f59429ba981..738eeb21e2e 100644 --- a/source/blender/editors/space_action/space_action.c +++ b/source/blender/editors/space_action/space_action.c @@ -247,7 +247,7 @@ static void action_main_region_draw_overlay(const bContext *C, ARegion *region) View2D *v2d = ®ion->v2d; /* scrubbing region */ - ED_time_scrub_draw_current_frame(region, scene, saction->flag & SACTION_DRAWTIME, true); + ED_time_scrub_draw_current_frame(region, scene, saction->flag & SACTION_DRAWTIME); /* scrollers */ UI_view2d_scrollers_draw(v2d, NULL); diff --git a/source/blender/editors/space_api/spacetypes.c b/source/blender/editors/space_api/spacetypes.c index 404c5698b42..149067a94fe 100644 --- a/source/blender/editors/space_api/spacetypes.c +++ b/source/blender/editors/space_api/spacetypes.c @@ -177,6 +177,7 @@ void ED_spacemacros_init(void) ED_operatormacros_gpencil(); /* Register dropboxes (can use macros). */ + ED_dropboxes_ui(); const ListBase *spacetypes = BKE_spacetypes_list(); LISTBASE_FOREACH (const SpaceType *, type, spacetypes) { if (type->dropboxes) { diff --git a/source/blender/editors/space_clip/clip_dopesheet_draw.c b/source/blender/editors/space_clip/clip_dopesheet_draw.c index 8aaf3faffec..42fda3ef464 100644 --- a/source/blender/editors/space_clip/clip_dopesheet_draw.c +++ b/source/blender/editors/space_clip/clip_dopesheet_draw.c @@ -224,7 +224,7 @@ void clip_draw_dopesheet_main(SpaceClip *sc, ARegion *region, Scene *scene) uint flags_id = GPU_vertformat_attr_add(format, "flags", GPU_COMP_U32, 1, GPU_FETCH_INT); GPU_program_point_size(true); - immBindBuiltinProgram(GPU_SHADER_KEYFRAME_DIAMOND); + immBindBuiltinProgram(GPU_SHADER_KEYFRAME_SHAPE); immUniform1f("outline_scale", 1.0f); immUniform2f( "ViewportSize", BLI_rcti_size_x(&v2d->mask) + 1, BLI_rcti_size_y(&v2d->mask) + 1); diff --git a/source/blender/editors/space_clip/tracking_ops.c b/source/blender/editors/space_clip/tracking_ops.c index ff62bcf0cfa..4965099642b 100644 --- a/source/blender/editors/space_clip/tracking_ops.c +++ b/source/blender/editors/space_clip/tracking_ops.c @@ -417,7 +417,7 @@ static SlideMarkerData *create_slide_marker_data(SpaceClip *sc, data->pos = marker->pos; data->offset = track->offset; data->old_markers = MEM_callocN(sizeof(*data->old_markers) * track->markersnr, - "slide marekrs"); + "slide markers"); for (int a = 0; a < track->markersnr; a++) { copy_v2_v2(data->old_markers[a], track->markers[a].pos); } diff --git a/source/blender/editors/space_clip/tracking_ops_track.c b/source/blender/editors/space_clip/tracking_ops_track.c index 0a99d1020f6..0e78a3e9a1e 100644 --- a/source/blender/editors/space_clip/tracking_ops_track.c +++ b/source/blender/editors/space_clip/tracking_ops_track.c @@ -196,8 +196,8 @@ static bool track_markers_initjob(bContext *C, TrackMarkersJob *tmj, bool backwa /* XXX: silly to store this, but this data is needed to update scene and * movie-clip numbers when tracking is finished. This introduces * better feedback for artists. - * Maybe there's another way to solve this problem, but can't think - * better way atm. + * Maybe there's another way to solve this problem, + * but can't think better way at the moment. * Anyway, this way isn't more unstable as animation rendering * animation which uses the same approach (except storing screen). */ diff --git a/source/blender/editors/space_file/CMakeLists.txt b/source/blender/editors/space_file/CMakeLists.txt index 993a52b9084..4b508f16c1e 100644 --- a/source/blender/editors/space_file/CMakeLists.txt +++ b/source/blender/editors/space_file/CMakeLists.txt @@ -34,6 +34,7 @@ set(INC ) set(SRC + asset_catalog_tree_view.cc file_draw.c file_ops.c file_panels.c @@ -49,6 +50,7 @@ set(SRC ) set(LIB + bf_blenkernel ) if(WITH_HEADLESS) diff --git a/source/blender/editors/space_file/asset_catalog_tree_view.cc b/source/blender/editors/space_file/asset_catalog_tree_view.cc new file mode 100644 index 00000000000..7eea9af925b --- /dev/null +++ b/source/blender/editors/space_file/asset_catalog_tree_view.cc @@ -0,0 +1,230 @@ +/* + * 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) 2007 Blender Foundation. + * All rights reserved. + */ + +/** \file + * \ingroup spfile + */ + +#include "ED_fileselect.h" + +#include "DNA_space_types.h" + +#include "BKE_asset_catalog.hh" +#include "BKE_asset_library.hh" + +#include "BLI_string_ref.hh" + +#include "BLT_translation.h" + +#include "RNA_access.h" + +#include "UI_interface.h" +#include "UI_interface.hh" +#include "UI_resources.h" +#include "UI_tree_view.hh" + +#include "WM_api.h" +#include "WM_types.h" + +#include "file_intern.h" + +using namespace blender; +using namespace blender::bke; + +namespace blender::ed::asset_browser { + +class AssetCatalogTreeView : public ui::AbstractTreeView { + /** The asset catalog tree this tree-view represents. */ + bke::AssetCatalogTree *catalog_tree_; + FileAssetSelectParams *params_; + + friend class AssetCatalogTreeViewItem; + + public: + AssetCatalogTreeView(::AssetLibrary *library, FileAssetSelectParams *params); + + void build_tree() override; + + private: + ui::BasicTreeViewItem &build_catalog_items_recursive(ui::TreeViewItemContainer &view_parent_item, + AssetCatalogTreeItem &catalog); + + void add_all_item(); + void add_unassigned_item(); + bool is_active_catalog(CatalogID catalog_id) const; +}; +/* ---------------------------------------------------------------------- */ + +class AssetCatalogTreeViewItem : public ui::BasicTreeViewItem { + /** The catalog tree item this tree view item represents. */ + AssetCatalogTreeItem &catalog_item_; + + public: + AssetCatalogTreeViewItem(AssetCatalogTreeItem *catalog_item) + : BasicTreeViewItem(catalog_item->get_name()), catalog_item_(*catalog_item) + { + } + + void on_activate() override + { + const AssetCatalogTreeView &tree_view = static_cast<const AssetCatalogTreeView &>( + get_tree_view()); + tree_view.params_->asset_catalog_visibility = FILE_SHOW_ASSETS_FROM_CATALOG; + tree_view.params_->catalog_id = catalog_item_.get_catalog_id(); + WM_main_add_notifier(NC_SPACE | ND_SPACE_ASSET_PARAMS, nullptr); + } + + void build_row(uiLayout &row) override + { + ui::BasicTreeViewItem::build_row(row); + + if (!is_active()) { + return; + } + + PointerRNA *props; + const CatalogID catalog_id = catalog_item_.get_catalog_id(); + + props = UI_but_extra_operator_icon_add( + button(), "ASSET_OT_catalog_new", WM_OP_INVOKE_DEFAULT, ICON_ADD); + RNA_string_set(props, "parent_path", catalog_item_.catalog_path().c_str()); + + /* Tree items without a catalog ID represent components of catalog paths that are not + * associated with an actual catalog. They exist merely by the presence of a child catalog, and + * thus cannot be deleted themselves. */ + if (!BLI_uuid_is_nil(catalog_id)) { + char catalog_id_str_buffer[UUID_STRING_LEN] = ""; + BLI_uuid_format(catalog_id_str_buffer, catalog_id); + + props = UI_but_extra_operator_icon_add( + button(), "ASSET_OT_catalog_delete", WM_OP_INVOKE_DEFAULT, ICON_X); + RNA_string_set(props, "catalog_id", catalog_id_str_buffer); + } + } +}; + +/** Only reason this isn't just `BasicTreeViewItem` is to add a '+' icon for adding a root level + * catalog. */ +class AssetCatalogTreeViewAllItem : public ui::BasicTreeViewItem { + using BasicTreeViewItem::BasicTreeViewItem; + + void build_row(uiLayout &row) override + { + ui::BasicTreeViewItem::build_row(row); + + if (!is_active()) { + return; + } + + PointerRNA *props; + props = UI_but_extra_operator_icon_add( + button(), "ASSET_OT_catalog_new", WM_OP_INVOKE_DEFAULT, ICON_ADD); + /* No parent path to use the root level. */ + RNA_string_set(props, "parent_path", nullptr); + } +}; + +AssetCatalogTreeView::AssetCatalogTreeView(::AssetLibrary *library, FileAssetSelectParams *params) + : catalog_tree_(BKE_asset_library_get_catalog_tree(library)), params_(params) +{ +} + +void AssetCatalogTreeView::build_tree() +{ + add_all_item(); + + if (catalog_tree_) { + catalog_tree_->foreach_root_item([this](AssetCatalogTreeItem &item) { + ui::BasicTreeViewItem &child_view_item = build_catalog_items_recursive(*this, item); + + /* Open root-level items by default. */ + child_view_item.set_collapsed(false); + }); + } + + add_unassigned_item(); +} + +ui::BasicTreeViewItem &AssetCatalogTreeView::build_catalog_items_recursive( + ui::TreeViewItemContainer &view_parent_item, AssetCatalogTreeItem &catalog) +{ + ui::BasicTreeViewItem &view_item = view_parent_item.add_tree_item<AssetCatalogTreeViewItem>( + &catalog); + if (is_active_catalog(catalog.get_catalog_id())) { + view_item.set_active(); + } + + catalog.foreach_child([&view_item, this](AssetCatalogTreeItem &child) { + build_catalog_items_recursive(view_item, child); + }); + return view_item; +} + +void AssetCatalogTreeView::add_all_item() +{ + FileAssetSelectParams *params = params_; + + ui::AbstractTreeViewItem &item = add_tree_item<AssetCatalogTreeViewAllItem>( + IFACE_("All"), ICON_HOME, [params](ui::BasicTreeViewItem & /*item*/) { + params->asset_catalog_visibility = FILE_SHOW_ASSETS_ALL_CATALOGS; + WM_main_add_notifier(NC_SPACE | ND_SPACE_ASSET_PARAMS, nullptr); + }); + if (params->asset_catalog_visibility == FILE_SHOW_ASSETS_ALL_CATALOGS) { + item.set_active(); + } +} + +void AssetCatalogTreeView::add_unassigned_item() +{ + FileAssetSelectParams *params = params_; + + ui::AbstractTreeViewItem &item = add_tree_item<ui::BasicTreeViewItem>( + IFACE_("Unassigned"), ICON_FILE_HIDDEN, [params](ui::BasicTreeViewItem & /*item*/) { + params->asset_catalog_visibility = FILE_SHOW_ASSETS_WITHOUT_CATALOG; + WM_main_add_notifier(NC_SPACE | ND_SPACE_ASSET_PARAMS, nullptr); + }); + if (params->asset_catalog_visibility == FILE_SHOW_ASSETS_WITHOUT_CATALOG) { + item.set_active(); + } +} + +bool AssetCatalogTreeView::is_active_catalog(CatalogID catalog_id) const +{ + return (params_->asset_catalog_visibility == FILE_SHOW_ASSETS_FROM_CATALOG) && + (params_->catalog_id == catalog_id); +} + +} // namespace blender::ed::asset_browser + +/* ---------------------------------------------------------------------- */ + +void file_create_asset_catalog_tree_view_in_layout(::AssetLibrary *asset_library, + uiLayout *layout, + FileAssetSelectParams *params) +{ + uiBlock *block = uiLayoutGetBlock(layout); + + ui::AbstractTreeView *tree_view = UI_block_add_view( + *block, + "asset catalog tree view", + std::make_unique<ed::asset_browser::AssetCatalogTreeView>(asset_library, params)); + + ui::TreeViewBuilder builder(*block); + builder.build_tree_view(*tree_view); +} diff --git a/source/blender/editors/space_file/file_intern.h b/source/blender/editors/space_file/file_intern.h index 905c0aeb8e0..d39aefff691 100644 --- a/source/blender/editors/space_file/file_intern.h +++ b/source/blender/editors/space_file/file_intern.h @@ -23,12 +23,19 @@ #pragma once +#ifdef __cplusplus +extern "C" { +#endif + /* internal exports only */ struct ARegion; struct ARegionType; +struct AssetLibrary; struct FileSelectParams; +struct FileAssetSelectParams; struct SpaceFile; +struct uiLayout; struct View2D; /* file_draw.c */ @@ -147,8 +154,19 @@ void file_on_reload_callback_register(struct SpaceFile *sfile, /* file_panels.c */ void file_tool_props_region_panels_register(struct ARegionType *art); void file_execute_region_panels_register(struct ARegionType *art); +void file_tools_region_panels_register(struct ARegionType *art); /* file_utils.c */ void file_tile_boundbox(const ARegion *region, FileLayout *layout, const int file, rcti *r_bounds); void file_path_to_ui_path(const char *path, char *r_pathi, int max_size); + +/* asset_catalog_tree_view.cc */ + +void file_create_asset_catalog_tree_view_in_layout(struct AssetLibrary *asset_library, + struct uiLayout *layout, + struct FileAssetSelectParams *params); + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/editors/space_file/file_ops.c b/source/blender/editors/space_file/file_ops.c index 2f1acd2ca4d..d0f2a4fdc4c 100644 --- a/source/blender/editors/space_file/file_ops.c +++ b/source/blender/editors/space_file/file_ops.c @@ -536,7 +536,7 @@ static rcti file_select_mval_to_select_rect(const int mval[2]) return rect; } -static int file_select_invoke(bContext *C, wmOperator *op, const wmEvent *event) +static int file_select_exec(bContext *C, wmOperator *op) { ARegion *region = CTX_wm_region(C); SpaceFile *sfile = CTX_wm_space_file(C); @@ -549,17 +549,27 @@ static int file_select_invoke(bContext *C, wmOperator *op, const wmEvent *event) const bool only_activate_if_selected = RNA_boolean_get(op->ptr, "only_activate_if_selected"); /* Used so right mouse clicks can do both, activate and spawn the context menu. */ const bool pass_through = RNA_boolean_get(op->ptr, "pass_through"); + bool wait_to_deselect_others = RNA_boolean_get(op->ptr, "wait_to_deselect_others"); if (region->regiontype != RGN_TYPE_WINDOW) { return OPERATOR_CANCELLED; } - rect = file_select_mval_to_select_rect(event->mval); + int mval[2]; + mval[0] = RNA_int_get(op->ptr, "mouse_x"); + mval[1] = RNA_int_get(op->ptr, "mouse_y"); + rect = file_select_mval_to_select_rect(mval); if (!ED_fileselect_layout_is_inside_pt(sfile->layout, ®ion->v2d, rect.xmin, rect.ymin)) { return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH; } + if (extend || fill) { + wait_to_deselect_others = false; + } + + int ret_val = OPERATOR_FINISHED; + const FileSelectParams *params = ED_fileselect_get_active_params(sfile); if (sfile && params) { int idx = params->highlight_file; @@ -571,6 +581,9 @@ static int file_select_invoke(bContext *C, wmOperator *op, const wmEvent *event) if (only_activate_if_selected && is_selected) { /* Don't deselect other items. */ } + else if (wait_to_deselect_others && is_selected) { + ret_val = OPERATOR_RUNNING_MODAL; + } /* single select, deselect all selected first */ else if (!extend) { file_select_deselect_all(sfile, FILE_SEL_SELECTED); @@ -601,7 +614,10 @@ static int file_select_invoke(bContext *C, wmOperator *op, const wmEvent *event) WM_event_add_mousemove(CTX_wm_window(C)); /* for directory changes */ WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_PARAMS, NULL); - return pass_through ? (OPERATOR_FINISHED | OPERATOR_PASS_THROUGH) : OPERATOR_FINISHED; + if ((ret_val == OPERATOR_FINISHED) && pass_through) { + ret_val |= OPERATOR_PASS_THROUGH; + } + return ret_val; } void FILE_OT_select(wmOperatorType *ot) @@ -614,11 +630,14 @@ void FILE_OT_select(wmOperatorType *ot) ot->description = "Handle mouse clicks to select and activate items"; /* api callbacks */ - ot->invoke = file_select_invoke; + ot->invoke = WM_generic_select_invoke; + ot->exec = file_select_exec; + ot->modal = WM_generic_select_modal; /* Operator works for file or asset browsing */ ot->poll = ED_operator_file_active; /* properties */ + WM_operator_properties_generic_select(ot); prop = RNA_def_boolean(ot->srna, "extend", false, diff --git a/source/blender/editors/space_file/file_panels.c b/source/blender/editors/space_file/file_panels.c index 7032d55b331..95aad202f1a 100644 --- a/source/blender/editors/space_file/file_panels.c +++ b/source/blender/editors/space_file/file_panels.c @@ -47,6 +47,7 @@ #include "WM_types.h" #include "file_intern.h" +#include "filelist.h" #include "fsmenu.h" #include <string.h> @@ -57,6 +58,12 @@ static bool file_panel_operator_poll(const bContext *C, PanelType *UNUSED(pt)) return (sfile && sfile->op); } +static bool file_panel_asset_browsing_poll(const bContext *C, PanelType *UNUSED(pt)) +{ + SpaceFile *sfile = CTX_wm_space_file(C); + return sfile && sfile->files && ED_fileselect_is_asset_browser(sfile); +} + static void file_panel_operator_header(const bContext *C, Panel *panel) { SpaceFile *sfile = CTX_wm_space_file(C); @@ -222,3 +229,28 @@ void file_execute_region_panels_register(ARegionType *art) pt->draw = file_panel_execution_buttons_draw; BLI_addtail(&art->paneltypes, pt); } + +static void file_panel_asset_catalog_buttons_draw(const bContext *C, Panel *panel) +{ + SpaceFile *sfile = CTX_wm_space_file(C); + /* May be null if the library wasn't loaded yet. */ + struct AssetLibrary *asset_library = filelist_asset_library(sfile->files); + FileAssetSelectParams *params = ED_fileselect_get_asset_params(sfile); + BLI_assert(params != NULL); + + file_create_asset_catalog_tree_view_in_layout(asset_library, panel->layout, params); +} + +void file_tools_region_panels_register(ARegionType *art) +{ + PanelType *pt; + + pt = MEM_callocN(sizeof(PanelType), "spacetype file asset catalog buttons"); + strcpy(pt->idname, "FILE_PT_asset_catalog_buttons"); + strcpy(pt->label, N_("Asset Catalogs")); + strcpy(pt->translation_context, BLT_I18NCONTEXT_DEFAULT_BPYRNA); + pt->flag = PANEL_TYPE_NO_HEADER; + pt->poll = file_panel_asset_browsing_poll; + pt->draw = file_panel_asset_catalog_buttons_draw; + BLI_addtail(&art->paneltypes, pt); +} diff --git a/source/blender/editors/space_file/filelist.c b/source/blender/editors/space_file/filelist.c index 511b5b255e9..bdf61e792d3 100644 --- a/source/blender/editors/space_file/filelist.c +++ b/source/blender/editors/space_file/filelist.c @@ -50,12 +50,14 @@ #include "BLI_task.h" #include "BLI_threads.h" #include "BLI_utildefines.h" +#include "BLI_uuid.h" #ifdef WIN32 # include "BLI_winstuff.h" #endif #include "BKE_asset.h" +#include "BKE_asset_library.h" #include "BKE_context.h" #include "BKE_global.h" #include "BKE_icons.h" @@ -368,6 +370,9 @@ typedef struct FileListFilter { char filter_glob[FILE_MAXFILE]; char filter_search[66]; /* + 2 for heading/trailing implicit '*' wildcards. */ short flags; + + eFileSel_Params_AssetCatalogVisibility asset_catalog_visibility; + bUUID asset_catalog_id; } FileListFilter; /* FileListFilter.flags */ @@ -386,6 +391,7 @@ typedef struct FileList { eFileSelectType type; /* The library this list was created for. Stored here so we know when to re-read. */ AssetLibraryReference *asset_library_ref; + struct AssetLibrary *asset_library; short flags; @@ -471,6 +477,10 @@ static void filelist_readjob_dir(struct FileListReadJob *job_params, short *stop, short *do_update, float *progress); +static void filelist_readjob_asset_library(struct FileListReadJob *job_params, + short *stop, + short *do_update, + float *progress); static void filelist_readjob_main_assets(struct FileListReadJob *job_params, short *stop, short *do_update, @@ -887,6 +897,39 @@ static bool is_filtered_id_file(const FileListInternEntry *file, return is_filtered; } +/** + * Get the asset metadata of a file, if it represents an asset. This may either be of a local ID + * (ID in the current #Main) or read from an external asset library. + */ +static AssetMetaData *filelist_file_internal_get_asset_data(const FileListInternEntry *file) +{ + const ID *local_id = file->local_data.id; + return local_id ? local_id->asset_data : file->imported_asset_data; +} + +static bool is_filtered_asset(FileListInternEntry *file, FileListFilter *filter) +{ + const AssetMetaData *asset_data = filelist_file_internal_get_asset_data(file); + bool is_visible = false; + + switch (filter->asset_catalog_visibility) { + case FILE_SHOW_ASSETS_WITHOUT_CATALOG: + is_visible = BLI_uuid_is_nil(asset_data->catalog_id); + break; + case FILE_SHOW_ASSETS_FROM_CATALOG: + /* TODO show all assets that are in child catalogs of the selected catalog. */ + is_visible = !BLI_uuid_is_nil(filter->asset_catalog_id) && + BLI_uuid_equal(filter->asset_catalog_id, asset_data->catalog_id); + break; + case FILE_SHOW_ASSETS_ALL_CATALOGS: + /* All asset files should be visible. */ + is_visible = true; + break; + } + + return is_visible; +} + static bool is_filtered_lib(FileListInternEntry *file, const char *root, FileListFilter *filter) { bool is_filtered; @@ -904,6 +947,13 @@ static bool is_filtered_lib(FileListInternEntry *file, const char *root, FileLis return is_filtered; } +static bool is_filtered_asset_library(FileListInternEntry *file, + const char *root, + FileListFilter *filter) +{ + return is_filtered_lib(file, root, filter) && is_filtered_asset(file, filter); +} + static bool is_filtered_main(FileListInternEntry *file, const char *UNUSED(dir), FileListFilter *filter) @@ -916,7 +966,8 @@ static bool is_filtered_main_assets(FileListInternEntry *file, FileListFilter *filter) { /* "Filtered" means *not* being filtered out... So return true if the file should be visible. */ - return is_filtered_id_file(file, file->relpath, file->name, filter); + return is_filtered_id_file(file, file->relpath, file->name, filter) && + is_filtered_asset(file, filter); } static void filelist_filter_clear(FileList *filelist) @@ -1032,6 +1083,33 @@ void filelist_setfilter_options(FileList *filelist, } /** + * \param catalog_id: The catalog that should be filtered by if \a catalog_visibility is + * #FILE_SHOW_ASSETS_FROM_CATALOG. May be NULL otherwise. + */ +void filelist_set_asset_catalog_filter_options( + FileList *filelist, + eFileSel_Params_AssetCatalogVisibility catalog_visibility, + const bUUID *catalog_id) +{ + bool update = false; + + if (filelist->filter_data.asset_catalog_visibility != catalog_visibility) { + filelist->filter_data.asset_catalog_visibility = catalog_visibility; + update = true; + } + + if (filelist->filter_data.asset_catalog_visibility == FILE_SHOW_ASSETS_FROM_CATALOG && + catalog_id && !BLI_uuid_equal(filelist->filter_data.asset_catalog_id, *catalog_id)) { + filelist->filter_data.asset_catalog_id = *catalog_id; + update = true; + } + + if (update) { + filelist_filter_clear(filelist); + } +} + +/** * Checks two libraries for equality. * \return True if the libraries match. */ @@ -1723,6 +1801,11 @@ void filelist_settype(FileList *filelist, short type) filelist->read_job_fn = filelist_readjob_lib; filelist->filter_fn = is_filtered_lib; break; + case FILE_ASSET_LIBRARY: + filelist->check_dir_fn = filelist_checkdir_lib; + filelist->read_job_fn = filelist_readjob_asset_library; + filelist->filter_fn = is_filtered_asset_library; + break; case FILE_MAIN_ASSET: filelist->check_dir_fn = filelist_checkdir_main_assets; filelist->read_job_fn = filelist_readjob_main_assets; @@ -1739,7 +1822,10 @@ void filelist_settype(FileList *filelist, short type) filelist->flags |= FL_FORCE_RESET; } -void filelist_clear_ex(struct FileList *filelist, const bool do_cache, const bool do_selection) +void filelist_clear_ex(struct FileList *filelist, + const bool do_asset_library, + const bool do_cache, + const bool do_selection) { if (!filelist) { return; @@ -1758,11 +1844,18 @@ void filelist_clear_ex(struct FileList *filelist, const bool do_cache, const boo if (do_selection && filelist->selection_state) { BLI_ghash_clear(filelist->selection_state, NULL, NULL); } + + if (do_asset_library && (filelist->asset_library != NULL)) { + /* There is no way to refresh the catalogs stored by the AssetLibrary struct, so instead of + * "clearing" it, the entire struct is freed. It will be reallocated when needed. */ + BKE_asset_library_free(filelist->asset_library); + filelist->asset_library = NULL; + } } void filelist_clear(struct FileList *filelist) { - filelist_clear_ex(filelist, true, true); + filelist_clear_ex(filelist, true, true, true); } void filelist_free(struct FileList *filelist) @@ -1773,7 +1866,7 @@ void filelist_free(struct FileList *filelist) } /* No need to clear cache & selection_state, we free them anyway. */ - filelist_clear_ex(filelist, false, false); + filelist_clear_ex(filelist, true, false, false); filelist_cache_free(&filelist->filelist_cache); if (filelist->selection_state) { @@ -1788,6 +1881,11 @@ void filelist_free(struct FileList *filelist) filelist->flags &= ~(FL_NEED_SORTING | FL_NEED_FILTERING); } +AssetLibrary *filelist_asset_library(FileList *filelist) +{ + return filelist->asset_library; +} + void filelist_freelib(struct FileList *filelist) { if (filelist->libfiledata) { @@ -2879,76 +2977,129 @@ static int filelist_readjob_list_dir(const char *root, return nbr_entries; } -static int filelist_readjob_list_lib(const char *root, ListBase *entries, const bool skip_currpar) +typedef enum ListLibOptions { + /* Will read both the groups + actual ids from the library. Reduces the amount of times that + * a library needs to be opened. */ + LIST_LIB_RECURSIVE = (1 << 0), + + /* Will only list assets. */ + LIST_LIB_ASSETS_ONLY = (1 << 1), + + /* Add given root as result. */ + LIST_LIB_ADD_PARENT = (1 << 2), +} ListLibOptions; + +static FileListInternEntry *filelist_readjob_list_lib_group_create(const int idcode, + const char *group_name) +{ + FileListInternEntry *entry = MEM_callocN(sizeof(*entry), __func__); + entry->relpath = BLI_strdup(group_name); + entry->typeflag |= FILE_TYPE_BLENDERLIB | FILE_TYPE_DIR; + entry->blentype = idcode; + return entry; +} + +static void filelist_readjob_list_lib_add_datablocks(ListBase *entries, + LinkNode *datablock_infos, + const bool prefix_relpath_with_group_name, + const int idcode, + const char *group_name) +{ + for (LinkNode *ln = datablock_infos; ln; ln = ln->next) { + struct BLODataBlockInfo *info = ln->link; + FileListInternEntry *entry = MEM_callocN(sizeof(*entry), __func__); + if (prefix_relpath_with_group_name) { + entry->relpath = BLI_sprintfN("%s/%s", group_name, info->name); + } + else { + entry->relpath = BLI_strdup(info->name); + } + entry->typeflag |= FILE_TYPE_BLENDERLIB; + if (info && info->asset_data) { + entry->typeflag |= FILE_TYPE_ASSET; + /* Moves ownership! */ + entry->imported_asset_data = info->asset_data; + } + entry->blentype = idcode; + BLI_addtail(entries, entry); + } +} + +static int filelist_readjob_list_lib(const char *root, + ListBase *entries, + const ListLibOptions options) { - FileListInternEntry *entry; - LinkNode *ln, *names = NULL, *datablock_infos = NULL; - int i, nitems, idcode = 0, nbr_entries = 0; char dir[FILE_MAX_LIBEXTRA], *group; - bool ok; struct BlendHandle *libfiledata = NULL; - /* name test */ - ok = BLO_library_path_explode(root, dir, &group, NULL); - if (!ok) { - return nbr_entries; + /* Check if the given root is actually a library. All folders are passed to + * `filelist_readjob_list_lib` and based on the number of found entries `filelist_readjob_do` + * will do a dir listing only when this function does not return any entries. */ + /* TODO: We should consider introducing its own function to detect if it is a lib and + * call it directly from `filelist_readjob_do` to increase readability. */ + const bool is_lib = BLO_library_path_explode(root, dir, &group, NULL); + if (!is_lib) { + return 0; } - /* there we go */ + /* Open the library file. */ BlendFileReadReport bf_reports = {.reports = NULL}; libfiledata = BLO_blendhandle_from_file(dir, &bf_reports); if (libfiledata == NULL) { - return nbr_entries; - } - - /* memory for strings is passed into filelist[i].entry->relpath - * and freed in filelist_entry_free. */ - if (group) { - idcode = groupname_to_code(group); - datablock_infos = BLO_blendhandle_get_datablock_info(libfiledata, idcode, &nitems); - } - else { - names = BLO_blendhandle_get_linkable_groups(libfiledata); - nitems = BLI_linklist_count(names); + return 0; } - BLO_blendhandle_close(libfiledata); - - if (!skip_currpar) { - entry = MEM_callocN(sizeof(*entry), __func__); + /* Add current parent when requested. */ + int parent_len = 0; + if (options & LIST_LIB_ADD_PARENT) { + FileListInternEntry *entry = MEM_callocN(sizeof(*entry), __func__); entry->relpath = BLI_strdup(FILENAME_PARENT); entry->typeflag |= (FILE_TYPE_BLENDERLIB | FILE_TYPE_DIR); BLI_addtail(entries, entry); - nbr_entries++; + parent_len = 1; } - for (i = 0, ln = (datablock_infos ? datablock_infos : names); i < nitems; i++, ln = ln->next) { - struct BLODataBlockInfo *info = datablock_infos ? ln->link : NULL; - const char *blockname = info ? info->name : ln->link; - - entry = MEM_callocN(sizeof(*entry), __func__); - entry->relpath = BLI_strdup(blockname); - entry->typeflag |= FILE_TYPE_BLENDERLIB; - if (info && info->asset_data) { - entry->typeflag |= FILE_TYPE_ASSET; - /* Moves ownership! */ - entry->imported_asset_data = info->asset_data; - } - if (!(group && idcode)) { - entry->typeflag |= FILE_TYPE_DIR; - entry->blentype = groupname_to_code(blockname); - } - else { - entry->blentype = idcode; + int group_len = 0; + int datablock_len = 0; + const bool group_came_from_path = group != NULL; + if (group_came_from_path) { + const int idcode = groupname_to_code(group); + LinkNode *datablock_infos = BLO_blendhandle_get_datablock_info( + libfiledata, idcode, options & LIST_LIB_ASSETS_ONLY, &datablock_len); + filelist_readjob_list_lib_add_datablocks(entries, datablock_infos, false, idcode, group); + BLI_linklist_freeN(datablock_infos); + } + else { + LinkNode *groups = BLO_blendhandle_get_linkable_groups(libfiledata); + group_len = BLI_linklist_count(groups); + + for (LinkNode *ln = groups; ln; ln = ln->next) { + const char *group_name = ln->link; + const int idcode = groupname_to_code(group_name); + FileListInternEntry *group_entry = filelist_readjob_list_lib_group_create(idcode, + group_name); + BLI_addtail(entries, group_entry); + + if (options & LIST_LIB_RECURSIVE) { + int group_datablock_len; + LinkNode *group_datablock_infos = BLO_blendhandle_get_datablock_info( + libfiledata, idcode, options & LIST_LIB_ASSETS_ONLY, &group_datablock_len); + filelist_readjob_list_lib_add_datablocks( + entries, group_datablock_infos, true, idcode, group_name); + BLI_linklist_freeN(group_datablock_infos); + datablock_len += group_datablock_len; + } } - BLI_addtail(entries, entry); - nbr_entries++; + + BLI_linklist_freeN(groups); } - BLI_linklist_freeN(datablock_infos ? datablock_infos : names); + BLO_blendhandle_close(libfiledata); - return nbr_entries; + /* Return the number of items added to entries. */ + int added_entries_len = group_len + datablock_len + parent_len; + return added_entries_len; } #if 0 @@ -3136,11 +3287,43 @@ typedef struct FileListReadJob { * The job system calls #filelist_readjob_update which moves any read file from #tmp_filelist * into #filelist in a thread-safe way. * + * #tmp_filelist also keeps an `AssetLibrary *` so that it can be loaded in the same thread, and + * moved to #filelist once all categories are loaded. + * * NOTE: #tmp_filelist is freed in #filelist_readjob_free, so any copied pointers need to be set * to NULL to avoid double-freeing them. */ struct FileList *tmp_filelist; } FileListReadJob; +static bool filelist_readjob_should_recurse_into_entry(const int max_recursion, + const int current_recursion_level, + FileListInternEntry *entry) +{ + if (max_recursion == 0) { + /* Recursive loading is disabled. */ + return false; + } + if (current_recursion_level >= max_recursion) { + /* No more levels of recursion left. */ + return false; + } + if (entry->typeflag & FILE_TYPE_BLENDERLIB) { + /* Libraries are already loaded recursively when recursive loaded is used. No need to add + * them another time. This loading is done with the `LIST_LIB_RECURSIVE` option. */ + return false; + } + if (!(entry->typeflag & FILE_TYPE_DIR)) { + /* Cannot recurse into regular file entries. */ + return false; + } + if (FILENAME_IS_CURRPAR(entry->relpath)) { + /* Don't schedule go to parent entry, (`..`) */ + return false; + } + + return true; +} + static void filelist_readjob_do(const bool do_lib, FileListReadJob *job_params, const short *stop, @@ -3177,7 +3360,6 @@ static void filelist_readjob_do(const bool do_lib, while (!BLI_stack_is_empty(todo_dirs) && !(*stop)) { FileListInternEntry *entry; int nbr_entries = 0; - bool is_lib = do_lib; char *subdir; char rel_subdir[FILE_MAX_LIBEXTRA]; @@ -3200,45 +3382,54 @@ static void filelist_readjob_do(const bool do_lib, BLI_path_normalize_dir(root, rel_subdir); BLI_path_rel(rel_subdir, root); + bool is_lib = false; if (do_lib) { - nbr_entries = filelist_readjob_list_lib(subdir, &entries, skip_currpar); + ListLibOptions list_lib_options = 0; + if (!skip_currpar) { + list_lib_options |= LIST_LIB_ADD_PARENT; + } + + /* Libraries are loaded recursively when max_recursion is set. It doesn't check if there is + * still a recursion level over. */ + if (max_recursion > 0) { + list_lib_options |= LIST_LIB_RECURSIVE; + } + /* Only load assets when browsing an asset library. For normal file browsing we return all + * entries. `FLF_ASSETS_ONLY` filter can be enabled/disabled by the user.*/ + if (filelist->asset_library_ref) { + list_lib_options |= LIST_LIB_ASSETS_ONLY; + } + nbr_entries = filelist_readjob_list_lib(subdir, &entries, list_lib_options); + if (nbr_entries > 0) { + is_lib = true; + } } - if (!nbr_entries) { - is_lib = false; + + if (!is_lib) { nbr_entries = filelist_readjob_list_dir( subdir, &entries, filter_glob, do_lib, job_params->main_name, skip_currpar); } for (entry = entries.first; entry; entry = entry->next) { - BLI_join_dirfile(dir, sizeof(dir), rel_subdir, entry->relpath); - entry->uid = filelist_uid_generate(filelist); - /* Only thing we change in direntry here, so we need to free it first. */ + /* When loading entries recursive, the rel_path should be relative from the root dir. + * we combine the relative path to the subdir with the relative path of the entry. */ + BLI_join_dirfile(dir, sizeof(dir), rel_subdir, entry->relpath); MEM_freeN(entry->relpath); entry->relpath = BLI_strdup(dir + 2); /* + 2 to remove '//' * added by BLI_path_rel to rel_subdir. */ entry->name = fileentry_uiname(root, entry->relpath, entry->typeflag, dir); entry->free_name = true; - /* Here we decide whether current filedirentry is to be listed too, or not. */ - if (max_recursion && (is_lib || (recursion_level <= max_recursion))) { - if (((entry->typeflag & FILE_TYPE_DIR) == 0) || FILENAME_IS_CURRPAR(entry->relpath)) { - /* Skip... */ - } - else if (!is_lib && (recursion_level >= max_recursion) && - ((entry->typeflag & (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP)) == 0)) { - /* Do not recurse in real directories in this case, only in .blend libs. */ - } - else { - /* We have a directory we want to list, add it to todo list! */ - BLI_join_dirfile(dir, sizeof(dir), root, entry->relpath); - BLI_path_normalize_dir(job_params->main_name, dir); - td_dir = BLI_stack_push_r(todo_dirs); - td_dir->level = recursion_level + 1; - td_dir->dir = BLI_strdup(dir); - nbr_todo_dirs++; - } + if (filelist_readjob_should_recurse_into_entry(max_recursion, recursion_level, entry)) { + /* We have a directory we want to list, add it to todo list! */ + BLI_join_dirfile(dir, sizeof(dir), root, entry->relpath); + BLI_path_normalize_dir(job_params->main_name, dir); + td_dir = BLI_stack_push_r(todo_dirs); + td_dir->level = recursion_level + 1; + td_dir->dir = BLI_strdup(dir); + nbr_todo_dirs++; } } @@ -3284,6 +3475,47 @@ static void filelist_readjob_lib(FileListReadJob *job_params, filelist_readjob_do(true, job_params, stop, do_update, progress); } +static void filelist_asset_library_path(const FileListReadJob *job_params, + char r_library_root_path[FILE_MAX]) +{ + if (job_params->filelist->type == FILE_MAIN_ASSET) { + /* For the "Current File" library (#FILE_MAIN_ASSET) we get the asset library root path based + * on main. */ + BKE_asset_library_find_suitable_root_path_from_main(job_params->current_main, + r_library_root_path); + } + else { + BLI_strncpy(r_library_root_path, job_params->tmp_filelist->filelist.root, FILE_MAX); + } +} + +/** + * Load asset library data, which currently means loading the asset catalogs for the library. + */ +static void filelist_readjob_load_asset_library_data(FileListReadJob *job_params, short *do_update) +{ + FileList *tmp_filelist = job_params->tmp_filelist; /* Use the thread-safe filelist queue. */ + + if (job_params->filelist->asset_library_ref != NULL) { + char library_root_path[FILE_MAX]; + filelist_asset_library_path(job_params, library_root_path); + + /* Load asset catalogs, into the temp filelist for thread-safety. + * #filelist_readjob_endjob() will move it into the real filelist. */ + tmp_filelist->asset_library = BKE_asset_library_load(library_root_path); + *do_update = true; + } +} + +static void filelist_readjob_asset_library(FileListReadJob *job_params, + short *stop, + short *do_update, + float *progress) +{ + filelist_readjob_load_asset_library_data(job_params, do_update); + filelist_readjob_lib(job_params, stop, do_update, progress); +} + static void filelist_readjob_main(FileListReadJob *job_params, short *stop, short *do_update, @@ -3305,6 +3537,8 @@ static void filelist_readjob_main_assets(FileListReadJob *job_params, BLI_assert(BLI_listbase_is_empty(&filelist->filelist.entries) && (filelist->filelist.nbr_entries == FILEDIR_NBR_ENTRIES_UNSET)); + filelist_readjob_load_asset_library_data(job_params, do_update); + /* A valid, but empty directory from now. */ filelist->filelist.nbr_entries = 0; @@ -3355,6 +3589,8 @@ static void filelist_readjob_startjob(void *flrjv, short *stop, short *do_update BLI_mutex_lock(&flrj->lock); BLI_assert((flrj->tmp_filelist == NULL) && flrj->filelist); + BLI_assert_msg(flrj->filelist->asset_library == NULL, + "Asset library should not yet be assigned at start of read job"); flrj->tmp_filelist = MEM_dupallocN(flrj->filelist); @@ -3394,11 +3630,17 @@ static void filelist_readjob_update(void *flrjv) flrj->tmp_filelist->filelist.nbr_entries = 0; } + if (flrj->tmp_filelist->asset_library) { + flrj->filelist->asset_library = flrj->tmp_filelist->asset_library; + flrj->tmp_filelist->asset_library = NULL; /* MUST be NULL to avoid double-free. */ + } + BLI_mutex_unlock(&flrj->lock); if (new_nbr_entries) { - /* Do not clear selection cache, we can assume already 'selected' UIDs are still valid! */ - filelist_clear_ex(flrj->filelist, true, false); + /* Do not clear selection cache, we can assume already 'selected' UIDs are still valid! Keep + * the asset library data we just read. */ + filelist_clear_ex(flrj->filelist, false, true, false); flrj->filelist->flags |= (FL_NEED_SORTING | FL_NEED_FILTERING); } diff --git a/source/blender/editors/space_file/filelist.h b/source/blender/editors/space_file/filelist.h index 1fb05e0f9ac..d1f37b5b365 100644 --- a/source/blender/editors/space_file/filelist.h +++ b/source/blender/editors/space_file/filelist.h @@ -31,6 +31,7 @@ struct AssetLibraryReference; struct BlendHandle; struct FileList; struct FileSelection; +struct bUUID; struct wmWindowManager; struct FileDirEntry; @@ -71,6 +72,10 @@ void filelist_setfilter_options(struct FileList *filelist, const bool filter_assets_only, const char *filter_glob, const char *filter_search); +void filelist_set_asset_catalog_filter_options( + struct FileList *filelist, + eFileSel_Params_AssetCatalogVisibility catalog_visibility, + const struct bUUID *catalog_id); void filelist_filter(struct FileList *filelist); void filelist_setlibrary(struct FileList *filelist, const struct AssetLibraryReference *asset_library_ref); @@ -86,7 +91,10 @@ int filelist_geticon(struct FileList *filelist, const int index, const bool is_m struct FileList *filelist_new(short type); void filelist_settype(struct FileList *filelist, short type); void filelist_clear(struct FileList *filelist); -void filelist_clear_ex(struct FileList *filelist, const bool do_cache, const bool do_selection); +void filelist_clear_ex(struct FileList *filelist, + const bool do_asset_library, + const bool do_cache, + const bool do_selection); void filelist_free(struct FileList *filelist); const char *filelist_dir(struct FileList *filelist); @@ -141,6 +149,8 @@ void filelist_entry_parent_select_set(struct FileList *filelist, void filelist_setrecursion(struct FileList *filelist, const int recursion_level); +struct AssetLibrary *filelist_asset_library(struct FileList *filelist); + struct BlendHandle *filelist_lib(struct FileList *filelist); bool filelist_islibrary(struct FileList *filelist, char *dir, char **r_group); void filelist_freelib(struct FileList *filelist); diff --git a/source/blender/editors/space_file/filesel.c b/source/blender/editors/space_file/filesel.c index f7bdb4326a5..83b33fe8aa9 100644 --- a/source/blender/editors/space_file/filesel.c +++ b/source/blender/editors/space_file/filesel.c @@ -120,19 +120,16 @@ static void fileselect_ensure_updated_asset_params(SpaceFile *sfile) asset_params->base_params.details_flags = U_default.file_space_data.details_flags; asset_params->asset_library_ref.type = ASSET_LIBRARY_LOCAL; asset_params->asset_library_ref.custom_library_index = -1; - asset_params->import_type = FILE_ASSET_IMPORT_APPEND; + asset_params->import_type = FILE_ASSET_IMPORT_APPEND_REUSE; } FileSelectParams *base_params = &asset_params->base_params; base_params->file[0] = '\0'; base_params->filter_glob[0] = '\0'; - /* TODO: this way of using filters to form categories is notably slower than specifying a - * "group" to read. That's because all types are read and filtering is applied afterwards. Would - * be nice if we could lazy-read individual groups. */ base_params->flag |= U_default.file_space_data.flag | FILE_ASSETS_ONLY | FILE_FILTER; base_params->flag &= ~FILE_DIRSEL_ONLY; base_params->filter |= FILE_TYPE_BLENDERLIB; - base_params->filter_id = FILTER_ID_OB | FILTER_ID_GR; + base_params->filter_id = FILTER_ID_ALL; base_params->display = FILE_IMGDISPLAY; base_params->sort = FILE_SORT_ALPHA; /* Asset libraries include all sub-directories, so enable maximal recursion. */ @@ -440,7 +437,8 @@ static void fileselect_refresh_asset_params(FileAssetSelectParams *asset_params) BLI_strncpy(base_params->dir, user_library->path, sizeof(base_params->dir)); break; } - base_params->type = (library->type == ASSET_LIBRARY_LOCAL) ? FILE_MAIN_ASSET : FILE_LOADLIB; + base_params->type = (library->type == ASSET_LIBRARY_LOCAL) ? FILE_MAIN_ASSET : + FILE_ASSET_LIBRARY; } void fileselect_refresh_params(SpaceFile *sfile) @@ -461,6 +459,15 @@ bool ED_fileselect_is_asset_browser(const SpaceFile *sfile) return (sfile->browse_mode == FILE_BROWSE_MODE_ASSETS); } +struct AssetLibrary *ED_fileselect_active_asset_library_get(const SpaceFile *sfile) +{ + if (!ED_fileselect_is_asset_browser(sfile) || !sfile->files) { + return NULL; + } + + return filelist_asset_library(sfile->files); +} + struct ID *ED_fileselect_active_asset_get(const SpaceFile *sfile) { if (!ED_fileselect_is_asset_browser(sfile)) { diff --git a/source/blender/editors/space_file/fsmenu.c b/source/blender/editors/space_file/fsmenu.c index 776bb0b3bb7..091c2d5f434 100644 --- a/source/blender/editors/space_file/fsmenu.c +++ b/source/blender/editors/space_file/fsmenu.c @@ -958,7 +958,7 @@ void fsmenu_read_system(struct FSMenu *fsmenu, int read_bookmarks) found = 1; } if (endmntent(fp) == 0) { - fprintf(stderr, "could not close the list of mounted filesystems\n"); + fprintf(stderr, "could not close the list of mounted file-systems\n"); } } /* Check gvfs shares. */ diff --git a/source/blender/editors/space_file/space_file.c b/source/blender/editors/space_file/space_file.c index 42a9c4aa2d5..a563f24e24e 100644 --- a/source/blender/editors/space_file/space_file.c +++ b/source/blender/editors/space_file/space_file.c @@ -1,4 +1,4 @@ -/* +/* * 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 @@ -335,9 +335,9 @@ static void file_refresh(const bContext *C, ScrArea *area) params->highlight_file = -1; /* added this so it opens nicer (ton) */ } - if (!U.experimental.use_extended_asset_browser && ED_fileselect_is_asset_browser(sfile)) { + if (ED_fileselect_is_asset_browser(sfile)) { /* Only poses supported as non-experimental right now. */ - params->filter_id = FILTER_ID_AC; + params->filter_id = U.experimental.use_extended_asset_browser ? FILTER_ID_ALL : FILTER_ID_AC; } filelist_settype(sfile->files, params->type); @@ -355,6 +355,10 @@ static void file_refresh(const bContext *C, ScrArea *area) (params->flag & FILE_ASSETS_ONLY) != 0, params->filter_glob, params->filter_search); + if (asset_params) { + filelist_set_asset_catalog_filter_options( + sfile->files, asset_params->asset_catalog_visibility, &asset_params->catalog_id); + } /* Update the active indices of bookmarks & co. */ sfile->systemnr = fsmenu_get_active_indices(fsmenu, FS_CATEGORY_SYSTEM, params->dir); @@ -1050,6 +1054,7 @@ void ED_spacetype_file(void) art->init = file_tools_region_init; art->draw = file_tools_region_draw; BLI_addhead(&st->regiontypes, art); + file_tools_region_panels_register(art); /* regions: tool properties */ art = MEM_callocN(sizeof(ARegionType), "spacetype file operator region"); diff --git a/source/blender/editors/space_graph/space_graph.c b/source/blender/editors/space_graph/space_graph.c index 720d69eaf4f..0e2c9b85bc6 100644 --- a/source/blender/editors/space_graph/space_graph.c +++ b/source/blender/editors/space_graph/space_graph.c @@ -237,29 +237,27 @@ static void graph_main_region_draw(const bContext *C, ARegion *region) v2d->tot.xmax += 10.0f; } - if (((sipo->flag & SIPO_NODRAWCURSOR) == 0) || (sipo->mode == SIPO_MODE_DRIVERS)) { + if (((sipo->flag & SIPO_NODRAWCURSOR) == 0)) { uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); /* horizontal component of value-cursor (value line before the current frame line) */ - if ((sipo->flag & SIPO_NODRAWCURSOR) == 0) { - float y = sipo->cursorVal; + float y = sipo->cursorVal; - /* Draw a green line to indicate the cursor value */ - immUniformThemeColorShadeAlpha(TH_CFRAME, -10, -50); - GPU_blend(GPU_BLEND_ALPHA); - GPU_line_width(2.0); + /* Draw a line to indicate the cursor value. */ + immUniformThemeColorShadeAlpha(TH_CFRAME, -10, -50); + GPU_blend(GPU_BLEND_ALPHA); + GPU_line_width(2.0); - immBegin(GPU_PRIM_LINES, 2); - immVertex2f(pos, v2d->cur.xmin, y); - immVertex2f(pos, v2d->cur.xmax, y); - immEnd(); + immBegin(GPU_PRIM_LINES, 2); + immVertex2f(pos, v2d->cur.xmin, y); + immVertex2f(pos, v2d->cur.xmax, y); + immEnd(); - GPU_blend(GPU_BLEND_NONE); - } + GPU_blend(GPU_BLEND_NONE); - /* current frame or vertical component of vertical component of the cursor */ + /* Vertical component of of the cursor. */ if (sipo->mode == SIPO_MODE_DRIVERS) { /* cursor x-value */ float x = sipo->cursorTime; @@ -311,12 +309,17 @@ static void graph_main_region_draw_overlay(const bContext *C, ARegion *region) { /* draw entirely, view changes should be handled here */ const SpaceGraph *sipo = CTX_wm_space_graph(C); + + /* Driver Editor's X axis is not time. */ + if (sipo->mode == SIPO_MODE_DRIVERS) { + return; + } + const Scene *scene = CTX_data_scene(C); - const bool draw_vert_line = sipo->mode != SIPO_MODE_DRIVERS; View2D *v2d = ®ion->v2d; /* scrubbing region */ - ED_time_scrub_draw_current_frame(region, scene, sipo->flag & SIPO_DRAWTIME, draw_vert_line); + ED_time_scrub_draw_current_frame(region, scene, sipo->flag & SIPO_DRAWTIME); /* scrollers */ /* FIXME: args for scrollers depend on the type of data being shown. */ diff --git a/source/blender/editors/space_image/image_draw.c b/source/blender/editors/space_image/image_draw.c index d2af26aa1d7..fc04ec1fe02 100644 --- a/source/blender/editors/space_image/image_draw.c +++ b/source/blender/editors/space_image/image_draw.c @@ -34,6 +34,7 @@ #include "DNA_scene_types.h" #include "DNA_screen_types.h" #include "DNA_space_types.h" +#include "DNA_view2d_types.h" #include "PIL_time.h" @@ -576,3 +577,62 @@ void draw_image_cache(const bContext *C, ARegion *region) ED_mask_draw_frames(mask, region, cfra, sfra, efra); } } + +float ED_space_image_zoom_level(const View2D *v2d, const int grid_dimension) +{ + /* UV-space length per pixel */ + float xzoom = (v2d->cur.xmax - v2d->cur.xmin) / ((float)(v2d->mask.xmax - v2d->mask.xmin)); + float yzoom = (v2d->cur.ymax - v2d->cur.ymin) / ((float)(v2d->mask.ymax - v2d->mask.ymin)); + + /* Zoom_factor for UV/Image editor is calculated based on: + * - Default grid size on startup, which is 256x256 pixels + * - How blend factor for grid lines is set up in the fragment shader `grid_frag.glsl`. */ + float zoom_factor; + zoom_factor = (xzoom + yzoom) / 2.0f; /* Average for accuracy. */ + zoom_factor *= 256.0f / (powf(grid_dimension, 2)); + return zoom_factor; +} + +void ED_space_image_grid_steps(SpaceImage *sima, + float grid_steps[SI_GRID_STEPS_LEN], + const int grid_dimension) +{ + if (sima->flag & SI_CUSTOM_GRID) { + for (int step = 0; step < SI_GRID_STEPS_LEN; step++) { + grid_steps[step] = powf(1, step) * (1.0f / ((float)sima->custom_grid_subdiv)); + } + } + else { + for (int step = 0; step < SI_GRID_STEPS_LEN; step++) { + grid_steps[step] = powf(grid_dimension, step) * + (1.0f / (powf(grid_dimension, SI_GRID_STEPS_LEN))); + } + } +} + +/** + * Calculate the increment snapping value for UV/image editor based on the zoom factor + * The code in here (except the offset part) is used in `grid_frag.glsl` (see `grid_res`) for + * drawing the grid overlay for the UV/Image editor. + */ +float ED_space_image_increment_snap_value(const int grid_dimesnions, + const float grid_steps[SI_GRID_STEPS_LEN], + const float zoom_factor) +{ + /* Small offset on each grid_steps[] so that snapping value doesn't change until grid lines are + * significantly visible. + * `Offset = 3/4 * (grid_steps[i] - (grid_steps[i] / grid_dimesnsions))` + * + * Refer `grid_frag.glsl` to find out when grid lines actually start appearing */ + + for (int step = 0; step < SI_GRID_STEPS_LEN; step++) { + float offset = (3.0f / 4.0f) * (grid_steps[step] - (grid_steps[step] / grid_dimesnions)); + + if ((grid_steps[step] - offset) > zoom_factor) { + return grid_steps[step]; + } + } + + /* Fallback */ + return grid_steps[0]; +} diff --git a/source/blender/editors/space_image/space_image.c b/source/blender/editors/space_image/space_image.c index de8e4684d45..f14a8266cdd 100644 --- a/source/blender/editors/space_image/space_image.c +++ b/source/blender/editors/space_image/space_image.c @@ -126,13 +126,7 @@ static SpaceLink *image_create(const ScrArea *UNUSED(area), const Scene *UNUSED( simage->tile_grid_shape[0] = 1; simage->tile_grid_shape[1] = 1; - /* tool header */ - region = MEM_callocN(sizeof(ARegion), "tool header for image"); - - BLI_addtail(&simage->regionbase, region); - region->regiontype = RGN_TYPE_TOOL_HEADER; - region->alignment = (U.uiflag & USER_HEADER_BOTTOM) ? RGN_ALIGN_BOTTOM : RGN_ALIGN_TOP; - region->flag = RGN_FLAG_HIDDEN | RGN_FLAG_HIDDEN_BY_USER; + simage->custom_grid_subdiv = 10; /* header */ region = MEM_callocN(sizeof(ARegion), "header for image"); @@ -141,6 +135,14 @@ static SpaceLink *image_create(const ScrArea *UNUSED(area), const Scene *UNUSED( region->regiontype = RGN_TYPE_HEADER; region->alignment = (U.uiflag & USER_HEADER_BOTTOM) ? RGN_ALIGN_BOTTOM : RGN_ALIGN_TOP; + /* tool header */ + region = MEM_callocN(sizeof(ARegion), "tool header for image"); + + BLI_addtail(&simage->regionbase, region); + region->regiontype = RGN_TYPE_TOOL_HEADER; + region->alignment = (U.uiflag & USER_HEADER_BOTTOM) ? RGN_ALIGN_BOTTOM : RGN_ALIGN_TOP; + region->flag = RGN_FLAG_HIDDEN | RGN_FLAG_HIDDEN_BY_USER; + /* buttons/list view */ region = MEM_callocN(sizeof(ARegion), "buttons for image"); diff --git a/source/blender/editors/space_info/info_ops.c b/source/blender/editors/space_info/info_ops.c index a99396ecdf0..8e37e5fe9a8 100644 --- a/source/blender/editors/space_info/info_ops.c +++ b/source/blender/editors/space_info/info_ops.c @@ -564,7 +564,7 @@ void FILE_OT_find_missing_files(wmOperatorType *ot) /** \name Report Box Operator * \{ */ -/* NOTE(matt): Hard to decide whether to keep this as an operator, +/* NOTE(@broken): Hard to decide whether to keep this as an operator, * or turn it into a hard_coded UI control feature, * handling TIMER events for all regions in `interface_handlers.c`. * Not sure how good that is to be accessing UI data from diff --git a/source/blender/editors/space_nla/nla_draw.c b/source/blender/editors/space_nla/nla_draw.c index c1b308d213f..4694d8652f6 100644 --- a/source/blender/editors/space_nla/nla_draw.c +++ b/source/blender/editors/space_nla/nla_draw.c @@ -152,7 +152,7 @@ static void nla_action_draw_keyframes( format, "flags", GPU_COMP_U32, 1, GPU_FETCH_INT); GPU_program_point_size(true); - immBindBuiltinProgram(GPU_SHADER_KEYFRAME_DIAMOND); + immBindBuiltinProgram(GPU_SHADER_KEYFRAME_SHAPE); immUniform1f("outline_scale", 1.0f); immUniform2f("ViewportSize", BLI_rcti_size_x(&v2d->mask) + 1, BLI_rcti_size_y(&v2d->mask) + 1); immBegin(GPU_PRIM_POINTS, key_len); diff --git a/source/blender/editors/space_nla/space_nla.c b/source/blender/editors/space_nla/space_nla.c index 987d06cfe5c..8b44c26f07c 100644 --- a/source/blender/editors/space_nla/space_nla.c +++ b/source/blender/editors/space_nla/space_nla.c @@ -289,7 +289,7 @@ static void nla_main_region_draw_overlay(const bContext *C, ARegion *region) View2D *v2d = ®ion->v2d; /* scrubbing region */ - ED_time_scrub_draw_current_frame(region, scene, snla->flag & SNLA_DRAWTIME, true); + ED_time_scrub_draw_current_frame(region, scene, snla->flag & SNLA_DRAWTIME); /* scrollers */ UI_view2d_scrollers_draw(v2d, NULL); diff --git a/source/blender/editors/space_node/drawnode.cc b/source/blender/editors/space_node/drawnode.cc index 62f40152416..8d6d56fc383 100644 --- a/source/blender/editors/space_node/drawnode.cc +++ b/source/blender/editors/space_node/drawnode.cc @@ -164,6 +164,11 @@ static void node_buts_curvevec(uiLayout *layout, bContext *UNUSED(C), PointerRNA uiTemplateCurveMapping(layout, ptr, "mapping", 'v', false, false, false, false); } +static void node_buts_curvefloat(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiTemplateCurveMapping(layout, ptr, "mapping", 0, false, false, false, false); +} + #define SAMPLE_FLT_ISNONE FLT_MAX /* Bad bad, 2.5 will do better? ... no it won't! */ static float _sample_col[4] = {SAMPLE_FLT_ISNONE}; @@ -1183,6 +1188,9 @@ static void node_shader_set_butfunc(bNodeType *ntype) case SH_NODE_CURVE_RGB: ntype->draw_buttons = node_buts_curvecol; break; + case SH_NODE_CURVE_FLOAT: + ntype->draw_buttons = node_buts_curvefloat; + break; case SH_NODE_MAPPING: ntype->draw_buttons = node_shader_buts_mapping; break; @@ -1563,7 +1571,7 @@ static void node_composit_buts_antialiasing(uiLayout *layout, bContext *UNUSED(C uiItemR(col, ptr, "corner_rounding", 0, nullptr, ICON_NONE); } -/* qdn: glare node */ +/* glare node */ static void node_composit_buts_glare(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiItemR(layout, ptr, "glare_type", DEFAULT_FLAGS, "", ICON_NONE); @@ -2079,7 +2087,7 @@ static void node_composit_buts_premulkey(uiLayout *layout, bContext *UNUSED(C), static void node_composit_buts_view_levels(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { - uiItemR(layout, ptr, "channel", DEFAULT_FLAGS | UI_ITEM_R_EXPAND, nullptr, ICON_NONE); + uiItemR(layout, ptr, "channel", DEFAULT_FLAGS, "", ICON_NONE); } static void node_composit_buts_colorbalance(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) @@ -3933,9 +3941,13 @@ static struct { uint p0_id, p1_id, p2_id, p3_id; uint colid_id, muted_id; uint dim_factor_id; + uint thickness_id; + uint dash_factor_id; GPUVertBufRaw p0_step, p1_step, p2_step, p3_step; GPUVertBufRaw colid_step, muted_step; GPUVertBufRaw dim_factor_step; + GPUVertBufRaw thickness_step; + GPUVertBufRaw dash_factor_step; uint count; bool enabled; } g_batch_link; @@ -3952,6 +3964,10 @@ static void nodelink_batch_reset() g_batch_link.inst_vbo, g_batch_link.muted_id, &g_batch_link.muted_step); GPU_vertbuf_attr_get_raw_data( g_batch_link.inst_vbo, g_batch_link.dim_factor_id, &g_batch_link.dim_factor_step); + GPU_vertbuf_attr_get_raw_data( + g_batch_link.inst_vbo, g_batch_link.thickness_id, &g_batch_link.thickness_step); + GPU_vertbuf_attr_get_raw_data( + g_batch_link.inst_vbo, g_batch_link.dash_factor_id, &g_batch_link.dash_factor_step); g_batch_link.count = 0; } @@ -4071,6 +4087,10 @@ static void nodelink_batch_init() &format_inst, "domuted", GPU_COMP_U8, 2, GPU_FETCH_INT); g_batch_link.dim_factor_id = GPU_vertformat_attr_add( &format_inst, "dim_factor", GPU_COMP_F32, 1, GPU_FETCH_FLOAT); + g_batch_link.thickness_id = GPU_vertformat_attr_add( + &format_inst, "thickness", GPU_COMP_F32, 1, GPU_FETCH_FLOAT); + g_batch_link.dash_factor_id = GPU_vertformat_attr_add( + &format_inst, "dash_factor", GPU_COMP_F32, 1, GPU_FETCH_FLOAT); g_batch_link.inst_vbo = GPU_vertbuf_create_with_format_ex(&format_inst, GPU_USAGE_STREAM); /* Alloc max count but only draw the range we need. */ GPU_vertbuf_data_alloc(g_batch_link.inst_vbo, NODELINK_GROUP_SIZE); @@ -4147,7 +4167,9 @@ static void nodelink_batch_add_link(const SpaceNode *snode, int th_col3, bool drawarrow, bool drawmuted, - float dim_factor) + float dim_factor, + float thickness, + float dash_factor) { /* Only allow these colors. If more is needed, you need to modify the shader accordingly. */ BLI_assert(ELEM(th_col1, TH_WIRE_INNER, TH_WIRE, TH_ACTIVE, TH_EDGE_SELECT, TH_REDALERT)); @@ -4167,6 +4189,8 @@ static void nodelink_batch_add_link(const SpaceNode *snode, char *muted = (char *)GPU_vertbuf_raw_step(&g_batch_link.muted_step); muted[0] = drawmuted; *(float *)GPU_vertbuf_raw_step(&g_batch_link.dim_factor_step) = dim_factor; + *(float *)GPU_vertbuf_raw_step(&g_batch_link.thickness_step) = thickness; + *(float *)GPU_vertbuf_raw_step(&g_batch_link.dash_factor_step) = dash_factor; if (g_batch_link.count == NODELINK_GROUP_SIZE) { nodelink_batch_draw(snode); @@ -4182,6 +4206,16 @@ void node_draw_link_bezier(const View2D *v2d, int th_col3) { const float dim_factor = node_link_dim_factor(v2d, link); + float thickness = 1.5f; + float dash_factor = 1.0f; + if (snode->edittree->type == NTREE_GEOMETRY) { + if (link->fromsock && link->fromsock->display_shape == SOCK_DISPLAY_SHAPE_DIAMOND) { + /* Make field links a bit thinner. */ + thickness = 1.0f; + /* Draw field as dashes. */ + dash_factor = 0.75f; + } + } float vec[4][2]; const bool highlighted = link->flag & NODE_LINK_TEMP_HIGHLIGHT; @@ -4205,7 +4239,9 @@ void node_draw_link_bezier(const View2D *v2d, th_col3, drawarrow, drawmuted, - dim_factor); + dim_factor, + thickness, + dash_factor); } else { /* Draw single link. */ @@ -4231,6 +4267,8 @@ void node_draw_link_bezier(const View2D *v2d, GPU_batch_uniform_1i(batch, "doArrow", drawarrow); GPU_batch_uniform_1i(batch, "doMuted", drawmuted); GPU_batch_uniform_1f(batch, "dim_factor", dim_factor); + GPU_batch_uniform_1f(batch, "thickness", thickness); + GPU_batch_uniform_1f(batch, "dash_factor", dash_factor); GPU_batch_draw(batch); } } @@ -4282,6 +4320,13 @@ void node_draw_link(View2D *v2d, SpaceNode *snode, bNodeLink *link) // th_col3 = -1; /* no shadow */ } } + /* Links from field to non-field sockets are not allowed. */ + if (snode->edittree->type == NTREE_GEOMETRY && !(link->flag & NODE_LINK_DRAGGED)) { + if ((link->fromsock && link->fromsock->display_shape == SOCK_DISPLAY_SHAPE_DIAMOND) && + (link->tosock && link->tosock->display_shape == SOCK_DISPLAY_SHAPE_CIRCLE)) { + th_col1 = th_col2 = th_col3 = TH_REDALERT; + } + } node_draw_link_bezier(v2d, snode, link, th_col1, th_col2, th_col3); } diff --git a/source/blender/editors/space_node/node_draw.cc b/source/blender/editors/space_node/node_draw.cc index 10a3285be8b..9b243290566 100644 --- a/source/blender/editors/space_node/node_draw.cc +++ b/source/blender/editors/space_node/node_draw.cc @@ -79,6 +79,7 @@ #include "RNA_access.h" #include "NOD_geometry_nodes_eval_log.hh" +#include "NOD_node_declaration.hh" #include "FN_field_cpp_type.hh" @@ -733,12 +734,6 @@ static void node_draw_mute_line(const View2D *v2d, const SpaceNode *snode, const GPU_blend(GPU_BLEND_NONE); } -/* Flags used in gpu_shader_keyframe_diamond_frag.glsl. */ -#define MARKER_SHAPE_DIAMOND 0x1 -#define MARKER_SHAPE_SQUARE 0xC -#define MARKER_SHAPE_CIRCLE 0x2 -#define MARKER_SHAPE_INNER_DOT 0x10 - static void node_socket_draw(const bNodeSocket *sock, const float color[4], const float color_outline[4], @@ -757,16 +752,16 @@ static void node_socket_draw(const bNodeSocket *sock, switch (sock->display_shape) { case SOCK_DISPLAY_SHAPE_DIAMOND: case SOCK_DISPLAY_SHAPE_DIAMOND_DOT: - flags = MARKER_SHAPE_DIAMOND; + flags = GPU_KEYFRAME_SHAPE_DIAMOND; break; case SOCK_DISPLAY_SHAPE_SQUARE: case SOCK_DISPLAY_SHAPE_SQUARE_DOT: - flags = MARKER_SHAPE_SQUARE; + flags = GPU_KEYFRAME_SHAPE_SQUARE; break; default: case SOCK_DISPLAY_SHAPE_CIRCLE: case SOCK_DISPLAY_SHAPE_CIRCLE_DOT: - flags = MARKER_SHAPE_CIRCLE; + flags = GPU_KEYFRAME_SHAPE_CIRCLE; break; } @@ -774,7 +769,7 @@ static void node_socket_draw(const bNodeSocket *sock, SOCK_DISPLAY_SHAPE_DIAMOND_DOT, SOCK_DISPLAY_SHAPE_SQUARE_DOT, SOCK_DISPLAY_SHAPE_CIRCLE_DOT)) { - flags |= MARKER_SHAPE_INNER_DOT; + flags |= GPU_KEYFRAME_SHAPE_INNER_DOT; } immAttr4fv(col_id, color); @@ -1091,12 +1086,37 @@ static void node_socket_draw_nested(const bContext *C, but, [](bContext *C, void *argN, const char *UNUSED(tip)) { SocketTooltipData *data = (SocketTooltipData *)argN; - std::optional<std::string> str = create_socket_inspection_string( + std::optional<std::string> socket_inspection_str = create_socket_inspection_string( C, *data->ntree, *data->node, *data->socket); - if (str.has_value()) { - return BLI_strdup(str->c_str()); + + std::stringstream output; + if (data->node->declaration != nullptr) { + ListBase *list; + Span<blender::nodes::SocketDeclarationPtr> decl_list; + + if (data->socket->in_out == SOCK_IN) { + list = &data->node->inputs; + decl_list = data->node->declaration->inputs(); + } + else { + list = &data->node->outputs; + decl_list = data->node->declaration->outputs(); + } + + const int socket_index = BLI_findindex(list, data->socket); + const blender::nodes::SocketDeclaration &socket_decl = *decl_list[socket_index]; + blender::StringRef description = socket_decl.description(); + if (!description.is_empty()) { + output << TIP_(description.data()) << ".\n\n"; + } + } + if (socket_inspection_str.has_value()) { + output << *socket_inspection_str; + } + else { + output << TIP_("The socket value has not been computed yet"); } - return BLI_strdup(TIP_("The socket value has not been computed yet")); + return BLI_strdup(output.str().c_str()); }, data, MEM_freeN); @@ -1132,7 +1152,7 @@ void ED_node_socket_draw(bNodeSocket *sock, const rcti *rect, const float color[ GPU_blend(GPU_BLEND_ALPHA); GPU_program_point_size(true); - immBindBuiltinProgram(GPU_SHADER_KEYFRAME_DIAMOND); + immBindBuiltinProgram(GPU_SHADER_KEYFRAME_SHAPE); immUniform1f("outline_scale", 0.7f); immUniform2f("ViewportSize", -1.0f, -1.0f); @@ -1277,7 +1297,7 @@ void node_draw_sockets(const View2D *v2d, GPU_blend(GPU_BLEND_ALPHA); GPU_program_point_size(true); - immBindBuiltinProgram(GPU_SHADER_KEYFRAME_DIAMOND); + immBindBuiltinProgram(GPU_SHADER_KEYFRAME_SHAPE); immUniform1f("outline_scale", 0.7f); immUniform2f("ViewportSize", -1.0f, -1.0f); diff --git a/source/blender/editors/space_node/node_relationships.cc b/source/blender/editors/space_node/node_relationships.cc index 7d95659e403..b69e7e98bca 100644 --- a/source/blender/editors/space_node/node_relationships.cc +++ b/source/blender/editors/space_node/node_relationships.cc @@ -220,6 +220,7 @@ static LinkData *create_drag_link(Main *bmain, SpaceNode *snode, bNode *node, bN if (node_connected_to_output(bmain, snode->edittree, node)) { oplink->flag |= NODE_LINK_TEST; } + oplink->flag |= NODE_LINK_DRAGGED; return linkdata; } @@ -894,6 +895,8 @@ static void node_link_exit(bContext *C, wmOperator *op, bool apply_links) */ do_tag_update |= (link->flag & NODE_LINK_TEST) != 0; + link->flag &= ~NODE_LINK_DRAGGED; + if (apply_links && link->tosock && link->fromsock) { /* before actually adding the link, * let nodes perform special link insertion handling @@ -1097,6 +1100,7 @@ static bNodeLinkDrag *node_link_init(Main *bmain, SpaceNode *snode, float cursor *oplink = *link; oplink->next = oplink->prev = nullptr; oplink->flag |= NODE_LINK_VALID; + oplink->flag |= NODE_LINK_DRAGGED; /* The link could be disconnected and in that case we * wouldn't be able to check whether tag update is @@ -1150,6 +1154,7 @@ static bNodeLinkDrag *node_link_init(Main *bmain, SpaceNode *snode, float cursor *oplink = *link_to_pick; oplink->next = oplink->prev = nullptr; oplink->flag |= NODE_LINK_VALID; + oplink->flag |= NODE_LINK_DRAGGED; oplink->flag &= ~NODE_LINK_TEST; if (node_connected_to_output(bmain, snode->edittree, link_to_pick->tonode)) { oplink->flag |= NODE_LINK_TEST; diff --git a/source/blender/editors/space_outliner/outliner_select.c b/source/blender/editors/space_outliner/outliner_select.c index 581892ebb3a..5e409db0059 100644 --- a/source/blender/editors/space_outliner/outliner_select.c +++ b/source/blender/editors/space_outliner/outliner_select.c @@ -34,6 +34,7 @@ #include "DNA_scene_types.h" #include "DNA_sequence_types.h" #include "DNA_shader_fx_types.h" +#include "DNA_text_types.h" #include "BLI_listbase.h" #include "BLI_utildefines.h" @@ -63,6 +64,7 @@ #include "ED_screen.h" #include "ED_select_utils.h" #include "ED_sequencer.h" +#include "ED_text.h" #include "ED_undo.h" #include "SEQ_select.h" @@ -737,6 +739,12 @@ static void tree_element_layer_collection_activate(bContext *C, TreeElement *te) WM_main_add_notifier(NC_SCENE | ND_LAYER | NS_LAYER_COLLECTION | NA_ACTIVATED, NULL); } +static void tree_element_text_activate(bContext *C, TreeElement *te) +{ + Text *text = (Text *)te->store_elem->id; + ED_text_activate_in_screen(C, text); +} + /* ---------------------------------------------- */ /* generic call for ID data check or make/check active in UI */ @@ -764,6 +772,9 @@ void tree_element_activate(bContext *C, case ID_CA: tree_element_camera_activate(C, tvc->scene, te); break; + case ID_TXT: + tree_element_text_activate(C, te); + break; } } diff --git a/source/blender/editors/space_sequencer/sequencer_draw.c b/source/blender/editors/space_sequencer/sequencer_draw.c index 53f1c35776c..dc5e11b6998 100644 --- a/source/blender/editors/space_sequencer/sequencer_draw.c +++ b/source/blender/editors/space_sequencer/sequencer_draw.c @@ -107,36 +107,53 @@ static Sequence *special_seq_update = NULL; -void color3ubv_from_seq(Scene *curscene, Sequence *seq, uchar col[3]) -{ +void color3ubv_from_seq(const Scene *curscene, + const Sequence *seq, + const bool show_strip_color_tag, + uchar r_col[3]) +{ + if (show_strip_color_tag && (uint)seq->color_tag < SEQUENCE_COLOR_TOT && + seq->color_tag != SEQUENCE_COLOR_NONE) { + bTheme *btheme = UI_GetTheme(); + const ThemeStripColor *strip_color = &btheme->strip_color[seq->color_tag]; + copy_v3_v3_uchar(r_col, strip_color->color); + return; + } + uchar blendcol[3]; + /* Sometimes the active theme is not the sequencer theme, e.g. when an operator invokes the file + * browser. This makes sure we get the right color values for the theme. */ + struct bThemeState theme_state; + UI_Theme_Store(&theme_state); + UI_SetTheme(SPACE_SEQ, RGN_TYPE_WINDOW); + switch (seq->type) { case SEQ_TYPE_IMAGE: - UI_GetThemeColor3ubv(TH_SEQ_IMAGE, col); + UI_GetThemeColor3ubv(TH_SEQ_IMAGE, r_col); break; case SEQ_TYPE_META: - UI_GetThemeColor3ubv(TH_SEQ_META, col); + UI_GetThemeColor3ubv(TH_SEQ_META, r_col); break; case SEQ_TYPE_MOVIE: - UI_GetThemeColor3ubv(TH_SEQ_MOVIE, col); + UI_GetThemeColor3ubv(TH_SEQ_MOVIE, r_col); break; case SEQ_TYPE_MOVIECLIP: - UI_GetThemeColor3ubv(TH_SEQ_MOVIECLIP, col); + UI_GetThemeColor3ubv(TH_SEQ_MOVIECLIP, r_col); break; case SEQ_TYPE_MASK: - UI_GetThemeColor3ubv(TH_SEQ_MASK, col); + UI_GetThemeColor3ubv(TH_SEQ_MASK, r_col); break; case SEQ_TYPE_SCENE: - UI_GetThemeColor3ubv(TH_SEQ_SCENE, col); + UI_GetThemeColor3ubv(TH_SEQ_SCENE, r_col); if (seq->scene == curscene) { - UI_GetColorPtrShade3ubv(col, col, 20); + UI_GetColorPtrShade3ubv(r_col, r_col, 20); } break; @@ -144,9 +161,9 @@ void color3ubv_from_seq(Scene *curscene, Sequence *seq, uchar col[3]) case SEQ_TYPE_CROSS: case SEQ_TYPE_GAMCROSS: case SEQ_TYPE_WIPE: - col[0] = 130; - col[1] = 130; - col[2] = 130; + r_col[0] = 130; + r_col[1] = 130; + r_col[2] = 130; break; /* Effects. */ @@ -163,72 +180,74 @@ void color3ubv_from_seq(Scene *curscene, Sequence *seq, uchar col[3]) case SEQ_TYPE_ADJUSTMENT: case SEQ_TYPE_GAUSSIAN_BLUR: case SEQ_TYPE_COLORMIX: - UI_GetThemeColor3ubv(TH_SEQ_EFFECT, col); + UI_GetThemeColor3ubv(TH_SEQ_EFFECT, r_col); /* Slightly offset hue to distinguish different effects. */ if (seq->type == SEQ_TYPE_ADD) { - rgb_byte_set_hue_float_offset(col, 0.03); + rgb_byte_set_hue_float_offset(r_col, 0.03); } else if (seq->type == SEQ_TYPE_SUB) { - rgb_byte_set_hue_float_offset(col, 0.06); + rgb_byte_set_hue_float_offset(r_col, 0.06); } else if (seq->type == SEQ_TYPE_MUL) { - rgb_byte_set_hue_float_offset(col, 0.13); + rgb_byte_set_hue_float_offset(r_col, 0.13); } else if (seq->type == SEQ_TYPE_ALPHAOVER) { - rgb_byte_set_hue_float_offset(col, 0.16); + rgb_byte_set_hue_float_offset(r_col, 0.16); } else if (seq->type == SEQ_TYPE_ALPHAUNDER) { - rgb_byte_set_hue_float_offset(col, 0.23); + rgb_byte_set_hue_float_offset(r_col, 0.23); } else if (seq->type == SEQ_TYPE_OVERDROP) { - rgb_byte_set_hue_float_offset(col, 0.26); + rgb_byte_set_hue_float_offset(r_col, 0.26); } else if (seq->type == SEQ_TYPE_COLORMIX) { - rgb_byte_set_hue_float_offset(col, 0.33); + rgb_byte_set_hue_float_offset(r_col, 0.33); } else if (seq->type == SEQ_TYPE_GAUSSIAN_BLUR) { - rgb_byte_set_hue_float_offset(col, 0.43); + rgb_byte_set_hue_float_offset(r_col, 0.43); } else if (seq->type == SEQ_TYPE_GLOW) { - rgb_byte_set_hue_float_offset(col, 0.46); + rgb_byte_set_hue_float_offset(r_col, 0.46); } else if (seq->type == SEQ_TYPE_ADJUSTMENT) { - rgb_byte_set_hue_float_offset(col, 0.55); + rgb_byte_set_hue_float_offset(r_col, 0.55); } else if (seq->type == SEQ_TYPE_SPEED) { - rgb_byte_set_hue_float_offset(col, 0.65); + rgb_byte_set_hue_float_offset(r_col, 0.65); } else if (seq->type == SEQ_TYPE_TRANSFORM) { - rgb_byte_set_hue_float_offset(col, 0.75); + rgb_byte_set_hue_float_offset(r_col, 0.75); } else if (seq->type == SEQ_TYPE_MULTICAM) { - rgb_byte_set_hue_float_offset(col, 0.85); + rgb_byte_set_hue_float_offset(r_col, 0.85); } break; case SEQ_TYPE_COLOR: - UI_GetThemeColor3ubv(TH_SEQ_COLOR, col); + UI_GetThemeColor3ubv(TH_SEQ_COLOR, r_col); break; case SEQ_TYPE_SOUND_RAM: - UI_GetThemeColor3ubv(TH_SEQ_AUDIO, col); + UI_GetThemeColor3ubv(TH_SEQ_AUDIO, r_col); blendcol[0] = blendcol[1] = blendcol[2] = 128; if (seq->flag & SEQ_MUTE) { - UI_GetColorPtrBlendShade3ubv(col, blendcol, col, 0.5, 20); + UI_GetColorPtrBlendShade3ubv(r_col, blendcol, r_col, 0.5, 20); } break; case SEQ_TYPE_TEXT: - UI_GetThemeColor3ubv(TH_SEQ_TEXT, col); + UI_GetThemeColor3ubv(TH_SEQ_TEXT, r_col); break; default: - col[0] = 10; - col[1] = 255; - col[2] = 40; + r_col[0] = 10; + r_col[1] = 255; + r_col[2] = 40; break; } + + UI_Theme_Restore(&theme_state); } typedef struct WaveVizData { @@ -558,7 +577,13 @@ static void draw_seq_waveform_overlay(View2D *v2d, } } -static void drawmeta_contents(Scene *scene, Sequence *seqm, float x1, float y1, float x2, float y2) +static void drawmeta_contents(Scene *scene, + Sequence *seqm, + float x1, + float y1, + float x2, + float y2, + const bool show_strip_color_tag) { Sequence *seq; uchar col[4]; @@ -614,7 +639,7 @@ static void drawmeta_contents(Scene *scene, Sequence *seqm, float x1, float y1, rgb_float_to_uchar(col, colvars->col); } else { - color3ubv_from_seq(scene, seq, col); + color3ubv_from_seq(scene, seq, show_strip_color_tag, col); } if ((seqm->flag & SEQ_MUTE) || (seq->flag & SEQ_MUTE)) { @@ -953,7 +978,8 @@ static void draw_seq_text_overlay(View2D *v2d, UI_view2d_text_cache_add_rectf(v2d, &rect, overlay_string, overlay_string_len, col); } -static void draw_sequence_extensions_overlay(Scene *scene, Sequence *seq, uint pos, float pixely) +static void draw_sequence_extensions_overlay( + Scene *scene, Sequence *seq, uint pos, float pixely, const bool show_strip_color_tag) { float x1, x2, y1, y2; uchar col[4], blend_col[3]; @@ -966,7 +992,7 @@ static void draw_sequence_extensions_overlay(Scene *scene, Sequence *seq, uint p GPU_blend(GPU_BLEND_ALPHA); - color3ubv_from_seq(scene, seq, col); + color3ubv_from_seq(scene, seq, show_strip_color_tag, col); if (seq->flag & SELECT) { UI_GetColorPtrShade3ubv(col, col, 50); } @@ -1036,7 +1062,8 @@ static void draw_seq_background(Scene *scene, float x2, float y1, float y2, - bool is_single_image) + bool is_single_image, + bool show_strip_color_tag) { uchar col[4]; GPU_blend(GPU_BLEND_ALPHA); @@ -1049,11 +1076,11 @@ static void draw_seq_background(Scene *scene, rgb_float_to_uchar(col, colvars->col); } else { - color3ubv_from_seq(scene, seq1, col); + color3ubv_from_seq(scene, seq1, show_strip_color_tag, col); } } else { - color3ubv_from_seq(scene, seq, col); + color3ubv_from_seq(scene, seq, show_strip_color_tag, col); } /* Draw muted strips semi-transparent. */ @@ -1108,7 +1135,7 @@ static void draw_seq_background(Scene *scene, rgb_float_to_uchar(col, colvars->col); } else { - color3ubv_from_seq(scene, seq2, col); + color3ubv_from_seq(scene, seq2, show_strip_color_tag, col); /* If the transition inputs are of the same type, draw the right side slightly darker. */ if (seq1->type == seq2->type) { UI_GetColorPtrShade3ubv(col, col, -15); @@ -1822,6 +1849,10 @@ static void draw_seq_strip(const bContext *C, /* Check if we are doing "solo preview". */ bool is_single_image = (char)SEQ_transform_single_image_check(seq); + /* Use the seq->color_tag to display the tag color. */ + const bool show_strip_color_tag = (sseq->timeline_overlay.flag & + SEQ_TIMELINE_SHOW_STRIP_COLOR_TAG); + /* Draw strip body. */ x1 = (seq->startstill) ? seq->start : seq->startdisp; y1 = seq->machine + SEQ_STRIP_OFSBOTTOM; @@ -1852,7 +1883,7 @@ static void draw_seq_strip(const bContext *C, uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); - draw_seq_background(scene, seq, pos, x1, x2, y1, y2, is_single_image); + draw_seq_background(scene, seq, pos, x1, x2, y1, y2, is_single_image, show_strip_color_tag); /* Draw a color band inside color strip. */ if (seq->type == SEQ_TYPE_COLOR && y_threshold) { @@ -1864,7 +1895,7 @@ static void draw_seq_strip(const bContext *C, if (!is_single_image && (seq->startofs || seq->endofs) && pixely > 0) { if ((sseq->timeline_overlay.flag & SEQ_TIMELINE_SHOW_STRIP_OFFSETS) || (seq == special_seq_update)) { - draw_sequence_extensions_overlay(scene, seq, pos, pixely); + draw_sequence_extensions_overlay(scene, seq, pos, pixely, show_strip_color_tag); } } } @@ -1875,7 +1906,7 @@ static void draw_seq_strip(const bContext *C, if ((seq->type == SEQ_TYPE_META) || ((seq->type == SEQ_TYPE_SCENE) && (seq->flag & SEQ_SCENE_STRIPS))) { - drawmeta_contents(scene, seq, x1, y1, x2, y2); + drawmeta_contents(scene, seq, x1, y1, x2, y2, show_strip_color_tag); } if ((sseq->flag & SEQ_SHOW_OVERLAY) && @@ -2585,7 +2616,7 @@ static int sequencer_draw_get_transform_preview_frame(Scene *scene) return preview_frame; } -static void seq_draw_image_origin_and_outline(const bContext *C, Sequence *seq) +static void seq_draw_image_origin_and_outline(const bContext *C, Sequence *seq, bool is_active_seq) { SpaceSeq *sseq = CTX_wm_space_seq(C); if ((seq->flag & SELECT) == 0) { @@ -2628,7 +2659,12 @@ static void seq_draw_image_origin_and_outline(const bContext *C, Sequence *seq) immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); float col[3]; - UI_GetThemeColor3fv(TH_SEQ_SELECTED, col); + if (is_active_seq) { + UI_GetThemeColor3fv(TH_SEQ_ACTIVE, col); + } + else { + UI_GetThemeColor3fv(TH_SEQ_SELECTED, col); + } immUniformColor3fv(col); immUniform1f("lineWidth", U.pixelsize); immBegin(GPU_PRIM_LINE_LOOP, 4); @@ -2719,12 +2755,15 @@ void sequencer_draw_preview(const bContext *C, sequencer_draw_borders_overlay(sseq, v2d, scene); } - SeqCollection *collection = SEQ_query_rendered_strips(&scene->ed->seqbase, timeline_frame, 0); - Sequence *seq; - SEQ_ITERATOR_FOREACH (seq, collection) { - seq_draw_image_origin_and_outline(C, seq); + if (!draw_backdrop && scene->ed != NULL) { + SeqCollection *collection = SEQ_query_rendered_strips(&scene->ed->seqbase, timeline_frame, 0); + Sequence *seq; + Sequence *active_seq = SEQ_select_active_get(scene); + SEQ_ITERATOR_FOREACH (seq, collection) { + seq_draw_image_origin_and_outline(C, seq, seq == active_seq); + } + SEQ_collection_free(collection); } - SEQ_collection_free(collection); if (draw_gpencil && show_imbuf && (sseq->flag & SEQ_SHOW_OVERLAY)) { sequencer_draw_gpencil_overlay(C); @@ -3292,6 +3331,6 @@ void draw_timeline_seq_display(const bContext *C, ARegion *region) UI_view2d_view_restore(C); } - ED_time_scrub_draw_current_frame(region, scene, !(sseq->flag & SEQ_DRAWFRAMES), true); + ED_time_scrub_draw_current_frame(region, scene, !(sseq->flag & SEQ_DRAWFRAMES)); UI_view2d_scrollers_draw(v2d, NULL); } diff --git a/source/blender/editors/space_sequencer/sequencer_edit.c b/source/blender/editors/space_sequencer/sequencer_edit.c index 9f21fc0676c..9be947b9112 100644 --- a/source/blender/editors/space_sequencer/sequencer_edit.c +++ b/source/blender/editors/space_sequencer/sequencer_edit.c @@ -63,6 +63,7 @@ #include "WM_types.h" #include "RNA_define.h" +#include "RNA_enum_types.h" /* For menu, popup, icons, etc. */ #include "ED_numinput.h" @@ -869,9 +870,9 @@ static int sequencer_slip_modal(bContext *C, wmOperator *op, const wmEvent *even void SEQUENCER_OT_slip(struct wmOperatorType *ot) { /* Identifiers. */ - ot->name = "Trim Strips"; + ot->name = "Slip Strips"; ot->idname = "SEQUENCER_OT_slip"; - ot->description = "Trim the contents of the active strip"; + ot->description = "Slip the contents of selected strips"; /* Api callbacks. */ ot->invoke = sequencer_slip_invoke; @@ -3322,4 +3323,38 @@ void SEQUENCER_OT_strip_transform_fit(struct wmOperatorType *ot) "Scale fit fit_method"); } +static int sequencer_strip_color_tag_set_exec(bContext *C, wmOperator *op) +{ + Scene *scene = CTX_data_scene(C); + const Editing *ed = SEQ_editing_get(scene); + const short color_tag = RNA_enum_get(op->ptr, "color"); + + LISTBASE_FOREACH (Sequence *, seq, &ed->seqbase) { + if (seq->flag & SELECT) { + seq->color_tag = color_tag; + } + } + + WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene); + return OPERATOR_FINISHED; +} + +void SEQUENCER_OT_strip_color_tag_set(struct wmOperatorType *ot) +{ + /* Identifiers. */ + ot->name = "Set Color Tag"; + ot->idname = "SEQUENCER_OT_strip_color_tag_set"; + ot->description = "Set a color tag for the selected strips"; + + /* Api callbacks. */ + ot->exec = sequencer_strip_color_tag_set_exec; + ot->poll = sequencer_edit_poll; + + /* Flags. */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_enum( + ot->srna, "color", rna_enum_strip_color_items, SEQUENCE_COLOR_NONE, "Color Tag", ""); +} + /** \} */ diff --git a/source/blender/editors/space_sequencer/sequencer_intern.h b/source/blender/editors/space_sequencer/sequencer_intern.h index 5b5c381509f..202eda85dca 100644 --- a/source/blender/editors/space_sequencer/sequencer_intern.h +++ b/source/blender/editors/space_sequencer/sequencer_intern.h @@ -51,7 +51,10 @@ void sequencer_draw_preview(const struct bContext *C, int offset, bool draw_overlay, bool draw_backdrop); -void color3ubv_from_seq(struct Scene *curscene, struct Sequence *seq, unsigned char col[3]); +void color3ubv_from_seq(const struct Scene *curscene, + const struct Sequence *seq, + const bool show_strip_color_tag, + uchar r_col[3]); void sequencer_special_update_set(Sequence *seq); float sequence_handle_size_get_clamped(struct Sequence *seq, const float pixelx); @@ -148,6 +151,8 @@ void SEQUENCER_OT_set_range_to_strips(struct wmOperatorType *ot); void SEQUENCER_OT_strip_transform_clear(struct wmOperatorType *ot); void SEQUENCER_OT_strip_transform_fit(struct wmOperatorType *ot); +void SEQUENCER_OT_strip_color_tag_set(struct wmOperatorType *ot); + /* sequencer_select.c */ void SEQUENCER_OT_select_all(struct wmOperatorType *ot); void SEQUENCER_OT_select(struct wmOperatorType *ot); diff --git a/source/blender/editors/space_sequencer/sequencer_ops.c b/source/blender/editors/space_sequencer/sequencer_ops.c index 48e6cfcdcd0..95f7b44264c 100644 --- a/source/blender/editors/space_sequencer/sequencer_ops.c +++ b/source/blender/editors/space_sequencer/sequencer_ops.c @@ -79,6 +79,8 @@ void sequencer_operatortypes(void) WM_operatortype_append(SEQUENCER_OT_strip_transform_clear); WM_operatortype_append(SEQUENCER_OT_strip_transform_fit); + WM_operatortype_append(SEQUENCER_OT_strip_color_tag_set); + /* sequencer_select.c */ WM_operatortype_append(SEQUENCER_OT_select_all); WM_operatortype_append(SEQUENCER_OT_select); diff --git a/source/blender/editors/space_sequencer/space_sequencer.c b/source/blender/editors/space_sequencer/space_sequencer.c index 99b75f82922..ad0ceb82709 100644 --- a/source/blender/editors/space_sequencer/space_sequencer.c +++ b/source/blender/editors/space_sequencer/space_sequencer.c @@ -104,25 +104,25 @@ static SpaceLink *sequencer_create(const ScrArea *UNUSED(area), const Scene *sce sseq->preview_overlay.flag = SEQ_PREVIEW_SHOW_GPENCIL | SEQ_PREVIEW_SHOW_OUTLINE_SELECTED; sseq->timeline_overlay.flag = SEQ_TIMELINE_SHOW_STRIP_NAME | SEQ_TIMELINE_SHOW_STRIP_SOURCE | SEQ_TIMELINE_SHOW_STRIP_DURATION | SEQ_TIMELINE_SHOW_GRID | - SEQ_TIMELINE_SHOW_FCURVES; + SEQ_TIMELINE_SHOW_FCURVES | SEQ_TIMELINE_SHOW_STRIP_COLOR_TAG; BLI_rctf_init(&sseq->runtime.last_thumbnail_area, 0.0f, 0.0f, 0.0f, 0.0f); sseq->runtime.last_displayed_thumbnails = NULL; - /* Tool header. */ - region = MEM_callocN(sizeof(ARegion), "tool header for sequencer"); + /* Header. */ + region = MEM_callocN(sizeof(ARegion), "header for sequencer"); BLI_addtail(&sseq->regionbase, region); - region->regiontype = RGN_TYPE_TOOL_HEADER; + region->regiontype = RGN_TYPE_HEADER; region->alignment = (U.uiflag & USER_HEADER_BOTTOM) ? RGN_ALIGN_BOTTOM : RGN_ALIGN_TOP; - region->flag = RGN_FLAG_HIDDEN | RGN_FLAG_HIDDEN_BY_USER; - /* Header. */ - region = MEM_callocN(sizeof(ARegion), "header for sequencer"); + /* Tool header. */ + region = MEM_callocN(sizeof(ARegion), "tool header for sequencer"); BLI_addtail(&sseq->regionbase, region); - region->regiontype = RGN_TYPE_HEADER; + region->regiontype = RGN_TYPE_TOOL_HEADER; region->alignment = (U.uiflag & USER_HEADER_BOTTOM) ? RGN_ALIGN_BOTTOM : RGN_ALIGN_TOP; + region->flag = RGN_FLAG_HIDDEN | RGN_FLAG_HIDDEN_BY_USER; /* Buttons/list view. */ region = MEM_callocN(sizeof(ARegion), "buttons for sequencer"); diff --git a/source/blender/editors/space_text/text_draw.c b/source/blender/editors/space_text/text_draw.c index 99fcb2092c3..b541b65d676 100644 --- a/source/blender/editors/space_text/text_draw.c +++ b/source/blender/editors/space_text/text_draw.c @@ -48,6 +48,9 @@ #include "text_format.h" #include "text_intern.h" +#include "WM_api.h" +#include "WM_types.h" + /******************** text font drawing ******************/ typedef struct TextDrawContext { @@ -1734,6 +1737,23 @@ void text_update_character_width(SpaceText *st) text_font_end(&tdc); } +bool ED_text_activate_in_screen(bContext *C, Text *text) +{ + ScrArea *area = BKE_screen_find_big_area(CTX_wm_screen(C), SPACE_TEXT, 0); + if (area) { + SpaceText *st = area->spacedata.first; + ARegion *region = BKE_area_find_region_type(area, RGN_TYPE_WINDOW); + st->text = text; + if (region) { + ED_text_scroll_to_cursor(st, region, true); + } + WM_event_add_notifier(C, NC_TEXT | ND_CURSOR, text); + return true; + } + + return false; +} + /* Moves the view to the cursor location, * also used to make sure the view isn't outside the file */ void ED_text_scroll_to_cursor(SpaceText *st, ARegion *region, const bool center) diff --git a/source/blender/editors/space_view3d/space_view3d.c b/source/blender/editors/space_view3d/space_view3d.c index cbc70fc7497..bedc24a6287 100644 --- a/source/blender/editors/space_view3d/space_view3d.c +++ b/source/blender/editors/space_view3d/space_view3d.c @@ -276,20 +276,20 @@ static SpaceLink *view3d_create(const ScrArea *UNUSED(area), const Scene *scene) v3d->camera = scene->camera; } - /* tool header */ - region = MEM_callocN(sizeof(ARegion), "tool header for view3d"); + /* header */ + region = MEM_callocN(sizeof(ARegion), "header for view3d"); BLI_addtail(&v3d->regionbase, region); - region->regiontype = RGN_TYPE_TOOL_HEADER; + region->regiontype = RGN_TYPE_HEADER; region->alignment = (U.uiflag & USER_HEADER_BOTTOM) ? RGN_ALIGN_BOTTOM : RGN_ALIGN_TOP; - region->flag = RGN_FLAG_HIDDEN | RGN_FLAG_HIDDEN_BY_USER; - /* header */ - region = MEM_callocN(sizeof(ARegion), "header for view3d"); + /* tool header */ + region = MEM_callocN(sizeof(ARegion), "tool header for view3d"); BLI_addtail(&v3d->regionbase, region); - region->regiontype = RGN_TYPE_HEADER; + region->regiontype = RGN_TYPE_TOOL_HEADER; region->alignment = (U.uiflag & USER_HEADER_BOTTOM) ? RGN_ALIGN_BOTTOM : RGN_ALIGN_TOP; + region->flag = RGN_FLAG_HIDDEN | RGN_FLAG_HIDDEN_BY_USER; /* tool shelf */ region = MEM_callocN(sizeof(ARegion), "toolshelf for view3d"); @@ -539,6 +539,11 @@ static char *view3d_mat_drop_tooltip(bContext *C, return ED_object_ot_drop_named_material_tooltip(C, drop->ptr, event); } +static bool view3d_world_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event) +{ + return view3d_drop_id_in_main_region_poll(C, drag, event, ID_WO); +} + static bool view3d_object_data_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event) { ID_Type id_type = view3d_drop_id_in_main_region_poll_get_id_type(C, drag, event); @@ -732,6 +737,12 @@ static void view3d_dropboxes(void) view3d_id_drop_copy_with_type, WM_drag_free_imported_drag_ID, view3d_object_data_drop_tooltip); + WM_dropbox_add(lb, + "VIEW3D_OT_drop_world", + view3d_world_drop_poll, + view3d_id_drop_copy, + WM_drag_free_imported_drag_ID, + NULL); } static void view3d_widgets(void) @@ -1555,11 +1566,16 @@ static void space_view3d_listener(const wmSpaceTypeListenerParams *params) switch (wmn->category) { case NC_SCENE: switch (wmn->data) { - case ND_WORLD: - if (v3d->flag2 & V3D_HIDE_OVERLAYS) { + case ND_WORLD: { + const bool use_scene_world = ((v3d->shading.type == OB_MATERIAL) && + (v3d->shading.flag & V3D_SHADING_SCENE_WORLD)) || + ((v3d->shading.type == OB_RENDER) && + (v3d->shading.flag & V3D_SHADING_SCENE_WORLD_RENDER)); + if (v3d->flag2 & V3D_HIDE_OVERLAYS || use_scene_world) { ED_area_tag_redraw_regiontype(area, RGN_TYPE_WINDOW); } break; + } } break; case NC_WORLD: diff --git a/source/blender/editors/space_view3d/view3d_edit.c b/source/blender/editors/space_view3d/view3d_edit.c index 8ed134c7fd1..15ccf5891d4 100644 --- a/source/blender/editors/space_view3d/view3d_edit.c +++ b/source/blender/editors/space_view3d/view3d_edit.c @@ -34,10 +34,12 @@ #include "DNA_gpencil_types.h" #include "DNA_object_types.h" #include "DNA_scene_types.h" +#include "DNA_world_types.h" #include "MEM_guardedalloc.h" #include "BLI_blenlib.h" +#include "BLI_dial_2d.h" #include "BLI_math.h" #include "BLI_utildefines.h" @@ -210,6 +212,9 @@ typedef struct ViewOpsData { * If we want the value before running the operator, add a separate member. */ char persp; + + /** Used for roll */ + Dial *dial; } init; /** Previous state (previous modal event handled). */ @@ -577,6 +582,10 @@ static void viewops_data_free(bContext *C, wmOperator *op) WM_event_remove_timer(CTX_wm_manager(C), vod->timer->win, vod->timer); } + if (vod->init.dial) { + MEM_freeN(vod->init.dial); + } + MEM_freeN(vod); op->customdata = NULL; } @@ -4352,18 +4361,9 @@ static void view_roll_angle( rv3d->view = RV3D_VIEW_USER; } -static void viewroll_apply(ViewOpsData *vod, int x, int UNUSED(y)) +static void viewroll_apply(ViewOpsData *vod, int x, int y) { - float angle = 0.0; - - { - float len1, len2, tot; - - tot = vod->region->winrct.xmax - vod->region->winrct.xmin; - len1 = (vod->region->winrct.xmax - x) / tot; - len2 = (vod->region->winrct.xmax - vod->init.event_xy[0]) / tot; - angle = (len1 - len2) * (float)M_PI * 4.0f; - } + float angle = BLI_dial_angle(vod->init.dial, (const float[2]){x, y}); if (angle != 0.0f) { view_roll_angle(vod->region, vod->rv3d->viewquat, vod->init.quat, vod->init.mousevec, angle); @@ -4409,6 +4409,13 @@ static int viewroll_modal(bContext *C, wmOperator *op, const wmEvent *event) break; } } + else if (ELEM(event->type, EVT_ESCKEY, RIGHTMOUSE)) { + /* Note this does not remove auto-keys on locked cameras. */ + copy_qt_qt(vod->rv3d->viewquat, vod->init.quat); + ED_view3d_camera_lock_sync(vod->depsgraph, vod->v3d, vod->rv3d); + viewops_data_free(C, op); + return OPERATOR_CANCELLED; + } else if (event->type == vod->init.event_type && event->val == KM_RELEASE) { event_code = VIEW_CONFIRM; } @@ -4517,6 +4524,9 @@ static int viewroll_invoke(bContext *C, wmOperator *op, const wmEvent *event) viewops_data_alloc(C, op); viewops_data_create(C, op, event, viewops_flag_from_prefs()); vod = op->customdata; + vod->init.dial = BLI_dial_init((const float[2]){BLI_rcti_cent_x(&vod->region->winrct), + BLI_rcti_cent_y(&vod->region->winrct)}, + FLT_EPSILON); ED_view3d_smooth_view_force_finish(C, vod->v3d, vod->region); @@ -4865,6 +4875,59 @@ void VIEW3D_OT_background_image_remove(wmOperatorType *ot) /** \} */ /* -------------------------------------------------------------------- */ +/** \name Drop World Operator + * \{ */ + +static int drop_world_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, "name", name); + World *world = (World *)BKE_libblock_find_name(bmain, ID_WO, name); + if (world == NULL) { + return OPERATOR_CANCELLED; + } + + id_us_plus(&world->id); + scene->world = world; + + DEG_id_tag_update(&scene->id, 0); + DEG_relations_tag_update(bmain); + + WM_event_add_notifier(C, NC_SCENE | ND_WORLD, scene); + + return OPERATOR_FINISHED; +} + +static bool drop_world_poll(bContext *C) +{ + return ED_operator_scene_editable(C); +} + +void VIEW3D_OT_drop_world(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Drop World"; + ot->description = "Drop a world into the scene"; + ot->idname = "VIEW3D_OT_drop_world"; + + /* api callbacks */ + ot->exec = drop_world_exec; + ot->poll = drop_world_poll; + + /* flags */ + ot->flag = OPTYPE_UNDO | OPTYPE_INTERNAL; + + /* properties */ + RNA_def_string(ot->srna, "name", "World", MAX_ID_NAME - 2, "Name", "World to assign"); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ /** \name View Clipping Planes Operator * * Draw border or toggle off. diff --git a/source/blender/editors/space_view3d/view3d_intern.h b/source/blender/editors/space_view3d/view3d_intern.h index ab80928e0c1..a21fc006b02 100644 --- a/source/blender/editors/space_view3d/view3d_intern.h +++ b/source/blender/editors/space_view3d/view3d_intern.h @@ -80,6 +80,7 @@ void VIEW3D_OT_view_persportho(struct wmOperatorType *ot); void VIEW3D_OT_navigate(struct wmOperatorType *ot); void VIEW3D_OT_background_image_add(struct wmOperatorType *ot); void VIEW3D_OT_background_image_remove(struct wmOperatorType *ot); +void VIEW3D_OT_drop_world(struct wmOperatorType *ot); void VIEW3D_OT_view_orbit(struct wmOperatorType *ot); void VIEW3D_OT_view_roll(struct wmOperatorType *ot); void VIEW3D_OT_clip_border(struct wmOperatorType *ot); diff --git a/source/blender/editors/space_view3d/view3d_ops.c b/source/blender/editors/space_view3d/view3d_ops.c index 56dedbbdbb2..eb8c043319c 100644 --- a/source/blender/editors/space_view3d/view3d_ops.c +++ b/source/blender/editors/space_view3d/view3d_ops.c @@ -169,6 +169,7 @@ void view3d_operatortypes(void) WM_operatortype_append(VIEW3D_OT_view_persportho); WM_operatortype_append(VIEW3D_OT_background_image_add); WM_operatortype_append(VIEW3D_OT_background_image_remove); + WM_operatortype_append(VIEW3D_OT_drop_world); WM_operatortype_append(VIEW3D_OT_view_selected); WM_operatortype_append(VIEW3D_OT_view_lock_clear); WM_operatortype_append(VIEW3D_OT_view_lock_to_active); diff --git a/source/blender/editors/space_view3d/view3d_select.c b/source/blender/editors/space_view3d/view3d_select.c index bca4f8b4857..ac5a06d22bb 100644 --- a/source/blender/editors/space_view3d/view3d_select.c +++ b/source/blender/editors/space_view3d/view3d_select.c @@ -2082,7 +2082,7 @@ static int mixed_bones_object_selectbuffer_extended(ViewContext *vc, /** * \param has_bones: When true, skip non-bone hits, also allow bases to be used * that are visible but not select-able, - * since you may be in pose mode with an unselect-able object. + * since you may be in pose mode with an un-selectable object. * * \return the active base or NULL. */ diff --git a/source/blender/editors/transform/transform.c b/source/blender/editors/transform/transform.c index e58e524e341..6ed2c28a7eb 100644 --- a/source/blender/editors/transform/transform.c +++ b/source/blender/editors/transform/transform.c @@ -28,6 +28,7 @@ #include "DNA_gpencil_types.h" #include "DNA_mask_types.h" #include "DNA_mesh_types.h" +#include "DNA_screen_types.h" #include "BLI_math.h" #include "BLI_rect.h" @@ -1609,8 +1610,16 @@ static void initSnapSpatial(TransInfo *t, float r_snap[2]) } } else if (t->spacetype == SPACE_IMAGE) { - r_snap[0] = 0.0625f; - r_snap[1] = 0.03125f; + SpaceImage *sima = t->area->spacedata.first; + View2D *v2d = &t->region->v2d; + int grid_size = SI_GRID_STEPS_LEN; + float zoom_factor = ED_space_image_zoom_level(v2d, grid_size); + float grid_steps[SI_GRID_STEPS_LEN]; + + ED_space_image_grid_steps(sima, grid_steps, grid_size); + /* Snapping value based on what type of grid is used (adaptive-subdividing or custom-grid). */ + r_snap[0] = ED_space_image_increment_snap_value(grid_size, grid_steps, zoom_factor); + r_snap[1] = r_snap[0] / 2.0f; } else if (t->spacetype == SPACE_CLIP) { r_snap[0] = 0.125f; diff --git a/source/blender/editors/transform/transform_convert_armature.c b/source/blender/editors/transform/transform_convert_armature.c index d3e0f55b127..d19ff123037 100644 --- a/source/blender/editors/transform/transform_convert_armature.c +++ b/source/blender/editors/transform/transform_convert_armature.c @@ -1496,8 +1496,10 @@ static void bone_children_clear_transflag(int mode, short around, ListBase *lb) } } -/* Sets transform flags in the bones. - * Returns total number of bones with `BONE_TRANSFORM`. */ +/** + * Sets transform flags in the bones. + * Returns total number of bones with #BONE_TRANSFORM. + */ int transform_convert_pose_transflags_update(Object *ob, const int mode, const short around, @@ -1730,7 +1732,7 @@ void special_aftertrans_update__pose(bContext *C, TransInfo *t) BKE_pose_where_is(t->depsgraph, t->scene, pose_ob); } - /* set BONE_TRANSFORM flags for autokey, gizmo draw might have changed them */ + /* Set BONE_TRANSFORM flags for auto-key, gizmo draw might have changed them. */ if (!canceled && (t->mode != TFM_DUMMY)) { transform_convert_pose_transflags_update(ob, t->mode, t->around, NULL); } diff --git a/source/blender/editors/transform/transform_convert_nla.c b/source/blender/editors/transform/transform_convert_nla.c index 7e5b80c2453..acef8a666e3 100644 --- a/source/blender/editors/transform/transform_convert_nla.c +++ b/source/blender/editors/transform/transform_convert_nla.c @@ -208,30 +208,18 @@ void createTransNlaData(bContext *C, TransInfo *t) /* just set tdn to assume that it only has one handle for now */ tdn->handle = -1; - /* now, link the transform data up to this data */ - if (ELEM(t->mode, TFM_TRANSLATION, TFM_TIME_EXTEND)) { - td->loc = tdn->h1; - copy_v3_v3(td->iloc, tdn->h1); + /* Now, link the transform data up to this data. */ + td->loc = tdn->h1; + copy_v3_v3(td->iloc, tdn->h1); - /* store all the other gunk that is required by transform */ + if (ELEM(t->mode, TFM_TRANSLATION, TFM_TIME_EXTEND)) { + /* Store all the other gunk that is required by transform. */ copy_v3_v3(td->center, center); - memset(td->axismtx, 0, sizeof(td->axismtx)); td->axismtx[2][2] = 1.0f; - - td->ext = NULL; - td->val = NULL; - td->flag |= TD_SELECTED; - td->dist = 0.0f; - unit_m3(td->mtx); unit_m3(td->smtx); } - else { - /* time scaling only needs single value */ - td->val = &tdn->h1[0]; - td->ival = tdn->h1[0]; - } td->extra = tdn; td++; @@ -241,30 +229,18 @@ void createTransNlaData(bContext *C, TransInfo *t) * then we're doing both, otherwise, only end */ tdn->handle = (tdn->handle) ? 2 : 1; - /* now, link the transform data up to this data */ - if (ELEM(t->mode, TFM_TRANSLATION, TFM_TIME_EXTEND)) { - td->loc = tdn->h2; - copy_v3_v3(td->iloc, tdn->h2); + /* Now, link the transform data up to this data. */ + td->loc = tdn->h2; + copy_v3_v3(td->iloc, tdn->h2); - /* store all the other gunk that is required by transform */ + if (ELEM(t->mode, TFM_TRANSLATION, TFM_TIME_EXTEND)) { + /* Store all the other gunk that is required by transform. */ copy_v3_v3(td->center, center); - memset(td->axismtx, 0, sizeof(td->axismtx)); td->axismtx[2][2] = 1.0f; - - td->ext = NULL; - td->val = NULL; - td->flag |= TD_SELECTED; - td->dist = 0.0f; - unit_m3(td->mtx); unit_m3(td->smtx); } - else { - /* time scaling only needs single value */ - td->val = &tdn->h2[0]; - td->ival = tdn->h2[0]; - } td->extra = tdn; td++; diff --git a/source/blender/editors/transform/transform_convert_object.c b/source/blender/editors/transform/transform_convert_object.c index ad22b0fc444..09aa4314b32 100644 --- a/source/blender/editors/transform/transform_convert_object.c +++ b/source/blender/editors/transform/transform_convert_object.c @@ -958,25 +958,25 @@ void special_aftertrans_update__object(bContext *C, TransInfo *t) } BLI_freelistN(&pidlist); - /* pointcache refresh */ + /* Point-cache refresh. */ if (BKE_ptcache_object_reset(t->scene, ob, PTCACHE_RESET_OUTDATED)) { DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); } - /* Needed for proper updating of "quick cached" dynamics. */ - /* Creates troubles for moving animated objects without */ - /* autokey though, probably needed is an anim sys override? */ - /* Please remove if some other solution is found. -jahka */ + /* Needed for proper updating of "quick cached" dynamics. + * Creates troubles for moving animated objects without + * auto-key though, probably needed is an animation-system override? + * NOTE(@jahka): Please remove if some other solution is found. */ DEG_id_tag_update(&ob->id, ID_RECALC_TRANSFORM); - /* Set autokey if necessary */ + /* Set auto-key if necessary. */ if (!canceled) { ED_transform_autokeyframe_object(C, t->scene, t->view_layer, ob, t->mode); } motionpath_update |= ED_transform_motionpath_need_update_object(t->scene, ob); - /* restore rigid body transform */ + /* Restore rigid body transform. */ if (ob->rigidbody_object && canceled) { float ctime = BKE_scene_ctime_get(t->scene); if (BKE_rigidbody_check_sim_running(t->scene->rigidbody_world, ctime)) { diff --git a/source/blender/editors/transform/transform_convert_sequencer_image.c b/source/blender/editors/transform/transform_convert_sequencer_image.c index 5db9a2e092f..6e3f12de472 100644 --- a/source/blender/editors/transform/transform_convert_sequencer_image.c +++ b/source/blender/editors/transform/transform_convert_sequencer_image.c @@ -113,12 +113,17 @@ static void freeSeqData(TransInfo *UNUSED(t), void createTransSeqImageData(TransInfo *t) { Editing *ed = SEQ_editing_get(t->scene); + + if (ed == NULL) { + return; + } + ListBase *seqbase = SEQ_active_seqbase_get(ed); SeqCollection *strips = SEQ_query_rendered_strips(seqbase, t->scene->r.cfra, 0); SEQ_filter_selected_strips(strips); const int count = SEQ_collection_len(strips); - if (ed == NULL || count == 0) { + if (count == 0) { SEQ_collection_free(strips); return; } diff --git a/source/blender/editors/transform/transform_gizmo_2d.c b/source/blender/editors/transform/transform_gizmo_2d.c index 0d66db0d7e1..4f6556cd2a2 100644 --- a/source/blender/editors/transform/transform_gizmo_2d.c +++ b/source/blender/editors/transform/transform_gizmo_2d.c @@ -32,6 +32,7 @@ #include "DNA_view3d_types.h" #include "BKE_context.h" +#include "BKE_global.h" #include "BKE_layer.h" #include "RNA_access.h" @@ -70,6 +71,10 @@ static bool gizmo2d_generic_poll(const bContext *C, wmGizmoGroupType *gzgt) return false; } + if (G.moving) { + return false; + } + ScrArea *area = CTX_wm_area(C); switch (area->spacetype) { case SPACE_IMAGE: { diff --git a/source/blender/editors/transform/transform_mode_timescale.c b/source/blender/editors/transform/transform_mode_timescale.c index 50fd714727b..0a7ae54982e 100644 --- a/source/blender/editors/transform/transform_mode_timescale.c +++ b/source/blender/editors/transform/transform_mode_timescale.c @@ -87,7 +87,7 @@ static void applyTimeScaleValue(TransInfo *t, float value) } /* now, calculate the new value */ - *(td->val) = ((td->ival - startx) * fac) + startx; + td->loc[0] = ((td->iloc[0] - startx) * fac) + startx; } } } diff --git a/source/blender/editors/transform/transform_snap.c b/source/blender/editors/transform/transform_snap.c index 05a20a14477..39a70f5477e 100644 --- a/source/blender/editors/transform/transform_snap.c +++ b/source/blender/editors/transform/transform_snap.c @@ -590,6 +590,11 @@ static void initSnappingMode(TransInfo *t) t->tsnap.project = 0; t->tsnap.mode = ts->snap_uv_mode; + if ((t->tsnap.mode & SCE_SNAP_MODE_INCREMENT) && (ts->snap_uv_flag & SCE_SNAP_ABS_GRID) && + (t->mode == TFM_TRANSLATION)) { + t->tsnap.mode &= ~SCE_SNAP_MODE_INCREMENT; + t->tsnap.mode |= SCE_SNAP_MODE_GRID; + } } else if (t->spacetype == SPACE_SEQ) { t->tsnap.mode = SEQ_tool_settings_snap_mode_get(t->scene); @@ -1502,7 +1507,8 @@ bool transform_snap_grid(TransInfo *t, float *val) return false; } - if (t->spacetype != SPACE_VIEW3D) { + /* Don't do grid snapping if not in 3D viewport or UV editor */ + if (!ELEM(t->spacetype, SPACE_VIEW3D, SPACE_IMAGE)) { return false; } diff --git a/source/blender/editors/transform/transform_snap_object.c b/source/blender/editors/transform/transform_snap_object.c index 891919fd46c..70297fad4ff 100644 --- a/source/blender/editors/transform/transform_snap_object.c +++ b/source/blender/editors/transform/transform_snap_object.c @@ -734,7 +734,7 @@ static bool raycastMesh(SnapObjectContext *sctx, } /* Test BoundBox */ - BoundBox *bb = BKE_mesh_boundbox_get(ob_eval); + BoundBox *bb = BKE_object_boundbox_get(ob_eval); if (bb) { /* was BKE_boundbox_ray_hit_check, see: cf6ca226fa58 */ if (!isect_ray_aabb_v3_simple( diff --git a/source/blender/editors/util/CMakeLists.txt b/source/blender/editors/util/CMakeLists.txt index b396e348845..b339bfbdc47 100644 --- a/source/blender/editors/util/CMakeLists.txt +++ b/source/blender/editors/util/CMakeLists.txt @@ -103,8 +103,10 @@ set(SRC ../include/ED_view3d_offscreen.h ../include/UI_icons.h ../include/UI_interface.h + ../include/UI_interface.hh ../include/UI_interface_icons.h ../include/UI_resources.h + ../include/UI_tree_view.hh ../include/UI_view2d.h ) diff --git a/source/blender/editors/uvedit/uvedit_islands.c b/source/blender/editors/uvedit/uvedit_islands.c index 56bcbc63de1..6159758dbcd 100644 --- a/source/blender/editors/uvedit/uvedit_islands.c +++ b/source/blender/editors/uvedit/uvedit_islands.c @@ -29,6 +29,7 @@ #include "DNA_meshdata_types.h" #include "DNA_scene_types.h" +#include "DNA_space_types.h" #include "BLI_boxpack_2d.h" #include "BLI_convexhull_2d.h" @@ -38,6 +39,7 @@ #include "BKE_customdata.h" #include "BKE_editmesh.h" +#include "BKE_image.h" #include "DEG_depsgraph.h" @@ -232,6 +234,101 @@ static void bm_face_array_uv_scale_y(BMFace **faces, /** \} */ /* -------------------------------------------------------------------- */ +/** \name UDIM packing helper functions + * \{ */ + +/** + * Returns true if UV coordinates lie on a valid tile in UDIM grid or tiled image. + */ +bool uv_coords_isect_udim(const Image *image, const int udim_grid[2], const float coords[2]) +{ + const float coords_floor[2] = {floorf(coords[0]), floorf(coords[1])}; + const bool is_tiled_image = image && (image->source == IMA_SRC_TILED); + + if (coords[0] < udim_grid[0] && coords[0] > 0 && coords[1] < udim_grid[1] && coords[1] > 0) { + return true; + } + /* Check if selection lies on a valid UDIM image tile. */ + if (is_tiled_image) { + LISTBASE_FOREACH (const ImageTile *, tile, &image->tiles) { + const int tile_index = tile->tile_number - 1001; + const int target_x = (tile_index % 10); + const int target_y = (tile_index / 10); + if (coords_floor[0] == target_x && coords_floor[1] == target_y) { + return true; + } + } + } + /* Probably not required since UDIM grid checks for 1001. */ + else if (image && !is_tiled_image) { + if (is_zero_v2(coords_floor)) { + return true; + } + } + + return false; +} + +/** + * Calculates distance to nearest UDIM image tile in UV space and its UDIM tile number. + */ +static float uv_nearest_image_tile_distance(const Image *image, + float coords[2], + float nearest_tile_co[2]) +{ + int nearest_image_tile_index = BKE_image_find_nearest_tile(image, coords); + if (nearest_image_tile_index == -1) { + nearest_image_tile_index = 1001; + } + + nearest_tile_co[0] = (nearest_image_tile_index - 1001) % 10; + nearest_tile_co[1] = (nearest_image_tile_index - 1001) / 10; + /* Add 0.5 to get tile center coordinates. */ + float nearest_tile_center_co[2] = {nearest_tile_co[0], nearest_tile_co[1]}; + add_v2_fl(nearest_tile_center_co, 0.5f); + + return len_squared_v2v2(coords, nearest_tile_center_co); +} + +/** + * Calculates distance to nearest UDIM grid tile in UV space and its UDIM tile number. + */ +static float uv_nearest_grid_tile_distance(const int udim_grid[2], + float coords[2], + float nearest_tile_co[2]) +{ + const float coords_floor[2] = {floorf(coords[0]), floorf(coords[1])}; + + if (coords[0] > udim_grid[0]) { + nearest_tile_co[0] = udim_grid[0] - 1; + } + else if (coords[0] < 0) { + nearest_tile_co[0] = 0; + } + else { + nearest_tile_co[0] = coords_floor[0]; + } + + if (coords[1] > udim_grid[1]) { + nearest_tile_co[1] = udim_grid[1] - 1; + } + else if (coords[1] < 0) { + nearest_tile_co[1] = 0; + } + else { + nearest_tile_co[1] = coords_floor[1]; + } + + /* Add 0.5 to get tile center coordinates. */ + float nearest_tile_center_co[2] = {nearest_tile_co[0], nearest_tile_co[1]}; + add_v2_fl(nearest_tile_center_co, 0.5f); + + return len_squared_v2v2(coords, nearest_tile_center_co); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ /** \name Calculate UV Islands * * \note Currently this is a private API/type, it could be made public. @@ -359,6 +456,7 @@ static int bm_mesh_calc_uv_islands(const Scene *scene, void ED_uvedit_pack_islands_multi(const Scene *scene, Object **objects, const uint objects_len, + const struct UVMapUDIM_Params *udim_params, const struct UVPackIsland_Params *params) { /* Align to the Y axis, could make this configurable. */ @@ -407,8 +505,27 @@ void ED_uvedit_pack_islands_multi(const Scene *scene, BoxPack *boxarray = MEM_mallocN(sizeof(*boxarray) * island_list_len, __func__); int index; + /* Coordinates of bounding box containing all selected UVs. */ + float selection_min_co[2], selection_max_co[2]; + INIT_MINMAX2(selection_min_co, selection_max_co); + LISTBASE_FOREACH_INDEX (struct FaceIsland *, island, &island_list, index) { + /* Skip calculation if using specified UDIM option. */ + if (udim_params && (udim_params->use_target_udim == false)) { + float bounds_min[2], bounds_max[2]; + INIT_MINMAX2(bounds_min, bounds_max); + for (int i = 0; i < island->faces_len; i++) { + BMFace *f = island->faces[i]; + BM_face_uv_minmax(f, bounds_min, bounds_max, island->cd_loop_uv_offset); + } + + selection_min_co[0] = MIN2(bounds_min[0], selection_min_co[0]); + selection_min_co[1] = MIN2(bounds_min[1], selection_min_co[1]); + selection_max_co[0] = MAX2(bounds_max[0], selection_max_co[0]); + selection_max_co[1] = MAX2(bounds_max[1], selection_max_co[1]); + } + if (params->rotate) { if (island->aspect_y != 1.0f) { bm_face_array_uv_scale_y( @@ -441,6 +558,13 @@ void ED_uvedit_pack_islands_multi(const Scene *scene, } } + /* Center of bounding box containing all selected UVs. */ + float selection_center[2]; + if (udim_params && (udim_params->use_target_udim == false)) { + selection_center[0] = (selection_min_co[0] + selection_max_co[0]) / 2.0f; + selection_center[1] = (selection_min_co[1] + selection_max_co[1]) / 2.0f; + } + if (margin > 0.0f) { /* Logic matches behavior from #param_pack, * use area so multiply the margin by the area to give @@ -464,6 +588,53 @@ void ED_uvedit_pack_islands_multi(const Scene *scene, const float scale[2] = {1.0f / boxarray_size[0], 1.0f / boxarray_size[1]}; + /* Tile offset. */ + float base_offset[2] = {0.0f, 0.0f}; + + /* CASE: ignore UDIM. */ + if (udim_params == NULL) { + /* pass */ + } + /* CASE: Active/specified(smart uv project) UDIM. */ + else if (udim_params->use_target_udim) { + + /* Calculate offset based on specified_tile_index. */ + base_offset[0] = (udim_params->target_udim - 1001) % 10; + base_offset[1] = (udim_params->target_udim - 1001) / 10; + } + + /* CASE: Closest UDIM. */ + else { + const Image *image = udim_params->image; + const int *udim_grid = udim_params->grid_shape; + /* Check if selection lies on a valid UDIM grid tile. */ + bool is_valid_udim = uv_coords_isect_udim(image, udim_grid, selection_center); + if (is_valid_udim) { + base_offset[0] = floorf(selection_center[0]); + base_offset[1] = floorf(selection_center[1]); + } + /* If selection doesn't lie on any UDIM then find the closest UDIM grid or image tile. */ + else { + float nearest_image_tile_co[2] = {FLT_MAX, FLT_MAX}; + float nearest_image_tile_dist = FLT_MAX, nearest_grid_tile_dist = FLT_MAX; + if (image) { + nearest_image_tile_dist = uv_nearest_image_tile_distance( + image, selection_center, nearest_image_tile_co); + } + + float nearest_grid_tile_co[2] = {0.0f, 0.0f}; + nearest_grid_tile_dist = uv_nearest_grid_tile_distance( + udim_grid, selection_center, nearest_grid_tile_co); + + base_offset[0] = (nearest_image_tile_dist < nearest_grid_tile_dist) ? + nearest_image_tile_co[0] : + nearest_grid_tile_co[0]; + base_offset[1] = (nearest_image_tile_dist < nearest_grid_tile_dist) ? + nearest_image_tile_co[1] : + nearest_grid_tile_co[1]; + } + } + for (int i = 0; i < island_list_len; i++) { struct FaceIsland *island = island_array[boxarray[i].index]; const float pivot[2] = { @@ -471,8 +642,8 @@ void ED_uvedit_pack_islands_multi(const Scene *scene, island->bounds_rect.ymin, }; const float offset[2] = { - (boxarray[i].x * scale[0]) - island->bounds_rect.xmin, - (boxarray[i].y * scale[1]) - island->bounds_rect.ymin, + ((boxarray[i].x * scale[0]) - island->bounds_rect.xmin) + base_offset[0], + ((boxarray[i].y * scale[1]) - island->bounds_rect.ymin) + base_offset[1], }; for (int j = 0; j < island->faces_len; j++) { BMFace *efa = island->faces[j]; diff --git a/source/blender/editors/uvedit/uvedit_unwrap_ops.c b/source/blender/editors/uvedit/uvedit_unwrap_ops.c index 3d5dabda23d..38233b55d15 100644 --- a/source/blender/editors/uvedit/uvedit_unwrap_ops.c +++ b/source/blender/editors/uvedit/uvedit_unwrap_ops.c @@ -37,6 +37,7 @@ #include "BLI_alloca.h" #include "BLI_array.h" #include "BLI_linklist.h" +#include "BLI_listbase.h" #include "BLI_math.h" #include "BLI_memarena.h" #include "BLI_string.h" @@ -64,6 +65,7 @@ #include "PIL_time.h" #include "UI_interface.h" +#include "UI_view2d.h" #include "ED_image.h" #include "ED_mesh.h" @@ -143,6 +145,61 @@ static bool ED_uvedit_ensure_uvs(Object *obedit) /** \} */ /* -------------------------------------------------------------------- */ +/** \name UDIM Access + * \{ */ + +bool ED_uvedit_udim_params_from_image_space(const SpaceImage *sima, + bool use_active, + struct UVMapUDIM_Params *udim_params) +{ + memset(udim_params, 0, sizeof(*udim_params)); + + udim_params->grid_shape[0] = 1; + udim_params->grid_shape[1] = 1; + udim_params->target_udim = 0; + udim_params->use_target_udim = false; + + if (sima == NULL) { + return false; + } + + udim_params->image = sima->image; + udim_params->grid_shape[0] = sima->tile_grid_shape[0]; + udim_params->grid_shape[1] = sima->tile_grid_shape[1]; + + if (use_active) { + int active_udim = 1001; + /* NOTE: Presently, when UDIM grid and tiled image are present together, only active tile for + * the tiled image is considered. */ + Image *image = sima->image; + if (image && image->source == IMA_SRC_TILED) { + ImageTile *active_tile = BLI_findlink(&image->tiles, image->active_tile_index); + if (active_tile) { + active_udim = active_tile->tile_number; + } + } + else { + /* TODO: Support storing an active UDIM when there are no tiles present. + * Until then, use 2D cursor to find the active tile index for the UDIM grid. */ + const float cursor_loc[2] = {sima->cursor[0], sima->cursor[1]}; + if (uv_coords_isect_udim(sima->image, sima->tile_grid_shape, cursor_loc)) { + int tile_number = 1001; + tile_number += floorf(cursor_loc[1]) * 10; + tile_number += floorf(cursor_loc[0]); + active_udim = tile_number; + } + } + + udim_params->target_udim = active_udim; + udim_params->use_target_udim = true; + } + + return true; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ /** \name Parametrizer Conversion * \{ */ @@ -1005,10 +1062,17 @@ static void uvedit_pack_islands_multi(const Scene *scene, } } +/* Packing targets. */ +enum { + PACK_UDIM_SRC_CLOSEST = 0, + PACK_UDIM_SRC_ACTIVE = 1, +}; + static int pack_islands_exec(bContext *C, wmOperator *op) { ViewLayer *view_layer = CTX_data_view_layer(C); const Scene *scene = CTX_data_scene(C); + const SpaceImage *sima = CTX_wm_space_image(C); const UnwrapOptions options = { .topology_from_uvs = true, @@ -1018,17 +1082,19 @@ static int pack_islands_exec(bContext *C, wmOperator *op) .correct_aspect = true, }; - bool rotate = RNA_boolean_get(op->ptr, "rotate"); - uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( view_layer, CTX_wm_view3d(C), &objects_len); + /* Early exit in case no UVs are selected. */ if (!uvedit_have_selection_multi(scene, objects, objects_len, &options)) { MEM_freeN(objects); return OPERATOR_CANCELLED; } + /* RNA props */ + const bool rotate = RNA_boolean_get(op->ptr, "rotate"); + const int udim_source = RNA_enum_get(op->ptr, "udim_source"); if (RNA_struct_property_is_set(op->ptr, "margin")) { scene->toolsettings->uvcalc_margin = RNA_float_get(op->ptr, "margin"); } @@ -1036,9 +1102,15 @@ static int pack_islands_exec(bContext *C, wmOperator *op) RNA_float_set(op->ptr, "margin", scene->toolsettings->uvcalc_margin); } + struct UVMapUDIM_Params udim_params; + const bool use_active = (udim_source == PACK_UDIM_SRC_ACTIVE); + const bool use_udim_params = ED_uvedit_udim_params_from_image_space( + sima, use_active, &udim_params); + ED_uvedit_pack_islands_multi(scene, objects, objects_len, + use_udim_params ? &udim_params : NULL, &(struct UVPackIsland_Params){ .rotate = rotate, .rotate_align_axis = -1, @@ -1048,16 +1120,25 @@ static int pack_islands_exec(bContext *C, wmOperator *op) }); MEM_freeN(objects); - return OPERATOR_FINISHED; } void UV_OT_pack_islands(wmOperatorType *ot) { + static const EnumPropertyItem pack_target[] = { + {PACK_UDIM_SRC_CLOSEST, "CLOSEST_UDIM", 0, "Closest UDIM", "Pack islands to closest UDIM"}, + {PACK_UDIM_SRC_ACTIVE, + "ACTIVE_UDIM", + 0, + "Active UDIM", + "Pack islands to active UDIM image tile or UDIM grid tile where 2D cursor is located"}, + {0, NULL, 0, NULL, NULL}, + }; /* identifiers */ ot->name = "Pack Islands"; ot->idname = "UV_OT_pack_islands"; - ot->description = "Transform all islands so that they fill up the UV space as much as possible"; + ot->description = + "Transform all islands so that they fill up the UV/UDIM space as much as possible"; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; @@ -1066,6 +1147,7 @@ void UV_OT_pack_islands(wmOperatorType *ot) ot->poll = ED_operator_uvedit; /* properties */ + RNA_def_enum(ot->srna, "udim_source", pack_target, PACK_UDIM_SRC_CLOSEST, "Pack to", ""); RNA_def_boolean(ot->srna, "rotate", true, "Rotate", "Rotate islands for best fit"); RNA_def_float_factor( ot->srna, "margin", 0.001f, 0.0f, 1.0f, "Margin", "Space between islands", 0.0f, 1.0f); @@ -2206,6 +2288,7 @@ static int smart_project_exec(bContext *C, wmOperator *op) ED_uvedit_pack_islands_multi(scene, objects_changed, object_changed_len, + NULL, &(struct UVPackIsland_Params){ .rotate = true, /* We could make this optional. */ diff --git a/source/blender/freestyle/intern/blender_interface/FRS_freestyle.cpp b/source/blender/freestyle/intern/blender_interface/FRS_freestyle.cpp index c74fd60fe35..405deaf00b0 100644 --- a/source/blender/freestyle/intern/blender_interface/FRS_freestyle.cpp +++ b/source/blender/freestyle/intern/blender_interface/FRS_freestyle.cpp @@ -494,7 +494,7 @@ void FRS_composite_result(Render *re, ViewLayer *view_layer, Render *freestyle_r if (view_layer->freestyle_config.flags & FREESTYLE_AS_RENDER_PASS) { // Create a blank render pass output. RE_create_render_pass( - re->result, RE_PASSNAME_FREESTYLE, 4, "RGBA", view_layer->name, re->viewname); + re->result, RE_PASSNAME_FREESTYLE, 4, "RGBA", view_layer->name, re->viewname, true); } return; } @@ -530,7 +530,7 @@ void FRS_composite_result(Render *re, ViewLayer *view_layer, Render *freestyle_r if (view_layer->freestyle_config.flags & FREESTYLE_AS_RENDER_PASS) { RE_create_render_pass( - re->result, RE_PASSNAME_FREESTYLE, 4, "RGBA", view_layer->name, re->viewname); + re->result, RE_PASSNAME_FREESTYLE, 4, "RGBA", view_layer->name, re->viewname, true); dest = RE_RenderLayerGetPass(rl, RE_PASSNAME_FREESTYLE, re->viewname); } else { diff --git a/source/blender/freestyle/intern/python/StrokeShader/BPy_SmoothingShader.cpp b/source/blender/freestyle/intern/python/StrokeShader/BPy_SmoothingShader.cpp index ab39b9ad883..bcb7af0e5a7 100644 --- a/source/blender/freestyle/intern/python/StrokeShader/BPy_SmoothingShader.cpp +++ b/source/blender/freestyle/intern/python/StrokeShader/BPy_SmoothingShader.cpp @@ -63,7 +63,7 @@ static char SmoothingShader___doc__[] = "\n" ".. method:: shade(stroke)\n" "\n" - " Smoothes the stroke by moving the vertices to make the stroke\n" + " Smooths the stroke by moving the vertices to make the stroke\n" " smoother. Uses curvature flow to converge towards a curve of\n" " constant curvature. The diffusion method we use is anisotropic to\n" " prevent the diffusion across corners.\n" diff --git a/source/blender/functions/CMakeLists.txt b/source/blender/functions/CMakeLists.txt index 856668f01d7..309b92f1cb4 100644 --- a/source/blender/functions/CMakeLists.txt +++ b/source/blender/functions/CMakeLists.txt @@ -53,9 +53,9 @@ set(SRC FN_multi_function_builder.hh FN_multi_function_context.hh FN_multi_function_data_type.hh + FN_multi_function_parallel.hh FN_multi_function_param_type.hh FN_multi_function_params.hh - FN_multi_function_parallel.hh FN_multi_function_procedure.hh FN_multi_function_procedure_builder.hh FN_multi_function_procedure_executor.hh diff --git a/source/blender/functions/FN_field.hh b/source/blender/functions/FN_field.hh index d4375b625ce..3ce0993da59 100644 --- a/source/blender/functions/FN_field.hh +++ b/source/blender/functions/FN_field.hh @@ -484,4 +484,13 @@ template<typename T> Field<T> make_constant_field(T value) GField make_field_constant_if_possible(GField field); +class IndexFieldInput final : public FieldInput { + public: + IndexFieldInput(); + + const GVArray *get_varray_for_context(const FieldContext &context, + IndexMask mask, + ResourceScope &scope) const final; +}; + } // namespace blender::fn diff --git a/source/blender/functions/intern/field.cc b/source/blender/functions/intern/field.cc index 599e4d4595a..39688ef3daf 100644 --- a/source/blender/functions/intern/field.cc +++ b/source/blender/functions/intern/field.cc @@ -261,20 +261,6 @@ static void build_multi_function_procedure_for_fields(MFProcedure &procedure, } /** - * Utility class that destructs elements from a partially initialized array. - */ -struct PartiallyInitializedArray : NonCopyable, NonMovable { - void *buffer; - IndexMask mask; - const CPPType *type; - - ~PartiallyInitializedArray() - { - this->type->destruct_indices(this->buffer, this->mask); - } -}; - -/** * Evaluate fields in the given context. If possible, multiple fields should be evaluated together, * because that can be more efficient when they share common sub-fields. * @@ -387,11 +373,11 @@ Vector<const GVArray *> evaluate_fields(ResourceScope &scope, /* Allocate a new buffer for the computed result. */ buffer = scope.linear_allocator().allocate(type.size() * array_size, type.alignment()); - /* Make sure that elements in the buffer will be destructed. */ - PartiallyInitializedArray &destruct_helper = scope.construct<PartiallyInitializedArray>(); - destruct_helper.buffer = buffer; - destruct_helper.mask = mask; - destruct_helper.type = &type; + if (!type.is_trivially_destructible()) { + /* Destruct values in the end. */ + scope.add_destruct_call( + [buffer, mask, &type]() { type.destruct_indices(buffer, mask); }); + } r_varrays[out_index] = &scope.construct<GVArray_For_GSpan>( GSpan{type, buffer, array_size}); @@ -418,7 +404,10 @@ Vector<const GVArray *> evaluate_fields(ResourceScope &scope, build_multi_function_procedure_for_fields( procedure, scope, field_tree_info, constant_fields_to_evaluate); MFProcedureExecutor procedure_executor{"Procedure", procedure}; - MFParamsBuilder mf_params{procedure_executor, 1}; + /* Run the code below even when the mask is empty, so that outputs are properly prepared. + * Higher level code can detect this as well and just skip evaluating the field. */ + const int mask_size = mask.is_empty() ? 0 : 1; + MFParamsBuilder mf_params{procedure_executor, mask_size}; MFContextBuilder mf_context; /* Provide inputs to the procedure executor. */ @@ -432,14 +421,14 @@ Vector<const GVArray *> evaluate_fields(ResourceScope &scope, /* Allocate memory where the computed value will be stored in. */ void *buffer = scope.linear_allocator().allocate(type.size(), type.alignment()); - /* Use this to make sure that the value is destructed in the end. */ - PartiallyInitializedArray &destruct_helper = scope.construct<PartiallyInitializedArray>(); - destruct_helper.buffer = buffer; - destruct_helper.mask = IndexRange(1); - destruct_helper.type = &type; + if (!type.is_trivially_destructible() && mask_size > 0) { + BLI_assert(mask_size == 1); + /* Destruct value in the end. */ + scope.add_destruct_call([buffer, &type]() { type.destruct(buffer); }); + } /* Pass output buffer to the procedure executor. */ - mf_params.add_uninitialized_single_output({type, buffer, 1}); + mf_params.add_uninitialized_single_output({type, buffer, mask_size}); /* Create virtual array that can be used after the procedure has been executed below. */ const int out_index = constant_field_indices[i]; @@ -447,7 +436,7 @@ Vector<const GVArray *> evaluate_fields(ResourceScope &scope, type, array_size, buffer); } - procedure_executor.call(IndexRange(1), mf_params, mf_context); + procedure_executor.call(IndexRange(mask_size), mf_params, mf_context); } /* Copy data to supplied destination arrays if necessary. In some cases the evaluation above has @@ -526,6 +515,21 @@ const GVArray *FieldContext::get_varray_for_input(const FieldInput &field_input, return field_input.get_varray_for_context(*this, mask, scope); } +IndexFieldInput::IndexFieldInput() : FieldInput(CPPType::get<int>(), "Index") +{ +} + +const GVArray *IndexFieldInput::get_varray_for_context(const fn::FieldContext &UNUSED(context), + IndexMask mask, + ResourceScope &scope) const +{ + /* TODO: Investigate a similar method to IndexRange::as_span() */ + auto index_func = [](int i) { return i; }; + return &scope.construct< + fn::GVArray_For_EmbeddedVArray<int, VArray_For_Func<int, decltype(index_func)>>>( + mask.min_array_size(), mask.min_array_size(), index_func); +} + /* -------------------------------------------------------------------- * FieldOperation. */ diff --git a/source/blender/functions/intern/multi_function_procedure_executor.cc b/source/blender/functions/intern/multi_function_procedure_executor.cc index b97282accdd..6d2d121bafd 100644 --- a/source/blender/functions/intern/multi_function_procedure_executor.cc +++ b/source/blender/functions/intern/multi_function_procedure_executor.cc @@ -1022,7 +1022,13 @@ static void execute_call_instruction(const MFCallInstruction &instruction, } } - fn.call(IndexRange(1), params, context); + try { + fn.call(IndexRange(1), params, context); + } + catch (...) { + /* Multi-functions must not throw exceptions. */ + BLI_assert_unreachable(); + } } else { MFParamsBuilder params(fn, &mask); @@ -1038,7 +1044,13 @@ static void execute_call_instruction(const MFCallInstruction &instruction, } } - fn.call(mask, params, context); + try { + fn.call(mask, params, context); + } + catch (...) { + /* Multi-functions must not throw exceptions. */ + BLI_assert_unreachable(); + } } } diff --git a/source/blender/gpencil_modifiers/CMakeLists.txt b/source/blender/gpencil_modifiers/CMakeLists.txt index eb1f61b1862..afcd551d0af 100644 --- a/source/blender/gpencil_modifiers/CMakeLists.txt +++ b/source/blender/gpencil_modifiers/CMakeLists.txt @@ -69,8 +69,8 @@ set(SRC intern/MOD_gpencilthick.c intern/MOD_gpenciltime.c intern/MOD_gpenciltint.c - intern/MOD_gpencilweight_proximity.c intern/MOD_gpencilweight_angle.c + intern/MOD_gpencilweight_proximity.c MOD_gpencil_lineart.h MOD_gpencil_modifiertypes.h diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c b/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c index 01488a8b2de..c5ccf1d8229 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c @@ -587,6 +587,7 @@ static void chaining_panel_draw(const bContext *UNUSED(C), Panel *panel) is_geom ? IFACE_("Geometry Threshold") : NULL, ICON_NONE); + uiItemR(layout, ptr, "smooth_tolerance", UI_ITEM_R_SLIDER, NULL, ICON_NONE); uiItemR(layout, ptr, "split_angle", UI_ITEM_R_SLIDER, NULL, ICON_NONE); } diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilweight_proximity.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilweight_proximity.c index 0885828a3a0..4079485de8d 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencilweight_proximity.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilweight_proximity.c @@ -83,13 +83,13 @@ static float calc_point_weight_by_distance(Object *ob, float dist = len_v3v3(mmd->object->obmat[3], gvert); if (dist > dist_max) { - weight = 0.0f; + weight = 1.0f; } else if (dist <= dist_max && dist > dist_min) { - weight = (dist_max - dist) / max_ff((dist_max - dist_min), 0.0001f); + weight = 1.0f - ((dist_max - dist) / max_ff((dist_max - dist_min), 0.0001f)); } else { - weight = 1.0f; + weight = 0.0f; } return weight; diff --git a/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h b/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h index 134d9707ade..c00f34185dd 100644 --- a/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h +++ b/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h @@ -317,6 +317,8 @@ typedef struct LineartRenderBuffer { float chaining_image_threshold; float angle_splitting_threshold; + float chain_smooth_tolerance; + /* FIXME(Yiming): Temporary solution for speeding up calculation by not including lines that * are not in the selected source. This will not be needed after we have a proper scene-wise * cache running because multiple modifiers can then select results from that without further @@ -592,6 +594,7 @@ void MOD_lineart_chain_split_for_fixed_occlusion(LineartRenderBuffer *rb); void MOD_lineart_chain_connect(LineartRenderBuffer *rb); void MOD_lineart_chain_discard_short(LineartRenderBuffer *rb, const float threshold); void MOD_lineart_chain_split_angle(LineartRenderBuffer *rb, float angle_threshold_rad); +void MOD_lineart_smooth_chains(LineartRenderBuffer *rb, float tolerance); int MOD_lineart_chain_count(const LineartEdgeChain *ec); void MOD_lineart_chain_clear_picked_flag(LineartCache *lc); diff --git a/source/blender/gpencil_modifiers/intern/lineart/lineart_chain.c b/source/blender/gpencil_modifiers/intern/lineart/lineart_chain.c index d86253e7fe0..8935bdd1870 100644 --- a/source/blender/gpencil_modifiers/intern/lineart/lineart_chain.c +++ b/source/blender/gpencil_modifiers/intern/lineart/lineart_chain.c @@ -924,6 +924,34 @@ void MOD_lineart_chain_clear_picked_flag(LineartCache *lc) } } +void MOD_lineart_smooth_chains(LineartRenderBuffer *rb, float tolerance) +{ + LISTBASE_FOREACH (LineartEdgeChain *, rlc, &rb->chains) { + LineartEdgeChainItem *next_eci; + for (LineartEdgeChainItem *eci = rlc->chain.first; eci; eci = next_eci) { + next_eci = eci->next; + LineartEdgeChainItem *eci2, *eci3, *eci4; + + /* Not enough point to do simplify. */ + if ((!(eci2 = eci->next)) || (!(eci3 = eci2->next))) { + continue; + } + + /* No need to care for different line types/occlusion and so on, because at this stage they + * are all the same within a chain. */ + + /* If p3 is within the p1-p2 segment of a width of "tolerance" */ + if (dist_to_line_segment_v2(eci3->pos, eci->pos, eci2->pos) < tolerance) { + /* And if p4 is on the extension of p1-p2 , we remove p3. */ + if ((eci4 = eci3->next) && (dist_to_line_v2(eci4->pos, eci->pos, eci2->pos) < tolerance)) { + BLI_remlink(&rlc->chain, eci3); + next_eci = eci; + } + } + } + } +} + /** * This should always be the last stage!, see the end of * #MOD_lineart_chain_split_for_fixed_occlusion(). diff --git a/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c b/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c index 725cc0741f0..7441c9a909c 100644 --- a/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c +++ b/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c @@ -2168,6 +2168,12 @@ static void lineart_main_load_geometries( use_mesh = use_ob->data; } else { + /* If DEG_ITER_OBJECT_FLAG_DUPLI is set, the curve objects are going to have a mesh + * equivalent already in the object list, so ignore converting the original curve in this + * case. */ + if (allow_duplicates) { + continue; + } use_mesh = BKE_mesh_new_from_object(depsgraph, use_ob, true, true); } @@ -3056,8 +3062,9 @@ static LineartRenderBuffer *lineart_create_render_buffer(Scene *scene, rb->shift_y /= (1 + rb->overscan); rb->crease_threshold = cos(M_PI - lmd->crease_threshold); - rb->angle_splitting_threshold = lmd->angle_splitting_threshold; rb->chaining_image_threshold = lmd->chaining_image_threshold; + rb->angle_splitting_threshold = lmd->angle_splitting_threshold; + rb->chain_smooth_tolerance = lmd->chain_smooth_tolerance; rb->fuzzy_intersections = (lmd->calculation_flags & LRT_INTERSECTION_AS_CONTOUR) != 0; rb->fuzzy_everything = (lmd->calculation_flags & LRT_EVERYTHING_AS_CONTOUR) != 0; @@ -4172,6 +4179,13 @@ bool MOD_lineart_compute_feature_lines(Depsgraph *depsgraph, /* This configuration ensures there won't be accidental lost of short unchained segments. */ MOD_lineart_chain_discard_short(rb, MIN2(*t_image, 0.001f) - FLT_EPSILON); + if (rb->chain_smooth_tolerance > FLT_EPSILON) { + /* Keeping UI range of 0-1 for ease of read while scaling down the actual value for best + * effective range in image-space (Coordinate only goes from -1 to 1). This value is somewhat + * arbitrary, but works best for the moment. */ + MOD_lineart_smooth_chains(rb, rb->chain_smooth_tolerance / 50); + } + if (rb->angle_splitting_threshold > FLT_EPSILON) { MOD_lineart_chain_split_angle(rb, rb->angle_splitting_threshold); } diff --git a/source/blender/gpu/CMakeLists.txt b/source/blender/gpu/CMakeLists.txt index b7dc3210c41..7a072900473 100644 --- a/source/blender/gpu/CMakeLists.txt +++ b/source/blender/gpu/CMakeLists.txt @@ -268,8 +268,8 @@ data_to_c_simple(shaders/gpu_shader_2D_edituvs_stretch_vert.glsl SRC) data_to_c_simple(shaders/gpu_shader_text_vert.glsl SRC) data_to_c_simple(shaders/gpu_shader_text_frag.glsl SRC) -data_to_c_simple(shaders/gpu_shader_keyframe_diamond_vert.glsl SRC) -data_to_c_simple(shaders/gpu_shader_keyframe_diamond_frag.glsl SRC) +data_to_c_simple(shaders/gpu_shader_keyframe_shape_vert.glsl SRC) +data_to_c_simple(shaders/gpu_shader_keyframe_shape_frag.glsl SRC) data_to_c_simple(shaders/gpu_shader_codegen_lib.glsl SRC) @@ -296,6 +296,7 @@ data_to_c_simple(shaders/material/gpu_shader_material_diffuse.glsl SRC) data_to_c_simple(shaders/material/gpu_shader_material_displacement.glsl SRC) data_to_c_simple(shaders/material/gpu_shader_material_eevee_specular.glsl SRC) data_to_c_simple(shaders/material/gpu_shader_material_emission.glsl SRC) +data_to_c_simple(shaders/material/gpu_shader_material_float_curve.glsl SRC) data_to_c_simple(shaders/material/gpu_shader_material_fractal_noise.glsl SRC) data_to_c_simple(shaders/material/gpu_shader_material_fresnel.glsl SRC) data_to_c_simple(shaders/material/gpu_shader_material_gamma.glsl SRC) diff --git a/source/blender/gpu/GPU_shader.h b/source/blender/gpu/GPU_shader.h index 62b748b7edf..c6cfac79699 100644 --- a/source/blender/gpu/GPU_shader.h +++ b/source/blender/gpu/GPU_shader.h @@ -169,7 +169,7 @@ void GPU_shader_set_framebuffer_srgb_target(int use_srgb_to_linear); typedef enum eGPUBuiltinShader { /* specialized drawing */ GPU_SHADER_TEXT, - GPU_SHADER_KEYFRAME_DIAMOND, + GPU_SHADER_KEYFRAME_SHAPE, GPU_SHADER_SIMPLE_LIGHTING, /* for simple 2D drawing */ /** @@ -423,6 +423,19 @@ void GPU_shader_free_builtin_shaders(void); /* Determined by the maximum uniform buffer size divided by chunk size. */ #define GPU_MAX_UNIFORM_ATTR 8 +typedef enum eGPUKeyframeShapes { + GPU_KEYFRAME_SHAPE_DIAMOND = (1 << 0), + GPU_KEYFRAME_SHAPE_CIRCLE = (1 << 1), + GPU_KEYFRAME_SHAPE_CLIPPED_VERTICAL = (1 << 2), + GPU_KEYFRAME_SHAPE_CLIPPED_HORIZONTAL = (1 << 3), + GPU_KEYFRAME_SHAPE_INNER_DOT = (1 << 4), + GPU_KEYFRAME_SHAPE_ARROW_END_MAX = (1 << 8), + GPU_KEYFRAME_SHAPE_ARROW_END_MIN = (1 << 9), + GPU_KEYFRAME_SHAPE_ARROW_END_MIXED = (1 << 10), +} eGPUKeyframeShapes; +#define GPU_KEYFRAME_SHAPE_SQUARE \ + (GPU_KEYFRAME_SHAPE_CLIPPED_VERTICAL | GPU_KEYFRAME_SHAPE_CLIPPED_HORIZONTAL) + #ifdef __cplusplus } #endif diff --git a/source/blender/gpu/intern/gpu_codegen.c b/source/blender/gpu/intern/gpu_codegen.c index bb1ebc0e85d..f0046e879a0 100644 --- a/source/blender/gpu/intern/gpu_codegen.c +++ b/source/blender/gpu/intern/gpu_codegen.c @@ -656,6 +656,8 @@ static const char *attr_prefix_get(CustomDataType type) return "c"; case CD_AUTO_FROM_NAME: return "a"; + case CD_HAIRLENGTH: + return "hl"; default: BLI_assert_msg(0, "GPUVertAttr Prefix type not found : This should not happen!"); return ""; @@ -675,7 +677,12 @@ static char *code_generate_interface(GPUNodeGraph *graph, int builtins) BLI_dynstr_append(ds, "\n"); LISTBASE_FOREACH (GPUMaterialAttribute *, attr, &graph->attributes) { - BLI_dynstr_appendf(ds, "%s var%d;\n", gpu_data_type_to_string(attr->gputype), attr->id); + if (attr->type == CD_HAIRLENGTH) { + BLI_dynstr_appendf(ds, "float var%d;\n", attr->id); + } + else { + BLI_dynstr_appendf(ds, "%s var%d;\n", gpu_data_type_to_string(attr->gputype), attr->id); + } } if (builtins & GPU_BARYCENTRIC_TEXCO) { BLI_dynstr_append(ds, "vec2 barycentricTexCo;\n"); @@ -711,6 +718,10 @@ static char *code_generate_vertex(GPUNodeGraph *graph, BLI_dynstr_append(ds, datatoc_gpu_shader_common_obinfos_lib_glsl); BLI_dynstr_append(ds, "DEFINE_ATTR(vec4, orco);\n"); } + else if (attr->type == CD_HAIRLENGTH) { + BLI_dynstr_append(ds, datatoc_gpu_shader_common_obinfos_lib_glsl); + BLI_dynstr_append(ds, "DEFINE_ATTR(float, hairLen);\n"); + } else if (attr->name[0] == '\0') { BLI_dynstr_appendf(ds, "DEFINE_ATTR(%s, %s);\n", type_str, prefix); BLI_dynstr_appendf(ds, "#define att%d %s\n", attr->id, prefix); @@ -755,6 +766,9 @@ static char *code_generate_vertex(GPUNodeGraph *graph, BLI_dynstr_appendf( ds, " var%d = orco_get(position, modelmatinv, OrcoTexCoFactors, orco);\n", attr->id); } + else if (attr->type == CD_HAIRLENGTH) { + BLI_dynstr_appendf(ds, " var%d = hair_len_get(hair_get_strand_id(), hairLen);\n", attr->id); + } else { const char *type_str = gpu_data_type_to_string(attr->gputype); BLI_dynstr_appendf(ds, " var%d = GET_ATTR(%s, att%d);\n", attr->id, type_str, attr->id); diff --git a/source/blender/gpu/intern/gpu_material_library.c b/source/blender/gpu/intern/gpu_material_library.c index 73a80c62bdc..74e0270c42a 100644 --- a/source/blender/gpu/intern/gpu_material_library.c +++ b/source/blender/gpu/intern/gpu_material_library.c @@ -62,6 +62,7 @@ extern char datatoc_gpu_shader_material_diffuse_glsl[]; extern char datatoc_gpu_shader_material_displacement_glsl[]; extern char datatoc_gpu_shader_material_eevee_specular_glsl[]; extern char datatoc_gpu_shader_material_emission_glsl[]; +extern char datatoc_gpu_shader_material_float_curve_glsl[]; extern char datatoc_gpu_shader_material_fractal_noise_glsl[]; extern char datatoc_gpu_shader_material_fresnel_glsl[]; extern char datatoc_gpu_shader_material_gamma_glsl[]; @@ -262,6 +263,11 @@ static GPUMaterialLibrary gpu_shader_material_emission_library = { .dependencies = {NULL}, }; +static GPUMaterialLibrary gpu_shader_material_float_curve_library = { + .code = datatoc_gpu_shader_material_float_curve_glsl, + .dependencies = {NULL}, +}; + static GPUMaterialLibrary gpu_shader_material_fresnel_library = { .code = datatoc_gpu_shader_material_fresnel_glsl, .dependencies = {NULL}, @@ -591,6 +597,7 @@ static GPUMaterialLibrary *gpu_material_libraries[] = { &gpu_shader_material_color_util_library, &gpu_shader_material_hash_library, &gpu_shader_material_noise_library, + &gpu_shader_material_float_curve_library, &gpu_shader_material_fractal_noise_library, &gpu_shader_material_add_shader_library, &gpu_shader_material_ambient_occlusion_library, diff --git a/source/blender/gpu/intern/gpu_select.c b/source/blender/gpu/intern/gpu_select.c index 88b704a84a1..661c462f60d 100644 --- a/source/blender/gpu/intern/gpu_select.c +++ b/source/blender/gpu/intern/gpu_select.c @@ -20,8 +20,8 @@ /** \file * \ingroup gpu * - * Interface for accessing gpu-related methods for selection. The semantics are - * similar to glRenderMode(GL_SELECT) from older OpenGL versions. + * Interface for accessing GPU-related methods for selection. The semantics are + * similar to `glRenderMode(GL_SELECT)` from older OpenGL versions. */ #include <stdlib.h> #include <string.h> diff --git a/source/blender/gpu/intern/gpu_select_sample_query.cc b/source/blender/gpu/intern/gpu_select_sample_query.cc index 7b9b3020639..047ce0cfb35 100644 --- a/source/blender/gpu/intern/gpu_select_sample_query.cc +++ b/source/blender/gpu/intern/gpu_select_sample_query.cc @@ -20,8 +20,8 @@ /** \file * \ingroup gpu * - * Interface for accessing gpu-related methods for selection. The semantics will be - * similar to glRenderMode(GL_SELECT) since the goal is to maintain compatibility. + * Interface for accessing GPU-related methods for selection. The semantics will be + * similar to `glRenderMode(GL_SELECT)` since the goal is to maintain compatibility. */ #include <cstdlib> diff --git a/source/blender/gpu/intern/gpu_shader_builtin.c b/source/blender/gpu/intern/gpu_shader_builtin.c index c5122b76001..9ea46788f44 100644 --- a/source/blender/gpu/intern/gpu_shader_builtin.c +++ b/source/blender/gpu/intern/gpu_shader_builtin.c @@ -121,8 +121,8 @@ extern char datatoc_gpu_shader_3D_line_dashed_uniform_color_vert_glsl[]; extern char datatoc_gpu_shader_text_vert_glsl[]; extern char datatoc_gpu_shader_text_frag_glsl[]; -extern char datatoc_gpu_shader_keyframe_diamond_vert_glsl[]; -extern char datatoc_gpu_shader_keyframe_diamond_frag_glsl[]; +extern char datatoc_gpu_shader_keyframe_shape_vert_glsl[]; +extern char datatoc_gpu_shader_keyframe_shape_frag_glsl[]; extern char datatoc_gpu_shader_gpencil_stroke_vert_glsl[]; extern char datatoc_gpu_shader_gpencil_stroke_frag_glsl[]; @@ -166,11 +166,11 @@ static const GPUShaderStages builtin_shader_stages[GPU_SHADER_BUILTIN_LEN] = { .vert = datatoc_gpu_shader_text_vert_glsl, .frag = datatoc_gpu_shader_text_frag_glsl, }, - [GPU_SHADER_KEYFRAME_DIAMOND] = + [GPU_SHADER_KEYFRAME_SHAPE] = { - .name = "GPU_SHADER_KEYFRAME_DIAMOND", - .vert = datatoc_gpu_shader_keyframe_diamond_vert_glsl, - .frag = datatoc_gpu_shader_keyframe_diamond_frag_glsl, + .name = "GPU_SHADER_KEYFRAME_SHAPE", + .vert = datatoc_gpu_shader_keyframe_shape_vert_glsl, + .frag = datatoc_gpu_shader_keyframe_shape_frag_glsl, }, [GPU_SHADER_SIMPLE_LIGHTING] = { diff --git a/source/blender/gpu/intern/gpu_texture.cc b/source/blender/gpu/intern/gpu_texture.cc index d5d13ea269f..2744c0c5e17 100644 --- a/source/blender/gpu/intern/gpu_texture.cc +++ b/source/blender/gpu/intern/gpu_texture.cc @@ -461,7 +461,7 @@ void GPU_texture_generate_mipmap(GPUTexture *tex) reinterpret_cast<Texture *>(tex)->generate_mipmap(); } -/* Copy a texture content to a similar texture. Only Mip 0 is copied. */ +/* Copy a texture content to a similar texture. Only MIP 0 is copied. */ void GPU_texture_copy(GPUTexture *dst_, GPUTexture *src_) { Texture *src = reinterpret_cast<Texture *>(src_); diff --git a/source/blender/gpu/opengl/gl_state.cc b/source/blender/gpu/opengl/gl_state.cc index 1106e3dab50..d737cf88a13 100644 --- a/source/blender/gpu/opengl/gl_state.cc +++ b/source/blender/gpu/opengl/gl_state.cc @@ -52,6 +52,7 @@ GLStateManager::GLStateManager() glDisable(GL_DITHER); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glPixelStorei(GL_PACK_ALIGNMENT, 1); glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); glPrimitiveRestartIndex((GLuint)0xFFFFFFFF); diff --git a/source/blender/gpu/shaders/gpu_shader_2D_nodelink_frag.glsl b/source/blender/gpu/shaders/gpu_shader_2D_nodelink_frag.glsl index f07bd7f1d6f..55d5d941290 100644 --- a/source/blender/gpu/shaders/gpu_shader_2D_nodelink_frag.glsl +++ b/source/blender/gpu/shaders/gpu_shader_2D_nodelink_frag.glsl @@ -1,11 +1,41 @@ in float colorGradient; in vec4 finalColor; +in float lineU; +flat in float lineLength; +flat in float dashFactor; +flat in int isMainLine; out vec4 fragColor; +#define DASH_WIDTH 20.0 +#define ANTIALIAS 1.0 + void main() { fragColor = finalColor; + + if ((isMainLine != 0) && (dashFactor < 1.0)) { + float distance_along_line = lineLength * lineU; + float normalized_distance = fract(distance_along_line / DASH_WIDTH); + + /* Checking if `normalized_distance <= dashFactor` is already enough for a basic + * dash, however we want to handle a nice antialias. */ + + float dash_center = DASH_WIDTH * dashFactor * 0.5; + float normalized_distance_triangle = + 1.0 - abs((fract((distance_along_line - dash_center) / DASH_WIDTH)) * 2.0 - 1.0); + float t = ANTIALIAS / DASH_WIDTH; + float slope = 1.0 / (2.0 * t); + + float alpha = min(1.0, max(0.0, slope * (normalized_distance_triangle - dashFactor + t))); + + if (alpha < 0.0) { + discard; + } + + fragColor.a *= 1.0 - alpha; + } + fragColor.a *= smoothstep(1.0, 0.1, abs(colorGradient)); } diff --git a/source/blender/gpu/shaders/gpu_shader_2D_nodelink_vert.glsl b/source/blender/gpu/shaders/gpu_shader_2D_nodelink_vert.glsl index aae7f641af8..8f46c8eda4b 100644 --- a/source/blender/gpu/shaders/gpu_shader_2D_nodelink_vert.glsl +++ b/source/blender/gpu/shaders/gpu_shader_2D_nodelink_vert.glsl @@ -19,6 +19,8 @@ in vec2 P3; in ivec4 colid_doarrow; in ivec2 domuted; in float dim_factor; +in float thickness; +in float dash_factor; uniform vec4 colors[6]; @@ -41,6 +43,8 @@ uniform vec4 colors[3]; uniform bool doArrow; uniform bool doMuted; uniform float dim_factor; +uniform float thickness; +uniform float dash_factor; # define colShadow colors[0] # define colStart colors[1] @@ -54,9 +58,21 @@ uniform mat4 ModelViewProjectionMatrix; out float colorGradient; out vec4 finalColor; +out float lineU; +flat out float lineLength; +flat out float dashFactor; +flat out int isMainLine; void main(void) { + /* Parameters for the dashed line. */ + isMainLine = expand.y != 1.0 ? 0 : 1; + dashFactor = dash_factor; + /* Approximate line length, no need for real bezier length calculation. */ + lineLength = distance(P0, P3); + /* TODO: Incorrect U, this leads to non-uniform dash distribution. */ + lineU = uv.x; + float t = uv.x; float t2 = t * t; float t2_3 = 3.0 * t2; @@ -103,7 +119,7 @@ void main(void) finalColor[3] *= dim_factor; /* Expand into a line */ - gl_Position.xy += exp_axis * expandSize * expand_dist; + gl_Position.xy += exp_axis * expandSize * expand_dist * thickness; /* If the link is not muted or is not a reroute arrow the points are squashed to the center of * the line. Magic numbers are defined in drawnode.c */ diff --git a/source/blender/gpu/shaders/gpu_shader_codegen_lib.glsl b/source/blender/gpu/shaders/gpu_shader_codegen_lib.glsl index f7bf3d33361..193a4190cbf 100644 --- a/source/blender/gpu/shaders/gpu_shader_codegen_lib.glsl +++ b/source/blender/gpu/shaders/gpu_shader_codegen_lib.glsl @@ -42,6 +42,11 @@ vec3 orco_get(vec3 local_pos, mat4 modelmatinv, vec4 orco_madd[2], const sampler return orco_madd[0].xyz + orco * orco_madd[1].xyz; } +float hair_len_get(int id, const samplerBuffer len) +{ + return texelFetch(len, id).x; +} + vec4 tangent_get(const samplerBuffer attr, mat3 normalmat) { /* Unsupported */ @@ -71,6 +76,11 @@ vec3 orco_get(vec3 local_pos, mat4 modelmatinv, vec4 orco_madd[2], vec4 orco) } } +float hair_len_get(int id, const float len) +{ + return len; +} + vec4 tangent_get(vec4 attr, mat3 normalmat) { vec4 tangent; diff --git a/source/blender/gpu/shaders/gpu_shader_keyframe_diamond_frag.glsl b/source/blender/gpu/shaders/gpu_shader_keyframe_shape_frag.glsl index 1c4039bc590..a3b61dca8b4 100644 --- a/source/blender/gpu/shaders/gpu_shader_keyframe_diamond_frag.glsl +++ b/source/blender/gpu/shaders/gpu_shader_keyframe_shape_frag.glsl @@ -1,3 +1,16 @@ + +/* Values in GPU_shader.h. */ +#define GPU_KEYFRAME_SHAPE_DIAMOND (1 << 0) +#define GPU_KEYFRAME_SHAPE_CIRCLE (1 << 1) +#define GPU_KEYFRAME_SHAPE_CLIPPED_VERTICAL (1 << 2) +#define GPU_KEYFRAME_SHAPE_CLIPPED_HORIZONTAL (1 << 3) +#define GPU_KEYFRAME_SHAPE_INNER_DOT (1 << 4) +#define GPU_KEYFRAME_SHAPE_ARROW_END_MAX (1 << 8) +#define GPU_KEYFRAME_SHAPE_ARROW_END_MIN (1 << 9) +#define GPU_KEYFRAME_SHAPE_ARROW_END_MIXED (1 << 10) +#define GPU_KEYFRAME_SHAPE_SQUARE \ + (GPU_KEYFRAME_SHAPE_CLIPPED_VERTICAL | GPU_KEYFRAME_SHAPE_CLIPPED_HORIZONTAL) + flat in vec4 radii; flat in vec4 thresholds; @@ -27,24 +40,24 @@ void main() float outline_dist = -1.0; /* Diamond outline */ - if (test(0x1)) { + if (test(GPU_KEYFRAME_SHAPE_DIAMOND)) { outline_dist = max(outline_dist, radius - radii[0]); } /* Circle outline */ - if (test(0x2)) { + if (test(GPU_KEYFRAME_SHAPE_CIRCLE)) { radius = length(absPos); outline_dist = max(outline_dist, radius - radii[1]); } /* Top & Bottom clamp */ - if (test(0x4)) { + if (test(GPU_KEYFRAME_SHAPE_CLIPPED_VERTICAL)) { outline_dist = max(outline_dist, absPos.y - radii[2]); } /* Left & Right clamp */ - if (test(0x8)) { + if (test(GPU_KEYFRAME_SHAPE_CLIPPED_HORIZONTAL)) { outline_dist = max(outline_dist, absPos.x - radii[2]); } @@ -53,20 +66,20 @@ void main() /* Inside the outline. */ if (outline_dist < 0) { /* Middle dot */ - if (test(0x10)) { - alpha = max(alpha, 1 - smoothstep(thresholds[2], thresholds[3], radius)); + if (test(GPU_KEYFRAME_SHAPE_INNER_DOT)) { + alpha = max(alpha, 1 - smoothstep(thresholds[2], thresholds[3], length(absPos))); } /* Up and down arrow-like shading. */ - if (test(0x300)) { + if (test(GPU_KEYFRAME_SHAPE_ARROW_END_MAX | GPU_KEYFRAME_SHAPE_ARROW_END_MIN)) { float ypos = -1.0; /* Up arrow (maximum) */ - if (test(0x100)) { + if (test(GPU_KEYFRAME_SHAPE_ARROW_END_MAX)) { ypos = max(ypos, pos.y); } /* Down arrow (minimum) */ - if (test(0x200)) { + if (test(GPU_KEYFRAME_SHAPE_ARROW_END_MIN)) { ypos = max(ypos, -pos.y); } @@ -75,7 +88,7 @@ void main() float minmax_step = smoothstep(thresholds[0], thresholds[1], minmax_dist * minmax_scale); /* Reduced alpha for uncertain extremes. */ - float minmax_alpha = test(0x400) ? 0.55 : 0.85; + float minmax_alpha = test(GPU_KEYFRAME_SHAPE_ARROW_END_MIXED) ? 0.55 : 0.85; alpha = max(alpha, minmax_step * minmax_alpha); } diff --git a/source/blender/gpu/shaders/gpu_shader_keyframe_diamond_vert.glsl b/source/blender/gpu/shaders/gpu_shader_keyframe_shape_vert.glsl index 2ba89230d80..18e8b76ba23 100644 --- a/source/blender/gpu/shaders/gpu_shader_keyframe_diamond_vert.glsl +++ b/source/blender/gpu/shaders/gpu_shader_keyframe_shape_vert.glsl @@ -1,4 +1,16 @@ +/* Values in GPU_shader.h. */ +#define GPU_KEYFRAME_SHAPE_DIAMOND (1 << 0) +#define GPU_KEYFRAME_SHAPE_CIRCLE (1 << 1) +#define GPU_KEYFRAME_SHAPE_CLIPPED_VERTICAL (1 << 2) +#define GPU_KEYFRAME_SHAPE_CLIPPED_HORIZONTAL (1 << 3) +#define GPU_KEYFRAME_SHAPE_INNER_DOT (1 << 4) +#define GPU_KEYFRAME_SHAPE_ARROW_END_MAX (1 << 8) +#define GPU_KEYFRAME_SHAPE_ARROW_END_MIN (1 << 9) +#define GPU_KEYFRAME_SHAPE_ARROW_END_MIXED (1 << 10) +#define GPU_KEYFRAME_SHAPE_SQUARE \ + (GPU_KEYFRAME_SHAPE_CLIPPED_VERTICAL | GPU_KEYFRAME_SHAPE_CLIPPED_HORIZONTAL) + uniform mat4 ModelViewProjectionMatrix; uniform vec2 ViewportSize = vec2(-1, -1); uniform float outline_scale = 1.0; @@ -49,8 +61,9 @@ void main() finalOutlineColor = outlineColor; finalFlags = flags; - if (!test(0xF)) { - finalFlags |= 1; + if (!test(GPU_KEYFRAME_SHAPE_DIAMOND | GPU_KEYFRAME_SHAPE_CIRCLE | + GPU_KEYFRAME_SHAPE_CLIPPED_VERTICAL | GPU_KEYFRAME_SHAPE_CLIPPED_HORIZONTAL)) { + finalFlags |= GPU_KEYFRAME_SHAPE_DIAMOND; } /* Size-dependent line thickness. */ diff --git a/source/blender/gpu/shaders/material/gpu_shader_material_float_curve.glsl b/source/blender/gpu/shaders/material/gpu_shader_material_float_curve.glsl new file mode 100644 index 00000000000..514409f7fdf --- /dev/null +++ b/source/blender/gpu/shaders/material/gpu_shader_material_float_curve.glsl @@ -0,0 +1,33 @@ +/* ext is vec4(in_x, in_dy, out_x, out_dy). */ +float curve_float_extrapolate(float x, float y, vec4 ext) +{ + if (x < 0.0) { + return y + x * ext.y; + } + else if (x > 1.0) { + return y + (x - 1.0) * ext.w; + } + else { + return y; + } +} + +#define RANGE_RESCALE(x, min, range) ((x - min) * range) + +void curve_float(float fac, + float vec, + sampler1DArray curvemap, + float layer, + float range, + vec4 ext, + out float outvec) +{ + float xyz_min = ext.x; + vec = RANGE_RESCALE(vec, xyz_min, range); + + outvec = texture(curvemap, vec2(vec, layer)).x; + + outvec = curve_float_extrapolate(vec, outvec, ext); + + outvec = mix(vec, outvec, fac); +} diff --git a/source/blender/gpu/shaders/material/gpu_shader_material_hair_info.glsl b/source/blender/gpu/shaders/material/gpu_shader_material_hair_info.glsl index 6330daa4391..6ffa6b59572 100644 --- a/source/blender/gpu/shaders/material/gpu_shader_material_hair_info.glsl +++ b/source/blender/gpu/shaders/material/gpu_shader_material_hair_info.glsl @@ -10,12 +10,15 @@ float wang_hash_noise(uint s) return fract(float(s) / 4294967296.0); } -void node_hair_info(out float is_strand, +void node_hair_info(float hair_length, + out float is_strand, out float intercept, + out float length, out float thickness, out vec3 tangent, out float random) { + length = hair_length; #ifdef HAIR_SHADER is_strand = 1.0; intercept = hairTime; diff --git a/source/blender/gpu/tests/gpu_shader_builtin_test.cc b/source/blender/gpu/tests/gpu_shader_builtin_test.cc index f0061a6bf5c..523a7e5b881 100644 --- a/source/blender/gpu/tests/gpu_shader_builtin_test.cc +++ b/source/blender/gpu/tests/gpu_shader_builtin_test.cc @@ -32,7 +32,7 @@ static void test_shader_builtin() test_compile_builtin_shader(GPU_SHADER_3D_POINT_UNIFORM_SIZE_UNIFORM_COLOR_OUTLINE_AA); test_compile_builtin_shader(GPU_SHADER_TEXT, GPU_SHADER_CFG_DEFAULT); - test_compile_builtin_shader(GPU_SHADER_KEYFRAME_DIAMOND, GPU_SHADER_CFG_DEFAULT); + test_compile_builtin_shader(GPU_SHADER_KEYFRAME_SHAPE, GPU_SHADER_CFG_DEFAULT); test_compile_builtin_shader(GPU_SHADER_SIMPLE_LIGHTING, GPU_SHADER_CFG_DEFAULT); test_compile_builtin_shader(GPU_SHADER_2D_UNIFORM_COLOR, GPU_SHADER_CFG_DEFAULT); test_compile_builtin_shader(GPU_SHADER_2D_FLAT_COLOR, GPU_SHADER_CFG_DEFAULT); diff --git a/source/blender/ikplugin/intern/itasc_plugin.cpp b/source/blender/ikplugin/intern/itasc_plugin.cpp index b9411f6dd2d..a9e1692ebf0 100644 --- a/source/blender/ikplugin/intern/itasc_plugin.cpp +++ b/source/blender/ikplugin/intern/itasc_plugin.cpp @@ -644,7 +644,7 @@ static bool base_callback(const iTaSC::Timestamp ×tamp, ikscene->baseFrame = iTaSC::F_identity; } next.setValue(&rootmat[0][0]); - /* if there is a polar target (only during solving otherwise we don't have end efffector) */ + /* If there is a polar target (only during solving otherwise we don't have end effector). */ if (ikscene->polarConstraint && timestamp.update) { /* compute additional rotation of base frame so that armature follows the polar target */ float imat[4][4]; /* IK tree base inverse matrix */ diff --git a/source/blender/imbuf/intern/IMB_indexer.h b/source/blender/imbuf/intern/IMB_indexer.h index 37309ccc13a..6c66b11df4f 100644 --- a/source/blender/imbuf/intern/IMB_indexer.h +++ b/source/blender/imbuf/intern/IMB_indexer.h @@ -33,7 +33,7 @@ * a) different time-codes within one file (like DTS/PTS, Time-code-Track, * "implicit" time-codes within DV-files and HDV-files etc.) * b) seeking difficulties within FFMPEG for files with timestamp holes - * c) broken files that miss several frames / have varying framerates + * c) broken files that miss several frames / have varying frame-rates * d) use proxies accordingly * * ... we need index files, that provide us with diff --git a/source/blender/imbuf/intern/openexr/openexr_api.cpp b/source/blender/imbuf/intern/openexr/openexr_api.cpp index cd323e72003..adf09f8dda8 100644 --- a/source/blender/imbuf/intern/openexr/openexr_api.cpp +++ b/source/blender/imbuf/intern/openexr/openexr_api.cpp @@ -555,7 +555,7 @@ static bool imb_save_openexr_float(ImBuf *ibuf, const char *name, const int flag int xstride = sizeof(float) * channels; int ystride = -xstride * width; - /* last scanline, stride negative */ + /* Last scan-line, stride negative. */ float *rect[4] = {nullptr, nullptr, nullptr, nullptr}; rect[0] = ibuf->rect_float + channels * (height - 1) * width; rect[1] = (channels >= 2) ? rect[0] + 1 : rect[0]; @@ -654,7 +654,7 @@ struct ExrChannel { char name[EXR_TOT_MAXNAME + 1]; /* full name with everything */ struct MultiViewChannelName *m; /* struct to store all multipart channel info */ - int xstride, ystride; /* step to next pixel, to next scanline */ + int xstride, ystride; /* step to next pixel, to next scan-line. */ float *rect; /* first pointer to write in */ char chan_id; /* quick lookup of channel char */ int view_id; /* quick lookup of channel view */ @@ -681,6 +681,8 @@ struct ExrLayer { ListBase passes; }; +static bool imb_exr_multilayer_parse_channels_from_file(ExrHandle *data); + /* ********************** */ void *IMB_exr_get_handle(void) @@ -839,12 +841,12 @@ void IMB_exr_add_channel(void *handle, } /* used for output files (from RenderResult) (single and multilayer, single and multiview) */ -int IMB_exr_begin_write(void *handle, - const char *filename, - int width, - int height, - int compress, - const StampData *stamp) +bool IMB_exr_begin_write(void *handle, + const char *filename, + int width, + int height, + int compress, + const StampData *stamp) { ExrHandle *data = (ExrHandle *)handle; Header header(width, height); @@ -962,51 +964,64 @@ void IMB_exrtile_begin_write( } /* read from file */ -int IMB_exr_begin_read(void *handle, const char *filename, int *width, int *height) +bool IMB_exr_begin_read( + void *handle, const char *filename, int *width, int *height, const bool parse_channels) { ExrHandle *data = (ExrHandle *)handle; ExrChannel *echan; /* 32 is arbitrary, but zero length files crashes exr. */ - if (BLI_exists(filename) && BLI_file_size(filename) > 32) { - /* avoid crash/abort when we don't have permission to write here */ - try { - data->ifile_stream = new IFileStream(filename); - data->ifile = new MultiPartInputFile(*(data->ifile_stream)); - } - catch (const std::exception &) { - delete data->ifile; - delete data->ifile_stream; + if (!(BLI_exists(filename) && BLI_file_size(filename) > 32)) { + return false; + } - data->ifile = nullptr; - data->ifile_stream = nullptr; - } + /* avoid crash/abort when we don't have permission to write here */ + try { + data->ifile_stream = new IFileStream(filename); + data->ifile = new MultiPartInputFile(*(data->ifile_stream)); + } + catch (const std::exception &) { + delete data->ifile; + delete data->ifile_stream; - if (data->ifile) { - Box2i dw = data->ifile->header(0).dataWindow(); - data->width = *width = dw.max.x - dw.min.x + 1; - data->height = *height = dw.max.y - dw.min.y + 1; + data->ifile = nullptr; + data->ifile_stream = nullptr; + } - imb_exr_get_views(*data->ifile, *data->multiView); + if (!data->ifile) { + return false; + } - std::vector<MultiViewChannelName> channels; - GetChannelsInMultiPartFile(*data->ifile, channels); + Box2i dw = data->ifile->header(0).dataWindow(); + data->width = *width = dw.max.x - dw.min.x + 1; + data->height = *height = dw.max.y - dw.min.y + 1; - for (const MultiViewChannelName &channel : channels) { - IMB_exr_add_channel( - data, nullptr, channel.name.c_str(), channel.view.c_str(), 0, 0, nullptr, false); + if (parse_channels) { + /* Parse channels into view/layer/pass. */ + if (!imb_exr_multilayer_parse_channels_from_file(data)) { + return false; + } + } + else { + /* Read view and channels without parsing. */ + imb_exr_get_views(*data->ifile, *data->multiView); - echan = (ExrChannel *)data->channels.last; - echan->m->name = channel.name; - echan->m->view = channel.view; - echan->m->part_number = channel.part_number; - echan->m->internal_name = channel.internal_name; - } + std::vector<MultiViewChannelName> channels; + GetChannelsInMultiPartFile(*data->ifile, channels); + + for (const MultiViewChannelName &channel : channels) { + IMB_exr_add_channel( + data, nullptr, channel.name.c_str(), channel.view.c_str(), 0, 0, nullptr, false); - return 1; + echan = (ExrChannel *)data->channels.last; + echan->m->name = channel.name; + echan->m->view = channel.view; + echan->m->part_number = channel.part_number; + echan->m->internal_name = channel.internal_name; } } - return 0; + + return true; } /* still clumsy name handling, layers/channels can be ordered as list in list later */ @@ -1112,7 +1127,7 @@ void IMB_exr_write_channels(void *handle) } for (echan = (ExrChannel *)data->channels.first; echan; echan = echan->next) { - /* Writing starts from last scanline, stride negative. */ + /* Writing starts from last scan-line, stride negative. */ if (echan->use_half_float) { float *rect = echan->rect; half *cur = current_rect_half; @@ -1254,7 +1269,7 @@ void IMB_exr_read_channels(void *handle) if (!flip) { /* Inverse correct first pixel for data-window coordinates. */ rect -= echan->xstride * (dw.min.x - dw.min.y * data->width); - /* move to last scanline to flip to Blender convention */ + /* Move to last scan-line to flip to Blender convention. */ rect += echan->xstride * (data->height - 1) * data->width; ystride = -ystride; } @@ -1524,25 +1539,8 @@ static ExrPass *imb_exr_get_pass(ListBase *lb, char *passname) return pass; } -/* creates channels, makes a hierarchy and assigns memory to channels */ -static ExrHandle *imb_exr_begin_read_mem(IStream &file_stream, - MultiPartInputFile &file, - int width, - int height) +static bool imb_exr_multilayer_parse_channels_from_file(ExrHandle *data) { - ExrLayer *lay; - ExrPass *pass; - ExrChannel *echan; - ExrHandle *data = (ExrHandle *)IMB_exr_get_handle(); - int a; - char layname[EXR_TOT_MAXNAME], passname[EXR_TOT_MAXNAME]; - - data->ifile_stream = &file_stream; - data->ifile = &file; - - data->width = width; - data->height = height; - std::vector<MultiViewChannelName> channels; GetChannelsInMultiPartFile(*data->ifile, channels); @@ -1552,7 +1550,7 @@ static ExrHandle *imb_exr_begin_read_mem(IStream &file_stream, IMB_exr_add_channel( data, nullptr, channel.name.c_str(), channel.view.c_str(), 0, 0, nullptr, false); - echan = (ExrChannel *)data->channels.last; + ExrChannel *echan = (ExrChannel *)data->channels.last; echan->m->name = channel.name; echan->m->view = channel.view; echan->m->part_number = channel.part_number; @@ -1561,7 +1559,9 @@ static ExrHandle *imb_exr_begin_read_mem(IStream &file_stream, /* now try to sort out how to assign memory to the channels */ /* first build hierarchical layer list */ - for (echan = (ExrChannel *)data->channels.first; echan; echan = echan->next) { + ExrChannel *echan = (ExrChannel *)data->channels.first; + for (; echan; echan = echan->next) { + char layname[EXR_TOT_MAXNAME], passname[EXR_TOT_MAXNAME]; if (imb_exr_split_channel_name(echan, layname, passname)) { const char *view = echan->m->view.c_str(); @@ -1591,21 +1591,20 @@ static ExrHandle *imb_exr_begin_read_mem(IStream &file_stream, } if (echan) { printf("error, too many channels in one pass: %s\n", echan->m->name.c_str()); - IMB_exr_close(data); - return nullptr; + return false; } /* with some heuristics, try to merge the channels in buffers */ - for (lay = (ExrLayer *)data->layers.first; lay; lay = lay->next) { - for (pass = (ExrPass *)lay->passes.first; pass; pass = pass->next) { + for (ExrLayer *lay = (ExrLayer *)data->layers.first; lay; lay = lay->next) { + for (ExrPass *pass = (ExrPass *)lay->passes.first; pass; pass = pass->next) { if (pass->totchan) { - pass->rect = (float *)MEM_callocN(width * height * pass->totchan * sizeof(float), - "pass rect"); + pass->rect = (float *)MEM_callocN( + data->width * data->height * pass->totchan * sizeof(float), "pass rect"); if (pass->totchan == 1) { - echan = pass->chan[0]; + ExrChannel *echan = pass->chan[0]; echan->rect = pass->rect; echan->xstride = 1; - echan->ystride = width; + echan->ystride = data->width; pass->chan_id[0] = echan->chan_id; } else { @@ -1634,20 +1633,20 @@ static ExrHandle *imb_exr_begin_read_mem(IStream &file_stream, lookup[(unsigned int)'V'] = 1; lookup[(unsigned int)'A'] = 2; } - for (a = 0; a < pass->totchan; a++) { + for (int a = 0; a < pass->totchan; a++) { echan = pass->chan[a]; echan->rect = pass->rect + lookup[(unsigned int)echan->chan_id]; echan->xstride = pass->totchan; - echan->ystride = width * pass->totchan; + echan->ystride = data->width * pass->totchan; pass->chan_id[(unsigned int)lookup[(unsigned int)echan->chan_id]] = echan->chan_id; } } else { /* unknown */ - for (a = 0; a < pass->totchan; a++) { - echan = pass->chan[a]; + for (int a = 0; a < pass->totchan; a++) { + ExrChannel *echan = pass->chan[a]; echan->rect = pass->rect + a; echan->xstride = pass->totchan; - echan->ystride = width * pass->totchan; + echan->ystride = data->width * pass->totchan; pass->chan_id[a] = echan->chan_id; } } @@ -1656,6 +1655,28 @@ static ExrHandle *imb_exr_begin_read_mem(IStream &file_stream, } } + return true; +} + +/* creates channels, makes a hierarchy and assigns memory to channels */ +static ExrHandle *imb_exr_begin_read_mem(IStream &file_stream, + MultiPartInputFile &file, + int width, + int height) +{ + ExrHandle *data = (ExrHandle *)IMB_exr_get_handle(); + + data->ifile_stream = &file_stream; + data->ifile = &file; + + data->width = width; + data->height = height; + + if (!imb_exr_multilayer_parse_channels_from_file(data)) { + IMB_exr_close(data); + return nullptr; + } + return data; } @@ -1988,7 +2009,7 @@ struct ImBuf *imb_load_openexr(const unsigned char *mem, /* Inverse correct first pixel for data-window * coordinates (- dw.min.y because of y flip). */ first = ibuf->rect_float - 4 * (dw.min.x - dw.min.y * width); - /* but, since we read y-flipped (negative y stride) we move to last scanline */ + /* But, since we read y-flipped (negative y stride) we move to last scan-line. */ first += 4 * (height - 1) * width; if (num_rgb_channels > 0) { diff --git a/source/blender/imbuf/intern/openexr/openexr_multi.h b/source/blender/imbuf/intern/openexr/openexr_multi.h index 556717ad618..82a5d161ded 100644 --- a/source/blender/imbuf/intern/openexr/openexr_multi.h +++ b/source/blender/imbuf/intern/openexr/openexr_multi.h @@ -50,13 +50,14 @@ void IMB_exr_add_channel(void *handle, float *rect, bool use_half_float); -int IMB_exr_begin_read(void *handle, const char *filename, int *width, int *height); -int IMB_exr_begin_write(void *handle, - const char *filename, - int width, - int height, - int compress, - const struct StampData *stamp); +bool IMB_exr_begin_read( + void *handle, const char *filename, int *width, int *height, const bool parse_channels); +bool IMB_exr_begin_write(void *handle, + const char *filename, + int width, + int height, + int compress, + const struct StampData *stamp); void IMB_exrtile_begin_write( void *handle, const char *filename, int mipmap, int width, int height, int tilex, int tiley); diff --git a/source/blender/imbuf/intern/openexr/openexr_stub.cpp b/source/blender/imbuf/intern/openexr/openexr_stub.cpp index 51bc2094053..9b4d6178613 100644 --- a/source/blender/imbuf/intern/openexr/openexr_stub.cpp +++ b/source/blender/imbuf/intern/openexr/openexr_stub.cpp @@ -43,21 +43,22 @@ void IMB_exr_add_channel(void * /*handle*/, { } -int IMB_exr_begin_read(void * /*handle*/, - const char * /*filename*/, - int * /*width*/, - int * /*height*/) +bool IMB_exr_begin_read(void * /*handle*/, + const char * /*filename*/, + int * /*width*/, + int * /*height*/, + const bool /*add_channels*/) { - return 0; + return false; } -int IMB_exr_begin_write(void * /*handle*/, - const char * /*filename*/, - int /*width*/, - int /*height*/, - int /*compress*/, - const struct StampData * /*stamp*/) +bool IMB_exr_begin_write(void * /*handle*/, + const char * /*filename*/, + int /*width*/, + int /*height*/, + int /*compress*/, + const struct StampData * /*stamp*/) { - return 0; + return false; } void IMB_exrtile_begin_write(void * /*handle*/, const char * /*filename*/, diff --git a/source/blender/imbuf/intern/radiance_hdr.c b/source/blender/imbuf/intern/radiance_hdr.c index 94b2a62aa26..7f4e4dd31df 100644 --- a/source/blender/imbuf/intern/radiance_hdr.c +++ b/source/blender/imbuf/intern/radiance_hdr.c @@ -294,7 +294,7 @@ struct ImBuf *imb_loadhdr(const unsigned char *mem, break; } for (x = 0; x < width; x++) { - /* convert to ldr */ + /* Convert to LDR. */ RGBE2FLOAT(sline[x], fcol); *rect_float++ = fcol[RED]; *rect_float++ = fcol[GRN]; @@ -328,7 +328,7 @@ static int fwritecolrs( rgbe_scan = (RGBE *)MEM_mallocN(sizeof(RGBE) * width, "radhdr_write_tmpscan"); - /* convert scanline */ + /* Convert scan-line. */ for (size_t i = 0, j = 0; i < width; i++) { if (fpscan) { fcol[RED] = fpscan[j]; diff --git a/source/blender/imbuf/intern/readimage.c b/source/blender/imbuf/intern/readimage.c index 50210650f05..c75bdfa375c 100644 --- a/source/blender/imbuf/intern/readimage.c +++ b/source/blender/imbuf/intern/readimage.c @@ -126,7 +126,7 @@ ImBuf *IMB_ibImageFromMemory(const unsigned char *mem, } if ((flags & IB_test) == 0) { - fprintf(stderr, "%s: unknown fileformat (%s)\n", __func__, descr); + fprintf(stderr, "%s: unknown file-format (%s)\n", __func__, descr); } return NULL; diff --git a/source/blender/imbuf/intern/rotate.c b/source/blender/imbuf/intern/rotate.c index c2fc2190ce5..83dc29aa107 100644 --- a/source/blender/imbuf/intern/rotate.c +++ b/source/blender/imbuf/intern/rotate.c @@ -69,7 +69,7 @@ void IMB_flipy(struct ImBuf *ibuf) topf = ibuf->rect_float; bottomf = topf + 4 * ((y - 1) * x); - linef = MEM_mallocN(4 * x * sizeof(float), "linebuff"); + linef = MEM_mallocN(4 * x * sizeof(float), "linebuf"); y >>= 1; diff --git a/source/blender/imbuf/intern/targa.c b/source/blender/imbuf/intern/targa.c index 8ed0b8b535c..333e29e6d97 100644 --- a/source/blender/imbuf/intern/targa.c +++ b/source/blender/imbuf/intern/targa.c @@ -192,7 +192,7 @@ static bool makebody_tga(ImBuf *ibuf, FILE *file, int (*out)(unsigned int, FILE else { while (*rect++ == this) { /* seek for first different byte */ if (--bytes == 0) { - break; /* oor end of line */ + break; /* Or end of line. */ } } rect--; @@ -470,7 +470,7 @@ static void decodetarga(struct ImBuf *ibuf, const unsigned char *mem, size_t mem if (psize & 2) { if (psize & 1) { - /* order = bgra */ + /* Order = BGRA. */ cp[0] = mem[3]; cp[1] = mem[0]; cp[2] = mem[1]; @@ -512,7 +512,7 @@ static void decodetarga(struct ImBuf *ibuf, const unsigned char *mem, size_t mem while (count > 0) { if (psize & 2) { if (psize & 1) { - /* order = bgra */ + /* Order = BGRA. */ cp[0] = mem[3]; cp[1] = mem[0]; cp[2] = mem[1]; @@ -589,7 +589,7 @@ static void ldtarga(struct ImBuf *ibuf, const unsigned char *mem, size_t mem_siz if (psize & 2) { if (psize & 1) { - /* order = bgra */ + /* Order = BGRA. */ cp[0] = mem[3]; cp[1] = mem[0]; cp[2] = mem[1]; diff --git a/source/blender/io/alembic/intern/abc_customdata.cc b/source/blender/io/alembic/intern/abc_customdata.cc index e3ed897458d..087d60f8896 100644 --- a/source/blender/io/alembic/intern/abc_customdata.cc +++ b/source/blender/io/alembic/intern/abc_customdata.cc @@ -247,13 +247,13 @@ void write_generated_coordinates(const OCompoundProperty &prop, CDStreamConfig & coords[vertex_idx].setValue(orco_yup[0], orco_yup[1], orco_yup[2]); } - if (!config.abc_ocro.valid()) { + if (!config.abc_orco.valid()) { /* Create the Alembic property and keep a reference so future frames can reuse it. */ - config.abc_ocro = OV3fGeomParam(prop, propNameOriginalCoordinates, false, kVertexScope, 1); + config.abc_orco = OV3fGeomParam(prop, propNameOriginalCoordinates, false, kVertexScope, 1); } OV3fGeomParam::Sample sample(coords, kVertexScope); - config.abc_ocro.set(sample); + config.abc_orco.set(sample); } void write_custom_data(const OCompoundProperty &prop, diff --git a/source/blender/io/alembic/intern/abc_customdata.h b/source/blender/io/alembic/intern/abc_customdata.h index 4fba6a2f0f7..03e6f697f0c 100644 --- a/source/blender/io/alembic/intern/abc_customdata.h +++ b/source/blender/io/alembic/intern/abc_customdata.h @@ -79,8 +79,8 @@ struct CDStreamConfig { * 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; + /* ORCO coordinates, aka Generated Coordinates. */ + Alembic::AbcGeom::OV3fGeomParam abc_orco; CDStreamConfig() : mloop(NULL), @@ -122,12 +122,6 @@ void read_custom_data(const std::string &iobject_full_name, const CDStreamConfig &config, const Alembic::Abc::ISampleSelector &iss); -void read_velocity(const Alembic::Abc::ICompoundProperty &prop, - const Alembic::Abc::PropertyHeader *prop_header, - const Alembic::Abc::ISampleSelector &selector, - const CDStreamConfig &config, - const char *velocity_name, - const float velocity_scale); typedef enum { ABC_UV_SCOPE_NONE, ABC_UV_SCOPE_LOOP, diff --git a/source/blender/io/alembic/intern/abc_reader_camera.h b/source/blender/io/alembic/intern/abc_reader_camera.h index 408e9623970..ca8dee80c9d 100644 --- a/source/blender/io/alembic/intern/abc_reader_camera.h +++ b/source/blender/io/alembic/intern/abc_reader_camera.h @@ -23,18 +23,18 @@ namespace blender::io::alembic { -class AbcCameraReader : public AbcObjectReader { +class AbcCameraReader final : public AbcObjectReader { Alembic::AbcGeom::ICameraSchema m_schema; public: AbcCameraReader(const Alembic::Abc::IObject &object, ImportSettings &settings); - bool valid() const; + bool valid() const override; bool accepts_object_type(const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header, const Object *const ob, - const char **err_str) const; + const char **err_str) const override; - void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel); + void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) override; }; } // namespace blender::io::alembic diff --git a/source/blender/io/alembic/intern/abc_reader_curves.h b/source/blender/io/alembic/intern/abc_reader_curves.h index 11b23c8a8cf..df5d68d7850 100644 --- a/source/blender/io/alembic/intern/abc_reader_curves.h +++ b/source/blender/io/alembic/intern/abc_reader_curves.h @@ -31,24 +31,24 @@ struct Curve; namespace blender::io::alembic { -class AbcCurveReader : public AbcObjectReader { +class AbcCurveReader final : public AbcObjectReader { Alembic::AbcGeom::ICurvesSchema m_curves_schema; public: AbcCurveReader(const Alembic::Abc::IObject &object, ImportSettings &settings); - bool valid() const; + bool valid() const override; bool accepts_object_type(const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header, const Object *const ob, - const char **err_str) const; + const char **err_str) const override; - void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel); + void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) override; struct Mesh *read_mesh(struct Mesh *existing_mesh, const Alembic::Abc::ISampleSelector &sample_sel, const int read_flag, const char *velocity_name, const float velocity_scale, - const char **err_str); + const char **err_str) override; void read_curve_sample(Curve *cu, const Alembic::AbcGeom::ICurvesSchema &schema, diff --git a/source/blender/io/alembic/intern/abc_reader_mesh.h b/source/blender/io/alembic/intern/abc_reader_mesh.h index d9f89cc8085..2e34ca8ded0 100644 --- a/source/blender/io/alembic/intern/abc_reader_mesh.h +++ b/source/blender/io/alembic/intern/abc_reader_mesh.h @@ -26,7 +26,7 @@ struct Mesh; namespace blender::io::alembic { -class AbcMeshReader : public AbcObjectReader { +class AbcMeshReader final : public AbcObjectReader { Alembic::AbcGeom::IPolyMeshSchema m_schema; CDStreamConfig m_mesh_data; @@ -60,7 +60,7 @@ class AbcMeshReader : public AbcObjectReader { std::map<std::string, int> &r_mat_map); }; -class AbcSubDReader : public AbcObjectReader { +class AbcSubDReader final : public AbcObjectReader { Alembic::AbcGeom::ISubDSchema m_schema; CDStreamConfig m_mesh_data; @@ -68,17 +68,17 @@ class AbcSubDReader : public AbcObjectReader { public: AbcSubDReader(const Alembic::Abc::IObject &object, ImportSettings &settings); - bool valid() const; + bool valid() const override; bool accepts_object_type(const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header, const Object *const ob, - const char **err_str) const; - void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel); + const char **err_str) const override; + void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) override; struct Mesh *read_mesh(struct Mesh *existing_mesh, const Alembic::Abc::ISampleSelector &sample_sel, const int read_flag, const char *velocity_name, const float velocity_scale, - const char **err_str); + const char **err_str) override; }; void read_mverts(MVert *mverts, diff --git a/source/blender/io/alembic/intern/abc_reader_nurbs.cc b/source/blender/io/alembic/intern/abc_reader_nurbs.cc index 25567aa8c24..4492d1e1ca0 100644 --- a/source/blender/io/alembic/intern/abc_reader_nurbs.cc +++ b/source/blender/io/alembic/intern/abc_reader_nurbs.cc @@ -71,6 +71,26 @@ bool AbcNurbsReader::valid() const return true; } +bool AbcNurbsReader::accepts_object_type( + const Alembic::AbcCoreAbstract::v12::ObjectHeader &alembic_header, + const Object *const ob, + const char **err_str) const +{ + if (!Alembic::AbcGeom::INuPatch::matches(alembic_header)) { + *err_str = + "Object type mismatch, Alembic object path pointed to NURBS when importing, but not any " + "more."; + return false; + } + + if (ob->type != OB_CURVE) { + *err_str = "Object type mismatch, Alembic object path points to NURBS."; + return false; + } + + return true; +} + static bool set_knots(const FloatArraySamplePtr &knots, float *&nu_knots) { if (!knots || knots->size() < 2) { diff --git a/source/blender/io/alembic/intern/abc_reader_nurbs.h b/source/blender/io/alembic/intern/abc_reader_nurbs.h index e8be2efba9f..66e68cf6942 100644 --- a/source/blender/io/alembic/intern/abc_reader_nurbs.h +++ b/source/blender/io/alembic/intern/abc_reader_nurbs.h @@ -23,15 +23,19 @@ namespace blender::io::alembic { -class AbcNurbsReader : public AbcObjectReader { +class AbcNurbsReader final : public AbcObjectReader { std::vector<std::pair<Alembic::AbcGeom::INuPatchSchema, Alembic::Abc::IObject>> m_schemas; public: AbcNurbsReader(const Alembic::Abc::IObject &object, ImportSettings &settings); - bool valid() const; + bool valid() const override; - void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel); + bool accepts_object_type(const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header, + const Object *const ob, + const char **err_str) const override; + + void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) override; private: void getNurbsPatches(const Alembic::Abc::IObject &obj); diff --git a/source/blender/io/alembic/intern/abc_reader_points.cc b/source/blender/io/alembic/intern/abc_reader_points.cc index 3aeacbd14fe..75ed18f57f2 100644 --- a/source/blender/io/alembic/intern/abc_reader_points.cc +++ b/source/blender/io/alembic/intern/abc_reader_points.cc @@ -82,7 +82,7 @@ bool AbcPointsReader::accepts_object_type( void AbcPointsReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) { Mesh *mesh = BKE_mesh_add(bmain, m_data_name.c_str()); - Mesh *read_mesh = this->read_mesh(mesh, sample_sel, 0, nullptr); + Mesh *read_mesh = this->read_mesh(mesh, sample_sel, 0, "", 0.0f, nullptr); if (read_mesh != mesh) { BKE_mesh_nomain_to_mesh(read_mesh, mesh, m_object, &CD_MASK_MESH, true); @@ -127,6 +127,8 @@ void read_points_sample(const IPointsSchema &schema, struct Mesh *AbcPointsReader::read_mesh(struct Mesh *existing_mesh, const ISampleSelector &sample_sel, int read_flag, + const char * /*velocity_name*/, + const float /*velocity_scale*/, const char **err_str) { IPointsSchema::Sample sample; diff --git a/source/blender/io/alembic/intern/abc_reader_points.h b/source/blender/io/alembic/intern/abc_reader_points.h index aed66699c75..105d1276f7a 100644 --- a/source/blender/io/alembic/intern/abc_reader_points.h +++ b/source/blender/io/alembic/intern/abc_reader_points.h @@ -27,24 +27,26 @@ namespace blender::io::alembic { -class AbcPointsReader : public AbcObjectReader { +class AbcPointsReader final : public AbcObjectReader { Alembic::AbcGeom::IPointsSchema m_schema; Alembic::AbcGeom::IPointsSchema::Sample m_sample; public: AbcPointsReader(const Alembic::Abc::IObject &object, ImportSettings &settings); - bool valid() const; + bool valid() const override; bool accepts_object_type(const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header, const Object *const ob, - const char **err_str) const; + const char **err_str) const override; - void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel); + void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) override; struct Mesh *read_mesh(struct Mesh *existing_mesh, const Alembic::Abc::ISampleSelector &sample_sel, int read_flag, - const char **err_str); + const char *velocity_name, + const float velocity_scale, + const char **err_str) override; }; void read_points_sample(const Alembic::AbcGeom::IPointsSchema &schema, diff --git a/source/blender/io/alembic/intern/abc_reader_transform.h b/source/blender/io/alembic/intern/abc_reader_transform.h index e515560912f..6810cc214b7 100644 --- a/source/blender/io/alembic/intern/abc_reader_transform.h +++ b/source/blender/io/alembic/intern/abc_reader_transform.h @@ -25,18 +25,18 @@ namespace blender::io::alembic { -class AbcEmptyReader : public AbcObjectReader { +class AbcEmptyReader final : public AbcObjectReader { Alembic::AbcGeom::IXformSchema m_schema; public: AbcEmptyReader(const Alembic::Abc::IObject &object, ImportSettings &settings); - bool valid() const; + bool valid() const override; bool accepts_object_type(const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header, const Object *const ob, - const char **err_str) const; + const char **err_str) const override; - void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel); + void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) override; }; } // namespace blender::io::alembic diff --git a/source/blender/makesdna/DNA_asset_types.h b/source/blender/makesdna/DNA_asset_types.h index 2975915eccd..f5bdad3e79e 100644 --- a/source/blender/makesdna/DNA_asset_types.h +++ b/source/blender/makesdna/DNA_asset_types.h @@ -22,6 +22,7 @@ #include "DNA_defs.h" #include "DNA_listBase.h" +#include "DNA_uuid_types.h" #ifdef __cplusplus extern "C" { @@ -58,6 +59,19 @@ typedef struct AssetMetaData { /** Custom asset meta-data. Cannot store pointers to IDs (#STRUCT_NO_DATABLOCK_IDPROPERTIES)! */ struct IDProperty *properties; + /** + * Asset Catalog identifier. Should not contain spaces. + * Mapped to a path in the asset catalog hierarchy by an #AssetCatalogService. + * Use #BKE_asset_metadata_catalog_id_set() to ensure a valid ID is set. + */ + struct bUUID catalog_id; + /** + * Short name of the asset's catalog. This is for debugging purposes only, to allow (partial) + * reconstruction of asset catalogs in the unfortunate case that the mapping from catalog UUID to + * catalog path is lost. The catalog's simple name is copied to #catalog_simple_name whenever + * #catalog_id is updated. */ + char catalog_simple_name[64]; /* MAX_NAME */ + /** Optional description of this asset for display in the UI. Dynamic length. */ char *description; /** User defined tags for this asset. The asset manager uses these for filtering, but how they diff --git a/source/blender/makesdna/DNA_cloth_types.h b/source/blender/makesdna/DNA_cloth_types.h index e2eea5e5422..49b2862032f 100644 --- a/source/blender/makesdna/DNA_cloth_types.h +++ b/source/blender/makesdna/DNA_cloth_types.h @@ -42,7 +42,7 @@ extern "C" { */ typedef struct ClothSimSettings { - /** UNUSED atm. */ + /** UNUSED. */ struct LinkNode *cache; /** See SB. */ float mingoal; diff --git a/source/blender/makesdna/DNA_constraint_types.h b/source/blender/makesdna/DNA_constraint_types.h index 4b4c24a7a4e..28756395f7d 100644 --- a/source/blender/makesdna/DNA_constraint_types.h +++ b/source/blender/makesdna/DNA_constraint_types.h @@ -81,8 +81,8 @@ typedef struct bConstraint { /** Local influence ipo or driver */ struct Ipo *ipo DNA_DEPRECATED; - /* below are readonly fields that are set at runtime - * by the solver for use in the GE (only IK atm) */ + /* Below are read-only fields that are set at runtime + * by the solver for use in the GE (only IK at the moment). */ /** Residual error on constraint expressed in blender unit. */ float lin_error; /** Residual error on constraint expressed in radiant. */ diff --git a/source/blender/makesdna/DNA_customdata_types.h b/source/blender/makesdna/DNA_customdata_types.h index 6acea8da15f..36bdd4915ff 100644 --- a/source/blender/makesdna/DNA_customdata_types.h +++ b/source/blender/makesdna/DNA_customdata_types.h @@ -84,7 +84,8 @@ typedef struct CustomData { * MUST be >= CD_NUMTYPES, but we can't use a define here. * Correct size is ensured in CustomData_update_typemap assert(). */ - int typemap[51]; + int typemap[52]; + char _pad[4]; /** Number of layers, size of layers array. */ int totlayer, maxlayer; /** In editmode, total size of all data layers. */ @@ -166,7 +167,9 @@ typedef enum CustomDataType { CD_PROP_BOOL = 50, - CD_NUMTYPES = 51, + CD_HAIRLENGTH = 51, + + CD_NUMTYPES = 52, } CustomDataType; /* Bits for CustomDataMask */ @@ -220,6 +223,8 @@ typedef enum CustomDataType { #define CD_MASK_PROP_FLOAT2 (1ULL << CD_PROP_FLOAT2) #define CD_MASK_PROP_BOOL (1ULL << CD_PROP_BOOL) +#define CD_MASK_HAIRLENGTH (1ULL << CD_HAIRLENGTH) + /** Multires loop data. */ #define CD_MASK_MULTIRES_GRIDS (CD_MASK_MDISPS | CD_GRID_PAINT_MASK) diff --git a/source/blender/makesdna/DNA_gpencil_modifier_defaults.h b/source/blender/makesdna/DNA_gpencil_modifier_defaults.h index 2a3c6f4e3db..11299ae9717 100644 --- a/source/blender/makesdna/DNA_gpencil_modifier_defaults.h +++ b/source/blender/makesdna/DNA_gpencil_modifier_defaults.h @@ -318,7 +318,7 @@ .calculation_flags = LRT_ALLOW_DUPLI_OBJECTS | LRT_ALLOW_CLIPPING_BOUNDARIES | LRT_USE_CREASE_ON_SHARP_EDGES, \ .angle_splitting_threshold = DEG2RAD(60.0f), \ .chaining_image_threshold = 0.001f, \ - .overscan = 0.1f,\ + .chain_smooth_tolerance = 0.2f,\ } #define _DNA_DEFAULT_LengthGpencilModifierData \ diff --git a/source/blender/makesdna/DNA_gpencil_modifier_types.h b/source/blender/makesdna/DNA_gpencil_modifier_types.h index 8d967a38808..ea5c81761c6 100644 --- a/source/blender/makesdna/DNA_gpencil_modifier_types.h +++ b/source/blender/makesdna/DNA_gpencil_modifier_types.h @@ -1040,7 +1040,11 @@ typedef struct LineartGpencilModifierData { /** `0..PI` angle, for splitting strokes at sharp points. */ float angle_splitting_threshold; - /* Doubles as geometry threshold when geometry space chaining is enabled */ + /** Strength for smoothing jagged chains. */ + float chain_smooth_tolerance; + int _pad1; + + /* CPU mode */ float chaining_image_threshold; /* Ported from SceneLineArt flags. */ diff --git a/source/blender/makesdna/DNA_gpencil_types.h b/source/blender/makesdna/DNA_gpencil_types.h index 68bd2961f23..0f570f8603d 100644 --- a/source/blender/makesdna/DNA_gpencil_types.h +++ b/source/blender/makesdna/DNA_gpencil_types.h @@ -49,6 +49,8 @@ struct MDeformVert; #define GPENCIL_MIN_FILL_FAC 0.05f #define GPENCIL_MAX_FILL_FAC 8.0f +#define GPENCIL_MAX_THICKNESS 5000 + /* ***************************************** */ /* GP Stroke Points */ diff --git a/source/blender/makesdna/DNA_modifier_types.h b/source/blender/makesdna/DNA_modifier_types.h index 31daa778b03..631db64ddd3 100644 --- a/source/blender/makesdna/DNA_modifier_types.h +++ b/source/blender/makesdna/DNA_modifier_types.h @@ -848,7 +848,7 @@ typedef struct CollisionModifierData { struct MVert *x; /** Position at the end of the frame. */ struct MVert *xnew; - /** Unused atm, but was discussed during sprint. */ + /** Unused at the moment, but was discussed during sprint. */ struct MVert *xold; /** New position at the actual inter-frame step. */ struct MVert *current_xnew; diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index 18545666796..ea87cef1118 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -120,7 +120,10 @@ typedef struct bNodeSocket { /* XXX deprecated, kept for forward compatibility */ short stack_type DNA_DEPRECATED; char display_shape; - char _pad[1]; + + /* #AttributeDomain used when the geometry nodes modifier creates an attribute for a group + * output. */ + char attribute_domain; /* Runtime-only cache of the number of input links, for multi-input sockets. */ short total_inputs; @@ -445,6 +448,7 @@ typedef struct bNodeLink { #define NODE_LINK_TEST (1 << 2) /* free test flag, undefined */ #define NODE_LINK_TEMP_HIGHLIGHT (1 << 3) /* Link is highlighted for picking. */ #define NODE_LINK_MUTED (1 << 4) /* Link is muted. */ +#define NODE_LINK_DRAGGED (1 << 5) /* Node link is being dragged by the user. */ /* tree->edit_quality/tree->render_quality */ #define NTREE_QUALITY_HIGH 0 @@ -459,6 +463,16 @@ typedef struct bNodeLink { #define NTREE_CHUNKSIZE_512 512 #define NTREE_CHUNKSIZE_1024 1024 +/** Workaround to forward-declare C++ type in C header. */ +#ifdef __cplusplus +namespace blender::nodes { +struct FieldInferencingInterface; +} +using FieldInferencingInterfaceHandle = blender::nodes::FieldInferencingInterface; +#else +typedef struct FieldInferencingInterfaceHandle FieldInferencingInterfaceHandle; +#endif + /* the basis for a Node tree, all links and nodes reside internal here */ /* only re-usable node trees are in the library though, * materials and textures allocate own tree struct */ @@ -481,6 +495,8 @@ typedef struct bNodeTree { float view_center[2]; ListBase nodes, links; + /** Information about how inputs and outputs of the node group interact with fields. */ + FieldInferencingInterfaceHandle *field_inferencing_interface; /** Set init on fileread. */ int type, init; @@ -575,6 +591,9 @@ typedef enum eNodeTreeUpdate { NTREE_UPDATE_NODES = (1 << 1), /* nodes or sockets have been added or removed */ NTREE_UPDATE_GROUP_IN = (1 << 4), /* group inputs have changed */ NTREE_UPDATE_GROUP_OUT = (1 << 5), /* group outputs have changed */ + /* The field interface has changed. So e.g. an output that was always a field before is not + * anymore. This implies that the field type inferencing has to be done again. */ + NTREE_UPDATE_FIELD_INFERENCING = (1 << 6), /* group has changed (generic flag including all other group flags) */ NTREE_UPDATE_GROUP = (NTREE_UPDATE_GROUP_IN | NTREE_UPDATE_GROUP_OUT), } eNodeTreeUpdate; @@ -836,7 +855,7 @@ typedef struct NodeVertexCol { char name[64]; } NodeVertexCol; -/* qdn: Defocus blur node */ +/** Defocus blur node. */ typedef struct NodeDefocus { char bktype, _pad0, preview, gamco; short samples, no_zbuf; @@ -852,7 +871,7 @@ typedef struct NodeScriptDict { void *node; } NodeScriptDict; -/* qdn: glare node */ +/** glare node. */ typedef struct NodeGlare { char quality, type, iter; /* XXX angle is only kept for backward/forward compatibility, @@ -863,14 +882,14 @@ typedef struct NodeGlare { char _pad1[4]; } NodeGlare; -/* qdn: tonemap node */ +/** Tonemap node. */ typedef struct NodeTonemap { float key, offset, gamma; float f, m, a, c; int type; } NodeTonemap; -/* qdn: lens distortion node */ +/** Lens distortion node. */ typedef struct NodeLensDist { short jit, proj, fit; char _pad[2]; @@ -1222,6 +1241,11 @@ typedef struct NodeAttributeMix { uint8_t input_type_b; } NodeAttributeMix; +typedef struct NodeRandomValue { + /* CustomDataType. */ + uint8_t data_type; +} NodeRandomValue; + typedef struct NodeAttributeRandomize { /* CustomDataType. */ uint8_t data_type; @@ -1338,6 +1362,11 @@ typedef struct NodeGeometryAttributeProximity { uint8_t target_geometry_element; } NodeGeometryAttributeProximity; +typedef struct NodeGeometryProximity { + /* GeometryNodeProximityTargetType. */ + uint8_t target_element; +} NodeGeometryProximity; + typedef struct NodeGeometryVolumeToMesh { /* VolumeToMeshResolutionMode */ uint8_t resolution_mode; @@ -1487,6 +1516,11 @@ typedef struct NodeGeometryCurveFill { uint8_t mode; } NodeGeometryCurveFill; +typedef struct NodeGeometryMeshToPoints { + /* GeometryNodeMeshToPointsMode */ + uint8_t mode; +} NodeGeometryMeshToPoints; + typedef struct NodeGeometryAttributeCapture { /* CustomDataType. */ int8_t data_type; @@ -1494,6 +1528,16 @@ typedef struct NodeGeometryAttributeCapture { int8_t domain; } NodeGeometryAttributeCapture; +typedef struct NodeGeometryStringToCurves { + /* GeometryNodeStringToCurvesOverflowMode */ + uint8_t overflow; + /* GeometryNodeStringToCurvesAlignXMode */ + uint8_t align_x; + /* GeometryNodeStringToCurvesAlignYMode */ + uint8_t align_y; + char _pad[1]; +} NodeGeometryStringToCurves; + /* script node mode */ #define NODE_SCRIPT_INTERNAL 0 #define NODE_SCRIPT_EXTERNAL 1 @@ -1907,6 +1951,12 @@ typedef enum GeometryNodeAttributeProximityTargetType { GEO_NODE_PROXIMITY_TARGET_FACES = 2, } GeometryNodeAttributeProximityTargetType; +typedef enum GeometryNodeProximityTargetType { + GEO_NODE_PROX_TARGET_POINTS = 0, + GEO_NODE_PROX_TARGET_EDGES = 1, + GEO_NODE_PROX_TARGET_FACES = 2, +} GeometryNodeProximityTargetType; + typedef enum GeometryNodeBooleanOperation { GEO_NODE_BOOLEAN_INTERSECT = 0, GEO_NODE_BOOLEAN_UNION = 1, @@ -1972,11 +2022,21 @@ typedef enum GeometryNodePointDistributeMode { GEO_NODE_POINT_DISTRIBUTE_POISSON = 1, } GeometryNodePointDistributeMode; +typedef enum GeometryNodeDistributePointsOnFacesMode { + GEO_NODE_POINT_DISTRIBUTE_POINTS_ON_FACES_RANDOM = 0, + GEO_NODE_POINT_DISTRIBUTE_POINTS_ON_FACES_POISSON = 1, +} GeometryNodeDistributePointsOnFacesMode; + typedef enum GeometryNodeRotatePointsType { GEO_NODE_POINT_ROTATE_TYPE_EULER = 0, GEO_NODE_POINT_ROTATE_TYPE_AXIS_ANGLE = 1, } GeometryNodeRotatePointsType; +typedef enum FunctionNodeRotatePointsType { + FN_NODE_ROTATE_EULER_TYPE_EULER = 0, + FN_NODE_ROTATE_EULER_TYPE_AXIS_ANGLE = 1, +} FunctionNodeRotatePointsType; + typedef enum GeometryNodeAttributeVectorRotateMode { GEO_NODE_VECTOR_ROTATE_TYPE_AXIS = 0, GEO_NODE_VECTOR_ROTATE_TYPE_AXIS_X = 1, @@ -1997,6 +2057,11 @@ typedef enum GeometryNodeRotatePointsSpace { GEO_NODE_POINT_ROTATE_SPACE_POINT = 1, } GeometryNodeRotatePointsSpace; +typedef enum FunctionNodeRotateEulerSpace { + FN_NODE_ROTATE_EULER_SPACE_OBJECT = 0, + FN_NODE_ROTATE_EULER_SPACE_POINT = 1, +} FunctionNodeRotateEulerSpace; + typedef enum GeometryNodeAlignRotationToVectorAxis { GEO_NODE_ALIGN_ROTATION_TO_VECTOR_AXIS_X = 0, GEO_NODE_ALIGN_ROTATION_TO_VECTOR_AXIS_Y = 1, @@ -2085,6 +2150,35 @@ typedef enum GeometryNodeCurveFillMode { GEO_NODE_CURVE_FILL_MODE_NGONS = 1, } GeometryNodeCurveFillMode; +typedef enum GeometryNodeMeshToPointsMode { + GEO_NODE_MESH_TO_POINTS_VERTICES = 0, + GEO_NODE_MESH_TO_POINTS_EDGES = 1, + GEO_NODE_MESH_TO_POINTS_FACES = 2, + GEO_NODE_MESH_TO_POINTS_CORNERS = 3, +} GeometryNodeMeshToPointsMode; + +typedef enum GeometryNodeStringToCurvesOverflowMode { + GEO_NODE_STRING_TO_CURVES_MODE_OVERFLOW = 0, + GEO_NODE_STRING_TO_CURVES_MODE_SCALE_TO_FIT = 1, + GEO_NODE_STRING_TO_CURVES_MODE_TRUNCATE = 2, +} GeometryNodeStringToCurvesOverflowMode; + +typedef enum GeometryNodeStringToCurvesAlignXMode { + GEO_NODE_STRING_TO_CURVES_ALIGN_X_LEFT = 0, + GEO_NODE_STRING_TO_CURVES_ALIGN_X_CENTER = 1, + GEO_NODE_STRING_TO_CURVES_ALIGN_X_RIGHT = 2, + GEO_NODE_STRING_TO_CURVES_ALIGN_X_JUSTIFY = 3, + GEO_NODE_STRING_TO_CURVES_ALIGN_X_FLUSH = 4, +} GeometryNodeStringToCurvesAlignXMode; + +typedef enum GeometryNodeStringToCurvesAlignYMode { + GEO_NODE_STRING_TO_CURVES_ALIGN_Y_TOP_BASELINE = 0, + GEO_NODE_STRING_TO_CURVES_ALIGN_Y_TOP = 1, + GEO_NODE_STRING_TO_CURVES_ALIGN_Y_MIDDLE = 2, + GEO_NODE_STRING_TO_CURVES_ALIGN_Y_BOTTOM_BASELINE = 3, + GEO_NODE_STRING_TO_CURVES_ALIGN_Y_BOTTOM = 4, +} GeometryNodeStringToCurvesAlignYMode; + #ifdef __cplusplus } #endif diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h index b28c3ac2b85..9a5c51825e1 100644 --- a/source/blender/makesdna/DNA_scene_types.h +++ b/source/blender/makesdna/DNA_scene_types.h @@ -1463,14 +1463,15 @@ typedef struct ToolSettings { char edge_mode_live_unwrap; - char _pad1[1]; - /* Transform */ char transform_pivot_point; char transform_flag; - char snap_mode, snap_node_mode; + char snap_mode; + char snap_node_mode; char snap_uv_mode; char snap_flag; + /** UV equivalent of `snap_flag`, limited to: #SCE_SNAP_ABS_GRID. */ + char snap_uv_flag; char snap_target; char snap_transform_mode_flag; diff --git a/source/blender/makesdna/DNA_sequence_types.h b/source/blender/makesdna/DNA_sequence_types.h index 25330acd486..a71f86eae9f 100644 --- a/source/blender/makesdna/DNA_sequence_types.h +++ b/source/blender/makesdna/DNA_sequence_types.h @@ -79,9 +79,13 @@ typedef struct StripTransform { } StripTransform; typedef struct StripColorBalance { + int method; float lift[3]; float gamma[3]; float gain[3]; + float slope[3]; + float offset[3]; + float power[3]; int flag; char _pad[4]; /* float exposure; */ @@ -232,15 +236,21 @@ typedef struct Sequence { int blend_mode; float blend_opacity; + /* Tag color showed if `SEQ_TIMELINE_SHOW_STRIP_COLOR_TAG` is set. */ + int8_t color_tag; + + char alpha_mode; + char _pad4[2]; + + int cache_flag; + /* is sfra needed anymore? - it looks like its only used in one place */ /** Starting frame according to the timeline of the scene. */ int sfra; - char alpha_mode; - char _pad[2]; - /* Multiview */ char views_format; + char _pad[3]; struct Stereo3dFormat *stereo3d_format; struct IDProperty *prop; @@ -248,9 +258,6 @@ typedef struct Sequence { /* modifiers */ ListBase modifiers; - int cache_flag; - int _pad2[3]; - SequenceRuntime runtime; } Sequence; @@ -431,6 +438,11 @@ typedef struct ColorBalanceModifierData { float color_multiply; } ColorBalanceModifierData; +enum { + SEQ_COLOR_BALANCE_METHOD_LIFTGAMMAGAIN = 0, + SEQ_COLOR_BALANCE_METHOD_SLOPEOFFSETPOWER = 1, +}; + typedef struct CurvesModifierData { SequenceModifierData modifier; @@ -486,7 +498,7 @@ typedef struct SequencerScopes { struct ImBuf *histogram_ibuf; } SequencerScopes; -#define MAXSEQ 32 +#define MAXSEQ 128 #define SELECT 1 @@ -568,6 +580,9 @@ enum { #define SEQ_COLOR_BALANCE_INVERSE_GAIN 1 #define SEQ_COLOR_BALANCE_INVERSE_GAMMA 2 #define SEQ_COLOR_BALANCE_INVERSE_LIFT 4 +#define SEQ_COLOR_BALANCE_INVERSE_SLOPE 8 +#define SEQ_COLOR_BALANCE_INVERSE_OFFSET 16 +#define SEQ_COLOR_BALANCE_INVERSE_POWER 32 /* !!! has to be same as IMB_imbuf.h IMB_PROXY_... and IMB_TC_... */ @@ -727,6 +742,22 @@ enum { SEQ_CACHE_STORE_THUMBNAIL = (1 << 12), }; +/* Sequence->color_tag. */ +typedef enum SequenceColorTag { + SEQUENCE_COLOR_NONE = -1, + SEQUENCE_COLOR_01, + SEQUENCE_COLOR_02, + SEQUENCE_COLOR_03, + SEQUENCE_COLOR_04, + SEQUENCE_COLOR_05, + SEQUENCE_COLOR_06, + SEQUENCE_COLOR_07, + SEQUENCE_COLOR_08, + SEQUENCE_COLOR_09, + + SEQUENCE_COLOR_TOT, +} SequenceColorTag; + #ifdef __cplusplus } #endif diff --git a/source/blender/makesdna/DNA_space_types.h b/source/blender/makesdna/DNA_space_types.h index e849039fa93..aa74e7712c0 100644 --- a/source/blender/makesdna/DNA_space_types.h +++ b/source/blender/makesdna/DNA_space_types.h @@ -599,6 +599,7 @@ typedef struct SequencerTimelineOverlay { typedef enum eSpaceSeq_SequencerTimelineOverlay_Flag { SEQ_TIMELINE_SHOW_STRIP_OFFSETS = (1 << 1), SEQ_TIMELINE_SHOW_THUMBNAILS = (1 << 2), + SEQ_TIMELINE_SHOW_STRIP_COLOR_TAG = (1 << 3), /* use Sequence->color_tag */ SEQ_TIMELINE_SHOW_FCURVES = (1 << 5), SEQ_TIMELINE_ALL_WAVEFORMS = (1 << 7), /* draw all waveforms */ SEQ_TIMELINE_NO_WAVEFORMS = (1 << 8), /* draw no waveforms */ @@ -805,14 +806,25 @@ typedef struct FileAssetSelectParams { FileSelectParams base_params; AssetLibraryReference asset_library_ref; + short asset_catalog_visibility; /* eFileSel_Params_AssetCatalogVisibility */ + char _pad[6]; + /** If #asset_catalog_visibility is #FILE_SHOW_ASSETS_FROM_CATALOG, this sets the ID of the + * catalog to show. */ + bUUID catalog_id; short import_type; /* eFileAssetImportType */ - char _pad[6]; + char _pad2[6]; } FileAssetSelectParams; typedef enum eFileAssetImportType { + /** Regular data-block linking. */ FILE_ASSET_IMPORT_LINK = 0, + /** Regular data-block appending (basically linking + "Make Local"). */ FILE_ASSET_IMPORT_APPEND = 1, + /** Append data-block with the #BLO_LIBLINK_APPEND_LOCAL_ID_REUSE flag enabled. Some typically + * heavy data dependencies (e.g. the image data-blocks of a material, the mesh of an object) may + * be reused from an earlier append. */ + FILE_ASSET_IMPORT_APPEND_REUSE = 2, } eFileAssetImportType; /** @@ -965,7 +977,10 @@ enum eFileDetails { typedef enum eFileSelectType { FILE_LOADLIB = 1, FILE_MAIN = 2, + /** Load assets from #Main. */ FILE_MAIN_ASSET = 3, + /** Load assets of an asset library containing external files. */ + FILE_ASSET_LIBRARY = 4, FILE_UNIX = 8, FILE_BLENDER = 8, /* don't display relative paths */ @@ -984,23 +999,31 @@ typedef enum eFileSel_Action { * (WM and BLO code area, see #eBLOLibLinkFlags in BLO_readfile.h). */ typedef enum eFileSel_Params_Flag { - FILE_APPEND_SET_FAKEUSER = (1 << 0), + FILE_PARAMS_FLAG_UNUSED_1 = (1 << 0), FILE_RELPATH = (1 << 1), FILE_LINK = (1 << 2), FILE_HIDE_DOT = (1 << 3), FILE_AUTOSELECT = (1 << 4), FILE_ACTIVE_COLLECTION = (1 << 5), - FILE_APPEND_RECURSIVE = (1 << 6), + FILE_PARAMS_FLAG_UNUSED_2 = (1 << 6), FILE_DIRSEL_ONLY = (1 << 7), FILE_FILTER = (1 << 8), - FILE_OBDATA_INSTANCE = (1 << 9), - FILE_COLLECTION_INSTANCE = (1 << 10), + FILE_PARAMS_FLAG_UNUSED_3 = (1 << 9), + FILE_PARAMS_FLAG_UNUSED_4 = (1 << 10), FILE_SORT_INVERT = (1 << 11), FILE_HIDE_TOOL_PROPS = (1 << 12), FILE_CHECK_EXISTING = (1 << 13), FILE_ASSETS_ONLY = (1 << 14), + /** Enables filtering by asset catalog. */ + FILE_FILTER_ASSET_CATALOG = (1 << 15), } eFileSel_Params_Flag; +typedef enum eFileSel_Params_AssetCatalogVisibility { + FILE_SHOW_ASSETS_ALL_CATALOGS, + FILE_SHOW_ASSETS_FROM_CATALOG, + FILE_SHOW_ASSETS_WITHOUT_CATALOG, +} eFileSel_Params_AssetCatalogVisibility; + /* sfile->params->rename_flag */ /* NOTE: short flag. Defined as bitflags, but currently only used as exclusive status markers... */ typedef enum eFileSel_Params_RenameFlag { @@ -1175,13 +1198,10 @@ typedef struct SpaceImage { char mode_prev; char pin; - char _pad1; - /** - * The currently active tile of the image when tile is enabled, - * is kept in sync with the active faces tile. - */ - short curtile; - short lock; + + char pixel_snap_mode; + + char lock; /** UV draw type. */ char dt_uv; /** Sticky selection type. */ @@ -1189,14 +1209,19 @@ typedef struct SpaceImage { char dt_uvstretch; char around; - int flag; + char _pad1[3]; - char pixel_snap_mode; - char _pad2[7]; + int flag; float uv_opacity; int tile_grid_shape[2]; + /** + * UV editor custom-grid. Value of `N` will produce `NxN` grid. + * Use when #SI_CUSTOM_GRID is set. + */ + int custom_grid_subdiv; + char _pad3[4]; MaskSpaceInfo mask_info; SpaceImageOverlay overlay; @@ -1252,6 +1277,7 @@ typedef enum eSpaceImage_Flag { SI_FLAG_UNUSED_7 = (1 << 7), /* cleared */ SI_FLAG_UNUSED_8 = (1 << 8), /* cleared */ SI_COORDFLOATS = (1 << 9), + SI_FLAG_UNUSED_10 = (1 << 10), SI_LIVE_UNWRAP = (1 << 11), SI_USE_ALPHA = (1 << 12), @@ -1263,7 +1289,7 @@ typedef enum eSpaceImage_Flag { SI_FULLWINDOW = (1 << 16), SI_FLAG_UNUSED_17 = (1 << 17), - SI_FLAG_UNUSED_18 = (1 << 18), /* cleared */ + SI_CUSTOM_GRID = (1 << 18), /** * This means that the image is drawn until it reaches the view edge, @@ -1289,6 +1315,9 @@ typedef enum eSpaceImageOverlay_Flag { SI_OVERLAY_SHOW_OVERLAYS = (1 << 0), } eSpaceImageOverlay_Flag; +/** Keep in sync with `STEPS_LEN` in `grid_frag.glsl`. */ +#define SI_GRID_STEPS_LEN 8 + /** \} */ /* -------------------------------------------------------------------- */ diff --git a/source/blender/makesdna/DNA_tracking_types.h b/source/blender/makesdna/DNA_tracking_types.h index fb2d985d353..0e313183300 100644 --- a/source/blender/makesdna/DNA_tracking_types.h +++ b/source/blender/makesdna/DNA_tracking_types.h @@ -398,6 +398,8 @@ typedef struct MovieTrackingDopesheetChannel { int *segments; /** Longest segment length and total number of tracked frames. */ int max_segment, total_frames; + /** These numbers are valid only if tot_segment > 0. */ + int first_not_disabled_marker_framenr, last_not_disabled_marker_framenr; } MovieTrackingDopesheetChannel; typedef struct MovieTrackingDopesheetCoverageSegment { @@ -592,6 +594,8 @@ enum { TRACKING_DOPE_SORT_LONGEST = 1, TRACKING_DOPE_SORT_TOTAL = 2, TRACKING_DOPE_SORT_AVERAGE_ERROR = 3, + TRACKING_DOPE_SORT_START = 4, + TRACKING_DOPE_SORT_END = 5, }; /* MovieTrackingDopesheet->flag */ diff --git a/source/blender/makesdna/DNA_userdef_types.h b/source/blender/makesdna/DNA_userdef_types.h index 4f86201ced2..291f6de5ba2 100644 --- a/source/blender/makesdna/DNA_userdef_types.h +++ b/source/blender/makesdna/DNA_userdef_types.h @@ -458,6 +458,10 @@ typedef struct ThemeCollectionColor { unsigned char color[4]; } ThemeCollectionColor; +typedef struct ThemeStripColor { + unsigned char color[4]; +} ThemeStripColor; + /** * A theme. * @@ -500,8 +504,10 @@ typedef struct bTheme { /* See COLLECTION_COLOR_TOT for the number of collection colors. */ ThemeCollectionColor collection_color[8]; + /* See SEQUENCE_COLOR_TOT for the total number of strip colors. */ + ThemeStripColor strip_color[9]; + int active_theme_area; - char _pad0[4]; } bTheme; #define UI_THEMESPACE_START(btheme) \ @@ -635,7 +641,9 @@ typedef struct UserDef_Experimental { /* Debug options, always available. */ char use_undo_legacy; char no_override_auto_resync; + char no_proxy_to_override_conversion; char use_cycles_debug; + char use_geometry_nodes_legacy; char SANITIZE_AFTER_HERE; /* The following options are automatically sanitized (set to 0) * when the release cycle is not alpha. */ @@ -646,8 +654,7 @@ typedef struct UserDef_Experimental { char use_sculpt_tools_tilt; char use_extended_asset_browser; char use_override_templates; - char use_geometry_nodes_fields; - char _pad[4]; + char _pad[3]; /** `makesdna` does not allow empty structs. */ } UserDef_Experimental; diff --git a/source/blender/makesdna/DNA_uuid_types.h b/source/blender/makesdna/DNA_uuid_types.h index fa0a78f074b..dcebfed6be7 100644 --- a/source/blender/makesdna/DNA_uuid_types.h +++ b/source/blender/makesdna/DNA_uuid_types.h @@ -40,6 +40,12 @@ typedef struct bUUID { uint8_t node[6]; } bUUID; +/** + * Memory required for a string representation of a UUID according to RFC4122. + * This is 36 characters for the string + a trailing zero byte. + */ +#define UUID_STRING_LEN 37 + #ifdef __cplusplus } #endif diff --git a/source/blender/makesrna/RNA_access.h b/source/blender/makesrna/RNA_access.h index ce53e3390e1..188f933dba5 100644 --- a/source/blender/makesrna/RNA_access.h +++ b/source/blender/makesrna/RNA_access.h @@ -558,6 +558,7 @@ extern StructRNA RNA_ShaderFxWave; extern StructRNA RNA_ShaderNode; extern StructRNA RNA_ShaderNodeCameraData; extern StructRNA RNA_ShaderNodeCombineRGB; +extern StructRNA RNA_ShaderNodeFloatCurve; extern StructRNA RNA_ShaderNodeGamma; extern StructRNA RNA_ShaderNodeHueSaturation; extern StructRNA RNA_ShaderNodeInvert; diff --git a/source/blender/makesrna/RNA_enum_items.h b/source/blender/makesrna/RNA_enum_items.h index c8f44262020..03d371be1f7 100644 --- a/source/blender/makesrna/RNA_enum_items.h +++ b/source/blender/makesrna/RNA_enum_items.h @@ -211,6 +211,7 @@ DEF_ENUM(rna_enum_attribute_domain_items) DEF_ENUM(rna_enum_attribute_domain_with_auto_items) DEF_ENUM(rna_enum_collection_color_items) +DEF_ENUM(rna_enum_strip_color_items) DEF_ENUM(rna_enum_subdivision_uv_smooth_items) DEF_ENUM(rna_enum_subdivision_boundary_smooth_items) diff --git a/source/blender/makesrna/intern/rna_access.c b/source/blender/makesrna/intern/rna_access.c index fceb6d045c3..f1980eed811 100644 --- a/source/blender/makesrna/intern/rna_access.c +++ b/source/blender/makesrna/intern/rna_access.c @@ -2206,6 +2206,7 @@ void RNA_property_update(bContext *C, PointerRNA *ptr, PropertyRNA *prop) rna_property_update(C, CTX_data_main(C), CTX_data_scene(C), ptr, prop); } +/* NOTE: `scene` pointer may be NULL. */ void RNA_property_update_main(Main *bmain, Scene *scene, PointerRNA *ptr, PropertyRNA *prop) { rna_property_update(NULL, bmain, scene, ptr, prop); diff --git a/source/blender/makesrna/intern/rna_access_compare_override.c b/source/blender/makesrna/intern/rna_access_compare_override.c index f8a36c1b2e6..be8972dbff3 100644 --- a/source/blender/makesrna/intern/rna_access_compare_override.c +++ b/source/blender/makesrna/intern/rna_access_compare_override.c @@ -614,20 +614,25 @@ static bool rna_property_override_operation_apply(Main *bmain, } /* get and set the default values as appropriate for the various types */ - return override_apply(bmain, - ptr_dst, - ptr_src, - ptr_storage, - prop_dst, - prop_src, - prop_storage, - len_dst, - len_src, - len_storage, - ptr_item_dst, - ptr_item_src, - ptr_item_storage, - opop); + const bool sucess = override_apply(bmain, + ptr_dst, + ptr_src, + ptr_storage, + prop_dst, + prop_src, + prop_storage, + len_dst, + len_src, + len_storage, + ptr_item_dst, + ptr_item_src, + ptr_item_storage, + opop); + if (sucess) { + RNA_property_update_main(bmain, NULL, ptr_dst, prop_dst); + } + + return sucess; } /** diff --git a/source/blender/makesrna/intern/rna_asset.c b/source/blender/makesrna/intern/rna_asset.c index 1e583f4ca52..80824df1bc8 100644 --- a/source/blender/makesrna/intern/rna_asset.c +++ b/source/blender/makesrna/intern/rna_asset.c @@ -32,19 +32,67 @@ #ifdef RNA_RUNTIME # include "BKE_asset.h" +# include "BKE_asset_library.h" +# include "BKE_context.h" # include "BKE_idprop.h" # include "BLI_listbase.h" +# include "BLI_uuid.h" # include "ED_asset.h" +# include "ED_fileselect.h" # include "RNA_access.h" -static AssetTag *rna_AssetMetaData_tag_new(AssetMetaData *asset_data, - ReportList *reports, - const char *name, - bool skip_if_exists) +static bool rna_AssetMetaData_editable_from_owner_id(const ID *owner_id, + const AssetMetaData *asset_data, + const char **r_info) { + if (owner_id && asset_data && (owner_id->asset_data == asset_data)) { + return true; + } + + if (r_info) { + *r_info = + "Asset metadata from external asset libraries can't be edited, only assets stored in the " + "current file can"; + } + return false; +} + +int rna_AssetMetaData_editable(PointerRNA *ptr, const char **r_info) +{ + AssetMetaData *asset_data = ptr->data; + + return rna_AssetMetaData_editable_from_owner_id(ptr->owner_id, asset_data, r_info) ? + PROP_EDITABLE : + 0; +} + +static int rna_AssetTag_editable(PointerRNA *ptr, const char **r_info) +{ + AssetTag *asset_tag = ptr->data; + ID *owner_id = ptr->owner_id; + if (owner_id && owner_id->asset_data) { + BLI_assert_msg(BLI_findindex(&owner_id->asset_data->tags, asset_tag) != -1, + "The owner of the asset tag pointer is not the asset ID containing the tag"); + UNUSED_VARS_NDEBUG(asset_tag); + } + + return rna_AssetMetaData_editable_from_owner_id(ptr->owner_id, owner_id->asset_data, r_info) ? + PROP_EDITABLE : + 0; +} + +static AssetTag *rna_AssetMetaData_tag_new( + ID *id, AssetMetaData *asset_data, ReportList *reports, const char *name, bool skip_if_exists) +{ + const char *disabled_info = NULL; + if (!rna_AssetMetaData_editable_from_owner_id(id, asset_data, &disabled_info)) { + BKE_report(reports, RPT_WARNING, disabled_info); + return NULL; + } + AssetTag *tag = NULL; if (skip_if_exists) { @@ -64,10 +112,17 @@ static AssetTag *rna_AssetMetaData_tag_new(AssetMetaData *asset_data, return tag; } -static void rna_AssetMetaData_tag_remove(AssetMetaData *asset_data, +static void rna_AssetMetaData_tag_remove(ID *id, + AssetMetaData *asset_data, ReportList *reports, PointerRNA *tag_ptr) { + const char *disabled_info = NULL; + if (!rna_AssetMetaData_editable_from_owner_id(id, asset_data, &disabled_info)) { + BKE_report(reports, RPT_WARNING, disabled_info); + return; + } + AssetTag *tag = tag_ptr->data; if (BLI_findindex(&asset_data->tags, tag) == -1) { BKE_reportf(reports, RPT_ERROR, "Tag '%s' not found in given asset", tag->name); @@ -126,6 +181,40 @@ static void rna_AssetMetaData_active_tag_range( *max = *softmax = MAX2(asset_data->tot_tags - 1, 0); } +static void rna_AssetMetaData_catalog_id_get(PointerRNA *ptr, char *value) +{ + const AssetMetaData *asset_data = ptr->data; + BLI_uuid_format(value, asset_data->catalog_id); +} + +static int rna_AssetMetaData_catalog_id_length(PointerRNA *UNUSED(ptr)) +{ + return UUID_STRING_LEN - 1; +} + +static void rna_AssetMetaData_catalog_id_set(PointerRNA *ptr, const char *value) +{ + AssetMetaData *asset_data = ptr->data; + bUUID new_uuid; + + if (value[0] == '\0') { + BKE_asset_metadata_catalog_id_clear(asset_data); + return; + } + + if (!BLI_uuid_parse_string(&new_uuid, value)) { + // TODO(Sybren): raise ValueError exception once that's possible from an RNA setter. + printf("UUID %s not formatted correctly, ignoring new value\n", value); + return; + } + + /* This just sets the new UUID and clears the catalog simple name. The actual + * catalog simple name will be updated by some update function, as it + * needs the asset library from the context. */ + /* TODO(Sybren): write that update function. */ + BKE_asset_metadata_catalog_id_set(asset_data, new_uuid, ""); +} + static PointerRNA rna_AssetHandle_file_data_get(PointerRNA *ptr) { AssetHandle *asset_handle = ptr->data; @@ -185,6 +274,7 @@ static void rna_def_asset_tag(BlenderRNA *brna) RNA_def_struct_ui_text(srna, "Asset Tag", "User defined tag (name token)"); prop = RNA_def_property(srna, "name", PROP_STRING, PROP_NONE); + RNA_def_property_editable_func(prop, "rna_AssetTag_editable"); RNA_def_property_string_maxlength(prop, MAX_NAME); RNA_def_property_ui_text(prop, "Name", "The identifier that makes up this tag"); RNA_def_struct_name_property(srna, prop); @@ -205,7 +295,7 @@ static void rna_def_asset_tags_api(BlenderRNA *brna, PropertyRNA *cprop) /* Tag collection */ func = RNA_def_function(srna, "new", "rna_AssetMetaData_tag_new"); RNA_def_function_ui_description(func, "Add a new tag to this asset"); - RNA_def_function_flag(func, FUNC_USE_REPORTS); + RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_REPORTS); parm = RNA_def_string(func, "name", NULL, MAX_NAME, "Name", ""); RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); parm = RNA_def_boolean(func, @@ -219,7 +309,7 @@ static void rna_def_asset_tags_api(BlenderRNA *brna, PropertyRNA *cprop) func = RNA_def_function(srna, "remove", "rna_AssetMetaData_tag_remove"); RNA_def_function_ui_description(func, "Remove an existing tag from this asset"); - RNA_def_function_flag(func, FUNC_USE_REPORTS); + RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_REPORTS); /* tag to remove */ parm = RNA_def_pointer(func, "tag", "AssetTag", "", "Removed tag"); RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED | PARM_RNAPTR); @@ -239,6 +329,7 @@ static void rna_def_asset_data(BlenderRNA *brna) RNA_def_struct_flag(srna, STRUCT_NO_DATABLOCK_IDPROPERTIES); /* Mandatory! */ prop = RNA_def_property(srna, "description", PROP_STRING, PROP_NONE); + RNA_def_property_editable_func(prop, "rna_AssetMetaData_editable"); RNA_def_property_string_funcs(prop, "rna_AssetMetaData_description_get", "rna_AssetMetaData_description_length", @@ -248,7 +339,7 @@ static void rna_def_asset_data(BlenderRNA *brna) prop = RNA_def_property(srna, "tags", PROP_COLLECTION, PROP_NONE); RNA_def_property_struct_type(prop, "AssetTag"); - RNA_def_property_flag(prop, PROP_EDITABLE); + RNA_def_property_editable_func(prop, "rna_AssetMetaData_editable"); RNA_def_property_ui_text(prop, "Tags", "Custom tags (name tokens) for the asset, used for filtering and " @@ -258,6 +349,24 @@ static void rna_def_asset_data(BlenderRNA *brna) prop = RNA_def_property(srna, "active_tag", PROP_INT, PROP_NONE); RNA_def_property_int_funcs(prop, NULL, NULL, "rna_AssetMetaData_active_tag_range"); RNA_def_property_ui_text(prop, "Active Tag", "Index of the tag set for editing"); + + prop = RNA_def_property(srna, "catalog_id", PROP_STRING, PROP_NONE); + RNA_def_property_string_funcs(prop, + "rna_AssetMetaData_catalog_id_get", + "rna_AssetMetaData_catalog_id_length", + "rna_AssetMetaData_catalog_id_set"); + RNA_def_property_flag(prop, PROP_CONTEXT_UPDATE); + RNA_def_property_ui_text(prop, + "Catalog UUID", + "Identifier for the asset's catalog, used by Blender to look up the " + "asset's catalog path. Must be a UUID according to RFC4122"); + + prop = RNA_def_property(srna, "catalog_simple_name", PROP_STRING, PROP_NONE); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_ui_text(prop, + "Catalog Simple Name", + "Simple name of the asset's catalog, for debugging and " + "data recovery purposes"); } static void rna_def_asset_handle_api(StructRNA *srna) diff --git a/source/blender/makesrna/intern/rna_brush.c b/source/blender/makesrna/intern/rna_brush.c index 25caa411979..1d3b8cd9f9c 100644 --- a/source/blender/makesrna/intern/rna_brush.c +++ b/source/blender/makesrna/intern/rna_brush.c @@ -734,6 +734,12 @@ static void rna_Brush_icon_update(Main *UNUSED(bmain), Scene *UNUSED(scene), Poi WM_main_add_notifier(NC_BRUSH | NA_EDITED, br); } +static bool rna_Brush_imagetype_poll(PointerRNA *UNUSED(ptr), PointerRNA value) +{ + Image *image = (Image *)value.owner_id; + return image->type != IMA_TYPE_R_RESULT && image->type != IMA_TYPE_COMPOSITE; +} + static void rna_TextureSlot_brush_angle_update(bContext *C, PointerRNA *ptr) { Scene *scene = CTX_data_scene(C); @@ -3434,6 +3440,7 @@ static void rna_def_brush(BlenderRNA *brna) RNA_def_property_flag(prop, PROP_EDITABLE); RNA_def_property_ui_text(prop, "Clone Image", "Image for clone tool"); RNA_def_property_update(prop, NC_SPACE | ND_SPACE_IMAGE, "rna_Brush_update"); + RNA_def_property_pointer_funcs(prop, NULL, NULL, NULL, "rna_Brush_imagetype_poll"); prop = RNA_def_property(srna, "clone_alpha", PROP_FLOAT, PROP_FACTOR); RNA_def_property_float_sdna(prop, NULL, "clone.alpha"); diff --git a/source/blender/makesrna/intern/rna_gpencil_modifier.c b/source/blender/makesrna/intern/rna_gpencil_modifier.c index 6480d16ccd2..675cff3e58c 100644 --- a/source/blender/makesrna/intern/rna_gpencil_modifier.c +++ b/source/blender/makesrna/intern/rna_gpencil_modifier.c @@ -26,6 +26,7 @@ #include "DNA_brush_types.h" #include "DNA_cachefile_types.h" #include "DNA_gpencil_modifier_types.h" +#include "DNA_gpencil_types.h" #include "DNA_mesh_types.h" #include "DNA_object_force_types.h" #include "DNA_object_types.h" @@ -59,6 +60,12 @@ const EnumPropertyItem rna_enum_object_greasepencil_modifier_type_items[] = { {0, "", 0, N_("Modify"), ""}, + {eGpencilModifierType_Texture, + "GP_TEXTURE", + ICON_MOD_UVPROJECT, + "Texture Mapping", + "Change stroke uv texture values"}, + {eGpencilModifierType_Time, "GP_TIME", ICON_MOD_TIME, "Time Offset", "Offset keyframes"}, {eGpencilModifierType_WeightAngle, "GP_WEIGHT_ANGLE", ICON_MOD_VERTEX_WEIGHT, @@ -143,7 +150,6 @@ const EnumPropertyItem rna_enum_object_greasepencil_modifier_type_items[] = { ICON_MOD_THICKNESS, "Thickness", "Change stroke thickness"}, - {eGpencilModifierType_Time, "GP_TIME", ICON_MOD_TIME, "Time Offset", "Offset keyframes"}, {0, "", 0, N_("Color"), ""}, {eGpencilModifierType_Color, "GP_COLOR", @@ -155,11 +161,6 @@ const EnumPropertyItem rna_enum_object_greasepencil_modifier_type_items[] = { ICON_MOD_OPACITY, "Opacity", "Opacity of the strokes"}, - {eGpencilModifierType_Texture, - "GP_TEXTURE", - ICON_TEXTURE, - "Texture Mapping", - "Change stroke uv texture values"}, {eGpencilModifierType_Tint, "GP_TINT", ICON_MOD_TINT, "Tint", "Tint strokes with new color"}, {0, NULL, 0, NULL, NULL}, }; @@ -2685,7 +2686,7 @@ static void rna_def_modifier_gpenciltexture(BlenderRNA *brna) RNA_def_struct_ui_text( srna, "Texture Modifier", "Transform stroke texture coordinates Modifier"); RNA_def_struct_sdna(srna, "TextureGpencilModifierData"); - RNA_def_struct_ui_icon(srna, ICON_TEXTURE); + RNA_def_struct_ui_icon(srna, ICON_MOD_UVPROJECT); RNA_define_lib_overridable(true); @@ -2864,10 +2865,11 @@ static void rna_def_modifier_gpencilweight_proximity(BlenderRNA *brna) RNA_def_property_flag(prop, PROP_EDITABLE | PROP_ID_SELF_CHECK); RNA_def_property_update(prop, 0, "rna_GpencilModifier_dependency_update"); - prop = RNA_def_property(srna, "distance_start", PROP_FLOAT, PROP_NONE); + prop = RNA_def_property(srna, "distance_start", PROP_FLOAT, PROP_DISTANCE); RNA_def_property_float_sdna(prop, NULL, "dist_start"); - RNA_def_property_ui_range(prop, 0, 1000.0, 1.0, 2); - RNA_def_property_ui_text(prop, "Lowest", "Start value for distance calculation"); + RNA_def_property_range(prop, 0.0, FLT_MAX); + RNA_def_property_ui_range(prop, 0.0, 1000.0, 1.0, 2); + RNA_def_property_ui_text(prop, "Lowest", "Distance mapping to 0.0 weight"); RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); prop = RNA_def_property(srna, "minimum_weight", PROP_FLOAT, PROP_FACTOR); @@ -2875,10 +2877,11 @@ static void rna_def_modifier_gpencilweight_proximity(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Minimum", "Minimum value for vertex weight"); RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); - prop = RNA_def_property(srna, "distance_end", PROP_FLOAT, PROP_NONE); + prop = RNA_def_property(srna, "distance_end", PROP_FLOAT, PROP_DISTANCE); RNA_def_property_float_sdna(prop, NULL, "dist_end"); - RNA_def_property_ui_range(prop, 0, 1000.0, 1.0, 2); - RNA_def_property_ui_text(prop, "Highest", "Max value for distance calculation"); + RNA_def_property_range(prop, 0.0, FLT_MAX); + RNA_def_property_ui_range(prop, 0.0, 1000.0, 1.0, 2); + RNA_def_property_ui_text(prop, "Highest", "Distance mapping to 1.0 weight"); RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); prop = RNA_def_property(srna, "pass_index", PROP_INT, PROP_NONE); @@ -3123,6 +3126,14 @@ static void rna_def_modifier_gpencillineart(BlenderRNA *brna) RNA_def_property_range(prop, 0.0f, DEG2RAD(180.0f)); RNA_def_property_update(prop, NC_SCENE, "rna_GpencilModifier_update"); + prop = RNA_def_property(srna, "smooth_tolerance", PROP_FLOAT, PROP_NONE); + RNA_def_property_float_sdna(prop, NULL, "chain_smooth_tolerance"); + RNA_def_property_ui_text( + prop, "Smooth Tolerance", "Strength of smoothing applied on jagged chains"); + RNA_def_property_ui_range(prop, 0.0f, 1.0f, 0.05f, 4); + RNA_def_property_range(prop, 0.0f, 30.0f); + RNA_def_property_update(prop, NC_SCENE, "rna_GpencilModifier_update"); + prop = RNA_def_property(srna, "use_remove_doubles", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "calculation_flags", LRT_REMOVE_DOUBLES); RNA_def_property_ui_text( diff --git a/source/blender/makesrna/intern/rna_image.c b/source/blender/makesrna/intern/rna_image.c index 4a013dc9bd7..2f42e521b52 100644 --- a/source/blender/makesrna/intern/rna_image.c +++ b/source/blender/makesrna/intern/rna_image.c @@ -161,7 +161,9 @@ static void rna_ImageUser_update(Main *bmain, Scene *scene, PointerRNA *ptr) ImageUser *iuser = ptr->data; ID *id = ptr->owner_id; - BKE_image_user_frame_calc(NULL, iuser, scene->r.cfra); + if (scene != NULL) { + BKE_image_user_frame_calc(NULL, iuser, scene->r.cfra); + } if (id) { if (GS(id->name) == ID_NT) { diff --git a/source/blender/makesrna/intern/rna_internal.h b/source/blender/makesrna/intern/rna_internal.h index 0bb76fd933a..fd6664ff0de 100644 --- a/source/blender/makesrna/intern/rna_internal.h +++ b/source/blender/makesrna/intern/rna_internal.h @@ -267,6 +267,7 @@ void rna_def_mtex_common(struct BlenderRNA *brna, void rna_def_texpaint_slots(struct BlenderRNA *brna, struct StructRNA *srna); void rna_def_view_layer_common(struct BlenderRNA *brna, struct StructRNA *srna, const bool scene); +int rna_AssetMetaData_editable(struct PointerRNA *ptr, const char **r_info); PropertyRNA *rna_def_asset_library_reference_common(struct StructRNA *srna, const char *get, const char *set); diff --git a/source/blender/makesrna/intern/rna_internal_types.h b/source/blender/makesrna/intern/rna_internal_types.h index 22a9b9d930a..29df7a53c44 100644 --- a/source/blender/makesrna/intern/rna_internal_types.h +++ b/source/blender/makesrna/intern/rna_internal_types.h @@ -44,11 +44,20 @@ typedef struct IDProperty IDProperty; /* Function Callbacks */ -typedef void (*UpdateFunc)(struct Main *main, struct Scene *scene, struct PointerRNA *ptr); +/** Update callback for an RNA property. + * + * \note This is NOT called automatically when writing into the property, it needs to be called + * manually (through #RNA_property_update or #RNA_property_update_main) when needed. + * + * \param bmain: the Main data-base to which `ptr` data belongs. + * \param active_scene: The current active scene (may be NULL in some cases). + * \param ptr: The RNA pointer data to update. */ +typedef void (*UpdateFunc)(struct Main *bmain, struct Scene *active_scene, struct PointerRNA *ptr); typedef void (*ContextPropUpdateFunc)(struct bContext *C, struct PointerRNA *ptr, struct PropertyRNA *prop); typedef void (*ContextUpdateFunc)(struct bContext *C, struct PointerRNA *ptr); + typedef int (*EditableFunc)(struct PointerRNA *ptr, const char **r_info); typedef int (*ItemEditableFunc)(struct PointerRNA *ptr, int index); typedef struct IDProperty **(*IDPropertiesFunc)(struct PointerRNA *ptr); diff --git a/source/blender/makesrna/intern/rna_material_api.c b/source/blender/makesrna/intern/rna_material_api.c index f5b13fdbc81..5f89664458c 100644 --- a/source/blender/makesrna/intern/rna_material_api.c +++ b/source/blender/makesrna/intern/rna_material_api.c @@ -38,8 +38,8 @@ void RNA_api_material(StructRNA *UNUSED(srna)) { - /* FunctionRNA *func; */ - /* PropertyRNA *parm; */ + // FunctionRNA *func; + // PropertyRNA *parm; } #endif diff --git a/source/blender/makesrna/intern/rna_nla.c b/source/blender/makesrna/intern/rna_nla.c index 17c7b331c88..d0711f28a6e 100644 --- a/source/blender/makesrna/intern/rna_nla.c +++ b/source/blender/makesrna/intern/rna_nla.c @@ -751,14 +751,14 @@ static void rna_def_nlastrip(BlenderRNA *brna) RNA_def_property_ui_text( prop, "Influence", "Amount the strip contributes to the current result"); /* XXX: Update temporarily disabled so that the property can be edited at all! - * Even autokey only applies after the curves have been re-evaluated, + * Even auto-key only applies after the curves have been re-evaluated, * causing the unkeyed values to be lost. */ RNA_def_property_update(prop, NC_ANIMATION | ND_NLA | NA_EDITED, /*"rna_NlaStrip_update"*/ NULL); prop = RNA_def_property(srna, "strip_time", PROP_FLOAT, PROP_TIME); RNA_def_property_ui_text(prop, "Strip Time", "Frame of referenced Action to evaluate"); /* XXX: Update temporarily disabled so that the property can be edited at all! - * Even autokey only applies after the curves have been re-evaluated, + * Even auto-key only applies after the curves have been re-evaluated, * causing the unkeyed values to be lost. */ RNA_def_property_update(prop, NC_ANIMATION | ND_NLA | NA_EDITED, /*"rna_NlaStrip_update"*/ NULL); diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c index b631e76c094..df1be412bc4 100644 --- a/source/blender/makesrna/intern/rna_nodetree.c +++ b/source/blender/makesrna/intern/rna_nodetree.c @@ -2092,6 +2092,19 @@ static const EnumPropertyItem *rna_GeometryNodeAttributeRandom_type_itemf( return itemf_function_check(rna_enum_attribute_type_items, attribute_random_type_supported); } +static bool random_value_type_supported(const EnumPropertyItem *item) +{ + return ELEM(item->value, CD_PROP_FLOAT, CD_PROP_FLOAT3, CD_PROP_BOOL, CD_PROP_INT32); +} +static const EnumPropertyItem *rna_FunctionNodeRandomValue_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, random_value_type_supported); +} + static const EnumPropertyItem *rna_GeometryNodeAttributeRandomize_operation_itemf( bContext *UNUSED(C), PointerRNA *ptr, PropertyRNA *UNUSED(prop), bool *r_free) { @@ -3729,7 +3742,7 @@ static void rna_Node_image_layer_update(Main *bmain, Scene *scene, PointerRNA *p rna_Node_update(bmain, scene, ptr); - if (scene->nodetree != NULL) { + if (scene != NULL && scene->nodetree != NULL) { ntreeCompositUpdateRLayers(scene->nodetree); } } @@ -3901,7 +3914,7 @@ static const EnumPropertyItem *rna_Node_view_layer_itemf(bContext *UNUSED(C), static void rna_Node_view_layer_update(Main *bmain, Scene *scene, PointerRNA *ptr) { rna_Node_update(bmain, scene, ptr); - if (scene->nodetree != NULL) { + if (scene != NULL && scene->nodetree != NULL) { ntreeCompositUpdateRLayers(scene->nodetree); } } @@ -4336,7 +4349,7 @@ static void rna_ShaderNodeScript_update(Main *bmain, Scene *scene, PointerRNA *p { bNodeTree *ntree = (bNodeTree *)ptr->owner_id; bNode *node = (bNode *)ptr->data; - RenderEngineType *engine_type = RE_engines_find(scene->r.engine); + RenderEngineType *engine_type = (scene != NULL) ? RE_engines_find(scene->r.engine) : NULL; if (engine_type && engine_type->update_script_node) { /* auto update node */ @@ -4887,6 +4900,17 @@ static void def_vector_curve(StructRNA *srna) RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); } +static void def_float_curve(StructRNA *srna) +{ + PropertyRNA *prop; + + prop = RNA_def_property(srna, "mapping", PROP_POINTER, PROP_NONE); + RNA_def_property_pointer_sdna(prop, NULL, "storage"); + 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_time(StructRNA *srna) { PropertyRNA *prop; @@ -6532,11 +6556,11 @@ static void def_cmp_levels(StructRNA *srna) PropertyRNA *prop; static const EnumPropertyItem channel_items[] = { - {1, "COMBINED_RGB", 0, "C", "Combined RGB"}, - {2, "RED", 0, "R", "Red Channel"}, - {3, "GREEN", 0, "G", "Green Channel"}, - {4, "BLUE", 0, "B", "Blue Channel"}, - {5, "LUMINANCE", 0, "L", "Luminance Channel"}, + {1, "COMBINED_RGB", 0, "Combined", "Combined RGB"}, + {2, "RED", 0, "Red", "Red Channel"}, + {3, "GREEN", 0, "Green", "Green Channel"}, + {4, "BLUE", 0, "Blue", "Blue Channel"}, + {5, "LUMINANCE", 0, "Luminance", "Luminance Channel"}, {0, NULL, 0, NULL, NULL}, }; @@ -9168,6 +9192,21 @@ static void def_geo_subdivision_surface(StructRNA *srna) RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); } +static void def_fn_random_value(StructRNA *srna) +{ + PropertyRNA *prop; + + RNA_def_struct_sdna_from(srna, "NodeRandomValue", "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_FunctionNodeRandomValue_type_itemf"); + RNA_def_property_enum_default(prop, CD_PROP_FLOAT); + RNA_def_property_ui_text(prop, "Data Type", "Type of data stored in attribute"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); +} + static void def_geo_attribute_randomize(StructRNA *srna) { PropertyRNA *prop; @@ -9479,6 +9518,33 @@ static void def_geo_point_distribute(StructRNA *srna) RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); } +static void def_geo_distribute_points_on_faces(StructRNA *srna) +{ + PropertyRNA *prop; + + static const EnumPropertyItem rna_node_geometry_distribute_points_on_faces_mode_items[] = { + {GEO_NODE_POINT_DISTRIBUTE_POINTS_ON_FACES_RANDOM, + "RANDOM", + 0, + "Random", + "Distribute points randomly on the surface"}, + {GEO_NODE_POINT_DISTRIBUTE_POINTS_ON_FACES_POISSON, + "POISSON", + 0, + "Poisson Disk", + "Distribute the points randomly on the surface while taking a minimum distance between " + "points into account"}, + {0, NULL, 0, NULL, NULL}, + }; + + prop = RNA_def_property(srna, "distribute_method", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "custom1"); + RNA_def_property_enum_items(prop, rna_node_geometry_distribute_points_on_faces_mode_items); + RNA_def_property_enum_default(prop, GEO_NODE_POINT_DISTRIBUTE_POINTS_ON_FACES_RANDOM); + RNA_def_property_ui_text(prop, "Distribution Method", "Method to use for scattering points"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); +} + static void def_geo_attribute_color_ramp(StructRNA *srna) { PropertyRNA *prop; @@ -9601,6 +9667,23 @@ static void def_geo_curve_set_handles(StructRNA *srna) prop = RNA_def_property(srna, "mode", PROP_ENUM, PROP_NONE); RNA_def_property_enum_items(prop, rna_node_geometry_curve_handle_side_items); RNA_def_property_ui_text(prop, "Mode", "Whether to update left and right handles"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); +} + +static void def_geo_legacy_curve_set_handles(StructRNA *srna) +{ + PropertyRNA *prop; + + RNA_def_struct_sdna_from(srna, "NodeGeometryCurveSetHandles", "storage"); + + prop = RNA_def_property(srna, "handle_type", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "handle_type"); + RNA_def_property_enum_items(prop, rna_node_geometry_curve_handle_type_items); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); + + prop = RNA_def_property(srna, "mode", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, rna_node_geometry_curve_handle_side_items); + RNA_def_property_ui_text(prop, "Mode", "Whether to update left and right handles"); RNA_def_property_flag(prop, PROP_ENUM_FLAG); RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); } @@ -9735,6 +9818,51 @@ static void def_geo_point_rotate(StructRNA *srna) RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); } +static void def_fn_rotate_euler(StructRNA *srna) +{ + static const EnumPropertyItem type_items[] = { + {FN_NODE_ROTATE_EULER_TYPE_AXIS_ANGLE, + "AXIS_ANGLE", + ICON_NONE, + "Axis Angle", + "Rotate around an axis by an angle"}, + {FN_NODE_ROTATE_EULER_TYPE_EULER, + "EULER", + ICON_NONE, + "Euler", + "Rotate around the X, Y, and Z axes"}, + {0, NULL, 0, NULL, NULL}, + }; + + static const EnumPropertyItem space_items[] = { + {FN_NODE_ROTATE_EULER_SPACE_OBJECT, + "OBJECT", + ICON_NONE, + "Object", + "Rotate the input rotation in the local space of the object"}, + {FN_NODE_ROTATE_EULER_SPACE_POINT, + "POINT", + ICON_NONE, + "Point", + "Rotate the input rotation in its local space"}, + {0, NULL, 0, NULL, NULL}, + }; + + PropertyRNA *prop; + + prop = RNA_def_property(srna, "type", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "custom1"); + RNA_def_property_enum_items(prop, type_items); + RNA_def_property_ui_text(prop, "Type", "Method used to describe the rotation"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); + + prop = RNA_def_property(srna, "space", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "custom2"); + RNA_def_property_enum_items(prop, space_items); + RNA_def_property_ui_text(prop, "Space", "Base orientation of the points"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); +} + static void def_geo_align_rotation_to_vector(StructRNA *srna) { static const EnumPropertyItem axis_items[] = { @@ -9917,7 +10045,7 @@ static void def_geo_collection_info(StructRNA *srna) RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); } -static void def_geo_attribute_proximity(StructRNA *srna) +static void def_geo_legacy_attribute_proximity(StructRNA *srna) { static const EnumPropertyItem target_geometry_element[] = { {GEO_NODE_PROXIMITY_TARGET_POINTS, @@ -9950,6 +10078,39 @@ static void def_geo_attribute_proximity(StructRNA *srna) RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); } +static void def_geo_proximity(StructRNA *srna) +{ + static const EnumPropertyItem target_element_items[] = { + {GEO_NODE_PROX_TARGET_POINTS, + "POINTS", + ICON_NONE, + "Points", + "Calculate the proximity to the target's points (faster than the other modes)"}, + {GEO_NODE_PROX_TARGET_EDGES, + "EDGES", + ICON_NONE, + "Edges", + "Calculate the proximity to the target's edges"}, + {GEO_NODE_PROX_TARGET_FACES, + "FACES", + ICON_NONE, + "Faces", + "Calculate the proximity to the target's faces"}, + {0, NULL, 0, NULL, NULL}, + }; + + PropertyRNA *prop; + + RNA_def_struct_sdna_from(srna, "NodeGeometryProximity", "storage"); + + prop = RNA_def_property(srna, "target_element", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, target_element_items); + RNA_def_property_enum_default(prop, GEO_NODE_PROX_TARGET_FACES); + RNA_def_property_ui_text( + prop, "Target Geometry", "Element of the target geometry to calculate the distance from"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); +} + static void def_geo_volume_to_mesh(StructRNA *srna) { PropertyRNA *prop; @@ -10036,7 +10197,7 @@ static void def_geo_mesh_cylinder(StructRNA *srna) prop = RNA_def_property(srna, "fill_type", PROP_ENUM, PROP_NONE); RNA_def_property_enum_items(prop, rna_node_geometry_mesh_circle_fill_type_items); RNA_def_property_ui_text(prop, "Fill Type", ""); - RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); } static void def_geo_mesh_cone(StructRNA *srna) @@ -10048,7 +10209,7 @@ static void def_geo_mesh_cone(StructRNA *srna) prop = RNA_def_property(srna, "fill_type", PROP_ENUM, PROP_NONE); RNA_def_property_enum_items(prop, rna_node_geometry_mesh_circle_fill_type_items); RNA_def_property_ui_text(prop, "Fill Type", ""); - RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); } static void def_geo_mesh_line(StructRNA *srna) @@ -10181,7 +10342,7 @@ static void def_geo_curve_resample(StructRNA *srna) RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); } -static void def_geo_curve_subdivide(StructRNA *srna) +static void def_geo_legacy_curve_subdivide(StructRNA *srna) { PropertyRNA *prop; @@ -10251,6 +10412,42 @@ static void def_geo_curve_to_points(StructRNA *srna) RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); } +static void def_geo_mesh_to_points(StructRNA *srna) +{ + PropertyRNA *prop; + + static EnumPropertyItem mode_items[] = { + {GEO_NODE_MESH_TO_POINTS_VERTICES, + "VERTICES", + 0, + "Vertices", + "Create a point in the point cloud for each selected vertex"}, + {GEO_NODE_MESH_TO_POINTS_EDGES, + "EDGES", + 0, + "Edges", + "Create a point in the point cloud for each selected edge"}, + {GEO_NODE_MESH_TO_POINTS_FACES, + "FACES", + 0, + "Faces", + "Create a point in the point cloud for each selected face"}, + {GEO_NODE_MESH_TO_POINTS_CORNERS, + "CORNERS", + 0, + "Corners", + "Create a point in the point cloud for each selected face corner"}, + {0, NULL, 0, NULL, NULL}, + }; + + RNA_def_struct_sdna_from(srna, "NodeGeometryMeshToPoints", "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", ""); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); +} + static void def_geo_curve_trim(StructRNA *srna) { PropertyRNA *prop; @@ -10398,6 +10595,120 @@ static void def_geo_attribute_capture(StructRNA *srna) RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); } +static void def_geo_string_to_curves(StructRNA *srna) +{ + static const EnumPropertyItem rna_node_geometry_string_to_curves_overflow_items[] = { + {GEO_NODE_STRING_TO_CURVES_MODE_OVERFLOW, + "OVERFLOW", + ICON_NONE, + "Overflow", + "Let the text use more space than the specified height"}, + {GEO_NODE_STRING_TO_CURVES_MODE_SCALE_TO_FIT, + "SCALE_TO_FIT", + ICON_NONE, + "Scale To Fit", + "Scale the text size to fit inside the width and height"}, + {GEO_NODE_STRING_TO_CURVES_MODE_TRUNCATE, + "TRUNCATE", + ICON_NONE, + "Truncate", + "Only output curves that fit within the width and height. Output the remainder to the " + "\"Remainder\" output"}, + {0, NULL, 0, NULL, NULL}, + }; + + static const EnumPropertyItem rna_node_geometry_string_to_curves_align_x_items[] = { + {GEO_NODE_STRING_TO_CURVES_ALIGN_X_LEFT, + "LEFT", + ICON_ALIGN_LEFT, + "Left", + "Align text to the left"}, + {GEO_NODE_STRING_TO_CURVES_ALIGN_X_CENTER, + "CENTER", + ICON_ALIGN_CENTER, + "Center", + "Align text to the center"}, + {GEO_NODE_STRING_TO_CURVES_ALIGN_X_RIGHT, + "RIGHT", + ICON_ALIGN_RIGHT, + "Right", + "Align text to the right"}, + {GEO_NODE_STRING_TO_CURVES_ALIGN_X_JUSTIFY, + "JUSTIFY", + ICON_ALIGN_JUSTIFY, + "Justify", + "Align text to the left and the right"}, + {GEO_NODE_STRING_TO_CURVES_ALIGN_X_FLUSH, + "FLUSH", + ICON_ALIGN_FLUSH, + "Flush", + "Align text to the left and the right, with equal character spacing"}, + {0, NULL, 0, NULL, NULL}, + }; + + static const EnumPropertyItem rna_node_geometry_string_to_curves_align_y_items[] = { + {GEO_NODE_STRING_TO_CURVES_ALIGN_Y_TOP_BASELINE, + "TOP_BASELINE", + ICON_ALIGN_TOP, + "Top Baseline", + "Align text to the top baseline"}, + {GEO_NODE_STRING_TO_CURVES_ALIGN_Y_TOP, + "TOP", + ICON_ALIGN_TOP, + "Top", + "Align text to the top"}, + {GEO_NODE_STRING_TO_CURVES_ALIGN_Y_MIDDLE, + "MIDDLE", + ICON_ALIGN_MIDDLE, + "Middle", + "Align text to the middle"}, + {GEO_NODE_STRING_TO_CURVES_ALIGN_Y_BOTTOM_BASELINE, + "BOTTOM_BASELINE", + ICON_ALIGN_BOTTOM, + "Bottom Baseline", + "Align text to the bottom baseline"}, + {GEO_NODE_STRING_TO_CURVES_ALIGN_Y_BOTTOM, + "BOTTOM", + ICON_ALIGN_BOTTOM, + "Bottom", + "Align text to the bottom"}, + {0, NULL, 0, NULL, NULL}, + }; + + PropertyRNA *prop; + + prop = RNA_def_property(srna, "font", PROP_POINTER, PROP_NONE); + RNA_def_property_pointer_sdna(prop, NULL, "id"); + RNA_def_property_struct_type(prop, "VectorFont"); + RNA_def_property_ui_text(prop, "Font", "Font of the text. Falls back to the UI font by default"); + RNA_def_property_flag(prop, PROP_EDITABLE); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); + + RNA_def_struct_sdna_from(srna, "NodeGeometryStringToCurves", "storage"); + + prop = RNA_def_property(srna, "overflow", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "overflow"); + RNA_def_property_enum_items(prop, rna_node_geometry_string_to_curves_overflow_items); + RNA_def_property_enum_default(prop, GEO_NODE_STRING_TO_CURVES_MODE_OVERFLOW); + RNA_def_property_ui_text(prop, "Overflow", ""); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); + + prop = RNA_def_property(srna, "align_x", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "align_x"); + RNA_def_property_enum_items(prop, rna_node_geometry_string_to_curves_align_x_items); + RNA_def_property_enum_default(prop, GEO_NODE_STRING_TO_CURVES_ALIGN_X_LEFT); + RNA_def_property_ui_text(prop, "Align X", ""); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); + + prop = RNA_def_property(srna, "align_y", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "align_y"); + RNA_def_property_enum_items(prop, rna_node_geometry_string_to_curves_align_y_items); + RNA_def_property_enum_default(prop, GEO_NODE_STRING_TO_CURVES_ALIGN_Y_TOP_BASELINE); + RNA_def_property_ui_text(prop, "Align Y", ""); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); +} + /* -------------------------------------------------------------------------- */ static void rna_def_shader_node(BlenderRNA *brna) @@ -10664,6 +10975,14 @@ static void rna_def_node_socket_interface(BlenderRNA *brna) prop, "Hide Value", "Hide the socket input value even when the socket is not connected"); RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeSocketInterface_update"); + prop = RNA_def_property(srna, "attribute_domain", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, rna_enum_attribute_domain_items); + RNA_def_property_ui_text( + prop, + "Attribute Domain", + "Attribute domain used by the geometry nodes modifier to create an attribute output"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeSocketInterface_update"); + /* registration */ prop = RNA_def_property(srna, "bl_socket_idname", PROP_STRING, PROP_NONE); RNA_def_property_string_sdna(prop, NULL, "typeinfo->idname"); diff --git a/source/blender/makesrna/intern/rna_object_force.c b/source/blender/makesrna/intern/rna_object_force.c index 98d59bf3a1a..7f997109920 100644 --- a/source/blender/makesrna/intern/rna_object_force.c +++ b/source/blender/makesrna/intern/rna_object_force.c @@ -1056,8 +1056,8 @@ static void rna_def_ptcache_point_caches(BlenderRNA *brna, PropertyRNA *cprop) StructRNA *srna; PropertyRNA *prop; - /* FunctionRNA *func; */ - /* PropertyRNA *parm; */ + // FunctionRNA *func; + // PropertyRNA *parm; RNA_def_property_srna(cprop, "PointCaches"); srna = RNA_def_struct(brna, "PointCaches", NULL); diff --git a/source/blender/makesrna/intern/rna_rigidbody.c b/source/blender/makesrna/intern/rna_rigidbody.c index 0b56a73efa2..c51931d0d1a 100644 --- a/source/blender/makesrna/intern/rna_rigidbody.c +++ b/source/blender/makesrna/intern/rna_rigidbody.c @@ -215,9 +215,10 @@ static void rna_RigidBodyWorld_constraints_collection_update(Main *bmain, static void rna_RigidBodyOb_reset(Main *UNUSED(bmain), Scene *scene, PointerRNA *UNUSED(ptr)) { - RigidBodyWorld *rbw = scene->rigidbody_world; - - BKE_rigidbody_cache_reset(rbw); + if (scene != NULL) { + RigidBodyWorld *rbw = scene->rigidbody_world; + BKE_rigidbody_cache_reset(rbw); + } } static void rna_RigidBodyOb_shape_update(Main *bmain, Scene *scene, PointerRNA *ptr) diff --git a/source/blender/makesrna/intern/rna_scene.c b/source/blender/makesrna/intern/rna_scene.c index 80fc13faab4..5b46dfa8210 100644 --- a/source/blender/makesrna/intern/rna_scene.c +++ b/source/blender/makesrna/intern/rna_scene.c @@ -676,7 +676,9 @@ static void rna_ToolSettings_snap_mode_set(struct PointerRNA *ptr, int value) /* Grease Pencil update cache */ static void rna_GPencil_update(Main *UNUSED(bmain), Scene *scene, PointerRNA *UNUSED(ptr)) { - ED_gpencil_tag_scene_gpencil(scene); + if (scene != NULL) { + ED_gpencil_tag_scene_gpencil(scene); + } } static void rna_Gpencil_extend_selection(bContext *C, PointerRNA *UNUSED(ptr)) @@ -848,8 +850,9 @@ static void rna_Scene_camera_update(Main *bmain, Scene *UNUSED(scene_unused), Po DEG_relations_tag_update(bmain); } -static void rna_Scene_fps_update(Main *bmain, Scene *scene, PointerRNA *UNUSED(ptr)) +static void rna_Scene_fps_update(Main *bmain, Scene *UNUSED(active_scene), PointerRNA *ptr) { + Scene *scene = (Scene *)ptr->owner_id; DEG_id_tag_update(&scene->id, ID_RECALC_AUDIO_FPS | ID_RECALC_SEQUENCER_STRIPS); /* NOTE: Tag via dependency graph will take care of all the updates ion the evaluated domain, * however, changes in FPS actually modifies an original skip length, @@ -857,9 +860,9 @@ static void rna_Scene_fps_update(Main *bmain, Scene *scene, PointerRNA *UNUSED(p SEQ_sound_update_length(bmain, scene); } -static void rna_Scene_listener_update(Main *UNUSED(bmain), Scene *scene, PointerRNA *UNUSED(ptr)) +static void rna_Scene_listener_update(Main *UNUSED(bmain), Scene *UNUSED(scene), PointerRNA *ptr) { - DEG_id_tag_update(&scene->id, ID_RECALC_AUDIO_LISTENER); + DEG_id_tag_update(ptr->owner_id, ID_RECALC_AUDIO_LISTENER); } static void rna_Scene_volume_update(Main *UNUSED(bmain), Scene *UNUSED(scene), PointerRNA *ptr) @@ -885,8 +888,11 @@ static const char *rna_Scene_statistics_string_get(Scene *scene, return ED_info_statistics_string(bmain, scene, view_layer); } -static void rna_Scene_framelen_update(Main *UNUSED(bmain), Scene *scene, PointerRNA *UNUSED(ptr)) +static void rna_Scene_framelen_update(Main *UNUSED(bmain), + Scene *UNUSED(active_scene), + PointerRNA *ptr) { + Scene *scene = (Scene *)ptr->owner_id; scene->r.framelen = (float)scene->r.framapto / (float)scene->r.images; } @@ -1940,9 +1946,9 @@ static void rna_Scene_use_audio_set(PointerRNA *ptr, bool value) } } -static void rna_Scene_use_audio_update(Main *UNUSED(bmain), Scene *scene, PointerRNA *UNUSED(ptr)) +static void rna_Scene_use_audio_update(Main *UNUSED(bmain), Scene *UNUSED(scene), PointerRNA *ptr) { - DEG_id_tag_update(&scene->id, ID_RECALC_AUDIO_MUTE); + DEG_id_tag_update(ptr->owner_id, ID_RECALC_AUDIO_MUTE); } static int rna_Scene_sync_mode_get(PointerRNA *ptr) @@ -2199,9 +2205,9 @@ static void rna_SceneCamera_update(Main *UNUSED(bmain), Scene *UNUSED(scene), Po } } -static void rna_SceneSequencer_update(Main *UNUSED(bmain), Scene *scene, PointerRNA *UNUSED(ptr)) +static void rna_SceneSequencer_update(Main *UNUSED(bmain), Scene *UNUSED(scene), PointerRNA *ptr) { - SEQ_cache_cleanup(scene); + SEQ_cache_cleanup((Scene *)ptr->owner_id); } static char *rna_ToolSettings_path(PointerRNA *UNUSED(ptr)) @@ -3156,6 +3162,14 @@ static void rna_def_tool_settings(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Snap UV Element", "Type of element to snap to"); RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); /* header redraw */ + prop = RNA_def_property(srna, "use_snap_uv_grid_absolute", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "snap_uv_flag", SCE_SNAP_ABS_GRID); + RNA_def_property_ui_text( + prop, + "Absolute Grid Snap", + "Absolute grid alignment while translating (based on the pivot center)"); + RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); /* header redraw */ + prop = RNA_def_property(srna, "snap_target", PROP_ENUM, PROP_NONE); RNA_def_property_enum_sdna(prop, NULL, "snap_target"); RNA_def_property_enum_items(prop, rna_enum_snap_target_items); diff --git a/source/blender/makesrna/intern/rna_sculpt_paint.c b/source/blender/makesrna/intern/rna_sculpt_paint.c index 1da08448c5b..a2db1536f55 100644 --- a/source/blender/makesrna/intern/rna_sculpt_paint.c +++ b/source/blender/makesrna/intern/rna_sculpt_paint.c @@ -130,7 +130,9 @@ const EnumPropertyItem rna_enum_symmetrize_direction_items[] = { static void rna_GPencil_update(Main *UNUSED(bmain), Scene *scene, PointerRNA *UNUSED(ptr)) { /* mark all grease pencil datablocks of the scene */ - ED_gpencil_tag_scene_gpencil(scene); + if (scene != NULL) { + ED_gpencil_tag_scene_gpencil(scene); + } } const EnumPropertyItem rna_enum_particle_edit_disconnected_hair_brush_items[] = { @@ -501,6 +503,12 @@ static void rna_ImaPaint_stencil_update(bContext *C, PointerRNA *UNUSED(ptr)) } } +static bool rna_ImaPaint_imagetype_poll(PointerRNA *UNUSED(ptr), PointerRNA value) +{ + Image *image = (Image *)value.owner_id; + return image->type != IMA_TYPE_R_RESULT && image->type != IMA_TYPE_COMPOSITE; +} + static void rna_ImaPaint_canvas_update(bContext *C, PointerRNA *UNUSED(ptr)) { Main *bmain = CTX_data_main(C); @@ -1033,17 +1041,20 @@ static void rna_def_image_paint(BlenderRNA *brna) RNA_def_property_flag(prop, PROP_EDITABLE | PROP_CONTEXT_UPDATE); RNA_def_property_ui_text(prop, "Stencil Image", "Image used as stencil"); RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, "rna_ImaPaint_stencil_update"); + RNA_def_property_pointer_funcs(prop, NULL, NULL, NULL, "rna_ImaPaint_imagetype_poll"); prop = RNA_def_property(srna, "canvas", PROP_POINTER, PROP_NONE); RNA_def_property_flag(prop, PROP_EDITABLE | PROP_CONTEXT_UPDATE); RNA_def_property_ui_text(prop, "Canvas", "Image used as canvas"); RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, "rna_ImaPaint_canvas_update"); + RNA_def_property_pointer_funcs(prop, NULL, NULL, NULL, "rna_ImaPaint_imagetype_poll"); prop = RNA_def_property(srna, "clone_image", PROP_POINTER, PROP_NONE); RNA_def_property_pointer_sdna(prop, NULL, "clone"); RNA_def_property_flag(prop, PROP_EDITABLE); RNA_def_property_ui_text(prop, "Clone Image", "Image used as clone source"); RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); + RNA_def_property_pointer_funcs(prop, NULL, NULL, NULL, "rna_ImaPaint_imagetype_poll"); prop = RNA_def_property(srna, "stencil_color", PROP_FLOAT, PROP_COLOR_GAMMA); RNA_def_property_range(prop, 0.0, 1.0); diff --git a/source/blender/makesrna/intern/rna_sequencer.c b/source/blender/makesrna/intern/rna_sequencer.c index b713ffb68b4..e519740259c 100644 --- a/source/blender/makesrna/intern/rna_sequencer.c +++ b/source/blender/makesrna/intern/rna_sequencer.c @@ -81,6 +81,20 @@ const EnumPropertyItem rna_enum_sequence_modifier_type_items[] = { {0, NULL, 0, NULL, NULL}, }; +const EnumPropertyItem rna_enum_strip_color_items[] = { + {SEQUENCE_COLOR_NONE, "NONE", ICON_X, "None", "Assign no color tag to the collection"}, + {SEQUENCE_COLOR_01, "COLOR_01", ICON_SEQUENCE_COLOR_01, "Color 01", ""}, + {SEQUENCE_COLOR_02, "COLOR_02", ICON_SEQUENCE_COLOR_02, "Color 02", ""}, + {SEQUENCE_COLOR_03, "COLOR_03", ICON_SEQUENCE_COLOR_03, "Color 03", ""}, + {SEQUENCE_COLOR_04, "COLOR_04", ICON_SEQUENCE_COLOR_04, "Color 04", ""}, + {SEQUENCE_COLOR_05, "COLOR_05", ICON_SEQUENCE_COLOR_05, "Color 05", ""}, + {SEQUENCE_COLOR_06, "COLOR_06", ICON_SEQUENCE_COLOR_06, "Color 06", ""}, + {SEQUENCE_COLOR_07, "COLOR_07", ICON_SEQUENCE_COLOR_07, "Color 07", ""}, + {SEQUENCE_COLOR_08, "COLOR_08", ICON_SEQUENCE_COLOR_08, "Color 08", ""}, + {SEQUENCE_COLOR_09, "COLOR_09", ICON_SEQUENCE_COLOR_09, "Color 09", ""}, + {0, NULL, 0, NULL, NULL}, +}; + #ifdef RNA_RUNTIME # include "BKE_global.h" @@ -840,9 +854,9 @@ static int rna_Sequence_proxy_filepath_length(PointerRNA *ptr) return strlen(path); } -static void rna_Sequence_audio_update(Main *UNUSED(bmain), Scene *scene, PointerRNA *UNUSED(ptr)) +static void rna_Sequence_audio_update(Main *UNUSED(bmain), Scene *UNUSED(scene), PointerRNA *ptr) { - DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS); + DEG_id_tag_update(ptr->owner_id, ID_RECALC_SEQUENCER_STRIPS); } static void rna_Sequence_pan_range( @@ -940,8 +954,9 @@ static void rna_Sequence_filepath_update(Main *bmain, Scene *UNUSED(scene), Poin rna_Sequence_invalidate_raw_update(bmain, scene, ptr); } -static void rna_Sequence_sound_update(Main *bmain, Scene *scene, PointerRNA *UNUSED(ptr)) +static void rna_Sequence_sound_update(Main *bmain, Scene *UNUSED(active_scene), PointerRNA *ptr) { + Scene *scene = (Scene *)ptr->owner_id; DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS | ID_RECALC_AUDIO); DEG_relations_tag_update(bmain); } @@ -999,6 +1014,18 @@ static void rna_Sequence_opacity_set(PointerRNA *ptr, float value) seq->blend_opacity = value * 100.0f; } +static int rna_Sequence_color_tag_get(PointerRNA *ptr) +{ + Sequence *seq = (Sequence *)(ptr->data); + return seq->color_tag; +} + +static void rna_Sequence_color_tag_set(PointerRNA *ptr, int value) +{ + Sequence *seq = (Sequence *)(ptr->data); + seq->color_tag = value; +} + static bool colbalance_seq_cmp_fn(Sequence *seq, void *arg_pt) { SequenceSearchData *data = arg_pt; @@ -1564,12 +1591,28 @@ static void rna_def_color_balance(BlenderRNA *brna) StructRNA *srna; PropertyRNA *prop; + static const EnumPropertyItem method_items[] = { + {SEQ_COLOR_BALANCE_METHOD_LIFTGAMMAGAIN, "LIFT_GAMMA_GAIN", 0, "Lift/Gamma/Gain", ""}, + {SEQ_COLOR_BALANCE_METHOD_SLOPEOFFSETPOWER, + "OFFSET_POWER_SLOPE", + 0, + "Offset/Power/Slope (ASC-CDL)", + "ASC-CDL standard color correction"}, + {0, NULL, 0, NULL, NULL}, + }; + srna = RNA_def_struct(brna, "SequenceColorBalanceData", NULL); RNA_def_struct_ui_text(srna, "Sequence Color Balance Data", "Color balance parameters for a sequence strip and its modifiers"); RNA_def_struct_sdna(srna, "StripColorBalance"); + prop = RNA_def_property(srna, "correction_method", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "method"); + RNA_def_property_enum_items(prop, method_items); + RNA_def_property_ui_text(prop, "Correction Method", ""); + RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_SequenceColorBalance_update"); + prop = RNA_def_property(srna, "lift", PROP_FLOAT, PROP_COLOR_GAMMA); RNA_def_property_ui_text(prop, "Lift", "Color balance lift (shadows)"); RNA_def_property_ui_range(prop, 0, 2, 0.1, 3); @@ -1588,14 +1631,22 @@ static void rna_def_color_balance(BlenderRNA *brna) RNA_def_property_float_default(prop, 1.0f); RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_SequenceColorBalance_update"); - prop = RNA_def_property(srna, "invert_gain", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_COLOR_BALANCE_INVERSE_GAIN); - RNA_def_property_ui_text(prop, "Inverse Gain", "Invert the gain color`"); + prop = RNA_def_property(srna, "slope", PROP_FLOAT, PROP_COLOR_GAMMA); + RNA_def_property_ui_text(prop, "Slope", "Correction for highlights"); + RNA_def_property_ui_range(prop, 0, 2, 0.1, 3); + RNA_def_property_float_default(prop, 1.0f); RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_SequenceColorBalance_update"); - prop = RNA_def_property(srna, "invert_gamma", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_COLOR_BALANCE_INVERSE_GAMMA); - RNA_def_property_ui_text(prop, "Inverse Gamma", "Invert the gamma color"); + prop = RNA_def_property(srna, "offset", PROP_FLOAT, PROP_COLOR_GAMMA); + RNA_def_property_ui_text(prop, "Offset", "Correction for entire tonal range"); + RNA_def_property_ui_range(prop, 0, 2, 0.1, 3); + RNA_def_property_float_default(prop, 1.0f); + RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_SequenceColorBalance_update"); + + prop = RNA_def_property(srna, "power", PROP_FLOAT, PROP_COLOR_GAMMA); + RNA_def_property_ui_text(prop, "Power", "Correction for midtones"); + RNA_def_property_ui_range(prop, 0, 2, 0.1, 3); + RNA_def_property_float_default(prop, 1.0f); RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_SequenceColorBalance_update"); prop = RNA_def_property(srna, "invert_lift", PROP_BOOLEAN, PROP_NONE); @@ -1603,6 +1654,31 @@ static void rna_def_color_balance(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Inverse Lift", "Invert the lift color"); RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_SequenceColorBalance_update"); + prop = RNA_def_property(srna, "invert_gamma", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_COLOR_BALANCE_INVERSE_GAMMA); + RNA_def_property_ui_text(prop, "Inverse Gamma", "Invert the gamma color"); + RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_SequenceColorBalance_update"); + + prop = RNA_def_property(srna, "invert_gain", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_COLOR_BALANCE_INVERSE_GAIN); + RNA_def_property_ui_text(prop, "Inverse Gain", "Invert the gain color`"); + RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_SequenceColorBalance_update"); + + prop = RNA_def_property(srna, "invert_slope", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_COLOR_BALANCE_INVERSE_SLOPE); + RNA_def_property_ui_text(prop, "Inverse Slope", "Invert the slope color`"); + RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_SequenceColorBalance_update"); + + prop = RNA_def_property(srna, "invert_offset", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_COLOR_BALANCE_INVERSE_OFFSET); + RNA_def_property_ui_text(prop, "Inverse Offset", "Invert the offset color"); + RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_SequenceColorBalance_update"); + + prop = RNA_def_property(srna, "invert_power", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_COLOR_BALANCE_INVERSE_POWER); + RNA_def_property_ui_text(prop, "Inverse Power", "Invert the power color"); + RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_SequenceColorBalance_update"); + /* not yet used */ # if 0 prop = RNA_def_property(srna, "exposure", PROP_FLOAT, PROP_NONE); @@ -1937,6 +2013,14 @@ static void rna_def_sequence(BlenderRNA *brna) RNA_def_property_update( prop, NC_SCENE | ND_SEQUENCER, "rna_Sequence_invalidate_preprocessed_update"); + prop = RNA_def_property(srna, "color_tag", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "color_tag"); + RNA_def_property_enum_funcs( + prop, "rna_Sequence_color_tag_get", "rna_Sequence_color_tag_set", NULL); + RNA_def_property_enum_items(prop, rna_enum_strip_color_items); + RNA_def_property_ui_text(prop, "Strip Color", "Color tag for a strip"); + RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, NULL); + /* modifiers */ prop = RNA_def_property(srna, "modifiers", PROP_COLLECTION, PROP_NONE); RNA_def_property_struct_type(prop, "SequenceModifier"); diff --git a/source/blender/makesrna/intern/rna_space.c b/source/blender/makesrna/intern/rna_space.c index a05cef7a1cd..652d2545e67 100644 --- a/source/blender/makesrna/intern/rna_space.c +++ b/source/blender/makesrna/intern/rna_space.c @@ -39,6 +39,7 @@ #include "BLI_listbase.h" #include "BLI_math.h" +#include "BLI_uuid.h" #include "DNA_action_types.h" #include "DNA_gpencil_types.h" @@ -856,6 +857,14 @@ static void rna_Space_view2d_sync_set(PointerRNA *ptr, bool value) ARegion *region; area = rna_area_from_space(ptr); /* can be NULL */ + if ((area != NULL) && !UI_view2d_area_supports_sync(area)) { + BKE_reportf(NULL, + RPT_ERROR, + "'show_locked_time' is not supported for the '%s' editor", + area->type->name); + return; + } + region = BKE_area_find_region_type(area, RGN_TYPE_WINDOW); if (region) { View2D *v2d = ®ion->v2d; @@ -906,7 +915,7 @@ static void rna_GPencil_update(Main *bmain, Scene *UNUSED(scene), PointerRNA *UN static void rna_SpaceView3D_camera_update(Main *bmain, Scene *scene, PointerRNA *ptr) { View3D *v3d = (View3D *)(ptr->data); - if (v3d->scenelock) { + if (v3d->scenelock && scene != NULL) { wmWindowManager *wm = bmain->wm.first; scene->camera = v3d->camera; @@ -1540,7 +1549,9 @@ static PointerRNA rna_SpaceImageEditor_uvedit_get(PointerRNA *ptr) static void rna_SpaceImageEditor_mode_update(Main *bmain, Scene *scene, PointerRNA *UNUSED(ptr)) { - ED_space_image_paint_update(bmain, bmain->wm.first, scene); + if (scene != NULL) { + ED_space_image_paint_update(bmain, bmain->wm.first, scene); + } } static void rna_SpaceImageEditor_show_stereo_set(PointerRNA *ptr, int value) @@ -2604,16 +2615,38 @@ static void rna_FileAssetSelectParams_asset_library_set(PointerRNA *ptr, int val params->asset_library_ref = ED_asset_library_reference_from_enum_value(value); } -static void rna_FileAssetSelectParams_asset_category_set(PointerRNA *ptr, uint64_t value) +static PointerRNA rna_FileBrowser_FileSelectEntry_asset_data_get(PointerRNA *ptr) { - FileSelectParams *params = ptr->data; - params->filter_id = value; + const FileDirEntry *entry = ptr->data; + + /* Note that the owning ID of the RNA pointer (`ptr->owner_id`) has to be set carefully: + * Local IDs (`entry->id`) own their asset metadata themselves. Asset metadata from other blend + * files are owned by the file browser (`entry`). Only if this is set correctly, we can tell from + * the metadata RNA pointer if the metadata is stored locally and can thus be edited or not. */ + + if (entry->id) { + PointerRNA id_ptr; + RNA_id_pointer_create(entry->id, &id_ptr); + return rna_pointer_inherit_refine(&id_ptr, &RNA_AssetMetaData, entry->asset_data); + } + + return rna_pointer_inherit_refine(ptr, &RNA_AssetMetaData, entry->asset_data); } -static uint64_t rna_FileAssetSelectParams_asset_category_get(PointerRNA *ptr) +static int rna_FileBrowser_FileSelectEntry_name_editable(PointerRNA *ptr, const char **r_info) { - FileSelectParams *params = ptr->data; - return params->filter_id; + const FileDirEntry *entry = ptr->data; + + /* This actually always returns 0 (the name is never editable) but we want to get a disabled + * message returned to `r_info` in some cases. */ + + if (entry->asset_data) { + PointerRNA asset_data_ptr = rna_FileBrowser_FileSelectEntry_asset_data_get(ptr); + /* Get disabled hint from asset metadata polling. */ + rna_AssetMetaData_editable(&asset_data_ptr, r_info); + } + + return 0; } static void rna_FileBrowser_FileSelectEntry_name_get(PointerRNA *ptr, char *value) @@ -2672,12 +2705,6 @@ static int rna_FileBrowser_FileSelectEntry_preview_icon_id_get(PointerRNA *ptr) return ED_file_icon(entry); } -static PointerRNA rna_FileBrowser_FileSelectEntry_asset_data_get(PointerRNA *ptr) -{ - const FileDirEntry *entry = ptr->data; - return rna_pointer_inherit_refine(ptr, &RNA_AssetMetaData, entry->asset_data); -} - static StructRNA *rna_FileBrowser_params_typef(PointerRNA *ptr) { SpaceFile *sfile = ptr->data; @@ -3153,6 +3180,17 @@ static void rna_SpaceSpreadsheet_context_path_guess(SpaceSpreadsheet *sspreadshe WM_main_add_notifier(NC_SPACE | ND_SPACE_SPREADSHEET, NULL); } +static void rna_FileAssetSelectParams_catalog_id_get(PointerRNA *ptr, char *value) +{ + const FileAssetSelectParams *params = ptr->data; + BLI_uuid_format(value, params->catalog_id); +} + +static int rna_FileAssetSelectParams_catalog_id_length(PointerRNA *UNUSED(ptr)) +{ + return UUID_STRING_LEN - 1; +} + #else static const EnumPropertyItem dt_uv_items[] = { @@ -3443,6 +3481,19 @@ static void rna_def_space_image_uv(BlenderRNA *brna) prop, "Tile Grid Shape", "How many tiles will be shown in the background"); RNA_def_property_update(prop, NC_SPACE | ND_SPACE_IMAGE, NULL); + prop = RNA_def_property(srna, "use_custom_grid", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", SI_CUSTOM_GRID); + RNA_def_property_boolean_default(prop, true); + RNA_def_property_ui_text(prop, "Custom Grid", "Use a grid with a user-defined number of steps"); + RNA_def_property_update(prop, NC_SPACE | ND_SPACE_IMAGE, NULL); + + prop = RNA_def_property(srna, "custom_grid_subdivisions", PROP_INT, PROP_NONE); + RNA_def_property_int_sdna(prop, NULL, "custom_grid_subdiv"); + RNA_def_property_range(prop, 1, 5000); + RNA_def_property_ui_text( + prop, "Dynamic Grid Size", "Number of grid units in UV space that make one UV Unit"); + RNA_def_property_update(prop, NC_SPACE | ND_SPACE_IMAGE, NULL); + prop = RNA_def_property(srna, "uv_opacity", PROP_FLOAT, PROP_FACTOR); RNA_def_property_float_sdna(prop, NULL, "uv_opacity"); RNA_def_property_range(prop, 0.0f, 1.0f); @@ -5449,6 +5500,12 @@ static void rna_def_space_sequencer_timeline_overlay(BlenderRNA *brna) RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_TIMELINE_SHOW_THUMBNAILS); RNA_def_property_ui_text(prop, "Show Thumbnails", "Show strip thumbnails"); RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SEQUENCER, NULL); + + prop = RNA_def_property(srna, "show_strip_tag_color", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_TIMELINE_SHOW_STRIP_COLOR_TAG); + RNA_def_property_ui_text( + prop, "Show Color Tags", "Display the strip color tags in the sequencer"); + RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SEQUENCER, NULL); } static void rna_def_space_sequencer(BlenderRNA *brna) @@ -6260,12 +6317,13 @@ static void rna_def_fileselect_entry(BlenderRNA *brna) RNA_def_struct_ui_text(srna, "File Select Entry", "A file viewable in the File Browser"); prop = RNA_def_property(srna, "name", PROP_STRING, PROP_NONE); + RNA_def_property_editable_func(prop, "rna_FileBrowser_FileSelectEntry_name_editable"); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); RNA_def_property_string_funcs(prop, "rna_FileBrowser_FileSelectEntry_name_get", "rna_FileBrowser_FileSelectEntry_name_length", NULL); RNA_def_property_ui_text(prop, "Name", ""); - RNA_def_property_clear_flag(prop, PROP_EDITABLE); RNA_def_struct_name_property(srna, prop); prop = RNA_def_property(srna, "relative_path", PROP_STRING, PROP_NONE); @@ -6537,47 +6595,6 @@ static void rna_def_fileselect_asset_params(BlenderRNA *brna) StructRNA *srna; PropertyRNA *prop; - /* XXX copied from rna_enum_id_type_filter_items. */ - static const EnumPropertyItem asset_category_items[] = { - {FILTER_ID_SCE, "SCENES", ICON_SCENE_DATA, "Scenes", "Show scenes"}, - {FILTER_ID_AC, "ANIMATIONS", ICON_ANIM_DATA, "Animations", "Show animation data"}, - {FILTER_ID_OB | FILTER_ID_GR, - "OBJECTS_AND_COLLECTIONS", - ICON_GROUP, - "Objects & Collections", - "Show objects and collections"}, - {FILTER_ID_AR | FILTER_ID_CU | FILTER_ID_LT | FILTER_ID_MB | FILTER_ID_ME - /* XXX avoid warning */ - // | FILTER_ID_HA | FILTER_ID_PT | FILTER_ID_VO - , - "GEOMETRY", - ICON_MESH_DATA, - "Geometry", - "Show meshes, curves, lattice, armatures and metaballs data"}, - {FILTER_ID_LS | FILTER_ID_MA | FILTER_ID_NT | FILTER_ID_TE, - "SHADING", - ICON_MATERIAL_DATA, - "Shading", - "Show materials, nodetrees, textures and Freestyle's linestyles"}, - {FILTER_ID_IM | FILTER_ID_MC | FILTER_ID_MSK | FILTER_ID_SO, - "IMAGES_AND_SOUNDS", - ICON_IMAGE_DATA, - "Images & Sounds", - "Show images, movie clips, sounds and masks"}, - {FILTER_ID_CA | FILTER_ID_LA | FILTER_ID_LP | FILTER_ID_SPK | FILTER_ID_WO, - "ENVIRONMENTS", - ICON_WORLD_DATA, - "Environment", - "Show worlds, lights, cameras and speakers"}, - {FILTER_ID_BR | FILTER_ID_GD | FILTER_ID_PA | FILTER_ID_PAL | FILTER_ID_PC | FILTER_ID_TXT | - FILTER_ID_VF | FILTER_ID_CF | FILTER_ID_WS, - "MISC", - ICON_GREASEPENCIL, - "Miscellaneous", - "Show other data types"}, - {0, NULL, 0, NULL, NULL}, - }; - static const EnumPropertyItem asset_import_type_items[] = { {FILE_ASSET_IMPORT_LINK, "LINK", 0, "Link", "Import the assets as linked data-block"}, {FILE_ASSET_IMPORT_APPEND, @@ -6585,6 +6602,14 @@ static void rna_def_fileselect_asset_params(BlenderRNA *brna) 0, "Append", "Import the assets as copied data-block, with no link to the original asset data-block"}, + {FILE_ASSET_IMPORT_APPEND_REUSE, + "APPEND_REUSE", + 0, + "Append (Reuse Data)", + "Import the assets as copied data-block while avoiding multiple copies of nested, " + "typically heavy data. For example the textures of a material asset, or the mesh of an " + "object asset, don't have to be copied every time this asset is imported. The instances of " + "the asset share the data instead"}, {0, NULL, 0, NULL, NULL}, }; @@ -6598,14 +6623,13 @@ static void rna_def_fileselect_asset_params(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Asset Library", ""); RNA_def_property_update(prop, NC_SPACE | ND_SPACE_FILE_PARAMS, NULL); - prop = RNA_def_property(srna, "asset_category", PROP_ENUM, PROP_NONE); - RNA_def_property_enum_items(prop, asset_category_items); - RNA_def_property_enum_funcs(prop, - "rna_FileAssetSelectParams_asset_category_get", - "rna_FileAssetSelectParams_asset_category_set", - NULL); - RNA_def_property_ui_text(prop, "Asset Category", "Determine which kind of assets to display"); - RNA_def_property_update(prop, NC_SPACE | ND_SPACE_FILE_LIST, NULL); + prop = RNA_def_property(srna, "catalog_id", PROP_STRING, PROP_NONE); + RNA_def_property_string_funcs(prop, + "rna_FileAssetSelectParams_catalog_id_get", + "rna_FileAssetSelectParams_catalog_id_length", + NULL); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_ui_text(prop, "Catalog UUID", "The UUID of the catalog shown in the browser"); prop = RNA_def_property(srna, "import_type", PROP_ENUM, PROP_NONE); RNA_def_property_enum_items(prop, asset_import_type_items); diff --git a/source/blender/makesrna/intern/rna_tracking.c b/source/blender/makesrna/intern/rna_tracking.c index 336359a9dc0..c81588aa8b5 100644 --- a/source/blender/makesrna/intern/rna_tracking.c +++ b/source/blender/makesrna/intern/rna_tracking.c @@ -2437,6 +2437,8 @@ static void rna_def_trackingDopesheet(BlenderRNA *brna) 0, "Average Error", "Sort channels by average reprojection error of tracks after solve"}, + {TRACKING_DOPE_SORT_START, "START", 0, "Start Frame", "Sort channels by first frame number"}, + {TRACKING_DOPE_SORT_END, "END", 0, "End Frame", "Sort channels by last frame number"}, {0, NULL, 0, NULL, NULL}, }; diff --git a/source/blender/makesrna/intern/rna_userdef.c b/source/blender/makesrna/intern/rna_userdef.c index 563c6ea35e0..ccfc1222d4c 100644 --- a/source/blender/makesrna/intern/rna_userdef.c +++ b/source/blender/makesrna/intern/rna_userdef.c @@ -3680,6 +3680,23 @@ static void rna_def_userdef_theme_collection_color(BlenderRNA *brna) RNA_def_property_update(prop, 0, "rna_userdef_theme_update"); } +static void rna_def_userdef_theme_strip_color(BlenderRNA *brna) +{ + StructRNA *srna; + PropertyRNA *prop; + + srna = RNA_def_struct(brna, "ThemeStripColor", NULL); + RNA_def_struct_sdna(srna, "ThemeStripColor"); + RNA_def_struct_clear_flag(srna, STRUCT_UNDO); + RNA_def_struct_ui_text(srna, "Theme Strip Color", "Theme settings for strip colors"); + + prop = RNA_def_property(srna, "color", PROP_FLOAT, PROP_COLOR_GAMMA); + RNA_def_property_float_sdna(prop, NULL, "color"); + RNA_def_property_array(prop, 3); + RNA_def_property_ui_text(prop, "Color", "Strip Color"); + RNA_def_property_update(prop, 0, "rna_userdef_theme_update"); +} + static void rna_def_userdef_theme_space_clip(BlenderRNA *brna) { StructRNA *srna; @@ -4029,6 +4046,12 @@ static void rna_def_userdef_themes(BlenderRNA *brna) RNA_def_property_collection_sdna(prop, NULL, "collection_color", ""); RNA_def_property_struct_type(prop, "ThemeCollectionColor"); RNA_def_property_ui_text(prop, "Collection Color", ""); + + prop = RNA_def_property(srna, "strip_color", PROP_COLLECTION, PROP_NONE); + RNA_def_property_flag(prop, PROP_NEVER_NULL); + RNA_def_property_collection_sdna(prop, NULL, "strip_color", ""); + RNA_def_property_struct_type(prop, "ThemeStripColor"); + RNA_def_property_ui_text(prop, "Strip Color", ""); } static void rna_def_userdef_addon(BlenderRNA *brna) @@ -4272,6 +4295,7 @@ static void rna_def_userdef_dothemes(BlenderRNA *brna) rna_def_userdef_theme_space_spreadsheet(brna); rna_def_userdef_theme_colorset(brna); rna_def_userdef_theme_collection_color(brna); + rna_def_userdef_theme_strip_color(brna); rna_def_userdef_themes(brna); } @@ -6285,6 +6309,13 @@ static void rna_def_userdef_experimental(BlenderRNA *brna) "Enable library overrides automatic resync detection and process on file load. Disable when " "dealing with older .blend files that need manual Resync (Enforce) handling"); + prop = RNA_def_property(srna, "proxy_to_override_auto_conversion", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_negative_sdna(prop, NULL, "no_proxy_to_override_conversion", 1); + RNA_def_property_ui_text( + prop, + "Proxy to Override Auto Conversion", + "Enable automatic conversion of proxies to library overrides on file load"); + prop = RNA_def_property(srna, "use_new_point_cloud_type", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "use_new_point_cloud_type", 1); RNA_def_property_ui_text( @@ -6328,9 +6359,10 @@ static void rna_def_userdef_experimental(BlenderRNA *brna) RNA_def_property_ui_text( prop, "Override Templates", "Enable library override template in the python API"); - prop = RNA_def_property(srna, "use_geometry_nodes_fields", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, NULL, "use_geometry_nodes_fields", 1); - RNA_def_property_ui_text(prop, "Geometry Nodes Fields", "Enable field nodes in geometry nodes"); + prop = RNA_def_property(srna, "use_geometry_nodes_legacy", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "use_geometry_nodes_legacy", 1); + RNA_def_property_ui_text( + prop, "Geometry Nodes Legacy", "Enable legacy geometry nodes in the menu"); } static void rna_def_userdef_addon_collection(BlenderRNA *brna, PropertyRNA *cprop) diff --git a/source/blender/makesrna/intern/rna_volume.c b/source/blender/makesrna/intern/rna_volume.c index 8ed53c9f70f..f6b8b55688c 100644 --- a/source/blender/makesrna/intern/rna_volume.c +++ b/source/blender/makesrna/intern/rna_volume.c @@ -41,6 +41,16 @@ # include "WM_api.h" # include "WM_types.h" +static char *rna_VolumeRender_path(PointerRNA *UNUSED(ptr)) +{ + return BLI_strdup("render"); +} + +static char *rna_VolumeDisplay_path(PointerRNA *UNUSED(ptr)) +{ + return BLI_strdup("display"); +} + /* Updates */ static void rna_Volume_update_display(Main *UNUSED(bmain), Scene *UNUSED(scene), PointerRNA *ptr) @@ -371,6 +381,7 @@ static void rna_def_volume_display(BlenderRNA *brna) srna = RNA_def_struct(brna, "VolumeDisplay", NULL); RNA_def_struct_ui_text(srna, "Volume Display", "Volume object display settings for 3D viewport"); RNA_def_struct_sdna(srna, "VolumeDisplay"); + RNA_def_struct_path_func(srna, "rna_VolumeDisplay_path"); prop = RNA_def_property(srna, "density", PROP_FLOAT, PROP_NONE); RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); @@ -477,6 +488,7 @@ static void rna_def_volume_render(BlenderRNA *brna) srna = RNA_def_struct(brna, "VolumeRender", NULL); RNA_def_struct_ui_text(srna, "Volume Render", "Volume object render settings"); RNA_def_struct_sdna(srna, "VolumeRender"); + RNA_def_struct_path_func(srna, "rna_VolumeRender_path"); static const EnumPropertyItem space_items[] = { {VOLUME_SPACE_OBJECT, diff --git a/source/blender/modifiers/intern/MOD_cloth.c b/source/blender/modifiers/intern/MOD_cloth.c index fa2f70e1a9c..cf0658d4b39 100644 --- a/source/blender/modifiers/intern/MOD_cloth.c +++ b/source/blender/modifiers/intern/MOD_cloth.c @@ -194,7 +194,7 @@ static void copyData(const ModifierData *md, ModifierData *target, const int fla } BKE_ptcache_free_list(&tclmd->ptcaches); - if (flag & LIB_ID_CREATE_NO_MAIN) { + if (flag & LIB_ID_COPY_SET_COPIED_ON_WRITE) { /* Share the cache with the original object's modifier. */ tclmd->modifier.flag |= eModifierFlag_SharedCaches; tclmd->ptcaches = clmd->ptcaches; diff --git a/source/blender/modifiers/intern/MOD_datatransfer.c b/source/blender/modifiers/intern/MOD_datatransfer.c index e2b2cc58d48..34bb93cbbbc 100644 --- a/source/blender/modifiers/intern/MOD_datatransfer.c +++ b/source/blender/modifiers/intern/MOD_datatransfer.c @@ -163,7 +163,6 @@ static bool isDisabled(const struct Scene *UNUSED(scene), return !dtmd->ob_source || dtmd->ob_source->type != OB_MESH; } -#define HIGH_POLY_WARNING 10000 #define DT_TYPES_AFFECT_MESH \ (DT_TYPE_BWEIGHT_VERT | DT_TYPE_BWEIGHT_EDGE | DT_TYPE_CREASE | DT_TYPE_SHARP_EDGE | \ DT_TYPE_LNOR | DT_TYPE_SHARP_FACE) @@ -239,13 +238,6 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh * BKE_modifier_set_error( ctx->object, (ModifierData *)dtmd, "Enable 'Auto Smooth' in Object Data Properties"); } - else if (result->totvert > HIGH_POLY_WARNING || - ((Mesh *)(ob_source->data))->totvert > HIGH_POLY_WARNING) { - BKE_modifier_set_error( - ctx->object, - md, - "Source or destination object has a high polygon count, computation might be slow"); - } return result; } @@ -473,7 +465,6 @@ static void panelRegister(ARegionType *region_type) region_type, "advanced", "Topology Mapping", NULL, advanced_panel_draw, panel_type); } -#undef HIGH_POLY_WARNING #undef DT_TYPES_AFFECT_MESH ModifierTypeInfo modifierType_DataTransfer = { diff --git a/source/blender/modifiers/intern/MOD_nodes.cc b/source/blender/modifiers/intern/MOD_nodes.cc index 8c02c83d479..d294491d51c 100644 --- a/source/blender/modifiers/intern/MOD_nodes.cc +++ b/source/blender/modifiers/intern/MOD_nodes.cc @@ -88,6 +88,7 @@ #include "NOD_derived_node_tree.hh" #include "NOD_geometry.h" #include "NOD_geometry_nodes_eval_log.hh" +#include "NOD_node_declaration.hh" #include "FN_field.hh" #include "FN_multi_function.hh" @@ -103,9 +104,13 @@ using blender::Span; using blender::StringRef; using blender::StringRefNull; using blender::Vector; +using blender::bke::OutputAttribute; +using blender::fn::GField; using blender::fn::GMutablePointer; using blender::fn::GPointer; +using blender::nodes::FieldInferencingInterface; using blender::nodes::GeoNodeExecParams; +using blender::nodes::InputSocketFieldType; using blender::threading::EnumerableThreadSpecific; using namespace blender::fn::multi_function_types; using namespace blender::nodes::derived_node_tree_types; @@ -306,6 +311,17 @@ static bool socket_type_has_attribute_toggle(const bNodeSocket &socket) return ELEM(socket.type, SOCK_FLOAT, SOCK_VECTOR, SOCK_BOOLEAN, SOCK_RGBA, SOCK_INT); } +/** + * \return Whether using an attribute to input values of this type is supported, and the node + * group's input for this socket accepts a field rather than just single values. + */ +static bool input_has_attribute_toggle(const bNodeTree &node_tree, const int socket_index) +{ + BLI_assert(node_tree.field_inferencing_interface != nullptr); + const FieldInferencingInterface &field_interface = *node_tree.field_inferencing_interface; + return field_interface.inputs[socket_index] != InputSocketFieldType::None; +} + static IDProperty *id_property_create_from_socket(const bNodeSocket &socket) { switch (socket.type) { @@ -531,7 +547,8 @@ void MOD_nodes_update_interface(Object *object, NodesModifierData *nmd) nmd->settings.properties = IDP_New(IDP_GROUP, &idprop, "Nodes Modifier Settings"); } - LISTBASE_FOREACH (bNodeSocket *, socket, &nmd->node_group->inputs) { + int socket_index; + LISTBASE_FOREACH_INDEX (bNodeSocket *, socket, &nmd->node_group->inputs, socket_index) { IDProperty *new_prop = id_property_create_from_socket(*socket); if (new_prop == nullptr) { /* Out of the set of supported input sockets, only @@ -562,7 +579,7 @@ void MOD_nodes_update_interface(Object *object, NodesModifierData *nmd) } } - if (socket_type_has_attribute_toggle(*socket)) { + if (input_has_attribute_toggle(*nmd->node_group, socket_index)) { const std::string use_attribute_id = socket->identifier + use_attribute_suffix; const std::string attribute_name_id = socket->identifier + attribute_name_suffix; @@ -589,6 +606,35 @@ void MOD_nodes_update_interface(Object *object, NodesModifierData *nmd) } } + LISTBASE_FOREACH (bNodeSocket *, socket, &nmd->node_group->outputs) { + if (!socket_type_has_attribute_toggle(*socket)) { + continue; + } + + const std::string idprop_name = socket->identifier + attribute_name_suffix; + IDProperty *new_prop = IDP_NewString("", idprop_name.c_str(), MAX_NAME); + if (socket->description[0] != '\0') { + IDPropertyUIData *ui_data = IDP_ui_data_ensure(new_prop); + ui_data->description = BLI_strdup(socket->description); + } + IDP_AddToGroup(nmd->settings.properties, new_prop); + + if (old_properties != nullptr) { + IDProperty *old_prop = IDP_GetPropertyFromGroup(old_properties, idprop_name.c_str()); + if (old_prop != nullptr) { + /* #IDP_CopyPropertyContent replaces the UI data as well, which we don't (we only + * want to replace the values). So release it temporarily and replace it after. */ + IDPropertyUIData *ui_data = new_prop->ui_data; + new_prop->ui_data = nullptr; + IDP_CopyPropertyContent(new_prop, old_prop); + if (new_prop->ui_data != nullptr) { + IDP_ui_data_free(new_prop); + } + new_prop->ui_data = ui_data; + } + } + } + if (old_properties != nullptr) { IDP_FreeProperty(old_properties); } @@ -624,37 +670,39 @@ void MOD_nodes_init(Main *bmain, NodesModifierData *nmd) } static void initialize_group_input(NodesModifierData &nmd, - const bNodeSocket &socket, + const OutputSocketRef &socket, void *r_value) { + const bNodeSocketType &socket_type = *socket.typeinfo(); + const bNodeSocket &bsocket = *socket.bsocket(); if (nmd.settings.properties == nullptr) { - socket.typeinfo->get_geometry_nodes_cpp_value(socket, r_value); + socket_type.get_geometry_nodes_cpp_value(bsocket, r_value); return; } const IDProperty *property = IDP_GetPropertyFromGroup(nmd.settings.properties, - socket.identifier); + socket.identifier().c_str()); if (property == nullptr) { - socket.typeinfo->get_geometry_nodes_cpp_value(socket, r_value); + socket_type.get_geometry_nodes_cpp_value(bsocket, r_value); return; } - if (!id_property_type_matches_socket(socket, *property)) { - socket.typeinfo->get_geometry_nodes_cpp_value(socket, r_value); + if (!id_property_type_matches_socket(bsocket, *property)) { + socket_type.get_geometry_nodes_cpp_value(bsocket, r_value); return; } - if (!socket_type_has_attribute_toggle(socket)) { + if (!input_has_attribute_toggle(*nmd.node_group, socket.index())) { init_socket_cpp_value_from_property( - *property, static_cast<eNodeSocketDatatype>(socket.type), r_value); + *property, static_cast<eNodeSocketDatatype>(bsocket.type), r_value); return; } const IDProperty *property_use_attribute = IDP_GetPropertyFromGroup( - nmd.settings.properties, (socket.identifier + use_attribute_suffix).c_str()); + nmd.settings.properties, (socket.identifier() + use_attribute_suffix).c_str()); const IDProperty *property_attribute_name = IDP_GetPropertyFromGroup( - nmd.settings.properties, (socket.identifier + attribute_name_suffix).c_str()); + nmd.settings.properties, (socket.identifier() + attribute_name_suffix).c_str()); if (property_use_attribute == nullptr || property_attribute_name == nullptr) { init_socket_cpp_value_from_property( - *property, static_cast<eNodeSocketDatatype>(socket.type), r_value); + *property, static_cast<eNodeSocketDatatype>(bsocket.type), r_value); return; } @@ -662,12 +710,12 @@ static void initialize_group_input(NodesModifierData &nmd, if (use_attribute) { const StringRef attribute_name{IDP_String(property_attribute_name)}; auto attribute_input = std::make_shared<blender::bke::AttributeFieldInput>( - attribute_name, *socket.typeinfo->get_base_cpp_type()); + attribute_name, *socket_type.get_base_cpp_type()); new (r_value) blender::fn::GField(std::move(attribute_input), 0); } else { init_socket_cpp_value_from_property( - *property, static_cast<eNodeSocketDatatype>(socket.type), r_value); + *property, static_cast<eNodeSocketDatatype>(bsocket.type), r_value); } } @@ -778,14 +826,78 @@ static void clear_runtime_data(NodesModifierData *nmd) } } +static void store_field_on_geometry_component(GeometryComponent &component, + const StringRef attribute_name, + AttributeDomain domain, + const GField &field) +{ + /* If the attribute name corresponds to a built-in attribute, use the domain of the built-in + * attribute instead. */ + if (component.attribute_is_builtin(attribute_name)) { + component.attribute_try_create_builtin(attribute_name, AttributeInitDefault()); + std::optional<AttributeMetaData> meta_data = component.attribute_get_meta_data(attribute_name); + if (meta_data.has_value()) { + domain = meta_data->domain; + } + else { + return; + } + } + const CustomDataType data_type = blender::bke::cpp_type_to_custom_data_type(field.cpp_type()); + OutputAttribute attribute = component.attribute_try_get_for_output_only( + attribute_name, domain, data_type); + if (attribute) { + /* In the future we could also evaluate all output fields at once. */ + const int domain_size = component.attribute_domain_size(domain); + blender::bke::GeometryComponentFieldContext field_context{component, domain}; + blender::fn::FieldEvaluator field_evaluator{field_context, domain_size}; + field_evaluator.add_with_destination(field, attribute.varray()); + field_evaluator.evaluate(); + attribute.save(); + } +} + +static void store_output_value_in_geometry(GeometrySet &geometry_set, + NodesModifierData *nmd, + const InputSocketRef &socket, + const GPointer value) +{ + if (!socket_type_has_attribute_toggle(*socket.bsocket())) { + return; + } + const std::string prop_name = socket.identifier() + attribute_name_suffix; + const IDProperty *prop = IDP_GetPropertyFromGroup(nmd->settings.properties, prop_name.c_str()); + if (prop == nullptr) { + return; + } + const StringRefNull attribute_name = IDP_String(prop); + if (attribute_name.is_empty()) { + return; + } + const GField &field = *(const GField *)value.get(); + const bNodeSocket *interface_socket = (bNodeSocket *)BLI_findlink(&nmd->node_group->outputs, + socket.index()); + const AttributeDomain domain = (AttributeDomain)interface_socket->attribute_domain; + if (geometry_set.has_mesh()) { + MeshComponent &component = geometry_set.get_component_for_write<MeshComponent>(); + store_field_on_geometry_component(component, attribute_name, domain, field); + } + if (geometry_set.has_pointcloud()) { + PointCloudComponent &component = geometry_set.get_component_for_write<PointCloudComponent>(); + store_field_on_geometry_component(component, attribute_name, domain, field); + } + if (geometry_set.has_curve()) { + CurveComponent &component = geometry_set.get_component_for_write<CurveComponent>(); + store_field_on_geometry_component(component, attribute_name, domain, field); + } +} + /** * Evaluate a node group to compute the output geometry. - * Currently, this uses a fairly basic and inefficient algorithm that might compute things more - * often than necessary. It's going to be replaced soon. */ static GeometrySet compute_geometry(const DerivedNodeTree &tree, Span<const NodeRef *> group_input_nodes, - const InputSocketRef &socket_to_compute, + const NodeRef &output_node, GeometrySet input_geometry_set, NodesModifierData *nmd, const ModifierEvalContext *ctx) @@ -819,7 +931,7 @@ static GeometrySet compute_geometry(const DerivedNodeTree &tree, for (const OutputSocketRef *socket : remaining_input_sockets) { const CPPType &cpp_type = *socket->typeinfo()->get_geometry_nodes_cpp_type(); void *value_in = allocator.allocate(cpp_type.size(), cpp_type.alignment()); - initialize_group_input(*nmd, *socket->bsocket(), value_in); + initialize_group_input(*nmd, *socket, value_in); group_inputs.add_new({root_context, socket}, {cpp_type, value_in}); } } @@ -828,7 +940,9 @@ static GeometrySet compute_geometry(const DerivedNodeTree &tree, input_geometry_set.clear(); Vector<DInputSocket> group_outputs; - group_outputs.append({root_context, &socket_to_compute}); + for (const InputSocketRef *socket_ref : output_node.inputs().drop_back(1)) { + group_outputs.append({root_context, socket_ref}); + } std::optional<geo_log::GeoLogger> geo_logger; @@ -856,9 +970,15 @@ static GeometrySet compute_geometry(const DerivedNodeTree &tree, nmd_orig->runtime_eval_log = new geo_log::ModifierLog(*geo_logger); } - BLI_assert(eval_params.r_output_values.size() == 1); - GMutablePointer result = eval_params.r_output_values[0]; - return result.relocate_out<GeometrySet>(); + GeometrySet output_geometry_set = eval_params.r_output_values[0].relocate_out<GeometrySet>(); + + for (const InputSocketRef *socket : output_node.inputs().drop_front(1).drop_back(1)) { + GMutablePointer socket_value = eval_params.r_output_values[socket->index()]; + store_output_value_in_geometry(output_geometry_set, nmd, *socket, socket_value); + socket_value.destruct(); + } + + return output_geometry_set; } /** @@ -928,24 +1048,23 @@ static void modifyGeometry(ModifierData *md, const NodeTreeRef &root_tree_ref = tree.root_context().tree(); Span<const NodeRef *> input_nodes = root_tree_ref.nodes_by_type("NodeGroupInput"); Span<const NodeRef *> output_nodes = root_tree_ref.nodes_by_type("NodeGroupOutput"); - if (output_nodes.size() != 1) { return; } - Span<const InputSocketRef *> group_outputs = output_nodes[0]->inputs().drop_back(1); - - if (group_outputs.size() == 0) { + const NodeRef &output_node = *output_nodes[0]; + Span<const InputSocketRef *> group_outputs = output_node.inputs().drop_back(1); + if (group_outputs.is_empty()) { return; } - const InputSocketRef *group_output = group_outputs[0]; - if (group_output->idname() != "NodeSocketGeometry") { + const InputSocketRef *first_output_socket = group_outputs[0]; + if (first_output_socket->idname() != "NodeSocketGeometry") { return; } geometry_set = compute_geometry( - tree, input_nodes, *group_outputs[0], std::move(geometry_set), nmd, ctx); + tree, input_nodes, output_node, std::move(geometry_set), nmd, ctx); } static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh *mesh) @@ -981,7 +1100,8 @@ static void draw_property_for_socket(uiLayout *layout, NodesModifierData *nmd, PointerRNA *bmain_ptr, PointerRNA *md_ptr, - const bNodeSocket &socket) + const bNodeSocket &socket, + const int socket_index) { /* The property should be created in #MOD_nodes_update_interface with the correct type. */ IDProperty *property = IDP_GetPropertyFromGroup(nmd->settings.properties, socket.identifier); @@ -1026,8 +1146,7 @@ static void draw_property_for_socket(uiLayout *layout, break; } default: { - if (socket_type_has_attribute_toggle(socket) && - USER_EXPERIMENTAL_TEST(&U, use_geometry_nodes_fields)) { + if (input_has_attribute_toggle(*nmd->node_group, socket_index)) { const std::string rna_path_use_attribute = "[\"" + std::string(socket_id_esc) + use_attribute_suffix + "\"]"; const std::string rna_path_attribute_name = "[\"" + std::string(socket_id_esc) + @@ -1060,6 +1179,18 @@ static void draw_property_for_socket(uiLayout *layout, } } +static void draw_property_for_output_socket(uiLayout *layout, + PointerRNA *md_ptr, + const bNodeSocket &socket) +{ + char socket_id_esc[sizeof(socket.identifier) * 2]; + BLI_str_escape(socket_id_esc, socket.identifier, sizeof(socket_id_esc)); + const std::string rna_path_attribute_name = "[\"" + StringRef(socket_id_esc) + + attribute_name_suffix + "\"]"; + + uiItemR(layout, md_ptr, rna_path_attribute_name.c_str(), 0, socket.name, ICON_NONE); +} + static void panel_draw(const bContext *C, Panel *panel) { uiLayout *layout = panel->layout; @@ -1086,8 +1217,9 @@ static void panel_draw(const bContext *C, Panel *panel) PointerRNA bmain_ptr; RNA_main_pointer_create(bmain, &bmain_ptr); - LISTBASE_FOREACH (bNodeSocket *, socket, &nmd->node_group->inputs) { - draw_property_for_socket(layout, nmd, &bmain_ptr, ptr, *socket); + int socket_index; + LISTBASE_FOREACH_INDEX (bNodeSocket *, socket, &nmd->node_group->inputs, socket_index) { + draw_property_for_socket(layout, nmd, &bmain_ptr, ptr, *socket, socket_index); } } @@ -1107,7 +1239,7 @@ static void panel_draw(const bContext *C, Panel *panel) }); } - if (USER_EXPERIMENTAL_TEST(&U, use_geometry_nodes_fields) && has_legacy_node) { + if (has_legacy_node) { uiLayout *row = uiLayoutRow(layout, false); uiItemL(row, IFACE_("Node tree has legacy node"), ICON_ERROR); uiLayout *sub = uiLayoutRow(row, false); @@ -1118,9 +1250,34 @@ static void panel_draw(const bContext *C, Panel *panel) modifier_panel_end(layout, ptr); } +static void output_attribute_panel_draw(const bContext *UNUSED(C), Panel *panel) +{ + uiLayout *layout = panel->layout; + + PointerRNA *ptr = modifier_panel_get_property_pointers(panel, nullptr); + NodesModifierData *nmd = static_cast<NodesModifierData *>(ptr->data); + + uiLayoutSetPropSep(layout, true); + uiLayoutSetPropDecorate(layout, true); + + if (nmd->node_group != nullptr && nmd->settings.properties != nullptr) { + LISTBASE_FOREACH (bNodeSocket *, socket, &nmd->node_group->outputs) { + if (socket_type_has_attribute_toggle(*socket)) { + draw_property_for_output_socket(layout, ptr, *socket); + } + } + } +} + static void panelRegister(ARegionType *region_type) { - modifier_panel_register(region_type, eModifierType_Nodes, panel_draw); + PanelType *panel_type = modifier_panel_register(region_type, eModifierType_Nodes, panel_draw); + modifier_subpanel_register(region_type, + "output_attributes", + N_("Output Attributes"), + nullptr, + output_attribute_panel_draw, + panel_type); } static void blendWrite(BlendWriter *writer, const ModifierData *md) diff --git a/source/blender/modifiers/intern/MOD_nodes_evaluator.cc b/source/blender/modifiers/intern/MOD_nodes_evaluator.cc index e50c07ce6f2..c5213dc304b 100644 --- a/source/blender/modifiers/intern/MOD_nodes_evaluator.cc +++ b/source/blender/modifiers/intern/MOD_nodes_evaluator.cc @@ -328,13 +328,24 @@ static void get_socket_value(const SocketRef &socket, void *r_value) * more complex defaults (other than just single values) in their socket declarations. */ if (bsocket.flag & SOCK_HIDE_VALUE) { const bNode &bnode = *socket.bnode(); - if (bsocket.type == SOCK_VECTOR && - ELEM(bnode.type, GEO_NODE_SET_POSITION, SH_NODE_TEX_NOISE)) { - new (r_value) Field<float3>( - std::make_shared<bke::AttributeFieldInput>("position", CPPType::get<float3>())); - return; + if (bsocket.type == SOCK_VECTOR) { + if (ELEM(bnode.type, + GEO_NODE_SET_POSITION, + SH_NODE_TEX_NOISE, + GEO_NODE_MESH_TO_POINTS, + GEO_NODE_PROXIMITY)) { + new (r_value) Field<float3>(bke::AttributeFieldInput::Create<float3>("position")); + return; + } + } + else if (bsocket.type == SOCK_INT) { + if (ELEM(bnode.type, FN_NODE_RANDOM_VALUE, GEO_NODE_INSTANCE_ON_POINTS)) { + new (r_value) Field<int>(std::make_shared<fn::IndexFieldInput>()); + return; + } } } + const bNodeSocketType *typeinfo = socket.typeinfo(); typeinfo->get_geometry_nodes_cpp_value(*socket.bsocket(), r_value); } @@ -870,11 +881,9 @@ class GeometryNodesEvaluator { NodeParamsProvider params_provider{*this, node, node_state}; GeoNodeExecParams params{params_provider}; - if (USER_EXPERIMENTAL_TEST(&U, use_geometry_nodes_fields)) { - if (node->idname().find("Legacy") != StringRef::not_found) { - params.error_message_add(geo_log::NodeWarningType::Legacy, - TIP_("Legacy node will be removed before Blender 4.0")); - } + if (node->idname().find("Legacy") != StringRef::not_found) { + params.error_message_add(geo_log::NodeWarningType::Legacy, + TIP_("Legacy node will be removed before Blender 4.0")); } bnode.typeinfo->geometry_node_execute(params); } @@ -883,6 +892,14 @@ class GeometryNodesEvaluator { const MultiFunction &fn, NodeState &node_state) { + if (node->idname().find("Legacy") != StringRef::not_found) { + /* Create geometry nodes params just for creating an error message. */ + NodeParamsProvider params_provider{*this, node, node_state}; + GeoNodeExecParams params{params_provider}; + params.error_message_add(geo_log::NodeWarningType::Legacy, + TIP_("Legacy node will be removed before Blender 4.0")); + } + LinearAllocator<> &allocator = local_allocators_.local(); /* Prepare the inputs for the multi function. */ diff --git a/source/blender/modifiers/intern/MOD_solidify_extrude.c b/source/blender/modifiers/intern/MOD_solidify_extrude.c index 8f9aa86e561..54a508ff5e2 100644 --- a/source/blender/modifiers/intern/MOD_solidify_extrude.c +++ b/source/blender/modifiers/intern/MOD_solidify_extrude.c @@ -1043,8 +1043,8 @@ Mesh *MOD_solidify_extrude_modifyMesh(ModifierData *md, const ModifierEvalContex #define SOLIDIFY_SIDE_NORMALS #ifdef SOLIDIFY_SIDE_NORMALS - /* Note that, due to the code setting cd_dirty_vert a few lines above, - * do_side_normals is always false. - Sybren */ + /* NOTE(@sybren): due to the code setting cd_dirty_vert a few lines above, + * do_side_normals is always false. */ const bool do_side_normals = !(result->runtime.cd_dirty_vert & CD_MASK_NORMAL); /* annoying to allocate these since we only need the edge verts, */ float(*edge_vert_nos)[3] = do_side_normals ? diff --git a/source/blender/nodes/CMakeLists.txt b/source/blender/nodes/CMakeLists.txt index e6af3ecafbc..903a30dd383 100644 --- a/source/blender/nodes/CMakeLists.txt +++ b/source/blender/nodes/CMakeLists.txt @@ -45,137 +45,159 @@ set(INC set(SRC - composite/nodes/node_composite_alphaOver.c - composite/nodes/node_composite_antialiasing.c - composite/nodes/node_composite_bilateralblur.c - composite/nodes/node_composite_blur.c - composite/nodes/node_composite_bokehblur.c - composite/nodes/node_composite_bokehimage.c - composite/nodes/node_composite_boxmask.c - composite/nodes/node_composite_brightness.c - composite/nodes/node_composite_channelMatte.c - composite/nodes/node_composite_chromaMatte.c - composite/nodes/node_composite_colorMatte.c - composite/nodes/node_composite_colorSpill.c - composite/nodes/node_composite_colorbalance.c - composite/nodes/node_composite_colorcorrection.c - composite/nodes/node_composite_common.c - composite/nodes/node_composite_composite.c - composite/nodes/node_composite_cornerpin.c - composite/nodes/node_composite_crop.c + composite/nodes/node_composite_alphaOver.cc + composite/nodes/node_composite_antialiasing.cc + composite/nodes/node_composite_bilateralblur.cc + composite/nodes/node_composite_blur.cc + composite/nodes/node_composite_bokehblur.cc + composite/nodes/node_composite_bokehimage.cc + composite/nodes/node_composite_boxmask.cc + composite/nodes/node_composite_brightness.cc + composite/nodes/node_composite_channelMatte.cc + composite/nodes/node_composite_chromaMatte.cc + composite/nodes/node_composite_colorMatte.cc + composite/nodes/node_composite_colorSpill.cc + composite/nodes/node_composite_colorbalance.cc + composite/nodes/node_composite_colorcorrection.cc + composite/nodes/node_composite_common.cc + composite/nodes/node_composite_composite.cc + composite/nodes/node_composite_cornerpin.cc + composite/nodes/node_composite_crop.cc composite/nodes/node_composite_cryptomatte.cc - composite/nodes/node_composite_curves.c - composite/nodes/node_composite_defocus.c - composite/nodes/node_composite_denoise.c - composite/nodes/node_composite_despeckle.c - composite/nodes/node_composite_diffMatte.c - composite/nodes/node_composite_dilate.c - composite/nodes/node_composite_directionalblur.c - composite/nodes/node_composite_displace.c - composite/nodes/node_composite_distanceMatte.c - composite/nodes/node_composite_doubleEdgeMask.c - composite/nodes/node_composite_ellipsemask.c - composite/nodes/node_composite_exposure.c - composite/nodes/node_composite_filter.c - composite/nodes/node_composite_flip.c - composite/nodes/node_composite_gamma.c - composite/nodes/node_composite_glare.c - composite/nodes/node_composite_hueSatVal.c - composite/nodes/node_composite_huecorrect.c - composite/nodes/node_composite_idMask.c - composite/nodes/node_composite_image.c - composite/nodes/node_composite_inpaint.c - composite/nodes/node_composite_invert.c - composite/nodes/node_composite_keying.c - composite/nodes/node_composite_keyingscreen.c - composite/nodes/node_composite_lensdist.c - composite/nodes/node_composite_levels.c - composite/nodes/node_composite_lummaMatte.c - composite/nodes/node_composite_mapRange.c - composite/nodes/node_composite_mapUV.c - composite/nodes/node_composite_mapValue.c - composite/nodes/node_composite_mask.c - composite/nodes/node_composite_math.c - composite/nodes/node_composite_mixrgb.c - composite/nodes/node_composite_movieclip.c - composite/nodes/node_composite_moviedistortion.c - composite/nodes/node_composite_normal.c - composite/nodes/node_composite_normalize.c - composite/nodes/node_composite_outputFile.c - composite/nodes/node_composite_pixelate.c - composite/nodes/node_composite_planetrackdeform.c - composite/nodes/node_composite_posterize.c - composite/nodes/node_composite_premulkey.c - composite/nodes/node_composite_rgb.c - composite/nodes/node_composite_rotate.c - composite/nodes/node_composite_scale.c - composite/nodes/node_composite_sepcombHSVA.c - composite/nodes/node_composite_sepcombRGBA.c - composite/nodes/node_composite_sepcombYCCA.c - composite/nodes/node_composite_sepcombYUVA.c - composite/nodes/node_composite_setalpha.c - composite/nodes/node_composite_splitViewer.c - composite/nodes/node_composite_stabilize2d.c - composite/nodes/node_composite_sunbeams.c - composite/nodes/node_composite_switch.c - composite/nodes/node_composite_switchview.c - composite/nodes/node_composite_texture.c - composite/nodes/node_composite_tonemap.c - composite/nodes/node_composite_trackpos.c - composite/nodes/node_composite_transform.c - composite/nodes/node_composite_translate.c - composite/nodes/node_composite_valToRgb.c - composite/nodes/node_composite_value.c - composite/nodes/node_composite_vecBlur.c - composite/nodes/node_composite_viewer.c - composite/nodes/node_composite_zcombine.c + composite/nodes/node_composite_curves.cc + composite/nodes/node_composite_defocus.cc + composite/nodes/node_composite_denoise.cc + composite/nodes/node_composite_despeckle.cc + composite/nodes/node_composite_diffMatte.cc + composite/nodes/node_composite_dilate.cc + composite/nodes/node_composite_directionalblur.cc + composite/nodes/node_composite_displace.cc + composite/nodes/node_composite_distanceMatte.cc + composite/nodes/node_composite_doubleEdgeMask.cc + composite/nodes/node_composite_ellipsemask.cc + composite/nodes/node_composite_exposure.cc + composite/nodes/node_composite_filter.cc + composite/nodes/node_composite_flip.cc + composite/nodes/node_composite_gamma.cc + composite/nodes/node_composite_glare.cc + composite/nodes/node_composite_hueSatVal.cc + composite/nodes/node_composite_huecorrect.cc + composite/nodes/node_composite_idMask.cc + composite/nodes/node_composite_image.cc + composite/nodes/node_composite_inpaint.cc + composite/nodes/node_composite_invert.cc + composite/nodes/node_composite_keying.cc + composite/nodes/node_composite_keyingscreen.cc + composite/nodes/node_composite_lensdist.cc + composite/nodes/node_composite_levels.cc + composite/nodes/node_composite_lummaMatte.cc + composite/nodes/node_composite_mapRange.cc + composite/nodes/node_composite_mapUV.cc + composite/nodes/node_composite_mapValue.cc + composite/nodes/node_composite_mask.cc + composite/nodes/node_composite_math.cc + composite/nodes/node_composite_mixrgb.cc + composite/nodes/node_composite_movieclip.cc + composite/nodes/node_composite_moviedistortion.cc + composite/nodes/node_composite_normal.cc + composite/nodes/node_composite_normalize.cc + composite/nodes/node_composite_outputFile.cc + composite/nodes/node_composite_pixelate.cc + composite/nodes/node_composite_planetrackdeform.cc + composite/nodes/node_composite_posterize.cc + composite/nodes/node_composite_premulkey.cc + composite/nodes/node_composite_rgb.cc + composite/nodes/node_composite_rotate.cc + composite/nodes/node_composite_scale.cc + composite/nodes/node_composite_sepcombHSVA.cc + composite/nodes/node_composite_sepcombRGBA.cc + composite/nodes/node_composite_sepcombYCCA.cc + composite/nodes/node_composite_sepcombYUVA.cc + composite/nodes/node_composite_setalpha.cc + composite/nodes/node_composite_splitViewer.cc + composite/nodes/node_composite_stabilize2d.cc + composite/nodes/node_composite_sunbeams.cc + composite/nodes/node_composite_switch.cc + composite/nodes/node_composite_switchview.cc + composite/nodes/node_composite_texture.cc + composite/nodes/node_composite_tonemap.cc + composite/nodes/node_composite_trackpos.cc + composite/nodes/node_composite_transform.cc + composite/nodes/node_composite_translate.cc + composite/nodes/node_composite_valToRgb.cc + composite/nodes/node_composite_value.cc + composite/nodes/node_composite_vecBlur.cc + composite/nodes/node_composite_viewer.cc + composite/nodes/node_composite_zcombine.cc - composite/node_composite_tree.c - composite/node_composite_util.c + composite/node_composite_tree.cc + composite/node_composite_util.cc + + function/nodes/legacy/node_fn_random_float.cc function/nodes/node_fn_boolean_math.cc function/nodes/node_fn_float_compare.cc function/nodes/node_fn_float_to_int.cc + function/nodes/node_fn_input_special_characters.cc function/nodes/node_fn_input_string.cc function/nodes/node_fn_input_vector.cc - function/nodes/node_fn_random_float.cc + function/nodes/node_fn_random_value.cc + function/nodes/node_fn_rotate_euler.cc function/nodes/node_fn_string_length.cc function/nodes/node_fn_string_substring.cc function/nodes/node_fn_value_to_string.cc function/node_function_util.cc + geometry/nodes/legacy/node_geo_align_rotation_to_vector.cc + geometry/nodes/legacy/node_geo_attribute_clamp.cc + geometry/nodes/legacy/node_geo_attribute_color_ramp.cc + geometry/nodes/legacy/node_geo_attribute_combine_xyz.cc + geometry/nodes/legacy/node_geo_attribute_compare.cc + geometry/nodes/legacy/node_geo_attribute_convert.cc + geometry/nodes/legacy/node_geo_attribute_curve_map.cc + geometry/nodes/legacy/node_geo_attribute_fill.cc + geometry/nodes/legacy/node_geo_attribute_map_range.cc + geometry/nodes/legacy/node_geo_attribute_math.cc + geometry/nodes/legacy/node_geo_attribute_mix.cc + geometry/nodes/legacy/node_geo_attribute_proximity.cc + geometry/nodes/legacy/node_geo_attribute_randomize.cc + geometry/nodes/legacy/node_geo_attribute_sample_texture.cc + geometry/nodes/legacy/node_geo_attribute_separate_xyz.cc + geometry/nodes/legacy/node_geo_attribute_transfer.cc + geometry/nodes/legacy/node_geo_attribute_vector_math.cc + geometry/nodes/legacy/node_geo_attribute_vector_rotate.cc + geometry/nodes/legacy/node_geo_curve_endpoints.cc + geometry/nodes/legacy/node_geo_curve_reverse.cc + geometry/nodes/legacy/node_geo_curve_select_by_handle_type.cc + geometry/nodes/legacy/node_geo_curve_set_handles.cc + geometry/nodes/legacy/node_geo_curve_spline_type.cc + geometry/nodes/legacy/node_geo_curve_subdivide.cc + geometry/nodes/legacy/node_geo_curve_to_points.cc + geometry/nodes/legacy/node_geo_delete_geometry.cc + geometry/nodes/legacy/node_geo_edge_split.cc geometry/nodes/legacy/node_geo_material_assign.cc + geometry/nodes/legacy/node_geo_mesh_to_curve.cc + geometry/nodes/legacy/node_geo_point_distribute.cc + geometry/nodes/legacy/node_geo_point_instance.cc + geometry/nodes/legacy/node_geo_point_rotate.cc + geometry/nodes/legacy/node_geo_point_scale.cc + geometry/nodes/legacy/node_geo_point_separate.cc + geometry/nodes/legacy/node_geo_point_translate.cc + geometry/nodes/legacy/node_geo_points_to_volume.cc + geometry/nodes/legacy/node_geo_raycast.cc geometry/nodes/legacy/node_geo_select_by_material.cc + geometry/nodes/legacy/node_geo_subdivision_surface.cc - geometry/nodes/node_geo_align_rotation_to_vector.cc geometry/nodes/node_geo_attribute_capture.cc - geometry/nodes/node_geo_attribute_clamp.cc - geometry/nodes/node_geo_attribute_color_ramp.cc - 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 - geometry/nodes/node_geo_attribute_mix.cc - geometry/nodes/node_geo_attribute_proximity.cc - geometry/nodes/node_geo_attribute_randomize.cc geometry/nodes/node_geo_attribute_remove.cc - geometry/nodes/node_geo_attribute_sample_texture.cc - geometry/nodes/node_geo_attribute_separate_xyz.cc geometry/nodes/node_geo_attribute_statistic.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_convex_hull.cc - geometry/nodes/node_geo_curve_sample.cc - geometry/nodes/node_geo_curve_endpoints.cc geometry/nodes/node_geo_curve_fill.cc + geometry/nodes/node_geo_curve_fillet.cc geometry/nodes/node_geo_curve_length.cc geometry/nodes/node_geo_curve_parameter.cc geometry/nodes/node_geo_curve_primitive_bezier_segment.cc @@ -187,21 +209,20 @@ set(SRC geometry/nodes/node_geo_curve_primitive_star.cc geometry/nodes/node_geo_curve_resample.cc geometry/nodes/node_geo_curve_reverse.cc - geometry/nodes/node_geo_curve_select_by_handle_type.cc + geometry/nodes/node_geo_curve_sample.cc geometry/nodes/node_geo_curve_set_handles.cc geometry/nodes/node_geo_curve_spline_type.cc geometry/nodes/node_geo_curve_subdivide.cc - geometry/nodes/node_geo_curve_fillet.cc geometry/nodes/node_geo_curve_to_mesh.cc - geometry/nodes/node_geo_curve_to_points.cc geometry/nodes/node_geo_curve_trim.cc - geometry/nodes/node_geo_delete_geometry.cc - geometry/nodes/node_geo_edge_split.cc + geometry/nodes/node_geo_distribute_points_on_faces.cc + geometry/nodes/node_geo_input_index.cc geometry/nodes/node_geo_input_material.cc geometry/nodes/node_geo_input_normal.cc geometry/nodes/node_geo_input_position.cc + geometry/nodes/node_geo_input_spline_length.cc geometry/nodes/node_geo_input_tangent.cc - geometry/nodes/node_geo_input_index.cc + geometry/nodes/node_geo_instance_on_points.cc geometry/nodes/node_geo_is_viewport.cc geometry/nodes/node_geo_join_geometry.cc geometry/nodes/node_geo_material_assign.cc @@ -216,26 +237,21 @@ set(SRC geometry/nodes/node_geo_mesh_primitive_line.cc geometry/nodes/node_geo_mesh_primitive_uv_sphere.cc geometry/nodes/node_geo_mesh_subdivide.cc - geometry/nodes/node_geo_mesh_to_curve.cc + geometry/nodes/node_geo_mesh_to_points.cc geometry/nodes/node_geo_object_info.cc - geometry/nodes/node_geo_point_distribute.cc - geometry/nodes/node_geo_point_instance.cc - geometry/nodes/node_geo_point_rotate.cc - geometry/nodes/node_geo_point_scale.cc - geometry/nodes/node_geo_point_separate.cc - geometry/nodes/node_geo_point_translate.cc - geometry/nodes/node_geo_points_to_volume.cc - geometry/nodes/node_geo_raycast.cc + geometry/nodes/node_geo_points_to_vertices.cc + geometry/nodes/node_geo_proximity.cc geometry/nodes/node_geo_realize_instances.cc geometry/nodes/node_geo_separate_components.cc geometry/nodes/node_geo_set_position.cc geometry/nodes/node_geo_string_join.cc - geometry/nodes/node_geo_subdivision_surface.cc + geometry/nodes/node_geo_string_to_curves.cc geometry/nodes/node_geo_switch.cc geometry/nodes/node_geo_transform.cc geometry/nodes/node_geo_triangulate.cc geometry/nodes/node_geo_viewer.cc geometry/nodes/node_geo_volume_to_mesh.cc + geometry/node_geometry_exec.cc geometry/node_geometry_tree.cc geometry/node_geometry_util.cc @@ -362,18 +378,18 @@ set(SRC intern/derived_node_tree.cc intern/geometry_nodes_eval_log.cc intern/math_functions.cc - intern/node_common.c + intern/node_common.cc + intern/node_declaration.cc intern/node_exec.cc intern/node_geometry_exec.cc intern/node_multi_function.cc - intern/node_declaration.cc - intern/node_socket_declarations.cc intern/node_socket.cc + intern/node_socket_declarations.cc intern/node_tree_ref.cc intern/node_util.c intern/type_conversions.cc - composite/node_composite_util.h + composite/node_composite_util.hh function/node_function_util.hh shader/node_shader_util.h geometry/node_geometry_util.hh @@ -389,10 +405,10 @@ set(SRC NOD_math_functions.hh NOD_multi_function.hh NOD_node_declaration.hh - NOD_socket_declarations.hh NOD_node_tree_ref.hh NOD_shader.h NOD_socket.h + NOD_socket_declarations.hh NOD_static_types.h NOD_texture.h NOD_type_conversions.hh diff --git a/source/blender/nodes/NOD_composite.h b/source/blender/nodes/NOD_composite.h index 2cbbd31c97a..d243577f68d 100644 --- a/source/blender/nodes/NOD_composite.h +++ b/source/blender/nodes/NOD_composite.h @@ -145,7 +145,7 @@ void node_cmp_rlayers_register_pass(struct bNodeTree *ntree, struct Scene *scene, struct ViewLayer *view_layer, const char *name, - int type); + eNodeSocketDatatype type); const char *node_cmp_rlayers_sock_to_pass(int sock_index); void register_node_type_cmp_custom_group(bNodeType *ntype); diff --git a/source/blender/nodes/NOD_function.h b/source/blender/nodes/NOD_function.h index a67458418f2..450e999bea4 100644 --- a/source/blender/nodes/NOD_function.h +++ b/source/blender/nodes/NOD_function.h @@ -20,12 +20,16 @@ extern "C" { #endif +void register_node_type_fn_legacy_random_float(void); + void register_node_type_fn_boolean_math(void); void register_node_type_fn_float_compare(void); void register_node_type_fn_float_to_int(void); +void register_node_type_fn_input_special_characters(void); void register_node_type_fn_input_string(void); void register_node_type_fn_input_vector(void); -void register_node_type_fn_random_float(void); +void register_node_type_fn_random_value(void); +void register_node_type_fn_rotate_euler(void); void register_node_type_fn_string_length(void); void register_node_type_fn_string_substring(void); void register_node_type_fn_value_to_string(void); diff --git a/source/blender/nodes/NOD_geometry.h b/source/blender/nodes/NOD_geometry.h index 47bc54132eb..bb90c7b6c51 100644 --- a/source/blender/nodes/NOD_geometry.h +++ b/source/blender/nodes/NOD_geometry.h @@ -29,10 +29,17 @@ void register_node_tree_type_geo(void); void register_node_type_geo_group(void); void register_node_type_geo_custom_group(bNodeType *ntype); +void register_node_type_geo_legacy_curve_set_handles(void); +void register_node_type_geo_legacy_attribute_proximity(void); +void register_node_type_geo_legacy_attribute_randomize(void); void register_node_type_geo_legacy_material_assign(void); void register_node_type_geo_legacy_select_by_material(void); +void register_node_type_geo_legacy_curve_spline_type(void); +void register_node_type_geo_legacy_curve_reverse(void); +void register_node_type_geo_legacy_curve_subdivide(void); void register_node_type_geo_align_rotation_to_vector(void); +void register_node_type_geo_attribute_capture(void); void register_node_type_geo_attribute_clamp(void); void register_node_type_geo_attribute_color_ramp(void); void register_node_type_geo_attribute_combine_xyz(void); @@ -40,12 +47,9 @@ 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_capture(void); void register_node_type_geo_attribute_map_range(void); void register_node_type_geo_attribute_math(void); void register_node_type_geo_attribute_mix(void); -void register_node_type_geo_attribute_proximity(void); -void register_node_type_geo_attribute_randomize(void); void register_node_type_geo_attribute_remove(void); void register_node_type_geo_attribute_separate_xyz(void); void register_node_type_geo_attribute_statistic(void); @@ -58,9 +62,9 @@ void register_node_type_geo_collection_info(void); void register_node_type_geo_convex_hull(void); void register_node_type_geo_curve_endpoints(void); void register_node_type_geo_curve_fill(void); +void register_node_type_geo_curve_fillet(void); void register_node_type_geo_curve_length(void); void register_node_type_geo_curve_parameter(void); -void register_node_type_geo_curve_sample(void); void register_node_type_geo_curve_primitive_bezier_segment(void); void register_node_type_geo_curve_primitive_circle(void); void register_node_type_geo_curve_primitive_line(void); @@ -70,20 +74,23 @@ void register_node_type_geo_curve_primitive_spiral(void); void register_node_type_geo_curve_primitive_star(void); void register_node_type_geo_curve_resample(void); void register_node_type_geo_curve_reverse(void); +void register_node_type_geo_curve_sample(void); void register_node_type_geo_curve_set_handles(void); void register_node_type_geo_curve_spline_type(void); void register_node_type_geo_curve_subdivide(void); -void register_node_type_geo_curve_fillet(void); void register_node_type_geo_curve_to_mesh(void); void register_node_type_geo_curve_to_points(void); void register_node_type_geo_curve_trim(void); void register_node_type_geo_delete_geometry(void); +void register_node_type_geo_distribute_points_on_faces(void); void register_node_type_geo_edge_split(void); void register_node_type_geo_input_index(void); void register_node_type_geo_input_material(void); void register_node_type_geo_input_normal(void); void register_node_type_geo_input_position(void); +void register_node_type_geo_input_spline_length(void); void register_node_type_geo_input_tangent(void); +void register_node_type_geo_instance_on_points(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); @@ -99,6 +106,7 @@ void register_node_type_geo_mesh_primitive_line(void); void register_node_type_geo_mesh_primitive_uv_sphere(void); void register_node_type_geo_mesh_subdivide(void); void register_node_type_geo_mesh_to_curve(void); +void register_node_type_geo_mesh_to_points(void); void register_node_type_geo_object_info(void); void register_node_type_geo_point_distribute(void); void register_node_type_geo_point_instance(void); @@ -106,7 +114,9 @@ void register_node_type_geo_point_rotate(void); void register_node_type_geo_point_scale(void); void register_node_type_geo_point_separate(void); void register_node_type_geo_point_translate(void); +void register_node_type_geo_points_to_vertices(void); void register_node_type_geo_points_to_volume(void); +void register_node_type_geo_proximity(void); void register_node_type_geo_raycast(void); void register_node_type_geo_realize_instances(void); void register_node_type_geo_sample_texture(void); @@ -114,6 +124,7 @@ void register_node_type_geo_select_by_handle_type(void); void register_node_type_geo_separate_components(void); void register_node_type_geo_set_position(void); void register_node_type_geo_string_join(void); +void register_node_type_geo_string_to_curves(void); void register_node_type_geo_subdivision_surface(void); void register_node_type_geo_switch(void); void register_node_type_geo_transform(void); diff --git a/source/blender/nodes/NOD_geometry_exec.hh b/source/blender/nodes/NOD_geometry_exec.hh index 6ce3d0f2ab5..962e1c3c48f 100644 --- a/source/blender/nodes/NOD_geometry_exec.hh +++ b/source/blender/nodes/NOD_geometry_exec.hh @@ -46,6 +46,8 @@ using bke::WeakAnonymousAttributeID; using bke::WriteAttributeLookup; using fn::CPPType; using fn::Field; +using fn::FieldContext; +using fn::FieldEvaluator; using fn::FieldInput; using fn::FieldOperation; using fn::GField; diff --git a/source/blender/nodes/NOD_node_declaration.hh b/source/blender/nodes/NOD_node_declaration.hh index 8e9e72bf4c8..07d4e05cda8 100644 --- a/source/blender/nodes/NOD_node_declaration.hh +++ b/source/blender/nodes/NOD_node_declaration.hh @@ -27,6 +27,109 @@ namespace blender::nodes { class NodeDeclarationBuilder; +enum class InputSocketFieldType { + /** The input is required to be a single value. */ + None, + /** The input can be a field. */ + IsSupported, + /** The input can be a field and is a field implicitly if nothing is connected. */ + Implicit, +}; + +enum class OutputSocketFieldType { + /** The output is always a single value. */ + None, + /** The output is always a field, independent of the inputs. */ + FieldSource, + /** If any input is a field, this output will be a field as well. */ + DependentField, + /** If any of a subset of inputs is a field, this out will be a field as well. + * The subset is defined by the vector of indices. */ + PartiallyDependent, +}; + +/** + * Contains information about how a node output's field state depends on inputs of the same node. + */ +class OutputFieldDependency { + private: + OutputSocketFieldType type_ = OutputSocketFieldType::None; + Vector<int> linked_input_indices_; + + public: + static OutputFieldDependency ForFieldSource() + { + OutputFieldDependency field_dependency; + field_dependency.type_ = OutputSocketFieldType::FieldSource; + return field_dependency; + } + + static OutputFieldDependency ForDataSource() + { + OutputFieldDependency field_dependency; + field_dependency.type_ = OutputSocketFieldType::None; + return field_dependency; + } + + static OutputFieldDependency ForPartiallyDependentField(Vector<int> indices) + { + OutputFieldDependency field_dependency; + if (indices.is_empty()) { + field_dependency.type_ = OutputSocketFieldType::None; + } + else { + field_dependency.type_ = OutputSocketFieldType::PartiallyDependent; + field_dependency.linked_input_indices_ = std::move(indices); + } + return field_dependency; + } + + static OutputFieldDependency ForDependentField() + { + OutputFieldDependency field_dependency; + field_dependency.type_ = OutputSocketFieldType::DependentField; + return field_dependency; + } + + OutputSocketFieldType field_type() const + { + return type_; + } + + Span<int> linked_input_indices() const + { + return linked_input_indices_; + } + + friend bool operator==(const OutputFieldDependency &a, const OutputFieldDependency &b) + { + return a.type_ == b.type_ && a.linked_input_indices_ == b.linked_input_indices_; + } + + friend bool operator!=(const OutputFieldDependency &a, const OutputFieldDependency &b) + { + return !(a == b); + } +}; + +/** + * Information about how a node interacts with fields. + */ +struct FieldInferencingInterface { + Vector<InputSocketFieldType> inputs; + Vector<OutputFieldDependency> outputs; + + friend bool operator==(const FieldInferencingInterface &a, const FieldInferencingInterface &b) + { + return a.inputs == b.inputs && a.outputs == b.outputs; + } + + friend bool operator!=(const FieldInferencingInterface &a, const FieldInferencingInterface &b) + { + return !(a == b); + } +}; + /** * Describes a single input or output socket. This is subclassed for different socket types. */ @@ -34,11 +137,15 @@ class SocketDeclaration { protected: std::string name_; std::string identifier_; + std::string description_; bool hide_label_ = false; bool hide_value_ = false; bool is_multi_input_ = false; bool no_mute_links_ = false; + InputSocketFieldType input_field_type_ = InputSocketFieldType::None; + OutputFieldDependency output_field_dependency_; + friend NodeDeclarationBuilder; template<typename SocketDecl> friend class SocketDeclarationBuilder; @@ -50,8 +157,12 @@ class SocketDeclaration { virtual bNodeSocket &update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const; StringRefNull name() const; + StringRefNull description() const; StringRefNull identifier() const; + InputSocketFieldType input_field_type() const; + const OutputFieldDependency &output_field_dependency() const; + protected: void set_common_flags(bNodeSocket &socket) const; bool matches_common_data(const bNodeSocket &socket) const; @@ -95,11 +206,52 @@ class SocketDeclarationBuilder : public BaseSocketDeclarationBuilder { return *(Self *)this; } + Self &description(std::string value = "") + { + decl_->description_ = std::move(value); + return *(Self *)this; + } Self &no_muted_links(bool value = true) { decl_->no_mute_links_ = value; return *(Self *)this; } + + /** The input socket allows passing in a field. */ + Self &supports_field() + { + decl_->input_field_type_ = InputSocketFieldType::IsSupported; + return *(Self *)this; + } + + /** The input supports a field and is a field by default when nothing is connected. */ + Self &implicit_field() + { + this->hide_value(); + decl_->input_field_type_ = InputSocketFieldType::Implicit; + return *(Self *)this; + } + + /** The output is always a field, regardless of any inputs. */ + Self &field_source() + { + decl_->output_field_dependency_ = OutputFieldDependency::ForFieldSource(); + return *(Self *)this; + } + + /** The output is a field if any of the inputs is a field. */ + Self &dependent_field() + { + decl_->output_field_dependency_ = OutputFieldDependency::ForDependentField(); + return *(Self *)this; + } + + /** The output is a field if any of the inputs with indices in the given list is a field. */ + Self &dependent_field(Vector<int> input_dependencies) + { + decl_->output_field_dependency_ = OutputFieldDependency::ForPartiallyDependentField( + std::move(input_dependencies)); + } }; using SocketDeclarationPtr = std::unique_ptr<SocketDeclaration>; @@ -108,6 +260,7 @@ class NodeDeclaration { private: Vector<SocketDeclarationPtr> inputs_; Vector<SocketDeclarationPtr> outputs_; + bool is_function_node_ = false; friend NodeDeclarationBuilder; @@ -118,6 +271,11 @@ class NodeDeclaration { Span<SocketDeclarationPtr> inputs() const; Span<SocketDeclarationPtr> outputs() const; + bool is_function_node() const + { + return is_function_node_; + } + MEM_CXX_CLASS_ALLOC_FUNCS("NodeDeclaration") }; @@ -129,6 +287,15 @@ class NodeDeclarationBuilder { public: NodeDeclarationBuilder(NodeDeclaration &declaration); + /** + * All inputs support fields, and all outputs are fields if any of the inputs is a field. + * Calling field status definitions on each socket is unnecessary. + */ + void is_function_node(bool value = true) + { + declaration_.is_function_node_ = value; + } + template<typename DeclType> typename DeclType::Builder &add_input(StringRef name, StringRef identifier = ""); template<typename DeclType> @@ -155,6 +322,20 @@ inline StringRefNull SocketDeclaration::identifier() const return identifier_; } +inline StringRefNull SocketDeclaration::description() const +{ + return description_; +} +inline InputSocketFieldType SocketDeclaration::input_field_type() const +{ + return input_field_type_; +} + +inline const OutputFieldDependency &SocketDeclaration::output_field_dependency() const +{ + return output_field_dependency_; +} + /* -------------------------------------------------------------------- * NodeDeclarationBuilder inline methods. */ diff --git a/source/blender/nodes/NOD_node_tree_ref.hh b/source/blender/nodes/NOD_node_tree_ref.hh index 4f2565cbbaf..1da42fb6425 100644 --- a/source/blender/nodes/NOD_node_tree_ref.hh +++ b/source/blender/nodes/NOD_node_tree_ref.hh @@ -182,6 +182,7 @@ class NodeRef : NonCopyable, NonMovable { Span<const InputSocketRef *> inputs() const; Span<const OutputSocketRef *> outputs() const; Span<const InternalLinkRef *> internal_links() const; + Span<const SocketRef *> sockets(eNodeSocketInOut in_out) const; const InputSocketRef &input(int index) const; const OutputSocketRef &output(int index) const; @@ -189,6 +190,10 @@ class NodeRef : NonCopyable, NonMovable { const InputSocketRef &input_by_identifier(StringRef identifier) const; const OutputSocketRef &output_by_identifier(StringRef identifier) const; + bool any_input_is_directly_linked() const; + bool any_output_is_directly_linked() const; + bool any_socket_is_directly_linked(eNodeSocketInOut in_out) const; + bNode *bnode() const; bNodeTree *btree() const; @@ -196,6 +201,7 @@ class NodeRef : NonCopyable, NonMovable { StringRefNull idname() const; StringRefNull name() const; bNodeType *typeinfo() const; + const NodeDeclaration *declaration() const; int id() const; @@ -272,6 +278,13 @@ class NodeTreeRef : NonCopyable, NonMovable { bool has_link_cycles() const; bool has_undefined_nodes_or_sockets() const; + enum class ToposortDirection { + LeftToRight, + RightToLeft, + }; + + Vector<const NodeRef *> toposort(ToposortDirection direction) const; + bNodeTree *btree() const; StringRefNull name() const; @@ -496,6 +509,12 @@ inline Span<const OutputSocketRef *> NodeRef::outputs() const return outputs_; } +inline Span<const SocketRef *> NodeRef::sockets(const eNodeSocketInOut in_out) const +{ + return in_out == SOCK_IN ? inputs_.as_span().cast<const SocketRef *>() : + outputs_.as_span().cast<const SocketRef *>(); +} + inline Span<const InternalLinkRef *> NodeRef::internal_links() const { return internal_links_; @@ -553,6 +572,13 @@ inline bNodeType *NodeRef::typeinfo() const return bnode_->typeinfo; } +/* Returns a pointer because not all nodes have declarations currently. */ +inline const NodeDeclaration *NodeRef::declaration() const +{ + nodeDeclarationEnsure(this->tree().btree(), bnode_); + return bnode_->declaration; +} + inline int NodeRef::id() const { return id_; diff --git a/source/blender/nodes/NOD_shader.h b/source/blender/nodes/NOD_shader.h index 2911e0fbea6..76c174201e8 100644 --- a/source/blender/nodes/NOD_shader.h +++ b/source/blender/nodes/NOD_shader.h @@ -49,6 +49,7 @@ void register_node_type_sh_normal(void); void register_node_type_sh_gamma(void); void register_node_type_sh_brightcontrast(void); void register_node_type_sh_mapping(void); +void register_node_type_sh_curve_float(void); void register_node_type_sh_curve_vec(void); void register_node_type_sh_curve_rgb(void); void register_node_type_sh_map_range(void); diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index ab673d814bb..c13ab199691 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -132,6 +132,7 @@ DefNode(ShaderNode, SH_NODE_VECTOR_DISPLACEMENT,def_sh_vector_displacement," DefNode(ShaderNode, SH_NODE_TEX_IES, def_sh_tex_ies, "TEX_IES", TexIES, "IES Texture", "" ) DefNode(ShaderNode, SH_NODE_TEX_WHITE_NOISE, def_sh_tex_white_noise, "TEX_WHITE_NOISE", TexWhiteNoise, "White Noise", "" ) DefNode(ShaderNode, SH_NODE_OUTPUT_AOV, def_sh_output_aov, "OUTPUT_AOV", OutputAOV, "AOV Output", "" ) +DefNode(ShaderNode, SH_NODE_CURVE_FLOAT, def_float_curve, "CURVE_FLOAT", FloatCurve, "Float Curve", "" ) DefNode(CompositorNode, CMP_NODE_VIEWER, def_cmp_viewer, "VIEWER", Viewer, "Viewer", "" ) DefNode(CompositorNode, CMP_NODE_RGB, 0, "RGB", RGB, "RGB", "" ) @@ -262,18 +263,22 @@ DefNode(TextureNode, TEX_NODE_PROC+TEX_NOISE, 0, "TEX_NO DefNode(TextureNode, TEX_NODE_PROC+TEX_STUCCI, 0, "TEX_STUCCI", TexStucci, "Stucci", "" ) DefNode(TextureNode, TEX_NODE_PROC+TEX_DISTNOISE, 0, "TEX_DISTNOISE", TexDistNoise, "Distorted Noise", "" ) +DefNode(FunctionNode, FN_NODE_LEGACY_RANDOM_FLOAT, 0, "LEGACY_RANDOM_FLOAT", LegacyRandomFloat, "Random Float", "") + 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_FLOAT_TO_INT, def_float_to_int, "FLOAT_TO_INT", FloatToInt, "Float to Integer", "") +DefNode(FunctionNode, FN_NODE_INPUT_SPECIAL_CHARACTERS, 0, "INPUT_SPECIAL_CHARACTERS", InputSpecialCharacters, "Special Characters", "") 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(FunctionNode, FN_NODE_VALUE_TO_STRING, 0, "VALUE_TO_STRING", ValueToString, "Value to String", "") +DefNode(FunctionNode, FN_NODE_RANDOM_VALUE, def_fn_random_value, "RANDOM_VALUE", RandomValue, "Random Value", "") +DefNode(FunctionNode, FN_NODE_ROTATE_EULER, def_fn_rotate_euler, "ROTATE_EULER", RotateEuler, "Rotate Euler", "") DefNode(FunctionNode, FN_NODE_STRING_LENGTH, 0, "STRING_LENGTH", StringLength, "String Length", "") DefNode(FunctionNode, FN_NODE_STRING_SUBSTRING, 0, "STRING_SUBSTRING", StringSubstring, "String Substring", "") +DefNode(FunctionNode, FN_NODE_VALUE_TO_STRING, 0, "VALUE_TO_STRING", ValueToString, "Value to String", "") -DefNode(GeometryNode, GEO_NODE_LECAGY_ATTRIBUTE_CLAMP, def_geo_attribute_clamp, "LEGACY_ATTRIBUTE_CLAMP", LegacyAttributeClamp, "Attribute Clamp", "") DefNode(GeometryNode, GEO_NODE_LEGACY_ALIGN_ROTATION_TO_VECTOR, def_geo_align_rotation_to_vector, "LEGACY_ALIGN_ROTATION_TO_VECTOR", LegacyAlignRotationToVector, "Align Rotation to Vector", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_CLAMP, def_geo_attribute_clamp, "LEGACY_ATTRIBUTE_CLAMP", LegacyAttributeClamp, "Attribute Clamp", "") DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_COLOR_RAMP, def_geo_attribute_color_ramp, "LEGACY_ATTRIBUTE_COLOR_RAMP", LegacyAttributeColorRamp, "Attribute Color Ramp", "") DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_COMBINE_XYZ, def_geo_attribute_combine_xyz, "LEGACY_ATTRIBUTE_COMBINE_XYZ", LegacyAttributeCombineXYZ, "Attribute Combine XYZ", "") DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_COMPARE, def_geo_attribute_attribute_compare, "LEGACY_ATTRIBUTE_COMPARE", LegacyAttributeCompare, "Attribute Compare", "") @@ -283,18 +288,22 @@ DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_FILL, def_geo_attribute_fill, "L DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_MAP_RANGE, def_geo_attribute_map_range, "LEGACY_ATTRIBUTE_MAP_RANGE", LegacyAttributeMapRange, "Attribute Map Range", "") DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_MATH, def_geo_attribute_math, "LEGACY_ATTRIBUTE_MATH", LegacyAttributeMath, "Attribute Math", "") DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_MIX, def_geo_attribute_mix, "LEGACY_ATTRIBUTE_MIX", LegacyAttributeMix, "Attribute Mix", "") -DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_PROXIMITY, def_geo_attribute_proximity, "LEGACY_ATTRIBUTE_PROXIMITY", LegacyAttributeProximity, "Attribute Proximity", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_PROXIMITY, def_geo_legacy_attribute_proximity, "LEGACY_ATTRIBUTE_PROXIMITY", LegacyAttributeProximity, "Attribute Proximity", "") DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_RANDOMIZE, def_geo_attribute_randomize, "LEGACY_ATTRIBUTE_RANDOMIZE", LegacyAttributeRandomize, "Attribute Randomize", "") DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_SAMPLE_TEXTURE, 0, "LEGACY_ATTRIBUTE_SAMPLE_TEXTURE", LegacyAttributeSampleTexture, "Attribute Sample Texture", "") DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_SEPARATE_XYZ, def_geo_attribute_separate_xyz, "LEGACY_ATTRIBUTE_SEPARATE_XYZ", LegacyAttributeSeparateXYZ, "Attribute Separate XYZ", "") DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_TRANSFER, def_geo_attribute_transfer, "LEGACY_ATTRIBUTE_TRANSFER", LegacyAttributeTransfer, "Attribute Transfer", "") DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_VECTOR_MATH, def_geo_attribute_vector_math, "LEGACY_ATTRIBUTE_VECTOR_MATH", LegacyAttributeVectorMath, "Attribute Vector Math", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_ATTRIBUTE_VECTOR_ROTATE, def_geo_attribute_vector_rotate, "LEGACY_ATTRIBUTE_VECTOR_ROTATE", LegacyAttributeVectorRotate, "Attribute Vector Rotate", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_CURVE_ENDPOINTS, 0, "LEGACY_CURVE_ENDPOINTS", LegacyCurveEndpoints, "Curve Endpoints", "") DefNode(GeometryNode, GEO_NODE_LEGACY_CURVE_REVERSE, 0, "LEGACY_CURVE_REVERSE", LegacyCurveReverse, "Curve Reverse", "") DefNode(GeometryNode, GEO_NODE_LEGACY_CURVE_SELECT_HANDLES, def_geo_curve_select_handles, "LEGACY_CURVE_SELECT_HANDLES", LegacyCurveSelectHandles, "Select by Handle Type", "") -DefNode(GeometryNode, GEO_NODE_LEGACY_CURVE_SET_HANDLES, def_geo_curve_set_handles, "LEGACY_CURVE_SET_HANDLES", LegacyCurveSetHandles, "Set Handle Type", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_CURVE_SET_HANDLES, def_geo_legacy_curve_set_handles, "LEGACY_CURVE_SET_HANDLES", LegacyCurveSetHandles, "Set Handle Type", "") DefNode(GeometryNode, GEO_NODE_LEGACY_CURVE_SPLINE_TYPE, def_geo_curve_spline_type, "LEGACY_CURVE_SPLINE_TYPE", LegacyCurveSplineType, "Set Spline Type", "") -DefNode(GeometryNode, GEO_NODE_LEGACY_CURVE_SUBDIVIDE, def_geo_curve_subdivide, "LEGACY_CURVE_SUBDIVIDE", LegacyCurveSubdivide, "Curve Subdivide", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_CURVE_SUBDIVIDE, def_geo_legacy_curve_subdivide, "LEGACY_CURVE_SUBDIVIDE", LegacyCurveSubdivide, "Curve Subdivide", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_CURVE_TO_POINTS, def_geo_curve_to_points, "LEGACY_CURVE_TO_POINTS", LegacyCurveToPoints, "Curve to Points", "") DefNode(GeometryNode, GEO_NODE_LEGACY_DELETE_GEOMETRY, 0, "LEGACY_DELETE_GEOMETRY", LegacyDeleteGeometry, "Delete Geometry", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_EDGE_SPLIT, 0, "LEGACY_EDGE_SPLIT", LegacyEdgeSplit, "Edge Split", "") DefNode(GeometryNode, GEO_NODE_LEGACY_MATERIAL_ASSIGN, 0, "LEGACY_MATERIAL_ASSIGN", LegacyMaterialAssign, "Material Assign", "") DefNode(GeometryNode, GEO_NODE_LEGACY_MESH_TO_CURVE, 0, "LEGACY_MESH_TO_CURVE", LegacyMeshToCurve, "Mesh to Curve", "") DefNode(GeometryNode, GEO_NODE_LEGACY_POINT_DISTRIBUTE, def_geo_point_distribute, "LEGACY_POINT_DISTRIBUTE", LegacyPointDistribute, "Point Distribute", "") @@ -306,18 +315,17 @@ DefNode(GeometryNode, GEO_NODE_LEGACY_POINT_TRANSLATE, def_geo_point_translate, DefNode(GeometryNode, GEO_NODE_LEGACY_POINTS_TO_VOLUME, def_geo_points_to_volume, "LEGACY_POINTS_TO_VOLUME", LegacyPointsToVolume, "Points to Volume", "") DefNode(GeometryNode, GEO_NODE_LEGACY_RAYCAST, def_geo_raycast, "LEGACY_RAYCAST", LegacyRaycast, "Raycast", "") DefNode(GeometryNode, GEO_NODE_LEGACY_SELECT_BY_MATERIAL, 0, "LEGACY_SELECT_BY_MATERIAL", LegacySelectByMaterial, "Select by Material", "") +DefNode(GeometryNode, GEO_NODE_LEGACY_SUBDIVISION_SURFACE, def_geo_subdivision_surface, "LEGACY_SUBDIVISION_SURFACE", LegacySubdivisionSurface, "Subdivision Surface", "") DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_CAPTURE, def_geo_attribute_capture, "ATTRIBUTE_CAPTURE", AttributeCapture, "Attribute Capture", "") DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_REMOVE, 0, "ATTRIBUTE_REMOVE", AttributeRemove, "Attribute Remove", "") -DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_VECTOR_ROTATE, def_geo_attribute_vector_rotate, "LEGACY_ATTRIBUTE_VECTOR_ROTATE", LegacyAttributeVectorRotate, "Attribute Vector Rotate", "") DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_STATISTIC, def_geo_attribute_statistic, "ATTRIBUTE_STATISTIC", AttributeStatistic, "Attribute Statistic", "") 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_CONVEX_HULL, 0, "CONVEX_HULL", ConvexHull, "Convex Hull", "") -DefNode(GeometryNode, GEO_NODE_CURVE_SAMPLE, def_geo_curve_sample, "CURVE_SAMPLE", CurveSample, "Curve Sample", "") -DefNode(GeometryNode, GEO_NODE_CURVE_ENDPOINTS, 0, "CURVE_ENDPOINTS", CurveEndpoints, "Curve Endpoints", "") DefNode(GeometryNode, GEO_NODE_CURVE_FILL, def_geo_curve_fill, "CURVE_FILL", CurveFill, "Curve Fill", "") +DefNode(GeometryNode, GEO_NODE_CURVE_FILLET, def_geo_curve_fillet, "CURVE_FILLET", CurveFillet, "Curve Fillet", "") DefNode(GeometryNode, GEO_NODE_CURVE_LENGTH, 0, "CURVE_LENGTH", CurveLength, "Curve Length", "") DefNode(GeometryNode, GEO_NODE_CURVE_PARAMETER, 0, "CURVE_PARAMETER", CurveParameter, "Curve Parameter", "") DefNode(GeometryNode, GEO_NODE_CURVE_PRIMITIVE_BEZIER_SEGMENT, def_geo_curve_primitive_bezier_segment, "CURVE_PRIMITIVE_BEZIER_SEGMENT", CurvePrimitiveBezierSegment, "Bezier Segment", "") @@ -328,16 +336,21 @@ DefNode(GeometryNode, GEO_NODE_CURVE_PRIMITIVE_QUADRILATERAL, def_geo_curve_prim DefNode(GeometryNode, GEO_NODE_CURVE_PRIMITIVE_SPIRAL, 0, "CURVE_PRIMITIVE_SPIRAL", CurveSpiral, "Curve Spiral", "") DefNode(GeometryNode, GEO_NODE_CURVE_PRIMITIVE_STAR, 0, "CURVE_PRIMITIVE_STAR", CurveStar, "Star", "") DefNode(GeometryNode, GEO_NODE_CURVE_RESAMPLE, def_geo_curve_resample, "CURVE_RESAMPLE", CurveResample, "Resample Curve", "") -DefNode(GeometryNode, GEO_NODE_CURVE_FILLET, def_geo_curve_fillet, "CURVE_FILLET", CurveFillet, "Curve Fillet", "") +DefNode(GeometryNode, GEO_NODE_CURVE_SPLINE_TYPE, def_geo_curve_spline_type, "CURVE_SPLINE_TYPE", CurveSplineType, "Set Spline Type", "") +DefNode(GeometryNode, GEO_NODE_CURVE_REVERSE, 0, "CURVE_REVERSE", CurveReverse, "Curve Reverse", "") +DefNode(GeometryNode, GEO_NODE_CURVE_SAMPLE, def_geo_curve_sample, "CURVE_SAMPLE", CurveSample, "Curve Sample", "") +DefNode(GeometryNode, GEO_NODE_CURVE_SET_HANDLES, def_geo_curve_set_handles, "CURVE_SET_HANDLES", CurveSetHandles, "Set Handle Type", "") +DefNode(GeometryNode, GEO_NODE_CURVE_SUBDIVIDE, 0, "CURVE_SUBDIVIDE", CurveSubdivide, "Curve Subdivide", "") DefNode(GeometryNode, GEO_NODE_CURVE_TO_MESH, 0, "CURVE_TO_MESH", CurveToMesh, "Curve to Mesh", "") -DefNode(GeometryNode, GEO_NODE_CURVE_TO_POINTS, def_geo_curve_to_points, "CURVE_TO_POINTS", CurveToPoints, "Curve to Points", "") DefNode(GeometryNode, GEO_NODE_CURVE_TRIM, def_geo_curve_trim, "CURVE_TRIM", CurveTrim, "Curve Trim", "") -DefNode(GeometryNode, GEO_NODE_EDGE_SPLIT, 0, "EDGE_SPLIT", EdgeSplit, "Edge Split", "") +DefNode(GeometryNode, GEO_NODE_DISTRIBUTE_POINTS_ON_FACES, def_geo_distribute_points_on_faces, "DISTRIBUTE_POINTS_ON_FACES", DistributePointsOnFaces, "Distribute Points on Faces", "") DefNode(GeometryNode, GEO_NODE_INPUT_INDEX, 0, "INDEX", InputIndex, "Index", "") DefNode(GeometryNode, GEO_NODE_INPUT_MATERIAL, def_geo_input_material, "INPUT_MATERIAL", InputMaterial, "Material", "") DefNode(GeometryNode, GEO_NODE_INPUT_NORMAL, 0, "INPUT_NORMAL", InputNormal, "Normal", "") DefNode(GeometryNode, GEO_NODE_INPUT_POSITION, 0, "POSITION", InputPosition, "Position", "") +DefNode(GeometryNode, GEO_NODE_INPUT_SPLINE_LENGTH, 0, "SPLINE_LENGTH", SplineLength, "Spline Length", "") DefNode(GeometryNode, GEO_NODE_INPUT_TANGENT, 0, "INPUT_TANGENT", InputTangent, "Curve Tangent", "") +DefNode(GeometryNode, GEO_NODE_INSTANCE_ON_POINTS, 0, "INSTANCE_ON_POINTS", InstanceOnPoints, "Instance on Points", "") DefNode(GeometryNode, GEO_NODE_IS_VIEWPORT, 0, "IS_VIEWPORT", IsViewport, "Is Viewport", "") DefNode(GeometryNode, GEO_NODE_JOIN_GEOMETRY, 0, "JOIN_GEOMETRY", JoinGeometry, "Join Geometry", "") DefNode(GeometryNode, GEO_NODE_MATERIAL_ASSIGN, 0, "MATERIAL_ASSIGN", MaterialAssign, "Material Assign", "") @@ -352,12 +365,15 @@ DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_ICO_SPHERE, 0, "MESH_PRIMITIVE_ICO DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_LINE, def_geo_mesh_line, "MESH_PRIMITIVE_LINE", MeshLine, "Mesh Line", "") DefNode(GeometryNode, GEO_NODE_MESH_PRIMITIVE_UV_SPHERE, 0, "MESH_PRIMITIVE_UV_SPHERE", MeshUVSphere, "UV Sphere", "") DefNode(GeometryNode, GEO_NODE_MESH_SUBDIVIDE, 0, "MESH_SUBDIVIDE", MeshSubdivide, "Mesh Subdivide", "") +DefNode(GeometryNode, GEO_NODE_MESH_TO_POINTS, def_geo_mesh_to_points, "MESH_TO_POINTS", MeshToPoints, "Mesh to Points", "") DefNode(GeometryNode, GEO_NODE_OBJECT_INFO, def_geo_object_info, "OBJECT_INFO", ObjectInfo, "Object Info", "") +DefNode(GeometryNode, GEO_NODE_POINTS_TO_VERTICES, 0, "POINTS_TO_VERTICES", PointsToVertices, "Points to Vertices", "") +DefNode(GeometryNode, GEO_NODE_PROXIMITY, def_geo_proximity, "PROXIMITY", Proximity, "Geometry Proximity", "") DefNode(GeometryNode, GEO_NODE_REALIZE_INSTANCES, 0, "REALIZE_INSTANCES", RealizeInstances, "Realize Instances", "") DefNode(GeometryNode, GEO_NODE_SEPARATE_COMPONENTS, 0, "SEPARATE_COMPONENTS", SeparateComponents, "Separate Components", "") DefNode(GeometryNode, GEO_NODE_SET_POSITION, 0, "SET_POSITION", SetPosition, "Set Position", "") DefNode(GeometryNode, GEO_NODE_STRING_JOIN, 0, "STRING_JOIN", StringJoin, "String Join", "") -DefNode(GeometryNode, GEO_NODE_SUBDIVISION_SURFACE, def_geo_subdivision_surface, "SUBDIVISION_SURFACE", SubdivisionSurface, "Subdivision Surface", "") +DefNode(GeometryNode, GEO_NODE_STRING_TO_CURVES, def_geo_string_to_curves, "STRING_TO_CURVES", StringToCurves, "String to Curves", "") DefNode(GeometryNode, GEO_NODE_SWITCH, def_geo_switch, "SWITCH", Switch, "Switch", "") DefNode(GeometryNode, GEO_NODE_TRANSFORM, 0, "TRANSFORM", Transform, "Transform", "") DefNode(GeometryNode, GEO_NODE_TRIANGULATE, def_geo_triangulate, "TRIANGULATE", Triangulate, "Triangulate", "") diff --git a/source/blender/nodes/composite/node_composite_tree.c b/source/blender/nodes/composite/node_composite_tree.cc index cc657d6f91d..a596a85b748 100644 --- a/source/blender/nodes/composite/node_composite_tree.c +++ b/source/blender/nodes/composite/node_composite_tree.cc @@ -21,7 +21,7 @@ * \ingroup nodes */ -#include <stdio.h> +#include <cstdio> #include "DNA_color_types.h" #include "DNA_node_types.h" @@ -41,7 +41,7 @@ #include "RNA_access.h" #include "NOD_composite.h" -#include "node_composite_util.h" +#include "node_composite_util.hh" #ifdef WITH_COMPOSITOR # include "COM_compositor.h" @@ -55,7 +55,7 @@ static void composite_get_from_context(const bContext *C, { Scene *scene = CTX_data_scene(C); - *r_from = NULL; + *r_from = nullptr; *r_id = &scene->id; *r_ntree = scene->nodetree; } @@ -77,19 +77,16 @@ static void foreach_nodeclass(Scene *UNUSED(scene), void *calldata, bNodeClassCa static void free_node_cache(bNodeTree *UNUSED(ntree), bNode *node) { - bNodeSocket *sock; - - for (sock = node->outputs.first; sock; sock = sock->next) { + LISTBASE_FOREACH (bNodeSocket *, sock, &node->outputs) { if (sock->cache) { - sock->cache = NULL; + sock->cache = nullptr; } } } static void free_cache(bNodeTree *ntree) { - bNode *node; - for (node = ntree->nodes.first; node; node = node->next) { + LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { free_node_cache(ntree, node); } } @@ -98,16 +95,16 @@ static void free_cache(bNodeTree *ntree) static void localize(bNodeTree *localtree, bNodeTree *ntree) { - bNode *node = ntree->nodes.first; - bNode *local_node = localtree->nodes.first; - while (node != NULL) { + bNode *node = (bNode *)ntree->nodes.first; + bNode *local_node = (bNode *)localtree->nodes.first; + while (node != nullptr) { /* Ensure new user input gets handled ok. */ node->need_exec = 0; local_node->original = node; /* move over the compbufs */ - /* right after ntreeCopyTree() oldsock pointers are valid */ + /* right after #ntreeCopyTree() `oldsock` pointers are valid */ if (ELEM(node->type, CMP_NODE_VIEWER, CMP_NODE_SPLITVIEWER)) { if (node->id) { @@ -115,16 +112,16 @@ static void localize(bNodeTree *localtree, bNodeTree *ntree) local_node->id = (ID *)node->id; } else { - local_node->id = NULL; + local_node->id = nullptr; } } } - bNodeSocket *output_sock = node->outputs.first; - bNodeSocket *local_output_sock = local_node->outputs.first; - while (output_sock != NULL) { + bNodeSocket *output_sock = (bNodeSocket *)node->outputs.first; + bNodeSocket *local_output_sock = (bNodeSocket *)local_node->outputs.first; + while (output_sock != nullptr) { local_output_sock->cache = output_sock->cache; - output_sock->cache = NULL; + output_sock->cache = nullptr; /* This is actually link to original: someone was just lazy enough and tried to save few * bytes in the cost of readability. */ local_output_sock->new_sock = output_sock; @@ -151,7 +148,7 @@ static void local_merge(Main *bmain, bNodeTree *localtree, bNodeTree *ntree) /* move over the compbufs and previews */ BKE_node_preview_merge_tree(ntree, localtree, true); - for (lnode = localtree->nodes.first; lnode; lnode = lnode->next) { + for (lnode = (bNode *)localtree->nodes.first; lnode; lnode = lnode->next) { if (ntreeNodeExists(ntree, lnode->new_node)) { if (ELEM(lnode->type, CMP_NODE_VIEWER, CMP_NODE_SPLITVIEWER)) { if (lnode->id && (lnode->flag & NODE_DO_OUTPUT)) { @@ -165,18 +162,19 @@ static void local_merge(Main *bmain, bNodeTree *localtree, bNodeTree *ntree) * copied back to original node */ if (lnode->storage) { if (lnode->new_node->storage) { - BKE_tracking_distortion_free(lnode->new_node->storage); + BKE_tracking_distortion_free((MovieDistortion *)lnode->new_node->storage); } - lnode->new_node->storage = BKE_tracking_distortion_copy(lnode->storage); + lnode->new_node->storage = BKE_tracking_distortion_copy( + (MovieDistortion *)lnode->storage); } } - for (lsock = lnode->outputs.first; lsock; lsock = lsock->next) { + for (lsock = (bNodeSocket *)lnode->outputs.first; lsock; lsock = lsock->next) { if (ntreeOutputExists(lnode->new_node, lsock->new_sock)) { lsock->new_sock->cache = lsock->cache; - lsock->cache = NULL; - lsock->new_sock = NULL; + lsock->cache = nullptr; + lsock->new_sock = nullptr; } } } @@ -216,13 +214,13 @@ bNodeTreeType *ntreeType_Composite; void register_node_tree_type_cmp(void) { - bNodeTreeType *tt = ntreeType_Composite = MEM_callocN(sizeof(bNodeTreeType), - "compositor node tree type"); + bNodeTreeType *tt = ntreeType_Composite = (bNodeTreeType *)MEM_callocN( + sizeof(bNodeTreeType), "compositor node tree type"); tt->type = NTREE_COMPOSIT; strcpy(tt->idname, "CompositorNodeTree"); strcpy(tt->ui_name, N_("Compositor")); - tt->ui_icon = 0; /* defined in drawnode.c */ + tt->ui_icon = 0; /* Defined in `drawnode.c`. */ strcpy(tt->ui_description, N_("Compositing nodes")); tt->free_cache = free_cache; @@ -241,9 +239,6 @@ void register_node_tree_type_cmp(void) ntreeTypeAdd(tt); } -extern void *COM_linker_hack; /* Quiet warning. */ -void *COM_linker_hack = NULL; - void ntreeCompositExecTree(Scene *scene, bNodeTree *ntree, RenderData *rd, @@ -276,13 +271,11 @@ void ntreeCompositExecTree(Scene *scene, */ void ntreeCompositUpdateRLayers(bNodeTree *ntree) { - bNode *node; - - if (ntree == NULL) { + if (ntree == nullptr) { return; } - for (node = ntree->nodes.first; node; node = node->next) { + LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { if (node->type == CMP_NODE_R_LAYERS) { node_cmp_rlayers_outputs(ntree, node); } @@ -295,13 +288,11 @@ void ntreeCompositRegisterPass(bNodeTree *ntree, const char *name, eNodeSocketDatatype type) { - bNode *node; - - if (ntree == NULL) { + if (ntree == nullptr) { return; } - for (node = ntree->nodes.first; node; node = node->next) { + LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { if (node->type == CMP_NODE_R_LAYERS) { node_cmp_rlayers_register_pass(ntree, node, scene, view_layer, name, type); } @@ -317,15 +308,14 @@ void ntreeCompositTagRender(Scene *scene) * This is still rather weak though, * ideally render struct would store own main AND original G_MAIN. */ - for (Scene *sce_iter = G_MAIN->scenes.first; sce_iter; sce_iter = sce_iter->id.next) { + for (Scene *sce_iter = (Scene *)G_MAIN->scenes.first; sce_iter; + sce_iter = (Scene *)sce_iter->id.next) { if (sce_iter->nodetree) { - bNode *node; - - for (node = sce_iter->nodetree->nodes.first; node; node = node->next) { + LISTBASE_FOREACH (bNode *, node, &sce_iter->nodetree->nodes) { if (node->id == (ID *)scene || node->type == CMP_NODE_COMPOSITE) { nodeUpdate(sce_iter->nodetree, node); } - else if (node->type == CMP_NODE_TEXTURE) /* uses scene sizex/sizey */ { + else if (node->type == CMP_NODE_TEXTURE) /* uses scene size_x/size_y */ { nodeUpdate(sce_iter->nodetree, node); } } @@ -336,13 +326,11 @@ void ntreeCompositTagRender(Scene *scene) /* XXX after render animation system gets a refresh, this call allows composite to end clean */ void ntreeCompositClearTags(bNodeTree *ntree) { - bNode *node; - - if (ntree == NULL) { + if (ntree == nullptr) { return; } - for (node = ntree->nodes.first; node; node = node->next) { + LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { node->need_exec = 0; if (node->type == NODE_GROUP) { ntreeCompositClearTags((bNodeTree *)node->id); diff --git a/source/blender/nodes/composite/node_composite_util.c b/source/blender/nodes/composite/node_composite_util.cc index 6cc17d8c272..86aaec61bc3 100644 --- a/source/blender/nodes/composite/node_composite_util.c +++ b/source/blender/nodes/composite/node_composite_util.cc @@ -21,7 +21,7 @@ * \ingroup nodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" bool cmp_node_poll_default(bNodeType *UNUSED(ntype), bNodeTree *ntree, @@ -36,11 +36,10 @@ bool cmp_node_poll_default(bNodeType *UNUSED(ntype), void cmp_node_update_default(bNodeTree *UNUSED(ntree), bNode *node) { - bNodeSocket *sock; - for (sock = node->outputs.first; sock; sock = sock->next) { + LISTBASE_FOREACH (bNodeSocket *, sock, &node->outputs) { if (sock->cache) { // free_compbuf(sock->cache); - // sock->cache = NULL; + // sock->cache = nullptr; } } node->need_exec = 1; diff --git a/source/blender/nodes/composite/node_composite_util.h b/source/blender/nodes/composite/node_composite_util.hh index 4fcccbb79f0..6fd82ffc93f 100644 --- a/source/blender/nodes/composite/node_composite_util.h +++ b/source/blender/nodes/composite/node_composite_util.hh @@ -47,20 +47,13 @@ /* only for forward declarations */ #include "NOD_composite.h" - -#ifdef __cplusplus -extern "C" { -#endif +#include "NOD_socket_declarations.hh" #define CMP_SCALE_MAX 12000 bool cmp_node_poll_default(struct bNodeType *ntype, struct bNodeTree *ntree, - const char **r_disabled_info); + const char **r_disabled_hint); void cmp_node_update_default(struct bNodeTree *ntree, struct bNode *node); void cmp_node_type_base( struct bNodeType *ntype, int type, const char *name, short nclass, short flag); - -#ifdef __cplusplus -} -#endif diff --git a/source/blender/nodes/composite/nodes/node_composite_alphaOver.c b/source/blender/nodes/composite/nodes/node_composite_alphaOver.cc index 7a08bd51575..6210d946bc7 100644 --- a/source/blender/nodes/composite/nodes/node_composite_alphaOver.c +++ b/source/blender/nodes/composite/nodes/node_composite_alphaOver.cc @@ -21,19 +21,21 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** ALPHAOVER ******************** */ -static bNodeSocketTemplate cmp_node_alphaover_in[] = { - {SOCK_FLOAT, N_("Fac"), 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, PROP_FACTOR}, - {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, - {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, - {-1, ""}, -}; -static bNodeSocketTemplate cmp_node_alphaover_out[] = { - {SOCK_RGBA, N_("Image")}, - {-1, ""}, -}; + +namespace blender::nodes { + +static void cmp_node_alphaover_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Float>("Fac").default_value(1.0f).min(0.0f).max(1.0f).subtype(PROP_FACTOR); + b.add_input<decl::Color>("Image").default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_input<decl::Color>("Image", "Image_001").default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_output<decl::Color>("Image"); +} + +} // namespace blender::nodes static void node_alphaover_init(bNodeTree *UNUSED(ntree), bNode *node) { @@ -45,7 +47,7 @@ void register_node_type_cmp_alphaover(void) static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_ALPHAOVER, "Alpha Over", NODE_CLASS_OP_COLOR, 0); - node_type_socket_templates(&ntype, cmp_node_alphaover_in, cmp_node_alphaover_out); + ntype.declare = blender::nodes::cmp_node_alphaover_declare; node_type_init(&ntype, node_alphaover_init); node_type_storage( &ntype, "NodeTwoFloats", node_free_standard_storage, node_copy_standard_storage); diff --git a/source/blender/nodes/composite/nodes/node_composite_antialiasing.c b/source/blender/nodes/composite/nodes/node_composite_antialiasing.cc index a5906c31093..23e63b9a53b 100644 --- a/source/blender/nodes/composite/nodes/node_composite_antialiasing.c +++ b/source/blender/nodes/composite/nodes/node_composite_antialiasing.cc @@ -23,7 +23,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** Anti-Aliasing (SMAA 1x) ******************** */ @@ -34,7 +34,8 @@ static bNodeSocketTemplate cmp_node_antialiasing_out[] = {{SOCK_RGBA, N_("Image" static void node_composit_init_antialiasing(bNodeTree *UNUSED(ntree), bNode *node) { - NodeAntiAliasingData *data = MEM_callocN(sizeof(NodeAntiAliasingData), "node antialiasing data"); + NodeAntiAliasingData *data = (NodeAntiAliasingData *)MEM_callocN(sizeof(NodeAntiAliasingData), + "node antialiasing data"); data->threshold = CMP_DEFAULT_SMAA_THRESHOLD; data->contrast_limit = CMP_DEFAULT_SMAA_CONTRAST_LIMIT; diff --git a/source/blender/nodes/composite/nodes/node_composite_bilateralblur.c b/source/blender/nodes/composite/nodes/node_composite_bilateralblur.cc index 270a137280c..3e724d17a10 100644 --- a/source/blender/nodes/composite/nodes/node_composite_bilateralblur.c +++ b/source/blender/nodes/composite/nodes/node_composite_bilateralblur.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** BILATERALBLUR ******************** */ static bNodeSocketTemplate cmp_node_bilateralblur_in[] = { @@ -36,8 +36,8 @@ static bNodeSocketTemplate cmp_node_bilateralblur_out[] = { static void node_composit_init_bilateralblur(bNodeTree *UNUSED(ntree), bNode *node) { - NodeBilateralBlurData *nbbd = MEM_callocN(sizeof(NodeBilateralBlurData), - "node bilateral blur data"); + NodeBilateralBlurData *nbbd = (NodeBilateralBlurData *)MEM_callocN(sizeof(NodeBilateralBlurData), + "node bilateral blur data"); node->storage = nbbd; nbbd->iter = 1; nbbd->sigma_color = 0.3; diff --git a/source/blender/nodes/composite/nodes/node_composite_blur.c b/source/blender/nodes/composite/nodes/node_composite_blur.cc index 92379f4552b..c5c0c21929e 100644 --- a/source/blender/nodes/composite/nodes/node_composite_blur.c +++ b/source/blender/nodes/composite/nodes/node_composite_blur.cc @@ -22,7 +22,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** BLUR ******************** */ static bNodeSocketTemplate cmp_node_blur_in[] = { @@ -33,7 +33,7 @@ static bNodeSocketTemplate cmp_node_blur_out[] = {{SOCK_RGBA, N_("Image")}, {-1, static void node_composit_init_blur(bNodeTree *UNUSED(ntree), bNode *node) { - NodeBlurData *data = MEM_callocN(sizeof(NodeBlurData), "node blur data"); + NodeBlurData *data = (NodeBlurData *)MEM_callocN(sizeof(NodeBlurData), "node blur data"); data->filtertype = R_FILTER_GAUSS; node->storage = data; } diff --git a/source/blender/nodes/composite/nodes/node_composite_bokehblur.c b/source/blender/nodes/composite/nodes/node_composite_bokehblur.cc index d724a83e5a2..f130a642e20 100644 --- a/source/blender/nodes/composite/nodes/node_composite_bokehblur.c +++ b/source/blender/nodes/composite/nodes/node_composite_bokehblur.cc @@ -22,7 +22,7 @@ * \ingroup cmpnodes */ -#include "../node_composite_util.h" +#include "../node_composite_util.hh" /* **************** BLUR ******************** */ static bNodeSocketTemplate cmp_node_bokehblur_in[] = { diff --git a/source/blender/nodes/composite/nodes/node_composite_bokehimage.c b/source/blender/nodes/composite/nodes/node_composite_bokehimage.cc index 744aba417be..3a4bf94d256 100644 --- a/source/blender/nodes/composite/nodes/node_composite_bokehimage.c +++ b/source/blender/nodes/composite/nodes/node_composite_bokehimage.cc @@ -21,18 +21,22 @@ * \ingroup cmpnodes */ -#include "../node_composite_util.h" +#include "../node_composite_util.hh" /* **************** Bokeh image Tools ******************** */ -static bNodeSocketTemplate cmp_node_bokehimage_out[] = { - {SOCK_RGBA, N_("Image"), 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f}, - {-1, ""}, -}; +namespace blender::nodes { + +static void cmp_node_bokehimage_declare(NodeDeclarationBuilder &b) +{ + b.add_output<decl::Color>("Image"); +} + +} // namespace blender::nodes static void node_composit_init_bokehimage(bNodeTree *UNUSED(ntree), bNode *node) { - NodeBokehImage *data = MEM_callocN(sizeof(NodeBokehImage), "NodeBokehImage"); + NodeBokehImage *data = (NodeBokehImage *)MEM_callocN(sizeof(NodeBokehImage), "NodeBokehImage"); data->angle = 0.0f; data->flaps = 5; data->rounding = 0.0f; @@ -46,7 +50,7 @@ void register_node_type_cmp_bokehimage(void) static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_BOKEHIMAGE, "Bokeh Image", NODE_CLASS_INPUT, NODE_PREVIEW); - node_type_socket_templates(&ntype, NULL, cmp_node_bokehimage_out); + ntype.declare = blender::nodes::cmp_node_bokehimage_declare; node_type_init(&ntype, node_composit_init_bokehimage); node_type_storage( &ntype, "NodeBokehImage", node_free_standard_storage, node_copy_standard_storage); diff --git a/source/blender/nodes/composite/nodes/node_composite_boxmask.c b/source/blender/nodes/composite/nodes/node_composite_boxmask.cc index e646b9a9adf..cdf96065f97 100644 --- a/source/blender/nodes/composite/nodes/node_composite_boxmask.c +++ b/source/blender/nodes/composite/nodes/node_composite_boxmask.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "../node_composite_util.h" +#include "../node_composite_util.hh" /* **************** SCALAR MATH ******************** */ static bNodeSocketTemplate cmp_node_boxmask_in[] = { @@ -34,7 +34,7 @@ static bNodeSocketTemplate cmp_node_boxmask_out[] = { static void node_composit_init_boxmask(bNodeTree *UNUSED(ntree), bNode *node) { - NodeBoxMask *data = MEM_callocN(sizeof(NodeBoxMask), "NodeBoxMask"); + NodeBoxMask *data = (NodeBoxMask *)MEM_callocN(sizeof(NodeBoxMask), "NodeBoxMask"); data->x = 0.5; data->y = 0.5; data->width = 0.2; diff --git a/source/blender/nodes/composite/nodes/node_composite_brightness.c b/source/blender/nodes/composite/nodes/node_composite_brightness.cc index 5beecb55665..ad4b09c69d0 100644 --- a/source/blender/nodes/composite/nodes/node_composite_brightness.c +++ b/source/blender/nodes/composite/nodes/node_composite_brightness.cc @@ -21,20 +21,21 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" -/* **************** Brigh and contrsast ******************** */ +/* **************** Bright and Contrast ******************** */ -static bNodeSocketTemplate cmp_node_brightcontrast_in[] = { - {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, - {SOCK_FLOAT, N_("Bright"), 0.0f, 0.0f, 0.0f, 0.0f, -100.0f, 100.0f, PROP_NONE}, - {SOCK_FLOAT, N_("Contrast"), 0.0f, 0.0f, 0.0f, 0.0f, -100.0f, 100.0f, PROP_NONE}, - {-1, ""}, -}; -static bNodeSocketTemplate cmp_node_brightcontrast_out[] = { - {SOCK_RGBA, N_("Image")}, - {-1, ""}, -}; +namespace blender::nodes { + +static void cmp_node_brightcontrast_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Color>("Image").default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_input<decl::Float>("Bright").min(-100.0f).max(100.0f); + b.add_input<decl::Float>("Contrast").min(-100.0f).max(100.0f); + b.add_output<decl::Color>("Image"); +} + +} // namespace blender::nodes static void node_composit_init_brightcontrast(bNodeTree *UNUSED(ntree), bNode *node) { @@ -46,7 +47,7 @@ void register_node_type_cmp_brightcontrast(void) static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_BRIGHTCONTRAST, "Bright/Contrast", NODE_CLASS_OP_COLOR, 0); - node_type_socket_templates(&ntype, cmp_node_brightcontrast_in, cmp_node_brightcontrast_out); + ntype.declare = blender::nodes::cmp_node_brightcontrast_declare; node_type_init(&ntype, node_composit_init_brightcontrast); nodeRegisterType(&ntype); diff --git a/source/blender/nodes/composite/nodes/node_composite_channelMatte.c b/source/blender/nodes/composite/nodes/node_composite_channelMatte.cc index 9912c10b368..e211bc45b17 100644 --- a/source/blender/nodes/composite/nodes/node_composite_channelMatte.c +++ b/source/blender/nodes/composite/nodes/node_composite_channelMatte.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* ******************* Channel Matte Node ********************************* */ static bNodeSocketTemplate cmp_node_channel_matte_in[] = { @@ -37,7 +37,7 @@ static bNodeSocketTemplate cmp_node_channel_matte_out[] = { static void node_composit_init_channel_matte(bNodeTree *UNUSED(ntree), bNode *node) { - NodeChroma *c = MEM_callocN(sizeof(NodeChroma), "node chroma"); + NodeChroma *c = (NodeChroma *)MEM_callocN(sizeof(NodeChroma), "node chroma"); node->storage = c; c->t1 = 1.0f; c->t2 = 0.0f; diff --git a/source/blender/nodes/composite/nodes/node_composite_chromaMatte.c b/source/blender/nodes/composite/nodes/node_composite_chromaMatte.cc index 705566df35a..990778160df 100644 --- a/source/blender/nodes/composite/nodes/node_composite_chromaMatte.c +++ b/source/blender/nodes/composite/nodes/node_composite_chromaMatte.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* ******************* Chroma Key ********************************************************** */ static bNodeSocketTemplate cmp_node_chroma_in[] = { @@ -38,7 +38,7 @@ static bNodeSocketTemplate cmp_node_chroma_out[] = { static void node_composit_init_chroma_matte(bNodeTree *UNUSED(ntree), bNode *node) { - NodeChroma *c = MEM_callocN(sizeof(NodeChroma), "node chroma"); + NodeChroma *c = (NodeChroma *)MEM_callocN(sizeof(NodeChroma), "node chroma"); node->storage = c; c->t1 = DEG2RADF(30.0f); c->t2 = DEG2RADF(10.0f); diff --git a/source/blender/nodes/composite/nodes/node_composite_colorMatte.c b/source/blender/nodes/composite/nodes/node_composite_colorMatte.cc index f5cf7bcbf22..fc9a0075b14 100644 --- a/source/blender/nodes/composite/nodes/node_composite_colorMatte.c +++ b/source/blender/nodes/composite/nodes/node_composite_colorMatte.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* ******************* Color Key ********************************************************** */ static bNodeSocketTemplate cmp_node_color_in[] = { @@ -38,7 +38,7 @@ static bNodeSocketTemplate cmp_node_color_out[] = { static void node_composit_init_color_matte(bNodeTree *UNUSED(ntree), bNode *node) { - NodeChroma *c = MEM_callocN(sizeof(NodeChroma), "node color"); + NodeChroma *c = (NodeChroma *)MEM_callocN(sizeof(NodeChroma), "node color"); node->storage = c; c->t1 = 0.01f; c->t2 = 0.1f; diff --git a/source/blender/nodes/composite/nodes/node_composite_colorSpill.c b/source/blender/nodes/composite/nodes/node_composite_colorSpill.cc index 8ff4bcdced3..7bdc2e8289e 100644 --- a/source/blender/nodes/composite/nodes/node_composite_colorSpill.c +++ b/source/blender/nodes/composite/nodes/node_composite_colorSpill.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* ******************* Color Spill Suppression ********************************* */ static bNodeSocketTemplate cmp_node_color_spill_in[] = { @@ -37,7 +37,7 @@ static bNodeSocketTemplate cmp_node_color_spill_out[] = { static void node_composit_init_color_spill(bNodeTree *UNUSED(ntree), bNode *node) { - NodeColorspill *ncs = MEM_callocN(sizeof(NodeColorspill), "node colorspill"); + NodeColorspill *ncs = (NodeColorspill *)MEM_callocN(sizeof(NodeColorspill), "node colorspill"); node->storage = ncs; node->custom1 = 2; /* green channel */ node->custom2 = 0; /* simple limit algorithm */ diff --git a/source/blender/nodes/composite/nodes/node_composite_colorbalance.c b/source/blender/nodes/composite/nodes/node_composite_colorbalance.cc index 0525229697a..440e37fe741 100644 --- a/source/blender/nodes/composite/nodes/node_composite_colorbalance.c +++ b/source/blender/nodes/composite/nodes/node_composite_colorbalance.cc @@ -21,19 +21,20 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* ******************* Color Balance ********************************* */ -static bNodeSocketTemplate cmp_node_colorbalance_in[] = { - {SOCK_FLOAT, N_("Fac"), 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, PROP_FACTOR}, - {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, - {-1, ""}, -}; -static bNodeSocketTemplate cmp_node_colorbalance_out[] = { - {SOCK_RGBA, N_("Image")}, - {-1, ""}, -}; +namespace blender::nodes { + +static void cmp_node_colorbalance_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Float>("Fac").default_value(1.0f).min(0.0f).max(1.0f).subtype(PROP_FACTOR); + b.add_input<decl::Color>("Image").default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_output<decl::Color>("Image"); +} + +} // namespace blender::nodes /* Sync functions update formula parameters for other modes, such that the result is comparable. * Note that the results are not exactly the same due to differences in color handling @@ -43,10 +44,9 @@ static bNodeSocketTemplate cmp_node_colorbalance_out[] = { void ntreeCompositColorBalanceSyncFromLGG(bNodeTree *UNUSED(ntree), bNode *node) { - NodeColorBalance *n = node->storage; - int c; + NodeColorBalance *n = (NodeColorBalance *)node->storage; - for (c = 0; c < 3; c++) { + for (int c = 0; c < 3; c++) { n->slope[c] = (2.0f - n->lift[c]) * n->gain[c]; n->offset[c] = (n->lift[c] - 1.0f) * n->gain[c]; n->power[c] = (n->gamma[c] != 0.0f) ? 1.0f / n->gamma[c] : 1000000.0f; @@ -55,10 +55,9 @@ void ntreeCompositColorBalanceSyncFromLGG(bNodeTree *UNUSED(ntree), bNode *node) void ntreeCompositColorBalanceSyncFromCDL(bNodeTree *UNUSED(ntree), bNode *node) { - NodeColorBalance *n = node->storage; - int c; + NodeColorBalance *n = (NodeColorBalance *)node->storage; - for (c = 0; c < 3; c++) { + for (int c = 0; c < 3; c++) { float d = n->slope[c] + n->offset[c]; n->lift[c] = (d != 0.0f ? n->slope[c] + 2.0f * n->offset[c] / d : 0.0f); n->gain[c] = d; @@ -68,7 +67,8 @@ void ntreeCompositColorBalanceSyncFromCDL(bNodeTree *UNUSED(ntree), bNode *node) static void node_composit_init_colorbalance(bNodeTree *UNUSED(ntree), bNode *node) { - NodeColorBalance *n = node->storage = MEM_callocN(sizeof(NodeColorBalance), "node colorbalance"); + NodeColorBalance *n = (NodeColorBalance *)MEM_callocN(sizeof(NodeColorBalance), + "node colorbalance"); n->lift[0] = n->lift[1] = n->lift[2] = 1.0f; n->gamma[0] = n->gamma[1] = n->gamma[2] = 1.0f; @@ -77,6 +77,7 @@ static void node_composit_init_colorbalance(bNodeTree *UNUSED(ntree), bNode *nod n->slope[0] = n->slope[1] = n->slope[2] = 1.0f; n->offset[0] = n->offset[1] = n->offset[2] = 0.0f; n->power[0] = n->power[1] = n->power[2] = 1.0f; + node->storage = n; } void register_node_type_cmp_colorbalance(void) @@ -84,7 +85,7 @@ void register_node_type_cmp_colorbalance(void) static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_COLORBALANCE, "Color Balance", NODE_CLASS_OP_COLOR, 0); - node_type_socket_templates(&ntype, cmp_node_colorbalance_in, cmp_node_colorbalance_out); + ntype.declare = blender::nodes::cmp_node_colorbalance_declare; node_type_size(&ntype, 400, 200, 400); node_type_init(&ntype, node_composit_init_colorbalance); node_type_storage( diff --git a/source/blender/nodes/composite/nodes/node_composite_colorcorrection.c b/source/blender/nodes/composite/nodes/node_composite_colorcorrection.cc index 45d39f8be8d..0682c66f1e8 100644 --- a/source/blender/nodes/composite/nodes/node_composite_colorcorrection.c +++ b/source/blender/nodes/composite/nodes/node_composite_colorcorrection.cc @@ -21,24 +21,25 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" -/* ******************* Color Balance ********************************* */ -static bNodeSocketTemplate cmp_node_colorcorrection_in[] = { - {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, - {SOCK_FLOAT, N_("Mask"), 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, PROP_NONE}, - {-1, ""}, -}; +/* ******************* Color Correction ********************************* */ -static bNodeSocketTemplate cmp_node_colorcorrection_out[] = { - {SOCK_RGBA, N_("Image")}, - {-1, ""}, -}; +namespace blender::nodes { + +static void cmp_node_colorcorrection_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Color>("Image").default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_input<decl::Float>("Mask").default_value(1.0f).min(0.0f).max(1.0f); + b.add_output<decl::Color>("Image"); +} + +} // namespace blender::nodes static void node_composit_init_colorcorrection(bNodeTree *UNUSED(ntree), bNode *node) { - NodeColorCorrection *n = node->storage = MEM_callocN(sizeof(NodeColorCorrection), - "node colorcorrection"); + NodeColorCorrection *n = (NodeColorCorrection *)MEM_callocN(sizeof(NodeColorCorrection), + "node colorcorrection"); n->startmidtones = 0.2f; n->endmidtones = 0.7f; n->master.contrast = 1.0f; @@ -62,6 +63,7 @@ static void node_composit_init_colorcorrection(bNodeTree *UNUSED(ntree), bNode * n->highlights.lift = 0.0f; n->highlights.saturation = 1.0f; node->custom1 = 7; // red + green + blue enabled + node->storage = n; } void register_node_type_cmp_colorcorrection(void) @@ -69,7 +71,7 @@ void register_node_type_cmp_colorcorrection(void) static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_COLORCORRECTION, "Color Correction", NODE_CLASS_OP_COLOR, 0); - node_type_socket_templates(&ntype, cmp_node_colorcorrection_in, cmp_node_colorcorrection_out); + ntype.declare = blender::nodes::cmp_node_colorcorrection_declare; node_type_size(&ntype, 400, 200, 600); node_type_init(&ntype, node_composit_init_colorcorrection); node_type_storage( diff --git a/source/blender/nodes/composite/nodes/node_composite_common.c b/source/blender/nodes/composite/nodes/node_composite_common.cc index 61abc80fe93..fecf6795ef7 100644 --- a/source/blender/nodes/composite/nodes/node_composite_common.c +++ b/source/blender/nodes/composite/nodes/node_composite_common.cc @@ -26,7 +26,7 @@ #include "NOD_common.h" #include "node_common.h" -#include "node_composite_util.h" +#include "node_composite_util.hh" #include "BKE_node.h" @@ -46,10 +46,10 @@ void register_node_type_cmp_group(void) ntype.insert_link = node_insert_link_default; ntype.update_internal_links = node_update_internal_links_default; ntype.rna_ext.srna = RNA_struct_find("CompositorNodeGroup"); - BLI_assert(ntype.rna_ext.srna != NULL); + BLI_assert(ntype.rna_ext.srna != nullptr); RNA_struct_blender_type_set(ntype.rna_ext.srna, &ntype); - node_type_socket_templates(&ntype, NULL, NULL); + node_type_socket_templates(&ntype, nullptr, nullptr); node_type_size(&ntype, 140, 60, 400); node_type_label(&ntype, node_group_label); node_type_group_update(&ntype, node_group_update); @@ -60,13 +60,13 @@ void register_node_type_cmp_group(void) void register_node_type_cmp_custom_group(bNodeType *ntype) { /* These methods can be overridden but need a default implementation otherwise. */ - if (ntype->poll == NULL) { + if (ntype->poll == nullptr) { ntype->poll = cmp_node_poll_default; } - if (ntype->insert_link == NULL) { + if (ntype->insert_link == nullptr) { ntype->insert_link = node_insert_link_default; } - if (ntype->update_internal_links == NULL) { + if (ntype->update_internal_links == nullptr) { ntype->update_internal_links = node_update_internal_links_default; } } diff --git a/source/blender/nodes/composite/nodes/node_composite_composite.c b/source/blender/nodes/composite/nodes/node_composite_composite.cc index dee2ce6b3ec..170fecb251c 100644 --- a/source/blender/nodes/composite/nodes/node_composite_composite.c +++ b/source/blender/nodes/composite/nodes/node_composite_composite.cc @@ -21,25 +21,30 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** COMPOSITE ******************** */ -static bNodeSocketTemplate cmp_node_composite_in[] = { - {SOCK_RGBA, N_("Image"), 0.0f, 0.0f, 0.0f, 1.0f}, - {SOCK_FLOAT, N_("Alpha"), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_NONE}, - {SOCK_FLOAT, N_("Z"), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_NONE}, - {-1, ""}, -}; + +namespace blender::nodes { + +static void cmp_node_composite_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Color>("Image").default_value({0.0f, 0.0f, 0.0f, 1.0f}); + b.add_input<decl::Float>("Alpha").default_value(1.0f).min(0.0f).max(1.0f); + b.add_input<decl::Float>("Z").default_value(1.0f).min(0.0f).max(1.0f); +} + +} // namespace blender::nodes void register_node_type_cmp_composite(void) { static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_COMPOSITE, "Composite", NODE_CLASS_OUTPUT, NODE_PREVIEW); - node_type_socket_templates(&ntype, cmp_node_composite_in, NULL); + ntype.declare = blender::nodes::cmp_node_composite_declare; /* Do not allow muting for this node. */ - node_type_internal_links(&ntype, NULL); + node_type_internal_links(&ntype, nullptr); nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_cornerpin.c b/source/blender/nodes/composite/nodes/node_composite_cornerpin.cc index 135120c45aa..b5ca1fb015e 100644 --- a/source/blender/nodes/composite/nodes/node_composite_cornerpin.c +++ b/source/blender/nodes/composite/nodes/node_composite_cornerpin.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" static bNodeSocketTemplate inputs[] = { {SOCK_RGBA, N_("Image"), 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, PROP_NONE}, diff --git a/source/blender/nodes/composite/nodes/node_composite_crop.c b/source/blender/nodes/composite/nodes/node_composite_crop.cc index 868df5367c4..f07dba8a74b 100644 --- a/source/blender/nodes/composite/nodes/node_composite_crop.c +++ b/source/blender/nodes/composite/nodes/node_composite_crop.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** Crop ******************** */ @@ -36,7 +36,7 @@ static bNodeSocketTemplate cmp_node_crop_out[] = { static void node_composit_init_crop(bNodeTree *UNUSED(ntree), bNode *node) { - NodeTwoXYs *nxy = MEM_callocN(sizeof(NodeTwoXYs), "node xy data"); + NodeTwoXYs *nxy = (NodeTwoXYs *)MEM_callocN(sizeof(NodeTwoXYs), "node xy data"); node->storage = nxy; nxy->x1 = 0; nxy->x2 = 0; diff --git a/source/blender/nodes/composite/nodes/node_composite_cryptomatte.cc b/source/blender/nodes/composite/nodes/node_composite_cryptomatte.cc index 51dd73a86af..6657267b016 100644 --- a/source/blender/nodes/composite/nodes/node_composite_cryptomatte.cc +++ b/source/blender/nodes/composite/nodes/node_composite_cryptomatte.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" #include "BLI_assert.h" #include "BLI_dynstr.h" diff --git a/source/blender/nodes/composite/nodes/node_composite_curves.c b/source/blender/nodes/composite/nodes/node_composite_curves.cc index 470540d3337..88d96e1ca4a 100644 --- a/source/blender/nodes/composite/nodes/node_composite_curves.c +++ b/source/blender/nodes/composite/nodes/node_composite_curves.cc @@ -21,16 +21,20 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** CURVE Time ******************** */ -/* custom1 = sfra, custom2 = efra */ -static bNodeSocketTemplate cmp_node_time_out[] = { - {SOCK_FLOAT, N_("Fac")}, - {-1, ""}, -}; +namespace blender::nodes { +static void cmp_node_time_declare(NodeDeclarationBuilder &b) +{ + b.add_output<decl::Float>("Fac"); +} + +} // namespace blender::nodes + +/* custom1 = start_frame, custom2 = end_frame */ static void node_composit_init_curves_time(bNodeTree *UNUSED(ntree), bNode *node) { node->custom1 = 1; @@ -43,7 +47,7 @@ void register_node_type_cmp_curve_time(void) static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_TIME, "Time", NODE_CLASS_INPUT, 0); - node_type_socket_templates(&ntype, NULL, cmp_node_time_out); + ntype.declare = blender::nodes::cmp_node_time_declare; node_type_size(&ntype, 140, 100, 320); node_type_init(&ntype, node_composit_init_curves_time); node_type_storage(&ntype, "CurveMapping", node_free_curves, node_copy_curves); @@ -81,18 +85,19 @@ void register_node_type_cmp_curve_vec(void) } /* **************** CURVE RGB ******************** */ -static bNodeSocketTemplate cmp_node_curve_rgb_in[] = { - {SOCK_FLOAT, N_("Fac"), 1.0f, 0.0f, 0.0f, 1.0f, -1.0f, 1.0f, PROP_FACTOR}, - {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, - {SOCK_RGBA, N_("Black Level"), 0.0f, 0.0f, 0.0f, 1.0f}, - {SOCK_RGBA, N_("White Level"), 1.0f, 1.0f, 1.0f, 1.0f}, - {-1, ""}, -}; -static bNodeSocketTemplate cmp_node_curve_rgb_out[] = { - {SOCK_RGBA, N_("Image")}, - {-1, ""}, -}; +namespace blender::nodes { + +static void cmp_node_rgbcurves_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Float>("Fac").default_value(1.0f).min(-1.0f).max(1.0f).subtype(PROP_FACTOR); + b.add_input<decl::Color>("Image").default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_input<decl::Color>("Black Level").default_value({0.0f, 0.0f, 0.0f, 1.0f}); + b.add_input<decl::Color>("White Level").default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_output<decl::Color>("Image"); +} + +} // namespace blender::nodes static void node_composit_init_curve_rgb(bNodeTree *UNUSED(ntree), bNode *node) { @@ -104,7 +109,7 @@ void register_node_type_cmp_curve_rgb(void) static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_CURVE_RGB, "RGB Curves", NODE_CLASS_OP_COLOR, 0); - node_type_socket_templates(&ntype, cmp_node_curve_rgb_in, cmp_node_curve_rgb_out); + ntype.declare = blender::nodes::cmp_node_rgbcurves_declare; node_type_size(&ntype, 200, 140, 320); node_type_init(&ntype, node_composit_init_curve_rgb); node_type_storage(&ntype, "CurveMapping", node_free_curves, node_copy_curves); diff --git a/source/blender/nodes/composite/nodes/node_composite_defocus.c b/source/blender/nodes/composite/nodes/node_composite_defocus.cc index 3803f450f49..1103aff4366 100644 --- a/source/blender/nodes/composite/nodes/node_composite_defocus.c +++ b/source/blender/nodes/composite/nodes/node_composite_defocus.cc @@ -21,11 +21,11 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" -#include <limits.h> +#include <climits> -/* ************ qdn: Defocus node ****************** */ +/* ************ Defocus Node ****************** */ static bNodeSocketTemplate cmp_node_defocus_in[] = { {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, {SOCK_FLOAT, N_("Z"), 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, PROP_NONE}, @@ -38,8 +38,8 @@ static bNodeSocketTemplate cmp_node_defocus_out[] = { static void node_composit_init_defocus(bNodeTree *UNUSED(ntree), bNode *node) { - /* qdn: defocus node */ - NodeDefocus *nbd = MEM_callocN(sizeof(NodeDefocus), "node defocus data"); + /* defocus node */ + NodeDefocus *nbd = (NodeDefocus *)MEM_callocN(sizeof(NodeDefocus), "node defocus data"); nbd->bktype = 0; nbd->rotation = 0.0f; nbd->preview = 1; diff --git a/source/blender/nodes/composite/nodes/node_composite_denoise.c b/source/blender/nodes/composite/nodes/node_composite_denoise.cc index e2c7c7b995f..ec085794462 100644 --- a/source/blender/nodes/composite/nodes/node_composite_denoise.c +++ b/source/blender/nodes/composite/nodes/node_composite_denoise.cc @@ -23,7 +23,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" static bNodeSocketTemplate cmp_node_denoise_in[] = { {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f}, @@ -34,7 +34,7 @@ static bNodeSocketTemplate cmp_node_denoise_out[] = {{SOCK_RGBA, N_("Image")}, { static void node_composit_init_denonise(bNodeTree *UNUSED(ntree), bNode *node) { - NodeDenoise *ndg = MEM_callocN(sizeof(NodeDenoise), "node denoise data"); + NodeDenoise *ndg = (NodeDenoise *)MEM_callocN(sizeof(NodeDenoise), "node denoise data"); ndg->hdr = true; ndg->prefilter = CMP_NODE_DENOISE_PREFILTER_ACCURATE; node->storage = ndg; diff --git a/source/blender/nodes/composite/nodes/node_composite_despeckle.c b/source/blender/nodes/composite/nodes/node_composite_despeckle.cc index 18567ee2006..52d91dabeb1 100644 --- a/source/blender/nodes/composite/nodes/node_composite_despeckle.c +++ b/source/blender/nodes/composite/nodes/node_composite_despeckle.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** FILTER ******************** */ static bNodeSocketTemplate cmp_node_despeckle_in[] = { diff --git a/source/blender/nodes/composite/nodes/node_composite_diffMatte.c b/source/blender/nodes/composite/nodes/node_composite_diffMatte.cc index 7871a9e8b04..1e1a48381b7 100644 --- a/source/blender/nodes/composite/nodes/node_composite_diffMatte.c +++ b/source/blender/nodes/composite/nodes/node_composite_diffMatte.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* ******************* channel Difference Matte ********************************* */ static bNodeSocketTemplate cmp_node_diff_matte_in[] = { @@ -38,7 +38,7 @@ static bNodeSocketTemplate cmp_node_diff_matte_out[] = { static void node_composit_init_diff_matte(bNodeTree *UNUSED(ntree), bNode *node) { - NodeChroma *c = MEM_callocN(sizeof(NodeChroma), "node chroma"); + NodeChroma *c = (NodeChroma *)MEM_callocN(sizeof(NodeChroma), "node chroma"); node->storage = c; c->t1 = 0.1f; c->t2 = 0.1f; diff --git a/source/blender/nodes/composite/nodes/node_composite_dilate.c b/source/blender/nodes/composite/nodes/node_composite_dilate.cc index 12f1f229258..57884a299da 100644 --- a/source/blender/nodes/composite/nodes/node_composite_dilate.c +++ b/source/blender/nodes/composite/nodes/node_composite_dilate.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** Dilate/Erode ******************** */ @@ -31,7 +31,8 @@ static bNodeSocketTemplate cmp_node_dilateerode_out[] = {{SOCK_FLOAT, N_("Mask") static void node_composit_init_dilateerode(bNodeTree *UNUSED(ntree), bNode *node) { - NodeDilateErode *data = MEM_callocN(sizeof(NodeDilateErode), "NodeDilateErode"); + NodeDilateErode *data = (NodeDilateErode *)MEM_callocN(sizeof(NodeDilateErode), + "NodeDilateErode"); data->falloff = PROP_SMOOTH; node->storage = data; } diff --git a/source/blender/nodes/composite/nodes/node_composite_directionalblur.c b/source/blender/nodes/composite/nodes/node_composite_directionalblur.cc index 6dd60526edf..d9f82ba5009 100644 --- a/source/blender/nodes/composite/nodes/node_composite_directionalblur.c +++ b/source/blender/nodes/composite/nodes/node_composite_directionalblur.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" static bNodeSocketTemplate cmp_node_dblur_in[] = {{SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, {-1, ""}}; @@ -30,7 +30,7 @@ static bNodeSocketTemplate cmp_node_dblur_out[] = {{SOCK_RGBA, N_("Image")}, {-1 static void node_composit_init_dblur(bNodeTree *UNUSED(ntree), bNode *node) { - NodeDBlurData *ndbd = MEM_callocN(sizeof(NodeDBlurData), "node dblur data"); + NodeDBlurData *ndbd = (NodeDBlurData *)MEM_callocN(sizeof(NodeDBlurData), "node dblur data"); node->storage = ndbd; ndbd->iter = 1; ndbd->center_x = 0.5; diff --git a/source/blender/nodes/composite/nodes/node_composite_displace.c b/source/blender/nodes/composite/nodes/node_composite_displace.cc index 819a4f29b3a..b1ed7f05794 100644 --- a/source/blender/nodes/composite/nodes/node_composite_displace.c +++ b/source/blender/nodes/composite/nodes/node_composite_displace.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** Displace ******************** */ diff --git a/source/blender/nodes/composite/nodes/node_composite_distanceMatte.c b/source/blender/nodes/composite/nodes/node_composite_distanceMatte.cc index 3e659fe662b..3f8767ecd08 100644 --- a/source/blender/nodes/composite/nodes/node_composite_distanceMatte.c +++ b/source/blender/nodes/composite/nodes/node_composite_distanceMatte.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* ******************* channel Distance Matte ********************************* */ static bNodeSocketTemplate cmp_node_distance_matte_in[] = { @@ -38,7 +38,7 @@ static bNodeSocketTemplate cmp_node_distance_matte_out[] = { static void node_composit_init_distance_matte(bNodeTree *UNUSED(ntree), bNode *node) { - NodeChroma *c = MEM_callocN(sizeof(NodeChroma), "node chroma"); + NodeChroma *c = (NodeChroma *)MEM_callocN(sizeof(NodeChroma), "node chroma"); node->storage = c; c->channel = 1; c->t1 = 0.1f; diff --git a/source/blender/nodes/composite/nodes/node_composite_doubleEdgeMask.c b/source/blender/nodes/composite/nodes/node_composite_doubleEdgeMask.cc index 6f68b187775..7c9a48efc2d 100644 --- a/source/blender/nodes/composite/nodes/node_composite_doubleEdgeMask.c +++ b/source/blender/nodes/composite/nodes/node_composite_doubleEdgeMask.cc @@ -20,7 +20,7 @@ /** \file * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** Double Edge Mask ******************** */ static bNodeSocketTemplate cmp_node_doubleedgemask_in[] = { diff --git a/source/blender/nodes/composite/nodes/node_composite_ellipsemask.c b/source/blender/nodes/composite/nodes/node_composite_ellipsemask.cc index d5e1d519a1c..67196fb0d35 100644 --- a/source/blender/nodes/composite/nodes/node_composite_ellipsemask.c +++ b/source/blender/nodes/composite/nodes/node_composite_ellipsemask.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "../node_composite_util.h" +#include "../node_composite_util.hh" /* **************** SCALAR MATH ******************** */ static bNodeSocketTemplate cmp_node_ellipsemask_in[] = { @@ -34,7 +34,8 @@ static bNodeSocketTemplate cmp_node_ellipsemask_out[] = { static void node_composit_init_ellipsemask(bNodeTree *UNUSED(ntree), bNode *node) { - NodeEllipseMask *data = MEM_callocN(sizeof(NodeEllipseMask), "NodeEllipseMask"); + NodeEllipseMask *data = (NodeEllipseMask *)MEM_callocN(sizeof(NodeEllipseMask), + "NodeEllipseMask"); data->x = 0.5; data->y = 0.5; data->width = 0.2; diff --git a/source/blender/nodes/composite/nodes/node_composite_exposure.c b/source/blender/nodes/composite/nodes/node_composite_exposure.cc index bd27e4a3d76..fd959376afe 100644 --- a/source/blender/nodes/composite/nodes/node_composite_exposure.c +++ b/source/blender/nodes/composite/nodes/node_composite_exposure.cc @@ -21,26 +21,27 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** Exposure ******************** */ -static bNodeSocketTemplate cmp_node_exposure_in[] = { - {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, - {SOCK_FLOAT, N_("Exposure"), 0.0f, 0.0f, 0.0f, 0.0f, -10.0f, 10.0f, PROP_NONE}, - {-1, ""}, -}; -static bNodeSocketTemplate cmp_node_exposure_out[] = { - {SOCK_RGBA, N_("Image")}, - {-1, ""}, -}; +namespace blender::nodes { + +static void cmp_node_exposure_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Color>("Image").default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_input<decl::Float>("Exposure").min(-10.0f).max(10.0f); + b.add_output<decl::Color>("Image"); +} + +} // namespace blender::nodes void register_node_type_cmp_exposure(void) { static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_EXPOSURE, "Exposure", NODE_CLASS_OP_COLOR, 0); - node_type_socket_templates(&ntype, cmp_node_exposure_in, cmp_node_exposure_out); + ntype.declare = blender::nodes::cmp_node_exposure_declare; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_filter.c b/source/blender/nodes/composite/nodes/node_composite_filter.cc index d0ad090ece4..f07619877f4 100644 --- a/source/blender/nodes/composite/nodes/node_composite_filter.c +++ b/source/blender/nodes/composite/nodes/node_composite_filter.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** FILTER ******************** */ static bNodeSocketTemplate cmp_node_filter_in[] = { diff --git a/source/blender/nodes/composite/nodes/node_composite_flip.c b/source/blender/nodes/composite/nodes/node_composite_flip.cc index 91a91bb5f5f..42aa3141f5c 100644 --- a/source/blender/nodes/composite/nodes/node_composite_flip.c +++ b/source/blender/nodes/composite/nodes/node_composite_flip.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** Flip ******************** */ static bNodeSocketTemplate cmp_node_flip_in[] = { diff --git a/source/blender/nodes/composite/nodes/node_composite_gamma.c b/source/blender/nodes/composite/nodes/node_composite_gamma.cc index ddcaf691fd2..a29a001688a 100644 --- a/source/blender/nodes/composite/nodes/node_composite_gamma.c +++ b/source/blender/nodes/composite/nodes/node_composite_gamma.cc @@ -21,26 +21,28 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** Gamma Tools ******************** */ -static bNodeSocketTemplate cmp_node_gamma_in[] = { - {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, - {SOCK_FLOAT, N_("Gamma"), 1.0f, 0.0f, 0.0f, 0.0f, 0.001f, 10.0f, PROP_UNSIGNED}, - {-1, ""}, -}; -static bNodeSocketTemplate cmp_node_gamma_out[] = { - {SOCK_RGBA, N_("Image")}, - {-1, ""}, -}; +namespace blender::nodes { + +static void cmp_node_gamma_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Color>("Image").default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_input<decl::Float>("Gamma").default_value(1.0f).min(0.001f).max(10.0f).subtype( + PROP_UNSIGNED); + b.add_output<decl::Color>("Image"); +} + +} // namespace blender::nodes void register_node_type_cmp_gamma(void) { static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_GAMMA, "Gamma", NODE_CLASS_OP_COLOR, 0); - node_type_socket_templates(&ntype, cmp_node_gamma_in, cmp_node_gamma_out); + ntype.declare = blender::nodes::cmp_node_gamma_declare; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_glare.c b/source/blender/nodes/composite/nodes/node_composite_glare.cc index a792fcc86cd..8a2fd1e1584 100644 --- a/source/blender/nodes/composite/nodes/node_composite_glare.c +++ b/source/blender/nodes/composite/nodes/node_composite_glare.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" static bNodeSocketTemplate cmp_node_glare_in[] = { {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, @@ -34,7 +34,7 @@ static bNodeSocketTemplate cmp_node_glare_out[] = { static void node_composit_init_glare(bNodeTree *UNUSED(ntree), bNode *node) { - NodeGlare *ndg = MEM_callocN(sizeof(NodeGlare), "node glare data"); + NodeGlare *ndg = (NodeGlare *)MEM_callocN(sizeof(NodeGlare), "node glare data"); ndg->quality = 1; ndg->type = 2; ndg->iter = 3; diff --git a/source/blender/nodes/composite/nodes/node_composite_hueSatVal.c b/source/blender/nodes/composite/nodes/node_composite_hueSatVal.cc index 494b6136a6e..07746918a94 100644 --- a/source/blender/nodes/composite/nodes/node_composite_hueSatVal.c +++ b/source/blender/nodes/composite/nodes/node_composite_hueSatVal.cc @@ -21,28 +21,34 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** Hue Saturation ******************** */ -static bNodeSocketTemplate cmp_node_hue_sat_in[] = { - {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, - {SOCK_FLOAT, N_("Hue"), 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_FACTOR}, - {SOCK_FLOAT, N_("Saturation"), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 2.0f, PROP_FACTOR}, - {SOCK_FLOAT, N_("Value"), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 2.0f, PROP_FACTOR}, - {SOCK_FLOAT, N_("Fac"), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_FACTOR}, - {-1, ""}, -}; -static bNodeSocketTemplate cmp_node_hue_sat_out[] = { - {SOCK_RGBA, N_("Image")}, - {-1, ""}, -}; + +namespace blender::nodes { + +static void cmp_node_huesatval_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Color>("Image").default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_input<decl::Float>("Hue").default_value(0.5f).min(0.0f).max(1.0f).subtype(PROP_FACTOR); + b.add_input<decl::Float>("Saturation") + .default_value(1.0f) + .min(0.0f) + .max(2.0f) + .subtype(PROP_FACTOR); + b.add_input<decl::Float>("Value").default_value(1.0f).min(0.0f).max(2.0f).subtype(PROP_FACTOR); + b.add_input<decl::Float>("Fac").default_value(1.0f).min(0.0f).max(1.0f).subtype(PROP_FACTOR); + b.add_output<decl::Color>("Image"); +} + +} // namespace blender::nodes void register_node_type_cmp_hue_sat(void) { static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_HUE_SAT, "Hue Saturation Value", NODE_CLASS_OP_COLOR, 0); - node_type_socket_templates(&ntype, cmp_node_hue_sat_in, cmp_node_hue_sat_out); + ntype.declare = blender::nodes::cmp_node_huesatval_declare; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_huecorrect.c b/source/blender/nodes/composite/nodes/node_composite_huecorrect.cc index 6a5c918d9ae..39014896a7b 100644 --- a/source/blender/nodes/composite/nodes/node_composite_huecorrect.c +++ b/source/blender/nodes/composite/nodes/node_composite_huecorrect.cc @@ -21,27 +21,28 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" -static bNodeSocketTemplate cmp_node_huecorrect_in[] = { - {SOCK_FLOAT, N_("Fac"), 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, PROP_FACTOR}, - {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, - {-1, ""}, -}; +namespace blender::nodes { -static bNodeSocketTemplate cmp_node_huecorrect_out[] = { - {SOCK_RGBA, N_("Image")}, - {-1, ""}, -}; +static void cmp_node_huecorrect_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Float>("Fac").default_value(1.0f).min(0.0f).max(1.0f).subtype(PROP_FACTOR); + b.add_input<decl::Color>("Image").default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_output<decl::Color>("Image"); +} + +} // namespace blender::nodes static void node_composit_init_huecorrect(bNodeTree *UNUSED(ntree), bNode *node) { - CurveMapping *cumapping = node->storage = BKE_curvemapping_add(1, 0.0f, 0.0f, 1.0f, 1.0f); - int c; + node->storage = BKE_curvemapping_add(1, 0.0f, 0.0f, 1.0f, 1.0f); + + CurveMapping *cumapping = (CurveMapping *)node->storage; cumapping->preset = CURVE_PRESET_MID9; - for (c = 0; c < 3; c++) { + for (int c = 0; c < 3; c++) { CurveMap *cuma = &cumapping->cm[c]; BKE_curvemap_reset(cuma, &cumapping->clipr, cumapping->preset, CURVEMAP_SLOPE_POSITIVE); } @@ -55,7 +56,7 @@ void register_node_type_cmp_huecorrect(void) static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_HUECORRECT, "Hue Correct", NODE_CLASS_OP_COLOR, 0); - node_type_socket_templates(&ntype, cmp_node_huecorrect_in, cmp_node_huecorrect_out); + ntype.declare = blender::nodes::cmp_node_huecorrect_declare; node_type_size(&ntype, 320, 140, 500); node_type_init(&ntype, node_composit_init_huecorrect); node_type_storage(&ntype, "CurveMapping", node_free_curves, node_copy_curves); diff --git a/source/blender/nodes/composite/nodes/node_composite_idMask.c b/source/blender/nodes/composite/nodes/node_composite_idMask.cc index 84563e7560b..de011dd6274 100644 --- a/source/blender/nodes/composite/nodes/node_composite_idMask.c +++ b/source/blender/nodes/composite/nodes/node_composite_idMask.cc @@ -21,25 +21,26 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** ID Mask ******************** */ -static bNodeSocketTemplate cmp_node_idmask_in[] = { - {SOCK_FLOAT, N_("ID value"), 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, PROP_NONE}, - {-1, ""}, -}; -static bNodeSocketTemplate cmp_node_idmask_out[] = { - {SOCK_FLOAT, N_("Alpha")}, - {-1, ""}, -}; +namespace blender::nodes { + +static void cmp_node_idmask_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Float>("ID value").default_value(1.0f).min(0.0f).max(1.0f); + b.add_output<decl::Float>("Alpha"); +} + +} // namespace blender::nodes void register_node_type_cmp_idmask(void) { static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_ID_MASK, "ID Mask", NODE_CLASS_CONVERTER, 0); - node_type_socket_templates(&ntype, cmp_node_idmask_in, cmp_node_idmask_out); + ntype.declare = blender::nodes::cmp_node_idmask_declare; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_image.c b/source/blender/nodes/composite/nodes/node_composite_image.cc index a56dfea9dbf..3cef3a7625f 100644 --- a/source/blender/nodes/composite/nodes/node_composite_image.c +++ b/source/blender/nodes/composite/nodes/node_composite_image.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" #include "BLI_linklist.h" #include "BLI_utildefines.h" @@ -84,16 +84,17 @@ static void cmp_node_image_add_pass_output(bNodeTree *ntree, LinkNodePair *available_sockets, int *prev_index) { - bNodeSocket *sock = BLI_findstring(&node->outputs, name, offsetof(bNodeSocket, name)); + bNodeSocket *sock = (bNodeSocket *)BLI_findstring( + &node->outputs, name, offsetof(bNodeSocket, name)); /* Replace if types don't match. */ if (sock && sock->type != type) { nodeRemoveSocket(ntree, node, sock); - sock = NULL; + sock = nullptr; } /* Create socket if it doesn't exist yet. */ - if (sock == NULL) { + if (sock == nullptr) { if (rres_index >= 0) { sock = node_add_socket_from_template( ntree, node, &cmp_node_rlayers_out[rres_index], SOCK_OUT); @@ -102,18 +103,19 @@ static void cmp_node_image_add_pass_output(bNodeTree *ntree, sock = nodeAddStaticSocket(ntree, node, SOCK_OUT, type, PROP_NONE, name, name); } /* extra socket info */ - NodeImageLayer *sockdata = MEM_callocN(sizeof(NodeImageLayer), "node image layer"); + NodeImageLayer *sockdata = (NodeImageLayer *)MEM_callocN(sizeof(NodeImageLayer), + "node image layer"); sock->storage = sockdata; } - NodeImageLayer *sockdata = sock->storage; + NodeImageLayer *sockdata = (NodeImageLayer *)sock->storage; if (sockdata) { BLI_strncpy(sockdata->pass_name, passname, sizeof(sockdata->pass_name)); } /* Reorder sockets according to order that passes are added. */ const int after_index = (*prev_index)++; - bNodeSocket *after_sock = BLI_findlink(&node->outputs, after_index); + bNodeSocket *after_sock = (bNodeSocket *)BLI_findlink(&node->outputs, after_index); BLI_remlink(&node->outputs, sock); BLI_insertlinkafter(&node->outputs, after_sock, sock); @@ -128,8 +130,8 @@ static void cmp_node_image_create_outputs(bNodeTree *ntree, ImBuf *ibuf; int prev_index = -1; if (ima) { - ImageUser *iuser = node->storage; - ImageUser load_iuser = {NULL}; + ImageUser *iuser = (ImageUser *)node->storage; + ImageUser load_iuser = {nullptr}; int offset = BKE_image_sequence_guess_offset(ima); /* It is possible that image user in this node is not @@ -138,21 +140,19 @@ static void cmp_node_image_create_outputs(bNodeTree *ntree, * * So we manually construct image user to be sure first * image from sequence (that one which is set as filename - * for image datablock) is used for sockets detection - */ + * for image data-block) is used for sockets detection. */ load_iuser.ok = 1; load_iuser.framenr = offset; /* make sure ima->type is correct */ - ibuf = BKE_image_acquire_ibuf(ima, &load_iuser, NULL); + ibuf = BKE_image_acquire_ibuf(ima, &load_iuser, nullptr); if (ima->rr) { - RenderLayer *rl = BLI_findlink(&ima->rr->layers, iuser->layer); + RenderLayer *rl = (RenderLayer *)BLI_findlink(&ima->rr->layers, iuser->layer); if (rl) { - RenderPass *rpass; - for (rpass = rl->passes.first; rpass; rpass = rpass->next) { - int type; + LISTBASE_FOREACH (RenderPass *, rpass, &rl->passes) { + eNodeSocketDatatype type; if (rpass->channels == 1) { type = SOCK_FLOAT; } @@ -182,7 +182,7 @@ static void cmp_node_image_create_outputs(bNodeTree *ntree, &prev_index); } } - BKE_image_release_ibuf(ima, ibuf, NULL); + BKE_image_release_ibuf(ima, ibuf, nullptr); return; } } @@ -219,14 +219,14 @@ static void cmp_node_image_create_outputs(bNodeTree *ntree, available_sockets, &prev_index); } - BKE_image_release_ibuf(ima, ibuf, NULL); + BKE_image_release_ibuf(ima, ibuf, nullptr); } } -typedef struct RLayerUpdateData { +struct RLayerUpdateData { LinkNodePair *available_sockets; int prev_index; -} RLayerUpdateData; +}; void node_cmp_rlayers_register_pass(bNodeTree *ntree, bNode *node, @@ -235,13 +235,13 @@ void node_cmp_rlayers_register_pass(bNodeTree *ntree, const char *name, eNodeSocketDatatype type) { - RLayerUpdateData *data = node->storage; + RLayerUpdateData *data = (RLayerUpdateData *)node->storage; - if (scene == NULL || view_layer == NULL || data == NULL || node->id != (ID *)scene) { + if (scene == nullptr || view_layer == nullptr || data == nullptr || node->id != (ID *)scene) { return; } - ViewLayer *node_view_layer = BLI_findlink(&scene->view_layers, node->custom1); + ViewLayer *node_view_layer = (ViewLayer *)BLI_findlink(&scene->view_layers, node->custom1); if (node_view_layer != view_layer) { return; } @@ -281,7 +281,7 @@ static void cmp_node_rlayer_create_outputs_cb(void *UNUSED(userdata), * unless we want to register that for every other temp Main we could generate??? */ ntreeCompositRegisterPass(scene->nodetree, scene, view_layer, name, type); - for (Scene *sce = G_MAIN->scenes.first; sce; sce = sce->id.next) { + for (Scene *sce = (Scene *)G_MAIN->scenes.first; sce; sce = (Scene *)sce->id.next) { if (sce->nodetree && sce != scene) { ntreeCompositRegisterPass(sce->nodetree, scene, view_layer, name, type); } @@ -297,16 +297,17 @@ static void cmp_node_rlayer_create_outputs(bNodeTree *ntree, if (scene) { RenderEngineType *engine_type = RE_engines_find(scene->r.engine); if (engine_type && engine_type->update_render_passes) { - ViewLayer *view_layer = BLI_findlink(&scene->view_layers, node->custom1); + ViewLayer *view_layer = (ViewLayer *)BLI_findlink(&scene->view_layers, node->custom1); if (view_layer) { - RLayerUpdateData *data = MEM_mallocN(sizeof(RLayerUpdateData), "render layer update data"); + RLayerUpdateData *data = (RLayerUpdateData *)MEM_mallocN(sizeof(RLayerUpdateData), + "render layer update data"); data->available_sockets = available_sockets; data->prev_index = -1; node->storage = data; RenderEngine *engine = RE_engine_create(engine_type); RE_engine_update_render_passes( - engine, scene, view_layer, cmp_node_rlayer_create_outputs_cb, NULL); + engine, scene, view_layer, cmp_node_rlayer_create_outputs_cb, nullptr); RE_engine_free(engine); if ((scene->r.mode & R_EDGE_FRS) && @@ -315,7 +316,7 @@ static void cmp_node_rlayer_create_outputs(bNodeTree *ntree, } MEM_freeN(data); - node->storage = NULL; + node->storage = nullptr; return; } @@ -348,8 +349,7 @@ static void cmp_node_rlayer_create_outputs(bNodeTree *ntree, static void cmp_node_image_verify_outputs(bNodeTree *ntree, bNode *node, bool rlayer) { bNodeSocket *sock, *sock_next; - LinkNodePair available_sockets = {NULL, NULL}; - int sock_index; + LinkNodePair available_sockets = {nullptr, nullptr}; /* XXX make callback */ if (rlayer) { @@ -369,15 +369,15 @@ static void cmp_node_image_verify_outputs(bNodeTree *ntree, bNode *node, bool rl * the first 31 passes to belong to a specific pass type. * So, we keep those 31 always allocated before the others as well, * even if they have no links attached. */ - sock_index = 0; - for (sock = node->outputs.first; sock; sock = sock_next, sock_index++) { + int sock_index = 0; + for (sock = (bNodeSocket *)node->outputs.first; sock; sock = sock_next, sock_index++) { sock_next = sock->next; if (BLI_linklist_index(available_sockets.list, sock) >= 0) { sock->flag &= ~(SOCK_UNAVAIL | SOCK_HIDDEN); } else { bNodeLink *link; - for (link = ntree->links.first; link; link = link->next) { + for (link = (bNodeLink *)ntree->links.first; link; link = link->next) { if (link->fromsock == sock) { break; } @@ -392,7 +392,7 @@ static void cmp_node_image_verify_outputs(bNodeTree *ntree, bNode *node, bool rl } } - BLI_linklist_free(available_sockets.list, NULL); + BLI_linklist_free(available_sockets.list, nullptr); } static void cmp_node_image_update(bNodeTree *ntree, bNode *node) @@ -407,7 +407,7 @@ static void cmp_node_image_update(bNodeTree *ntree, bNode *node) static void node_composit_init_image(bNodeTree *ntree, bNode *node) { - ImageUser *iuser = MEM_callocN(sizeof(ImageUser), "node image user"); + ImageUser *iuser = (ImageUser *)MEM_callocN(sizeof(ImageUser), "node image user"); node->storage = iuser; iuser->frames = 1; iuser->sfra = 1; @@ -420,10 +420,8 @@ static void node_composit_init_image(bNodeTree *ntree, bNode *node) static void node_composit_free_image(bNode *node) { - bNodeSocket *sock; - /* free extra socket info */ - for (sock = node->outputs.first; sock; sock = sock->next) { + LISTBASE_FOREACH (bNodeSocket *, sock, &node->outputs) { MEM_freeN(sock->storage); } @@ -436,9 +434,9 @@ static void node_composit_copy_image(bNodeTree *UNUSED(dest_ntree), { dest_node->storage = MEM_dupallocN(src_node->storage); - const bNodeSocket *src_output_sock = src_node->outputs.first; - bNodeSocket *dest_output_sock = dest_node->outputs.first; - while (dest_output_sock != NULL) { + const bNodeSocket *src_output_sock = (bNodeSocket *)src_node->outputs.first; + bNodeSocket *dest_output_sock = (bNodeSocket *)dest_node->outputs.first; + while (dest_output_sock != nullptr) { dest_output_sock->storage = MEM_dupallocN(src_output_sock->storage); src_output_sock = src_output_sock->next; @@ -469,7 +467,7 @@ void node_cmp_rlayers_outputs(bNodeTree *ntree, bNode *node) const char *node_cmp_rlayers_sock_to_pass(int sock_index) { if (sock_index >= NUM_LEGACY_SOCKETS) { - return NULL; + return nullptr; } const char *name = cmp_node_rlayers_out[sock_index].name; /* Exception for alpha, which is derived from Combined. */ @@ -479,14 +477,16 @@ const char *node_cmp_rlayers_sock_to_pass(int sock_index) static void node_composit_init_rlayers(const bContext *C, PointerRNA *ptr) { Scene *scene = CTX_data_scene(C); - bNode *node = ptr->data; + bNode *node = (bNode *)ptr->data; int sock_index = 0; node->id = &scene->id; id_us_plus(node->id); - for (bNodeSocket *sock = node->outputs.first; sock; sock = sock->next, sock_index++) { - NodeImageLayer *sockdata = MEM_callocN(sizeof(NodeImageLayer), "node image layer"); + for (bNodeSocket *sock = (bNodeSocket *)node->outputs.first; sock; + sock = sock->next, sock_index++) { + NodeImageLayer *sockdata = (NodeImageLayer *)MEM_callocN(sizeof(NodeImageLayer), + "node image layer"); sock->storage = sockdata; BLI_strncpy(sockdata->pass_name, @@ -510,13 +510,13 @@ static bool node_composit_poll_rlayers(bNodeType *UNUSED(ntype), * Render layers node can only be used in local scene->nodetree, * since it directly links to the scene. */ - for (scene = G.main->scenes.first; scene; scene = scene->id.next) { + for (scene = (Scene *)G.main->scenes.first; scene; scene = (Scene *)scene->id.next) { if (scene->nodetree == ntree) { break; } } - if (scene == NULL) { + if (scene == nullptr) { *r_disabled_hint = "The node tree must be the compositing node tree of any scene in the file"; return false; } @@ -525,10 +525,8 @@ static bool node_composit_poll_rlayers(bNodeType *UNUSED(ntype), static void node_composit_free_rlayers(bNode *node) { - bNodeSocket *sock; - /* free extra socket info */ - for (sock = node->outputs.first; sock; sock = sock->next) { + LISTBASE_FOREACH (bNodeSocket *, sock, &node->outputs) { if (sock->storage) { MEM_freeN(sock->storage); } @@ -540,9 +538,9 @@ static void node_composit_copy_rlayers(bNodeTree *UNUSED(dest_ntree), const bNode *src_node) { /* copy extra socket info */ - const bNodeSocket *src_output_sock = src_node->outputs.first; - bNodeSocket *dest_output_sock = dest_node->outputs.first; - while (dest_output_sock != NULL) { + const bNodeSocket *src_output_sock = (bNodeSocket *)src_node->outputs.first; + bNodeSocket *dest_output_sock = (bNodeSocket *)dest_node->outputs.first; + while (dest_output_sock != nullptr) { dest_output_sock->storage = MEM_dupallocN(src_output_sock->storage); src_output_sock = src_output_sock->next; @@ -562,10 +560,10 @@ void register_node_type_cmp_rlayers(void) static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_R_LAYERS, "Render Layers", NODE_CLASS_INPUT, NODE_PREVIEW); - node_type_socket_templates(&ntype, NULL, cmp_node_rlayers_out); + node_type_socket_templates(&ntype, nullptr, cmp_node_rlayers_out); ntype.initfunc_api = node_composit_init_rlayers; ntype.poll = node_composit_poll_rlayers; - node_type_storage(&ntype, NULL, node_composit_free_rlayers, node_composit_copy_rlayers); + node_type_storage(&ntype, nullptr, node_composit_free_rlayers, node_composit_copy_rlayers); node_type_update(&ntype, cmp_node_rlayers_update); node_type_init(&ntype, node_cmp_rlayers_outputs); node_type_size_preset(&ntype, NODE_SIZE_LARGE); diff --git a/source/blender/nodes/composite/nodes/node_composite_inpaint.c b/source/blender/nodes/composite/nodes/node_composite_inpaint.cc index 6c02bca9b39..d0ff97a2ca0 100644 --- a/source/blender/nodes/composite/nodes/node_composite_inpaint.c +++ b/source/blender/nodes/composite/nodes/node_composite_inpaint.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** Inpaint/ ******************** */ diff --git a/source/blender/nodes/composite/nodes/node_composite_invert.c b/source/blender/nodes/composite/nodes/node_composite_invert.cc index d229261f208..57b7ed36ccd 100644 --- a/source/blender/nodes/composite/nodes/node_composite_invert.c +++ b/source/blender/nodes/composite/nodes/node_composite_invert.cc @@ -21,15 +21,20 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** INVERT ******************** */ -static bNodeSocketTemplate cmp_node_invert_in[] = { - {SOCK_FLOAT, N_("Fac"), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_FACTOR}, - {SOCK_RGBA, N_("Color"), 1.0f, 1.0f, 1.0f, 1.0f}, - {-1, ""}}; -static bNodeSocketTemplate cmp_node_invert_out[] = {{SOCK_RGBA, N_("Color")}, {-1, ""}}; +namespace blender::nodes { + +static void cmp_node_invert_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Float>("Fac").default_value(1.0f).min(0.0f).max(1.0f).subtype(PROP_FACTOR); + b.add_input<decl::Color>("Color").default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_output<decl::Color>("Color"); +} + +} // namespace blender::nodes static void node_composit_init_invert(bNodeTree *UNUSED(ntree), bNode *node) { @@ -42,7 +47,7 @@ void register_node_type_cmp_invert(void) static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_INVERT, "Invert", NODE_CLASS_OP_COLOR, 0); - node_type_socket_templates(&ntype, cmp_node_invert_in, cmp_node_invert_out); + ntype.declare = blender::nodes::cmp_node_invert_declare; node_type_init(&ntype, node_composit_init_invert); nodeRegisterType(&ntype); diff --git a/source/blender/nodes/composite/nodes/node_composite_keying.c b/source/blender/nodes/composite/nodes/node_composite_keying.cc index 73e86a21ebe..d5547161069 100644 --- a/source/blender/nodes/composite/nodes/node_composite_keying.c +++ b/source/blender/nodes/composite/nodes/node_composite_keying.cc @@ -27,7 +27,7 @@ #include "BLI_math_base.h" -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** Translate ******************** */ @@ -48,9 +48,7 @@ static bNodeSocketTemplate cmp_node_keying_out[] = { static void node_composit_init_keying(bNodeTree *UNUSED(ntree), bNode *node) { - NodeKeyingData *data; - - data = MEM_callocN(sizeof(NodeKeyingData), "node keying data"); + NodeKeyingData *data = (NodeKeyingData *)MEM_callocN(sizeof(NodeKeyingData), "node keying data"); data->screen_balance = 0.5f; data->despill_balance = 0.5f; @@ -59,7 +57,6 @@ static void node_composit_init_keying(bNodeTree *UNUSED(ntree), bNode *node) data->edge_kernel_tolerance = 0.1f; data->clip_black = 0.0f; data->clip_white = 1.0f; - node->storage = data; } diff --git a/source/blender/nodes/composite/nodes/node_composite_keyingscreen.c b/source/blender/nodes/composite/nodes/node_composite_keyingscreen.cc index e5e97cd72e4..c976a92b92d 100644 --- a/source/blender/nodes/composite/nodes/node_composite_keyingscreen.c +++ b/source/blender/nodes/composite/nodes/node_composite_keyingscreen.cc @@ -26,7 +26,7 @@ #include "BLI_math_base.h" #include "BLI_math_color.h" -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** Translate ******************** */ @@ -37,10 +37,8 @@ static bNodeSocketTemplate cmp_node_keyingscreen_out[] = { static void node_composit_init_keyingscreen(bNodeTree *UNUSED(ntree), bNode *node) { - NodeKeyingScreenData *data; - - data = MEM_callocN(sizeof(NodeKeyingScreenData), "node keyingscreen data"); - + NodeKeyingScreenData *data = (NodeKeyingScreenData *)MEM_callocN(sizeof(NodeKeyingScreenData), + "node keyingscreen data"); node->storage = data; } @@ -49,7 +47,7 @@ void register_node_type_cmp_keyingscreen(void) static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_KEYINGSCREEN, "Keying Screen", NODE_CLASS_MATTE, 0); - node_type_socket_templates(&ntype, NULL, cmp_node_keyingscreen_out); + node_type_socket_templates(&ntype, nullptr, cmp_node_keyingscreen_out); node_type_init(&ntype, node_composit_init_keyingscreen); node_type_storage( &ntype, "NodeKeyingScreenData", node_free_standard_storage, node_copy_standard_storage); diff --git a/source/blender/nodes/composite/nodes/node_composite_lensdist.c b/source/blender/nodes/composite/nodes/node_composite_lensdist.cc index ce8c8c00e24..2a8dc035792 100644 --- a/source/blender/nodes/composite/nodes/node_composite_lensdist.c +++ b/source/blender/nodes/composite/nodes/node_composite_lensdist.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" static bNodeSocketTemplate cmp_node_lensdist_in[] = { {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, @@ -36,7 +36,7 @@ static bNodeSocketTemplate cmp_node_lensdist_out[] = { static void node_composit_init_lensdist(bNodeTree *UNUSED(ntree), bNode *node) { - NodeLensDist *nld = MEM_callocN(sizeof(NodeLensDist), "node lensdist data"); + NodeLensDist *nld = (NodeLensDist *)MEM_callocN(sizeof(NodeLensDist), "node lensdist data"); nld->jit = nld->proj = nld->fit = 0; node->storage = nld; } diff --git a/source/blender/nodes/composite/nodes/node_composite_levels.c b/source/blender/nodes/composite/nodes/node_composite_levels.cc index 7c70ccf412a..aaab8dcc874 100644 --- a/source/blender/nodes/composite/nodes/node_composite_levels.c +++ b/source/blender/nodes/composite/nodes/node_composite_levels.cc @@ -21,19 +21,20 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** LEVELS ******************** */ -static bNodeSocketTemplate cmp_node_view_levels_in[] = { - {SOCK_RGBA, N_("Image"), 0.0f, 0.0f, 0.0f, 1.0f}, - {-1, ""}, -}; -static bNodeSocketTemplate cmp_node_view_levels_out[] = { - {SOCK_FLOAT, N_("Mean")}, - {SOCK_FLOAT, N_("Std Dev")}, - {-1, ""}, -}; +namespace blender::nodes { + +static void cmp_node_levels_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Color>("Image").default_value({0.0f, 0.0f, 0.0f, 1.0f}); + b.add_output<decl::Float>("Mean"); + b.add_output<decl::Float>("Std Dev"); +} + +} // namespace blender::nodes static void node_composit_init_view_levels(bNodeTree *UNUSED(ntree), bNode *node) { @@ -45,7 +46,7 @@ void register_node_type_cmp_view_levels(void) static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_VIEW_LEVELS, "Levels", NODE_CLASS_OUTPUT, NODE_PREVIEW); - node_type_socket_templates(&ntype, cmp_node_view_levels_in, cmp_node_view_levels_out); + ntype.declare = blender::nodes::cmp_node_levels_declare; node_type_init(&ntype, node_composit_init_view_levels); nodeRegisterType(&ntype); diff --git a/source/blender/nodes/composite/nodes/node_composite_lummaMatte.c b/source/blender/nodes/composite/nodes/node_composite_lummaMatte.cc index cb0f59c2f4a..600406d22b9 100644 --- a/source/blender/nodes/composite/nodes/node_composite_lummaMatte.c +++ b/source/blender/nodes/composite/nodes/node_composite_lummaMatte.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* ******************* Luma Matte Node ********************************* */ static bNodeSocketTemplate cmp_node_luma_matte_in[] = { @@ -37,7 +37,7 @@ static bNodeSocketTemplate cmp_node_luma_matte_out[] = { static void node_composit_init_luma_matte(bNodeTree *UNUSED(ntree), bNode *node) { - NodeChroma *c = MEM_callocN(sizeof(NodeChroma), "node chroma"); + NodeChroma *c = (NodeChroma *)MEM_callocN(sizeof(NodeChroma), "node chroma"); node->storage = c; c->t1 = 1.0f; c->t2 = 0.0f; diff --git a/source/blender/nodes/composite/nodes/node_composite_mapRange.c b/source/blender/nodes/composite/nodes/node_composite_mapRange.cc index cd95e73ba5c..808ad538e55 100644 --- a/source/blender/nodes/composite/nodes/node_composite_mapRange.c +++ b/source/blender/nodes/composite/nodes/node_composite_mapRange.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** MAP VALUE ******************** */ static bNodeSocketTemplate cmp_node_map_range_in[] = { diff --git a/source/blender/nodes/composite/nodes/node_composite_mapUV.c b/source/blender/nodes/composite/nodes/node_composite_mapUV.cc index e88a303e449..99032bd042e 100644 --- a/source/blender/nodes/composite/nodes/node_composite_mapUV.c +++ b/source/blender/nodes/composite/nodes/node_composite_mapUV.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** Map UV ******************** */ diff --git a/source/blender/nodes/composite/nodes/node_composite_mapValue.c b/source/blender/nodes/composite/nodes/node_composite_mapValue.cc index c93807c3801..25c00c2ba13 100644 --- a/source/blender/nodes/composite/nodes/node_composite_mapValue.c +++ b/source/blender/nodes/composite/nodes/node_composite_mapValue.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** MAP VALUE ******************** */ static bNodeSocketTemplate cmp_node_map_value_in[] = { diff --git a/source/blender/nodes/composite/nodes/node_composite_mask.c b/source/blender/nodes/composite/nodes/node_composite_mask.cc index e6a5df6c24b..8b415bb8b63 100644 --- a/source/blender/nodes/composite/nodes/node_composite_mask.c +++ b/source/blender/nodes/composite/nodes/node_composite_mask.cc @@ -23,15 +23,22 @@ #include "DNA_mask_types.h" -#include "node_composite_util.h" +#include "node_composite_util.hh" -/* **************** Translate ******************** */ +/* **************** Mask ******************** */ -static bNodeSocketTemplate cmp_node_mask_out[] = {{SOCK_FLOAT, "Mask"}, {-1, ""}}; +namespace blender::nodes { + +static void cmp_node_mask_declare(NodeDeclarationBuilder &b) +{ + b.add_output<decl::Float>("Mask"); +} + +} // namespace blender::nodes static void node_composit_init_mask(bNodeTree *UNUSED(ntree), bNode *node) { - NodeMask *data = MEM_callocN(sizeof(NodeMask), "NodeMask"); + NodeMask *data = (NodeMask *)MEM_callocN(sizeof(NodeMask), "NodeMask"); data->size_x = data->size_y = 256; node->storage = data; @@ -41,7 +48,7 @@ static void node_composit_init_mask(bNodeTree *UNUSED(ntree), bNode *node) static void node_mask_label(bNodeTree *UNUSED(ntree), bNode *node, char *label, int maxlen) { - if (node->id != NULL) { + if (node->id != nullptr) { BLI_strncpy(label, node->id->name + 2, maxlen); } else { @@ -54,7 +61,7 @@ void register_node_type_cmp_mask(void) static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_MASK, "Mask", NODE_CLASS_INPUT, 0); - node_type_socket_templates(&ntype, NULL, cmp_node_mask_out); + ntype.declare = blender::nodes::cmp_node_mask_declare; node_type_init(&ntype, node_composit_init_mask); node_type_label(&ntype, node_mask_label); diff --git a/source/blender/nodes/composite/nodes/node_composite_math.c b/source/blender/nodes/composite/nodes/node_composite_math.cc index 2191c6bcdc3..a9859425e28 100644 --- a/source/blender/nodes/composite/nodes/node_composite_math.c +++ b/source/blender/nodes/composite/nodes/node_composite_math.cc @@ -21,23 +21,28 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** SCALAR MATH ******************** */ -static bNodeSocketTemplate cmp_node_math_in[] = { - {SOCK_FLOAT, N_("Value"), 0.5f, 0.5f, 0.5f, 1.0f, -10000.0f, 10000.0f, PROP_NONE}, - {SOCK_FLOAT, N_("Value"), 0.5f, 0.5f, 0.5f, 1.0f, -10000.0f, 10000.0f, PROP_NONE}, - {SOCK_FLOAT, N_("Value"), 0.0f, 0.5f, 0.5f, 1.0f, -10000.0f, 10000.0f, PROP_NONE}, - {-1, ""}}; -static bNodeSocketTemplate cmp_node_math_out[] = {{SOCK_FLOAT, N_("Value")}, {-1, ""}}; +namespace blender::nodes { + +static void cmp_node_math_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Float>("Value").default_value(0.5f).min(-10000.0f).max(10000.0f); + b.add_input<decl::Float>("Value", "Value_001").default_value(0.5f).min(-10000.0f).max(10000.0f); + b.add_input<decl::Float>("Value", "Value_002").default_value(0.5f).min(-10000.0f).max(10000.0f); + b.add_output<decl::Float>("Value"); +} + +} // namespace blender::nodes void register_node_type_cmp_math(void) { static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_MATH, "Math", NODE_CLASS_CONVERTER, 0); - node_type_socket_templates(&ntype, cmp_node_math_in, cmp_node_math_out); + ntype.declare = blender::nodes::cmp_node_math_declare; node_type_label(&ntype, node_math_label); node_type_update(&ntype, node_math_update); diff --git a/source/blender/nodes/composite/nodes/node_composite_mixrgb.c b/source/blender/nodes/composite/nodes/node_composite_mixrgb.cc index 9d3751c7da3..4f2a9c2717c 100644 --- a/source/blender/nodes/composite/nodes/node_composite_mixrgb.c +++ b/source/blender/nodes/composite/nodes/node_composite_mixrgb.cc @@ -21,19 +21,21 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** MIX RGB ******************** */ -static bNodeSocketTemplate cmp_node_mix_rgb_in[] = { - {SOCK_FLOAT, N_("Fac"), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_FACTOR}, - {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, - {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, - {-1, ""}, -}; -static bNodeSocketTemplate cmp_node_mix_rgb_out[] = { - {SOCK_RGBA, N_("Image")}, - {-1, ""}, -}; + +namespace blender::nodes { + +static void cmp_node_mixrgb_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Float>("Fac").default_value(1.0f).min(0.0f).max(1.0f).subtype(PROP_FACTOR); + b.add_input<decl::Color>("Image").default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_input<decl::Color>("Image", "Image_001").default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_output<decl::Color>("Image"); +} + +} // namespace blender::nodes /* custom1 = mix type */ void register_node_type_cmp_mix_rgb(void) @@ -41,7 +43,7 @@ void register_node_type_cmp_mix_rgb(void) static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_MIX_RGB, "Mix", NODE_CLASS_OP_COLOR, NODE_PREVIEW); - node_type_socket_templates(&ntype, cmp_node_mix_rgb_in, cmp_node_mix_rgb_out); + ntype.declare = blender::nodes::cmp_node_mixrgb_declare; node_type_label(&ntype, node_blend_label); nodeRegisterType(&ntype); diff --git a/source/blender/nodes/composite/nodes/node_composite_movieclip.c b/source/blender/nodes/composite/nodes/node_composite_movieclip.cc index 4f5aef05425..ae91212f811 100644 --- a/source/blender/nodes/composite/nodes/node_composite_movieclip.c +++ b/source/blender/nodes/composite/nodes/node_composite_movieclip.cc @@ -21,26 +21,31 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" #include "BKE_context.h" #include "BKE_lib_id.h" -static bNodeSocketTemplate cmp_node_movieclip_out[] = { - {SOCK_RGBA, N_("Image")}, - {SOCK_FLOAT, N_("Alpha")}, - {SOCK_FLOAT, N_("Offset X")}, - {SOCK_FLOAT, N_("Offset Y")}, - {SOCK_FLOAT, N_("Scale")}, - {SOCK_FLOAT, N_("Angle")}, - {-1, ""}, -}; +namespace blender::nodes { + +static void cmp_node_movieclip_declare(NodeDeclarationBuilder &b) +{ + b.add_output<decl::Color>("Image"); + b.add_output<decl::Float>("Alpha"); + b.add_output<decl::Float>("Offset X"); + b.add_output<decl::Float>("Offset Y"); + b.add_output<decl::Float>("Scale"); + b.add_output<decl::Float>("Angle"); +} + +} // namespace blender::nodes static void init(const bContext *C, PointerRNA *ptr) { - bNode *node = ptr->data; + bNode *node = (bNode *)ptr->data; Scene *scene = CTX_data_scene(C); - MovieClipUser *user = MEM_callocN(sizeof(MovieClipUser), "node movie clip user"); + MovieClipUser *user = (MovieClipUser *)MEM_callocN(sizeof(MovieClipUser), + "node movie clip user"); node->id = (ID *)scene->clip; id_us_plus(node->id); @@ -53,7 +58,7 @@ void register_node_type_cmp_movieclip(void) static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_MOVIECLIP, "Movie Clip", NODE_CLASS_INPUT, NODE_PREVIEW); - node_type_socket_templates(&ntype, NULL, cmp_node_movieclip_out); + ntype.declare = blender::nodes::cmp_node_movieclip_declare; ntype.initfunc_api = init; node_type_storage( &ntype, "MovieClipUser", node_free_standard_storage, node_copy_standard_storage); diff --git a/source/blender/nodes/composite/nodes/node_composite_moviedistortion.c b/source/blender/nodes/composite/nodes/node_composite_moviedistortion.cc index 7e30d004b45..2bac30cc152 100644 --- a/source/blender/nodes/composite/nodes/node_composite_moviedistortion.c +++ b/source/blender/nodes/composite/nodes/node_composite_moviedistortion.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" #include "BKE_context.h" #include "BKE_lib_id.h" @@ -50,7 +50,7 @@ static void label(bNodeTree *UNUSED(ntree), bNode *node, char *label, int maxlen static void init(const bContext *C, PointerRNA *ptr) { - bNode *node = ptr->data; + bNode *node = (bNode *)ptr->data; Scene *scene = CTX_data_scene(C); node->id = (ID *)scene->clip; @@ -60,16 +60,16 @@ static void init(const bContext *C, PointerRNA *ptr) static void storage_free(bNode *node) { if (node->storage) { - BKE_tracking_distortion_free(node->storage); + BKE_tracking_distortion_free((MovieDistortion *)node->storage); } - node->storage = NULL; + node->storage = nullptr; } static void storage_copy(bNodeTree *UNUSED(dest_ntree), bNode *dest_node, const bNode *src_node) { if (src_node->storage) { - dest_node->storage = BKE_tracking_distortion_copy(src_node->storage); + dest_node->storage = BKE_tracking_distortion_copy((MovieDistortion *)src_node->storage); } } @@ -82,7 +82,7 @@ void register_node_type_cmp_moviedistortion(void) node_type_label(&ntype, label); ntype.initfunc_api = init; - node_type_storage(&ntype, NULL, storage_free, storage_copy); + node_type_storage(&ntype, nullptr, storage_free, storage_copy); nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_normal.c b/source/blender/nodes/composite/nodes/node_composite_normal.cc index 91300e66339..7531025daa5 100644 --- a/source/blender/nodes/composite/nodes/node_composite_normal.c +++ b/source/blender/nodes/composite/nodes/node_composite_normal.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** NORMAL ******************** */ static bNodeSocketTemplate cmp_node_normal_in[] = { diff --git a/source/blender/nodes/composite/nodes/node_composite_normalize.c b/source/blender/nodes/composite/nodes/node_composite_normalize.cc index 26f2abc745f..7cc54e4eed6 100644 --- a/source/blender/nodes/composite/nodes/node_composite_normalize.c +++ b/source/blender/nodes/composite/nodes/node_composite_normalize.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** NORMALIZE single channel, useful for Z buffer ******************** */ static bNodeSocketTemplate cmp_node_normalize_in[] = { diff --git a/source/blender/nodes/composite/nodes/node_composite_outputFile.c b/source/blender/nodes/composite/nodes/node_composite_outputFile.cc index c10edd8d5ad..a372d2f7419 100644 --- a/source/blender/nodes/composite/nodes/node_composite_outputFile.c +++ b/source/blender/nodes/composite/nodes/node_composite_outputFile.cc @@ -23,13 +23,13 @@ #include "BLI_string_utils.h" #include "BLI_utildefines.h" -#include <string.h> +#include <cstring> #include "BKE_context.h" #include "RNA_access.h" -#include "node_composite_util.h" +#include "node_composite_util.hh" #include "intern/openexr/openexr_multi.h" @@ -38,14 +38,15 @@ /* find unique path */ static bool unique_path_unique_check(void *arg, const char *name) { - struct { + struct Args { ListBase *lb; bNodeSocket *sock; - } *data = arg; - bNodeSocket *sock; - for (sock = data->lb->first; sock; sock = sock->next) { + }; + Args *data = (Args *)arg; + + LISTBASE_FOREACH (bNodeSocket *, sock, data->lb) { if (sock != data->sock) { - NodeImageMultiFileSocket *sockdata = sock->storage; + NodeImageMultiFileSocket *sockdata = (NodeImageMultiFileSocket *)sock->storage; if (STREQ(sockdata->path, name)) { return true; } @@ -67,11 +68,11 @@ void ntreeCompositOutputFileUniquePath(ListBase *list, data.sock = sock; /* See if we are given an empty string */ - if (ELEM(NULL, sock, defname)) { + if (ELEM(nullptr, sock, defname)) { return; } - sockdata = sock->storage; + sockdata = (NodeImageMultiFileSocket *)sock->storage; BLI_uniquename_cb( unique_path_unique_check, &data, defname, delim, sockdata->path, sizeof(sockdata->path)); } @@ -79,14 +80,15 @@ void ntreeCompositOutputFileUniquePath(ListBase *list, /* find unique EXR layer */ static bool unique_layer_unique_check(void *arg, const char *name) { - struct { + struct Args { ListBase *lb; bNodeSocket *sock; - } *data = arg; - bNodeSocket *sock; - for (sock = data->lb->first; sock; sock = sock->next) { + }; + Args *data = (Args *)arg; + + LISTBASE_FOREACH (bNodeSocket *, sock, data->lb) { if (sock != data->sock) { - NodeImageMultiFileSocket *sockdata = sock->storage; + NodeImageMultiFileSocket *sockdata = (NodeImageMultiFileSocket *)sock->storage; if (STREQ(sockdata->layer, name)) { return true; } @@ -99,7 +101,6 @@ void ntreeCompositOutputFileUniqueLayer(ListBase *list, const char defname[], char delim) { - NodeImageMultiFileSocket *sockdata; struct { ListBase *lb; bNodeSocket *sock; @@ -108,11 +109,11 @@ void ntreeCompositOutputFileUniqueLayer(ListBase *list, data.sock = sock; /* See if we are given an empty string */ - if (ELEM(NULL, sock, defname)) { + if (ELEM(nullptr, sock, defname)) { return; } - sockdata = sock->storage; + NodeImageMultiFileSocket *sockdata = (NodeImageMultiFileSocket *)sock->storage; BLI_uniquename_cb( unique_layer_unique_check, &data, defname, delim, sockdata->layer, sizeof(sockdata->layer)); } @@ -122,12 +123,13 @@ bNodeSocket *ntreeCompositOutputFileAddSocket(bNodeTree *ntree, const char *name, ImageFormatData *im_format) { - NodeImageMultiFile *nimf = node->storage; - bNodeSocket *sock = nodeAddStaticSocket(ntree, node, SOCK_IN, SOCK_RGBA, PROP_NONE, NULL, name); + NodeImageMultiFile *nimf = (NodeImageMultiFile *)node->storage; + bNodeSocket *sock = nodeAddStaticSocket( + ntree, node, SOCK_IN, SOCK_RGBA, PROP_NONE, nullptr, name); /* create format data for the input socket */ - NodeImageMultiFileSocket *sockdata = MEM_callocN(sizeof(NodeImageMultiFileSocket), - "socket image format"); + NodeImageMultiFileSocket *sockdata = (NodeImageMultiFileSocket *)MEM_callocN( + sizeof(NodeImageMultiFileSocket), "socket image format"); sock->storage = sockdata; BLI_strncpy_utf8(sockdata->path, name, sizeof(sockdata->path)); @@ -155,8 +157,8 @@ bNodeSocket *ntreeCompositOutputFileAddSocket(bNodeTree *ntree, int ntreeCompositOutputFileRemoveActiveSocket(bNodeTree *ntree, bNode *node) { - NodeImageMultiFile *nimf = node->storage; - bNodeSocket *sock = BLI_findlink(&node->inputs, nimf->active_input); + NodeImageMultiFile *nimf = (NodeImageMultiFile *)node->storage; + bNodeSocket *sock = (bNodeSocket *)BLI_findlink(&node->inputs, nimf->active_input); int totinputs = BLI_listbase_count(&node->inputs); if (!sock) { @@ -176,14 +178,14 @@ int ntreeCompositOutputFileRemoveActiveSocket(bNodeTree *ntree, bNode *node) void ntreeCompositOutputFileSetPath(bNode *node, bNodeSocket *sock, const char *name) { - NodeImageMultiFileSocket *sockdata = sock->storage; + NodeImageMultiFileSocket *sockdata = (NodeImageMultiFileSocket *)sock->storage; BLI_strncpy_utf8(sockdata->path, name, sizeof(sockdata->path)); ntreeCompositOutputFileUniquePath(&node->inputs, sock, name, '_'); } void ntreeCompositOutputFileSetLayer(bNode *node, bNodeSocket *sock, const char *name) { - NodeImageMultiFileSocket *sockdata = sock->storage; + NodeImageMultiFileSocket *sockdata = (NodeImageMultiFileSocket *)sock->storage; BLI_strncpy_utf8(sockdata->layer, name, sizeof(sockdata->layer)); ntreeCompositOutputFileUniqueLayer(&node->inputs, sock, name, '_'); } @@ -193,9 +195,10 @@ static void init_output_file(const bContext *C, PointerRNA *ptr) { Scene *scene = CTX_data_scene(C); bNodeTree *ntree = (bNodeTree *)ptr->owner_id; - bNode *node = ptr->data; - NodeImageMultiFile *nimf = MEM_callocN(sizeof(NodeImageMultiFile), "node image multi file"); - ImageFormatData *format = NULL; + bNode *node = (bNode *)ptr->data; + NodeImageMultiFile *nimf = (NodeImageMultiFile *)MEM_callocN(sizeof(NodeImageMultiFile), + "node image multi file"); + ImageFormatData *format = nullptr; node->storage = nimf; if (scene) { @@ -219,10 +222,8 @@ static void init_output_file(const bContext *C, PointerRNA *ptr) static void free_output_file(bNode *node) { - bNodeSocket *sock; - /* free storage data in sockets */ - for (sock = node->inputs.first; sock; sock = sock->next) { + LISTBASE_FOREACH (bNodeSocket *, sock, &node->inputs) { MEM_freeN(sock->storage); } @@ -238,37 +239,35 @@ static void copy_output_file(bNodeTree *UNUSED(dest_ntree), dest_node->storage = MEM_dupallocN(src_node->storage); /* duplicate storage data in sockets */ - for (src_sock = src_node->inputs.first, dest_sock = dest_node->inputs.first; + for (src_sock = (bNodeSocket *)src_node->inputs.first, + dest_sock = (bNodeSocket *)dest_node->inputs.first; src_sock && dest_sock; - src_sock = src_sock->next, dest_sock = dest_sock->next) { + src_sock = src_sock->next, dest_sock = (bNodeSocket *)dest_sock->next) { dest_sock->storage = MEM_dupallocN(src_sock->storage); } } static void update_output_file(bNodeTree *ntree, bNode *node) { - bNodeSocket *sock, *sock_next; PointerRNA ptr; /* XXX fix for T36706: remove invalid sockets added with bpy API. * This is not ideal, but prevents crashes from missing storage. * FileOutput node needs a redesign to support this properly. */ - for (sock = node->inputs.first; sock; sock = sock_next) { - sock_next = sock->next; - if (sock->storage == NULL) { + LISTBASE_FOREACH (bNodeSocket *, sock, &node->inputs) { + if (sock->storage == nullptr) { nodeRemoveSocket(ntree, node, sock); } } - for (sock = node->outputs.first; sock; sock = sock_next) { - sock_next = sock->next; + LISTBASE_FOREACH (bNodeSocket *, sock, &node->outputs) { nodeRemoveSocket(ntree, node, sock); } cmp_node_update_default(ntree, node); /* automatically update the socket type based on linked input */ - for (sock = node->inputs.first; sock; sock = sock->next) { + LISTBASE_FOREACH (bNodeSocket *, sock, &node->inputs) { if (sock->link) { RNA_pointer_create((ID *)ntree, &RNA_NodeSocket, sock, &ptr); RNA_enum_set(&ptr, "type", sock->link->fromsock->type); @@ -281,7 +280,7 @@ void register_node_type_cmp_output_file(void) static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_OUTPUT_FILE, "File Output", NODE_CLASS_OUTPUT, NODE_PREVIEW); - node_type_socket_templates(&ntype, NULL, NULL); + node_type_socket_templates(&ntype, nullptr, nullptr); ntype.initfunc_api = init_output_file; node_type_storage(&ntype, "NodeImageMultiFile", free_output_file, copy_output_file); node_type_update(&ntype, update_output_file); diff --git a/source/blender/nodes/composite/nodes/node_composite_pixelate.c b/source/blender/nodes/composite/nodes/node_composite_pixelate.cc index 6e8a28df76f..19975c21a0b 100644 --- a/source/blender/nodes/composite/nodes/node_composite_pixelate.c +++ b/source/blender/nodes/composite/nodes/node_composite_pixelate.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** Pixelate ******************** */ diff --git a/source/blender/nodes/composite/nodes/node_composite_planetrackdeform.c b/source/blender/nodes/composite/nodes/node_composite_planetrackdeform.cc index ab5db41e5b5..e122b710b7b 100644 --- a/source/blender/nodes/composite/nodes/node_composite_planetrackdeform.c +++ b/source/blender/nodes/composite/nodes/node_composite_planetrackdeform.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" static bNodeSocketTemplate cmp_node_planetrackdeform_in[] = { {SOCK_RGBA, N_("Image"), 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, PROP_NONE}, {-1, ""}}; @@ -34,8 +34,8 @@ static bNodeSocketTemplate cmp_node_planetrackdeform_out[] = { static void init(bNodeTree *UNUSED(ntree), bNode *node) { - NodePlaneTrackDeformData *data = MEM_callocN(sizeof(NodePlaneTrackDeformData), - "node plane track deform data"); + NodePlaneTrackDeformData *data = (NodePlaneTrackDeformData *)MEM_callocN( + sizeof(NodePlaneTrackDeformData), "node plane track deform data"); data->motion_blur_samples = 16; data->motion_blur_shutter = 0.5f; node->storage = data; diff --git a/source/blender/nodes/composite/nodes/node_composite_posterize.c b/source/blender/nodes/composite/nodes/node_composite_posterize.cc index 5093e581cdc..45a98e68b4b 100644 --- a/source/blender/nodes/composite/nodes/node_composite_posterize.c +++ b/source/blender/nodes/composite/nodes/node_composite_posterize.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** Posterize ******************** */ diff --git a/source/blender/nodes/composite/nodes/node_composite_premulkey.c b/source/blender/nodes/composite/nodes/node_composite_premulkey.cc index be76bbf01cf..e557854c611 100644 --- a/source/blender/nodes/composite/nodes/node_composite_premulkey.c +++ b/source/blender/nodes/composite/nodes/node_composite_premulkey.cc @@ -21,25 +21,26 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** Premul and Key Alpha Convert ******************** */ -static bNodeSocketTemplate cmp_node_premulkey_in[] = { - {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, - {-1, ""}, -}; -static bNodeSocketTemplate cmp_node_premulkey_out[] = { - {SOCK_RGBA, N_("Image")}, - {-1, ""}, -}; +namespace blender::nodes { + +static void cmp_node_premulkey_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Color>("Image").default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_output<decl::Color>("Image"); +} + +} // namespace blender::nodes void register_node_type_cmp_premulkey(void) { static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_PREMULKEY, "Alpha Convert", NODE_CLASS_CONVERTER, 0); - node_type_socket_templates(&ntype, cmp_node_premulkey_in, cmp_node_premulkey_out); + ntype.declare = blender::nodes::cmp_node_premulkey_declare; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_rgb.c b/source/blender/nodes/composite/nodes/node_composite_rgb.cc index dae63f7a702..332e56e26b1 100644 --- a/source/blender/nodes/composite/nodes/node_composite_rgb.c +++ b/source/blender/nodes/composite/nodes/node_composite_rgb.cc @@ -21,20 +21,25 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** RGB ******************** */ -static bNodeSocketTemplate cmp_node_rgb_out[] = { - {SOCK_RGBA, N_("RGBA"), 0.5f, 0.5f, 0.5f, 1.0f}, - {-1, ""}, -}; + +namespace blender::nodes { + +static void cmp_node_rgb_declare(NodeDeclarationBuilder &b) +{ + b.add_output<decl::Color>("RGBA").default_value({0.5f, 0.5f, 0.5f, 1.0f}); +} + +} // namespace blender::nodes void register_node_type_cmp_rgb(void) { static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_RGB, "RGB", NODE_CLASS_INPUT, 0); - node_type_socket_templates(&ntype, NULL, cmp_node_rgb_out); + ntype.declare = blender::nodes::cmp_node_rgb_declare; node_type_size_preset(&ntype, NODE_SIZE_SMALL); nodeRegisterType(&ntype); diff --git a/source/blender/nodes/composite/nodes/node_composite_rotate.c b/source/blender/nodes/composite/nodes/node_composite_rotate.cc index 7dd39d5eaa1..d28b35ec9fb 100644 --- a/source/blender/nodes/composite/nodes/node_composite_rotate.c +++ b/source/blender/nodes/composite/nodes/node_composite_rotate.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** Rotate ******************** */ diff --git a/source/blender/nodes/composite/nodes/node_composite_scale.c b/source/blender/nodes/composite/nodes/node_composite_scale.cc index 963832de03a..3972fc0d949 100644 --- a/source/blender/nodes/composite/nodes/node_composite_scale.c +++ b/source/blender/nodes/composite/nodes/node_composite_scale.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** Scale ******************** */ @@ -38,7 +38,7 @@ static void node_composite_update_scale(bNodeTree *UNUSED(ntree), bNode *node) bool use_xy_scale = ELEM(node->custom1, CMP_SCALE_RELATIVE, CMP_SCALE_ABSOLUTE); /* Only show X/Y scale factor inputs for modes using them! */ - for (sock = node->inputs.first; sock; sock = sock->next) { + for (sock = (bNodeSocket *)node->inputs.first; sock; sock = sock->next) { if (STR_ELEM(sock->name, "X", "Y")) { if (use_xy_scale) { sock->flag &= ~SOCK_UNAVAIL; diff --git a/source/blender/nodes/composite/nodes/node_composite_sepcombHSVA.c b/source/blender/nodes/composite/nodes/node_composite_sepcombHSVA.cc index 001b197e23a..aa719a99b36 100644 --- a/source/blender/nodes/composite/nodes/node_composite_sepcombHSVA.c +++ b/source/blender/nodes/composite/nodes/node_composite_sepcombHSVA.cc @@ -21,50 +21,53 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** SEPARATE HSVA ******************** */ -static bNodeSocketTemplate cmp_node_sephsva_in[] = { - {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, - {-1, ""}, -}; -static bNodeSocketTemplate cmp_node_sephsva_out[] = { - {SOCK_FLOAT, N_("H")}, - {SOCK_FLOAT, N_("S")}, - {SOCK_FLOAT, N_("V")}, - {SOCK_FLOAT, N_("A")}, - {-1, ""}, -}; + +namespace blender::nodes { + +static void cmp_node_sephsva_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Color>("Image").default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_output<decl::Float>("H"); + b.add_output<decl::Float>("S"); + b.add_output<decl::Float>("V"); + b.add_output<decl::Float>("A"); +} + +} // namespace blender::nodes void register_node_type_cmp_sephsva(void) { static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_SEPHSVA, "Separate HSVA", NODE_CLASS_CONVERTER, 0); - node_type_socket_templates(&ntype, cmp_node_sephsva_in, cmp_node_sephsva_out); - + ntype.declare = blender::nodes::cmp_node_sephsva_declare; nodeRegisterType(&ntype); } /* **************** COMBINE HSVA ******************** */ -static bNodeSocketTemplate cmp_node_combhsva_in[] = { - {SOCK_FLOAT, N_("H"), 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, PROP_NONE}, - {SOCK_FLOAT, N_("S"), 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, PROP_NONE}, - {SOCK_FLOAT, N_("V"), 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, PROP_NONE}, - {SOCK_FLOAT, N_("A"), 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, PROP_NONE}, - {-1, ""}, -}; -static bNodeSocketTemplate cmp_node_combhsva_out[] = { - {SOCK_RGBA, N_("Image")}, - {-1, ""}, -}; + +namespace blender::nodes { + +static void cmp_node_combhsva_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Float>("H").min(0.0f).max(1.0f); + b.add_input<decl::Float>("S").min(0.0f).max(1.0f); + b.add_input<decl::Float>("V").min(0.0f).max(1.0f); + b.add_input<decl::Float>("A").default_value(1.0f).min(0.0f).max(1.0f); + b.add_output<decl::Color>("Image"); +} + +} // namespace blender::nodes void register_node_type_cmp_combhsva(void) { static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_COMBHSVA, "Combine HSVA", NODE_CLASS_CONVERTER, 0); - node_type_socket_templates(&ntype, cmp_node_combhsva_in, cmp_node_combhsva_out); + ntype.declare = blender::nodes::cmp_node_combhsva_declare; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_sepcombRGBA.c b/source/blender/nodes/composite/nodes/node_composite_sepcombRGBA.cc index e08f27db254..b29af1359f5 100644 --- a/source/blender/nodes/composite/nodes/node_composite_sepcombRGBA.c +++ b/source/blender/nodes/composite/nodes/node_composite_sepcombRGBA.cc @@ -21,50 +21,53 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** SEPARATE RGBA ******************** */ -static bNodeSocketTemplate cmp_node_seprgba_in[] = { - {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, - {-1, ""}, -}; -static bNodeSocketTemplate cmp_node_seprgba_out[] = { - {SOCK_FLOAT, N_("R")}, - {SOCK_FLOAT, N_("G")}, - {SOCK_FLOAT, N_("B")}, - {SOCK_FLOAT, N_("A")}, - {-1, ""}, -}; +namespace blender::nodes { + +static void cmp_node_seprgba_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Color>("Image").default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_output<decl::Float>("R"); + b.add_output<decl::Float>("G"); + b.add_output<decl::Float>("B"); + b.add_output<decl::Float>("A"); +} + +} // namespace blender::nodes void register_node_type_cmp_seprgba(void) { static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_SEPRGBA, "Separate RGBA", NODE_CLASS_CONVERTER, 0); - node_type_socket_templates(&ntype, cmp_node_seprgba_in, cmp_node_seprgba_out); + ntype.declare = blender::nodes::cmp_node_seprgba_declare; nodeRegisterType(&ntype); } /* **************** COMBINE RGBA ******************** */ -static bNodeSocketTemplate cmp_node_combrgba_in[] = { - {SOCK_FLOAT, N_("R"), 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, PROP_NONE}, - {SOCK_FLOAT, N_("G"), 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, PROP_NONE}, - {SOCK_FLOAT, N_("B"), 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, PROP_NONE}, - {SOCK_FLOAT, N_("A"), 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, PROP_NONE}, - {-1, ""}, -}; -static bNodeSocketTemplate cmp_node_combrgba_out[] = { - {SOCK_RGBA, N_("Image")}, - {-1, ""}, -}; + +namespace blender::nodes { + +static void cmp_node_combrgba_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Float>("R").min(0.0f).max(1.0f); + b.add_input<decl::Float>("G").min(0.0f).max(1.0f); + b.add_input<decl::Float>("B").min(0.0f).max(1.0f); + b.add_input<decl::Float>("A").default_value(1.0f).min(0.0f).max(1.0f); + b.add_output<decl::Color>("Image"); +} + +} // namespace blender::nodes void register_node_type_cmp_combrgba(void) { static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_COMBRGBA, "Combine RGBA", NODE_CLASS_CONVERTER, 0); - node_type_socket_templates(&ntype, cmp_node_combrgba_in, cmp_node_combrgba_out); + ntype.declare = blender::nodes::cmp_node_combrgba_declare; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_sepcombYCCA.c b/source/blender/nodes/composite/nodes/node_composite_sepcombYCCA.cc index b3884296600..526d6b4eb5b 100644 --- a/source/blender/nodes/composite/nodes/node_composite_sepcombYCCA.c +++ b/source/blender/nodes/composite/nodes/node_composite_sepcombYCCA.cc @@ -21,18 +21,22 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** SEPARATE YCCA ******************** */ -static bNodeSocketTemplate cmp_node_sepycca_in[] = { - {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, {-1, ""}}; -static bNodeSocketTemplate cmp_node_sepycca_out[] = { - {SOCK_FLOAT, N_("Y")}, - {SOCK_FLOAT, N_("Cb")}, - {SOCK_FLOAT, N_("Cr")}, - {SOCK_FLOAT, N_("A")}, - {-1, ""}, -}; + +namespace blender::nodes { + +static void cmp_node_sepycca_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Color>("Image").default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_output<decl::Float>("Y"); + b.add_output<decl::Float>("Cb"); + b.add_output<decl::Float>("Cr"); + b.add_output<decl::Float>("A"); +} + +} // namespace blender::nodes static void node_composit_init_mode_sepycca(bNodeTree *UNUSED(ntree), bNode *node) { @@ -44,24 +48,26 @@ void register_node_type_cmp_sepycca(void) static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_SEPYCCA, "Separate YCbCrA", NODE_CLASS_CONVERTER, 0); - node_type_socket_templates(&ntype, cmp_node_sepycca_in, cmp_node_sepycca_out); + ntype.declare = blender::nodes::cmp_node_sepycca_declare; node_type_init(&ntype, node_composit_init_mode_sepycca); nodeRegisterType(&ntype); } /* **************** COMBINE YCCA ******************** */ -static bNodeSocketTemplate cmp_node_combycca_in[] = { - {SOCK_FLOAT, N_("Y"), 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, PROP_NONE}, - {SOCK_FLOAT, N_("Cb"), 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, PROP_NONE}, - {SOCK_FLOAT, N_("Cr"), 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, PROP_NONE}, - {SOCK_FLOAT, N_("A"), 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, PROP_NONE}, - {-1, ""}, -}; -static bNodeSocketTemplate cmp_node_combycca_out[] = { - {SOCK_RGBA, N_("Image")}, - {-1, ""}, -}; + +namespace blender::nodes { + +static void cmp_node_combycca_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Float>("Y").min(0.0f).max(1.0f); + b.add_input<decl::Float>("Cb").default_value(0.5f).min(0.0f).max(1.0f); + b.add_input<decl::Float>("Cr").default_value(0.5f).min(0.0f).max(1.0f); + b.add_input<decl::Float>("A").default_value(1.0f).min(0.0f).max(1.0f); + b.add_output<decl::Color>("Image"); +} + +} // namespace blender::nodes static void node_composit_init_mode_combycca(bNodeTree *UNUSED(ntree), bNode *node) { @@ -73,7 +79,7 @@ void register_node_type_cmp_combycca(void) static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_COMBYCCA, "Combine YCbCrA", NODE_CLASS_CONVERTER, 0); - node_type_socket_templates(&ntype, cmp_node_combycca_in, cmp_node_combycca_out); + ntype.declare = blender::nodes::cmp_node_combycca_declare; node_type_init(&ntype, node_composit_init_mode_combycca); nodeRegisterType(&ntype); diff --git a/source/blender/nodes/composite/nodes/node_composite_sepcombYUVA.c b/source/blender/nodes/composite/nodes/node_composite_sepcombYUVA.cc index 4da79ec7981..4619b0c97f1 100644 --- a/source/blender/nodes/composite/nodes/node_composite_sepcombYUVA.c +++ b/source/blender/nodes/composite/nodes/node_composite_sepcombYUVA.cc @@ -21,48 +21,54 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** SEPARATE YUVA ******************** */ -static bNodeSocketTemplate cmp_node_sepyuva_in[] = { - {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, {-1, ""}}; -static bNodeSocketTemplate cmp_node_sepyuva_out[] = { - {SOCK_FLOAT, N_("Y")}, - {SOCK_FLOAT, N_("U")}, - {SOCK_FLOAT, N_("V")}, - {SOCK_FLOAT, N_("A")}, - {-1, ""}, -}; + +namespace blender::nodes { + +static void cmp_node_sepyuva_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Color>("Image").default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_output<decl::Float>("Y"); + b.add_output<decl::Float>("U"); + b.add_output<decl::Float>("V"); + b.add_output<decl::Float>("A"); +} + +} // namespace blender::nodes void register_node_type_cmp_sepyuva(void) { static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_SEPYUVA, "Separate YUVA", NODE_CLASS_CONVERTER, 0); - node_type_socket_templates(&ntype, cmp_node_sepyuva_in, cmp_node_sepyuva_out); + ntype.declare = blender::nodes::cmp_node_sepyuva_declare; nodeRegisterType(&ntype); } /* **************** COMBINE YUVA ******************** */ -static bNodeSocketTemplate cmp_node_combyuva_in[] = { - {SOCK_FLOAT, N_("Y"), 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, PROP_NONE}, - {SOCK_FLOAT, N_("U"), 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, PROP_NONE}, - {SOCK_FLOAT, N_("V"), 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, PROP_NONE}, - {SOCK_FLOAT, N_("A"), 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, PROP_NONE}, - {-1, ""}, -}; -static bNodeSocketTemplate cmp_node_combyuva_out[] = { - {SOCK_RGBA, N_("Image")}, - {-1, ""}, -}; + +namespace blender::nodes { + +static void cmp_node_combyuva_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Float>("Y").min(0.0f).max(1.0f); + b.add_input<decl::Float>("U").min(0.0f).max(1.0f); + b.add_input<decl::Float>("V").min(0.0f).max(1.0f); + b.add_input<decl::Float>("A").default_value(1.0f).min(0.0f).max(1.0f); + b.add_output<decl::Color>("Image"); +} + +} // namespace blender::nodes void register_node_type_cmp_combyuva(void) { static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_COMBYUVA, "Combine YUVA", NODE_CLASS_CONVERTER, 0); - node_type_socket_templates(&ntype, cmp_node_combyuva_in, cmp_node_combyuva_out); + ntype.declare = blender::nodes::cmp_node_combyuva_declare; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_setalpha.c b/source/blender/nodes/composite/nodes/node_composite_setalpha.cc index 1b44cc011e9..07a7ffcb426 100644 --- a/source/blender/nodes/composite/nodes/node_composite_setalpha.c +++ b/source/blender/nodes/composite/nodes/node_composite_setalpha.cc @@ -21,22 +21,24 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** SET ALPHA ******************** */ -static bNodeSocketTemplate cmp_node_setalpha_in[] = { - {SOCK_RGBA, N_("Image"), 0.0f, 0.0f, 0.0f, 1.0f}, - {SOCK_FLOAT, N_("Alpha"), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_NONE}, - {-1, ""}, -}; -static bNodeSocketTemplate cmp_node_setalpha_out[] = { - {SOCK_RGBA, N_("Image")}, - {-1, ""}, -}; + +namespace blender::nodes { + +static void cmp_node_setalpha_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Color>("Image").default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_input<decl::Float>("Alpha").default_value(1.0f).min(0.0f).max(1.0f); + b.add_output<decl::Color>("Image"); +} + +} // namespace blender::nodes static void node_composit_init_setalpha(bNodeTree *UNUSED(ntree), bNode *node) { - NodeSetAlpha *settings = MEM_callocN(sizeof(NodeSetAlpha), __func__); + NodeSetAlpha *settings = (NodeSetAlpha *)MEM_callocN(sizeof(NodeSetAlpha), __func__); node->storage = settings; settings->mode = CMP_NODE_SETALPHA_MODE_APPLY; } @@ -46,7 +48,7 @@ void register_node_type_cmp_setalpha(void) static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_SETALPHA, "Set Alpha", NODE_CLASS_CONVERTER, 0); - node_type_socket_templates(&ntype, cmp_node_setalpha_in, cmp_node_setalpha_out); + ntype.declare = blender::nodes::cmp_node_setalpha_declare; node_type_init(&ntype, node_composit_init_setalpha); node_type_storage( &ntype, "NodeSetAlpha", node_free_standard_storage, node_copy_standard_storage); diff --git a/source/blender/nodes/composite/nodes/node_composite_splitViewer.c b/source/blender/nodes/composite/nodes/node_composite_splitViewer.cc index 8afb3fd4841..f64abe87116 100644 --- a/source/blender/nodes/composite/nodes/node_composite_splitViewer.c +++ b/source/blender/nodes/composite/nodes/node_composite_splitViewer.cc @@ -21,21 +21,26 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" #include "BKE_global.h" #include "BKE_image.h" /* **************** SPLIT VIEWER ******************** */ -static bNodeSocketTemplate cmp_node_splitviewer_in[] = { - {SOCK_RGBA, N_("Image"), 0.0f, 0.0f, 0.0f, 1.0f}, - {SOCK_RGBA, N_("Image"), 0.0f, 0.0f, 0.0f, 1.0f}, - {-1, ""}, -}; + +namespace blender::nodes { + +static void cmp_node_splitviewer_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Color>("Image"); + b.add_input<decl::Color>("Image", "Image_001"); +} + +} // namespace blender::nodes static void node_composit_init_splitviewer(bNodeTree *UNUSED(ntree), bNode *node) { - ImageUser *iuser = MEM_callocN(sizeof(ImageUser), "node image user"); + ImageUser *iuser = (ImageUser *)MEM_callocN(sizeof(ImageUser), "node image user"); node->storage = iuser; iuser->sfra = 1; iuser->ok = 1; @@ -50,12 +55,12 @@ void register_node_type_cmp_splitviewer(void) cmp_node_type_base( &ntype, CMP_NODE_SPLITVIEWER, "Split Viewer", NODE_CLASS_OUTPUT, NODE_PREVIEW); - node_type_socket_templates(&ntype, cmp_node_splitviewer_in, NULL); + ntype.declare = blender::nodes::cmp_node_splitviewer_declare; node_type_init(&ntype, node_composit_init_splitviewer); node_type_storage(&ntype, "ImageUser", node_free_standard_storage, node_copy_standard_storage); /* Do not allow muting for this node. */ - node_type_internal_links(&ntype, NULL); + node_type_internal_links(&ntype, nullptr); nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_stabilize2d.c b/source/blender/nodes/composite/nodes/node_composite_stabilize2d.cc index b89f245c542..e5ce2e8ceb9 100644 --- a/source/blender/nodes/composite/nodes/node_composite_stabilize2d.c +++ b/source/blender/nodes/composite/nodes/node_composite_stabilize2d.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" #include "BKE_context.h" #include "BKE_lib_id.h" @@ -40,7 +40,7 @@ static bNodeSocketTemplate cmp_node_stabilize2d_out[] = { static void init(const bContext *C, PointerRNA *ptr) { - bNode *node = ptr->data; + bNode *node = (bNode *)ptr->data; Scene *scene = CTX_data_scene(C); node->id = (ID *)scene->clip; diff --git a/source/blender/nodes/composite/nodes/node_composite_sunbeams.c b/source/blender/nodes/composite/nodes/node_composite_sunbeams.cc index 84ab2d30d34..73907d2e27f 100644 --- a/source/blender/nodes/composite/nodes/node_composite_sunbeams.c +++ b/source/blender/nodes/composite/nodes/node_composite_sunbeams.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" static bNodeSocketTemplate inputs[] = { {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, @@ -34,11 +34,10 @@ static bNodeSocketTemplate outputs[] = { static void init(bNodeTree *UNUSED(ntree), bNode *node) { - NodeSunBeams *data = MEM_callocN(sizeof(NodeSunBeams), "sun beams node"); + NodeSunBeams *data = (NodeSunBeams *)MEM_callocN(sizeof(NodeSunBeams), "sun beams node"); data->source[0] = 0.5f; data->source[1] = 0.5f; - node->storage = data; } diff --git a/source/blender/nodes/composite/nodes/node_composite_switch.c b/source/blender/nodes/composite/nodes/node_composite_switch.cc index efbb3390e06..71226a6da0b 100644 --- a/source/blender/nodes/composite/nodes/node_composite_switch.c +++ b/source/blender/nodes/composite/nodes/node_composite_switch.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "../node_composite_util.h" +#include "../node_composite_util.hh" /* **************** MIX RGB ******************** */ static bNodeSocketTemplate cmp_node_switch_in[] = { diff --git a/source/blender/nodes/composite/nodes/node_composite_switchview.c b/source/blender/nodes/composite/nodes/node_composite_switchview.cc index b09d5119bc4..a61712f7f8d 100644 --- a/source/blender/nodes/composite/nodes/node_composite_switchview.c +++ b/source/blender/nodes/composite/nodes/node_composite_switchview.cc @@ -25,7 +25,7 @@ #include "BKE_context.h" #include "BKE_lib_id.h" -#include "../node_composite_util.h" +#include "../node_composite_util.hh" /* **************** SWITCH VIEW ******************** */ static bNodeSocketTemplate cmp_node_switch_view_out[] = { @@ -37,26 +37,23 @@ static bNodeSocket *ntreeCompositSwitchViewAddSocket(bNodeTree *ntree, bNode *node, const char *name) { - bNodeSocket *sock = nodeAddStaticSocket(ntree, node, SOCK_IN, SOCK_RGBA, PROP_NONE, NULL, name); + bNodeSocket *sock = nodeAddStaticSocket( + ntree, node, SOCK_IN, SOCK_RGBA, PROP_NONE, nullptr, name); return sock; } static void cmp_node_switch_view_sanitycheck(bNodeTree *ntree, bNode *node) { - bNodeSocket *sock; - if (!BLI_listbase_is_empty(&node->inputs)) { return; } - sock = ntreeCompositSwitchViewAddSocket(ntree, node, "No View"); + bNodeSocket *sock = ntreeCompositSwitchViewAddSocket(ntree, node, "No View"); sock->flag |= SOCK_HIDDEN; } static void cmp_node_switch_view_update(bNodeTree *ntree, bNode *node) { - bNodeSocket *sock; - SceneRenderView *srv; Scene *scene = (Scene *)node->id; /* only update when called from the operator button */ @@ -64,7 +61,7 @@ static void cmp_node_switch_view_update(bNodeTree *ntree, bNode *node) return; } - if (scene == NULL) { + if (scene == nullptr) { nodeRemoveAllSockets(ntree, node); /* make sure there is always one socket */ cmp_node_switch_view_sanitycheck(ntree, node); @@ -72,11 +69,12 @@ static void cmp_node_switch_view_update(bNodeTree *ntree, bNode *node) } /* remove the views that were removed */ - sock = node->inputs.last; + bNodeSocket *sock = (bNodeSocket *)node->inputs.last; while (sock) { - srv = BLI_findstring(&scene->r.views, sock->name, offsetof(SceneRenderView, name)); + SceneRenderView *srv = (SceneRenderView *)BLI_findstring( + &scene->r.views, sock->name, offsetof(SceneRenderView, name)); - if (srv == NULL) { + if (srv == nullptr) { bNodeSocket *sock_del = sock; sock = sock->prev; nodeRemoveSocket(ntree, node, sock_del); @@ -94,10 +92,10 @@ static void cmp_node_switch_view_update(bNodeTree *ntree, bNode *node) } /* add the new views */ - for (srv = scene->r.views.first; srv; srv = srv->next) { - sock = BLI_findstring(&node->inputs, srv->name, offsetof(bNodeSocket, name)); + LISTBASE_FOREACH (SceneRenderView *, srv, &scene->r.views) { + sock = (bNodeSocket *)BLI_findstring(&node->inputs, srv->name, offsetof(bNodeSocket, name)); - if (sock == NULL) { + if (sock == nullptr) { sock = ntreeCompositSwitchViewAddSocket(ntree, node, srv->name); } @@ -117,10 +115,7 @@ static void init_switch_view(const bContext *C, PointerRNA *ptr) { Scene *scene = CTX_data_scene(C); bNodeTree *ntree = (bNodeTree *)ptr->owner_id; - bNode *node = ptr->data; - SceneRenderView *srv; - bNodeSocket *sock; - int nr; + bNode *node = (bNode *)ptr->data; /* store scene for updates */ node->id = (ID *)scene; @@ -129,8 +124,8 @@ static void init_switch_view(const bContext *C, PointerRNA *ptr) if (scene) { RenderData *rd = &scene->r; - for (nr = 0, srv = rd->views.first; srv; srv = srv->next, nr++) { - sock = ntreeCompositSwitchViewAddSocket(ntree, node, srv->name); + LISTBASE_FOREACH (SceneRenderView *, srv, &rd->views) { + bNodeSocket *sock = ntreeCompositSwitchViewAddSocket(ntree, node, srv->name); if (srv->viewflag & SCE_VIEW_DISABLE) { sock->flag |= SOCK_HIDDEN; @@ -147,7 +142,7 @@ void register_node_type_cmp_switch_view(void) static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_SWITCH_VIEW, "Switch View", NODE_CLASS_CONVERTER, 0); - node_type_socket_templates(&ntype, NULL, cmp_node_switch_view_out); + node_type_socket_templates(&ntype, nullptr, cmp_node_switch_view_out); ntype.initfunc_api = init_switch_view; diff --git a/source/blender/nodes/composite/nodes/node_composite_texture.c b/source/blender/nodes/composite/nodes/node_composite_texture.cc index 50be05fe5a6..eff008b4b41 100644 --- a/source/blender/nodes/composite/nodes/node_composite_texture.c +++ b/source/blender/nodes/composite/nodes/node_composite_texture.cc @@ -21,26 +21,32 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** TEXTURE ******************** */ -static bNodeSocketTemplate cmp_node_texture_in[] = { - {SOCK_VECTOR, N_("Offset"), 0.0f, 0.0f, 0.0f, 0.0f, -2.0f, 2.0f, PROP_TRANSLATION}, - {SOCK_VECTOR, N_("Scale"), 1.0f, 1.0f, 1.0f, 1.0f, -10.0f, 10.0f, PROP_XYZ}, - {-1, ""}, -}; -static bNodeSocketTemplate cmp_node_texture_out[] = { - {SOCK_FLOAT, N_("Value")}, - {SOCK_RGBA, N_("Color")}, - {-1, ""}, -}; + +namespace blender::nodes { + +static void cmp_node_texture_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Vector>("Offset").min(-2.0f).max(2.0f).subtype(PROP_TRANSLATION); + b.add_input<decl::Vector>("Scale") + .default_value({1.0f, 1.0f, 1.0f}) + .min(-10.0f) + .max(10.0f) + .subtype(PROP_XYZ); + b.add_output<decl::Float>("Value"); + b.add_output<decl::Color>("Color"); +} + +} // namespace blender::nodes void register_node_type_cmp_texture(void) { static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_TEXTURE, "Texture", NODE_CLASS_INPUT, NODE_PREVIEW); - node_type_socket_templates(&ntype, cmp_node_texture_in, cmp_node_texture_out); + ntype.declare = blender::nodes::cmp_node_texture_declare; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_tonemap.c b/source/blender/nodes/composite/nodes/node_composite_tonemap.cc index 5fc86c997f5..85fd240ce2e 100644 --- a/source/blender/nodes/composite/nodes/node_composite_tonemap.c +++ b/source/blender/nodes/composite/nodes/node_composite_tonemap.cc @@ -21,20 +21,21 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" -static bNodeSocketTemplate cmp_node_tonemap_in[] = { - {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, - {-1, ""}, -}; -static bNodeSocketTemplate cmp_node_tonemap_out[] = { - {SOCK_RGBA, N_("Image")}, - {-1, ""}, -}; +namespace blender::nodes { + +static void cmp_node_tonemap_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Color>("Image").default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_output<decl::Color>("Image"); +} + +} // namespace blender::nodes static void node_composit_init_tonemap(bNodeTree *UNUSED(ntree), bNode *node) { - NodeTonemap *ntm = MEM_callocN(sizeof(NodeTonemap), "node tonemap data"); + NodeTonemap *ntm = (NodeTonemap *)MEM_callocN(sizeof(NodeTonemap), "node tonemap data"); ntm->type = 1; ntm->key = 0.18; ntm->offset = 1; @@ -53,7 +54,7 @@ void register_node_type_cmp_tonemap(void) static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_TONEMAP, "Tonemap", NODE_CLASS_OP_COLOR, 0); - node_type_socket_templates(&ntype, cmp_node_tonemap_in, cmp_node_tonemap_out); + ntype.declare = blender::nodes::cmp_node_tonemap_declare; node_type_init(&ntype, node_composit_init_tonemap); node_type_storage(&ntype, "NodeTonemap", node_free_standard_storage, node_copy_standard_storage); diff --git a/source/blender/nodes/composite/nodes/node_composite_trackpos.c b/source/blender/nodes/composite/nodes/node_composite_trackpos.cc index d59ce9b8b7a..cb5c9468daa 100644 --- a/source/blender/nodes/composite/nodes/node_composite_trackpos.c +++ b/source/blender/nodes/composite/nodes/node_composite_trackpos.cc @@ -21,18 +21,23 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" -static bNodeSocketTemplate cmp_node_trackpos_out[] = { - {SOCK_FLOAT, N_("X")}, - {SOCK_FLOAT, N_("Y")}, - {SOCK_VECTOR, N_("Speed"), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_VELOCITY}, - {-1, ""}, -}; +namespace blender::nodes { + +static void cmp_node_trackpos_declare(NodeDeclarationBuilder &b) +{ + b.add_output<decl::Float>("X"); + b.add_output<decl::Float>("Y"); + b.add_output<decl::Vector>("Speed").subtype(PROP_VELOCITY); +} + +} // namespace blender::nodes static void init(bNodeTree *UNUSED(ntree), bNode *node) { - NodeTrackPosData *data = MEM_callocN(sizeof(NodeTrackPosData), "node track position data"); + NodeTrackPosData *data = (NodeTrackPosData *)MEM_callocN(sizeof(NodeTrackPosData), + "node track position data"); node->storage = data; } @@ -42,7 +47,7 @@ void register_node_type_cmp_trackpos(void) static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_TRACKPOS, "Track Position", NODE_CLASS_INPUT, 0); - node_type_socket_templates(&ntype, NULL, cmp_node_trackpos_out); + ntype.declare = blender::nodes::cmp_node_trackpos_declare; node_type_init(&ntype, init); node_type_storage( &ntype, "NodeTrackPosData", node_free_standard_storage, node_copy_standard_storage); diff --git a/source/blender/nodes/composite/nodes/node_composite_transform.c b/source/blender/nodes/composite/nodes/node_composite_transform.cc index be526c1059c..1695101cdbf 100644 --- a/source/blender/nodes/composite/nodes/node_composite_transform.c +++ b/source/blender/nodes/composite/nodes/node_composite_transform.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** Transform ******************** */ diff --git a/source/blender/nodes/composite/nodes/node_composite_translate.c b/source/blender/nodes/composite/nodes/node_composite_translate.cc index 43337ec6f7e..0ee8a41a5ea 100644 --- a/source/blender/nodes/composite/nodes/node_composite_translate.c +++ b/source/blender/nodes/composite/nodes/node_composite_translate.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** Translate ******************** */ @@ -38,7 +38,8 @@ static bNodeSocketTemplate cmp_node_translate_out[] = { static void node_composit_init_translate(bNodeTree *UNUSED(ntree), bNode *node) { - NodeTranslateData *data = MEM_callocN(sizeof(NodeTranslateData), "node translate data"); + NodeTranslateData *data = (NodeTranslateData *)MEM_callocN(sizeof(NodeTranslateData), + "node translate data"); node->storage = data; } diff --git a/source/blender/nodes/composite/nodes/node_composite_valToRgb.c b/source/blender/nodes/composite/nodes/node_composite_valToRgb.cc index ed6dbfa2bf3..ba98ee12f30 100644 --- a/source/blender/nodes/composite/nodes/node_composite_valToRgb.c +++ b/source/blender/nodes/composite/nodes/node_composite_valToRgb.cc @@ -21,18 +21,20 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** VALTORGB ******************** */ -static bNodeSocketTemplate cmp_node_valtorgb_in[] = { - {SOCK_FLOAT, N_("Fac"), 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_FACTOR}, - {-1, ""}, -}; -static bNodeSocketTemplate cmp_node_valtorgb_out[] = { - {SOCK_RGBA, N_("Image")}, - {SOCK_FLOAT, N_("Alpha")}, - {-1, ""}, -}; + +namespace blender::nodes { + +static void cmp_node_valtorgb_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Float>("Fac").default_value(0.5f).min(0.0f).max(1.0f).subtype(PROP_FACTOR); + b.add_output<decl::Color>("Image"); + b.add_output<decl::Color>("Alpha"); +} + +} // namespace blender::nodes static void node_composit_init_valtorgb(bNodeTree *UNUSED(ntree), bNode *node) { @@ -44,7 +46,7 @@ void register_node_type_cmp_valtorgb(void) static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_VALTORGB, "ColorRamp", NODE_CLASS_CONVERTER, 0); - node_type_socket_templates(&ntype, cmp_node_valtorgb_in, cmp_node_valtorgb_out); + ntype.declare = blender::nodes::cmp_node_valtorgb_declare; node_type_size(&ntype, 240, 200, 320); node_type_init(&ntype, node_composit_init_valtorgb); node_type_storage(&ntype, "ColorBand", node_free_standard_storage, node_copy_standard_storage); @@ -53,21 +55,23 @@ void register_node_type_cmp_valtorgb(void) } /* **************** RGBTOBW ******************** */ -static bNodeSocketTemplate cmp_node_rgbtobw_in[] = { - {SOCK_RGBA, N_("Image"), 0.8f, 0.8f, 0.8f, 1.0f, 0.0f, 1.0f}, - {-1, ""}, -}; -static bNodeSocketTemplate cmp_node_rgbtobw_out[] = { - {SOCK_FLOAT, N_("Val"), 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f}, - {-1, ""}, -}; + +namespace blender::nodes { + +static void cmp_node_rgbtobw_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Color>("Image").default_value({0.8f, 0.8f, 0.8f, 1.0f}); + b.add_output<decl::Color>("Val"); +} + +} // namespace blender::nodes void register_node_type_cmp_rgbtobw(void) { static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_RGBTOBW, "RGB to BW", NODE_CLASS_CONVERTER, 0); - node_type_socket_templates(&ntype, cmp_node_rgbtobw_in, cmp_node_rgbtobw_out); + ntype.declare = blender::nodes::cmp_node_rgbtobw_declare; node_type_size_preset(&ntype, NODE_SIZE_SMALL); nodeRegisterType(&ntype); diff --git a/source/blender/nodes/composite/nodes/node_composite_value.c b/source/blender/nodes/composite/nodes/node_composite_value.cc index 2ede2cb8c83..5459801bcc7 100644 --- a/source/blender/nodes/composite/nodes/node_composite_value.c +++ b/source/blender/nodes/composite/nodes/node_composite_value.cc @@ -21,20 +21,25 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** VALUE ******************** */ -static bNodeSocketTemplate cmp_node_value_out[] = { - {SOCK_FLOAT, N_("Value"), 0.5f, 0, 0, 0, -FLT_MAX, FLT_MAX, PROP_NONE}, - {-1, ""}, -}; + +namespace blender::nodes { + +static void cmp_node_value_declare(NodeDeclarationBuilder &b) +{ + b.add_output<decl::Float>("Value").default_value(0.5f); +} + +} // namespace blender::nodes void register_node_type_cmp_value(void) { static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_VALUE, "Value", NODE_CLASS_INPUT, 0); - node_type_socket_templates(&ntype, NULL, cmp_node_value_out); + ntype.declare = blender::nodes::cmp_node_value_declare; node_type_size_preset(&ntype, NODE_SIZE_SMALL); nodeRegisterType(&ntype); diff --git a/source/blender/nodes/composite/nodes/node_composite_vecBlur.c b/source/blender/nodes/composite/nodes/node_composite_vecBlur.cc index 7005ea42afe..ce6ba659609 100644 --- a/source/blender/nodes/composite/nodes/node_composite_vecBlur.c +++ b/source/blender/nodes/composite/nodes/node_composite_vecBlur.cc @@ -21,7 +21,7 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** VECTOR BLUR ******************** */ static bNodeSocketTemplate cmp_node_vecblur_in[] = { @@ -33,13 +33,13 @@ static bNodeSocketTemplate cmp_node_vecblur_out[] = {{SOCK_RGBA, N_("Image")}, { static void node_composit_init_vecblur(bNodeTree *UNUSED(ntree), bNode *node) { - NodeBlurData *nbd = MEM_callocN(sizeof(NodeBlurData), "node blur data"); + NodeBlurData *nbd = (NodeBlurData *)MEM_callocN(sizeof(NodeBlurData), "node blur data"); node->storage = nbd; nbd->samples = 32; nbd->fac = 1.0f; } -/* custom1: iterations, custom2: maxspeed (0 = nolimit) */ +/* custom1: iterations, custom2: max_speed (0 = no_limit). */ void register_node_type_cmp_vecblur(void) { static bNodeType ntype; diff --git a/source/blender/nodes/composite/nodes/node_composite_viewer.c b/source/blender/nodes/composite/nodes/node_composite_viewer.cc index b5f74d5c937..7234d4d8eb2 100644 --- a/source/blender/nodes/composite/nodes/node_composite_viewer.c +++ b/source/blender/nodes/composite/nodes/node_composite_viewer.cc @@ -21,21 +21,27 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" #include "BKE_global.h" #include "BKE_image.h" /* **************** VIEWER ******************** */ -static bNodeSocketTemplate cmp_node_viewer_in[] = { - {SOCK_RGBA, N_("Image"), 0.0f, 0.0f, 0.0f, 1.0f}, - {SOCK_FLOAT, N_("Alpha"), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_NONE}, - {SOCK_FLOAT, N_("Z"), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_NONE}, - {-1, ""}}; + +namespace blender::nodes { + +static void cmp_node_viewer_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Color>("Image").default_value({0.0f, 0.0f, 0.0f, 1.0f}); + b.add_input<decl::Float>("Alpha").default_value(1.0f).min(0.0f).max(1.0f); + b.add_input<decl::Float>("Z").default_value(1.0f).min(0.0f).max(1.0f); +} + +} // namespace blender::nodes static void node_composit_init_viewer(bNodeTree *UNUSED(ntree), bNode *node) { - ImageUser *iuser = MEM_callocN(sizeof(ImageUser), "node image user"); + ImageUser *iuser = (ImageUser *)MEM_callocN(sizeof(ImageUser), "node image user"); node->storage = iuser; iuser->sfra = 1; iuser->ok = 1; @@ -50,11 +56,11 @@ void register_node_type_cmp_viewer(void) static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_VIEWER, "Viewer", NODE_CLASS_OUTPUT, NODE_PREVIEW); - node_type_socket_templates(&ntype, cmp_node_viewer_in, NULL); + ntype.declare = blender::nodes::cmp_node_viewer_declare; node_type_init(&ntype, node_composit_init_viewer); node_type_storage(&ntype, "ImageUser", node_free_standard_storage, node_copy_standard_storage); - node_type_internal_links(&ntype, NULL); + node_type_internal_links(&ntype, nullptr); nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_zcombine.c b/source/blender/nodes/composite/nodes/node_composite_zcombine.cc index 5041b16c303..79e4d449159 100644 --- a/source/blender/nodes/composite/nodes/node_composite_zcombine.c +++ b/source/blender/nodes/composite/nodes/node_composite_zcombine.cc @@ -21,29 +21,31 @@ * \ingroup cmpnodes */ -#include "node_composite_util.h" +#include "node_composite_util.hh" /* **************** Z COMBINE ******************** */ -/* lazy coder NOTE: node->custom2 is abused to send signal. */ -static bNodeSocketTemplate cmp_node_zcombine_in[] = { - {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, - {SOCK_FLOAT, N_("Z"), 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 10000.0f, PROP_NONE}, - {SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, - {SOCK_FLOAT, N_("Z"), 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 10000.0f, PROP_NONE}, - {-1, ""}, -}; -static bNodeSocketTemplate cmp_node_zcombine_out[] = { - {SOCK_RGBA, N_("Image")}, - {SOCK_FLOAT, N_("Z")}, - {-1, ""}, -}; +namespace blender::nodes { + +static void cmp_node_zcombine_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Color>("Image").default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_input<decl::Float>("Z").default_value(1.0f).min(0.0f).max(10000.0f); + b.add_input<decl::Color>("Image", "Image_001").default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_input<decl::Float>("Z", "Z_001").default_value(1.0f).min(0.0f).max(10000.0f); + b.add_output<decl::Color>("Image"); + b.add_output<decl::Float>("Z"); +} + +} // namespace blender::nodes + +/* lazy coder NOTE: node->custom2 is abused to send signal. */ void register_node_type_cmp_zcombine(void) { static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_ZCOMBINE, "Z Combine", NODE_CLASS_OP_COLOR, 0); - node_type_socket_templates(&ntype, cmp_node_zcombine_in, cmp_node_zcombine_out); + ntype.declare = blender::nodes::cmp_node_zcombine_declare; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/function/nodes/node_fn_random_float.cc b/source/blender/nodes/function/nodes/legacy/node_fn_random_float.cc index 1bd39aacdca..7f6f554ba93 100644 --- a/source/blender/nodes/function/nodes/node_fn_random_float.cc +++ b/source/blender/nodes/function/nodes/legacy/node_fn_random_float.cc @@ -20,8 +20,9 @@ namespace blender::nodes { -static void fn_node_random_float_declare(NodeDeclarationBuilder &b) +static void fn_node_legacy_random_float_declare(NodeDeclarationBuilder &b) { + b.is_function_node(); b.add_input<decl::Float>("Min").min(-10000.0f).max(10000.0f); b.add_input<decl::Float>("Max").default_value(1.0f).min(-10000.0f).max(10000.0f); b.add_input<decl::Int>("Seed").min(-10000).max(10000); @@ -67,19 +68,19 @@ class RandomFloatFunction : public blender::fn::MultiFunction { } }; -static void fn_node_random_float_build_multi_function( +static void fn_node_legacy_random_float_build_multi_function( blender::nodes::NodeMultiFunctionBuilder &builder) { static RandomFloatFunction fn; builder.set_matching_fn(fn); } -void register_node_type_fn_random_float() +void register_node_type_fn_legacy_random_float() { static bNodeType ntype; - fn_node_type_base(&ntype, FN_NODE_RANDOM_FLOAT, "Random Float", 0, 0); - ntype.declare = blender::nodes::fn_node_random_float_declare; - ntype.build_multi_function = fn_node_random_float_build_multi_function; + fn_node_type_base(&ntype, FN_NODE_LEGACY_RANDOM_FLOAT, "Random Float", 0, 0); + ntype.declare = blender::nodes::fn_node_legacy_random_float_declare; + ntype.build_multi_function = fn_node_legacy_random_float_build_multi_function; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/function/nodes/node_fn_boolean_math.cc b/source/blender/nodes/function/nodes/node_fn_boolean_math.cc index b71ee092de6..d10490bb2ee 100644 --- a/source/blender/nodes/function/nodes/node_fn_boolean_math.cc +++ b/source/blender/nodes/function/nodes/node_fn_boolean_math.cc @@ -28,6 +28,7 @@ namespace blender::nodes { static void fn_node_boolean_math_declare(NodeDeclarationBuilder &b) { + b.is_function_node(); b.add_input<decl::Bool>("Boolean", "Boolean"); b.add_input<decl::Bool>("Boolean", "Boolean_001"); b.add_output<decl::Bool>("Boolean"); diff --git a/source/blender/nodes/function/nodes/node_fn_float_compare.cc b/source/blender/nodes/function/nodes/node_fn_float_compare.cc index 4f4830afabc..9736c52e895 100644 --- a/source/blender/nodes/function/nodes/node_fn_float_compare.cc +++ b/source/blender/nodes/function/nodes/node_fn_float_compare.cc @@ -30,6 +30,7 @@ namespace blender::nodes { static void fn_node_float_compare_declare(NodeDeclarationBuilder &b) { + b.is_function_node(); b.add_input<decl::Float>("A").min(-10000.0f).max(10000.0f); b.add_input<decl::Float>("B").min(-10000.0f).max(10000.0f); b.add_input<decl::Float>("Epsilon").default_value(0.001f).min(-10000.0f).max(10000.0f); diff --git a/source/blender/nodes/function/nodes/node_fn_float_to_int.cc b/source/blender/nodes/function/nodes/node_fn_float_to_int.cc index e59c78d2c04..8bb5dafff8a 100644 --- a/source/blender/nodes/function/nodes/node_fn_float_to_int.cc +++ b/source/blender/nodes/function/nodes/node_fn_float_to_int.cc @@ -29,6 +29,7 @@ namespace blender::nodes { static void fn_node_float_to_int_declare(NodeDeclarationBuilder &b) { + b.is_function_node(); b.add_input<decl::Float>("Float"); b.add_output<decl::Int>("Integer"); }; diff --git a/source/blender/nodes/function/nodes/node_fn_input_special_characters.cc b/source/blender/nodes/function/nodes/node_fn_input_special_characters.cc new file mode 100644 index 00000000000..11c64d3f694 --- /dev/null +++ b/source/blender/nodes/function/nodes/node_fn_input_special_characters.cc @@ -0,0 +1,74 @@ +/* + * 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_function_util.hh" + +namespace blender::nodes { + +static void fn_node_input_special_characters_declare(NodeDeclarationBuilder &b) +{ + b.add_output<decl::String>("Line Break"); + b.add_output<decl::String>("Tab"); +}; + +class MF_SpecialCharacters : public fn::MultiFunction { + public: + MF_SpecialCharacters() + { + static fn::MFSignature signature = create_signature(); + this->set_signature(&signature); + } + + static fn::MFSignature create_signature() + { + fn::MFSignatureBuilder signature{"Special Characters"}; + signature.single_output<std::string>("Line Break"); + signature.single_output<std::string>("Tab"); + return signature.build(); + } + + void call(IndexMask mask, fn::MFParams params, fn::MFContext UNUSED(context)) const override + { + MutableSpan<std::string> lb = params.uninitialized_single_output<std::string>(0, "Line Break"); + MutableSpan<std::string> tab = params.uninitialized_single_output<std::string>(1, "Tab"); + + for (const int i : mask) { + new (&lb[i]) std::string("\n"); + new (&tab[i]) std::string("\t"); + } + } +}; + +static void fn_node_input_special_characters_build_multi_function( + NodeMultiFunctionBuilder &builder) +{ + static MF_SpecialCharacters special_characters_fn; + builder.set_matching_fn(special_characters_fn); +} + +} // namespace blender::nodes + +void register_node_type_fn_input_special_characters() +{ + static bNodeType ntype; + + fn_node_type_base( + &ntype, FN_NODE_INPUT_SPECIAL_CHARACTERS, "Special Characters", NODE_CLASS_INPUT, 0); + ntype.declare = blender::nodes::fn_node_input_special_characters_declare; + ntype.build_multi_function = + blender::nodes::fn_node_input_special_characters_build_multi_function; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/function/nodes/node_fn_input_string.cc b/source/blender/nodes/function/nodes/node_fn_input_string.cc index 4a8e898fb9b..704ae9d900c 100644 --- a/source/blender/nodes/function/nodes/node_fn_input_string.cc +++ b/source/blender/nodes/function/nodes/node_fn_input_string.cc @@ -23,6 +23,7 @@ namespace blender::nodes { static void fn_node_input_string_declare(NodeDeclarationBuilder &b) { + b.is_function_node(); b.add_output<decl::String>("String"); }; diff --git a/source/blender/nodes/function/nodes/node_fn_random_value.cc b/source/blender/nodes/function/nodes/node_fn_random_value.cc new file mode 100644 index 00000000000..53ca77aab0c --- /dev/null +++ b/source/blender/nodes/function/nodes/node_fn_random_value.cc @@ -0,0 +1,299 @@ +/* + * 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_hash.h" +#include "BLI_noise.hh" + +#include "node_function_util.hh" + +#include "UI_interface.h" +#include "UI_resources.h" + +namespace blender::nodes { + +static void fn_node_random_value_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Vector>("Min").supports_field(); + b.add_input<decl::Vector>("Max").default_value({1.0f, 1.0f, 1.0f}).supports_field(); + b.add_input<decl::Float>("Min", "Min_001").supports_field(); + b.add_input<decl::Float>("Max", "Max_001").default_value(1.0f).supports_field(); + b.add_input<decl::Int>("Min", "Min_002").min(-100000).max(100000).supports_field(); + b.add_input<decl::Int>("Max", "Max_002") + .default_value(100) + .min(-100000) + .max(100000) + .supports_field(); + b.add_input<decl::Float>("Probability") + .min(0.0f) + .max(1.0f) + .default_value(0.5f) + .subtype(PROP_FACTOR) + .supports_field(); + b.add_input<decl::Int>("ID").implicit_field(); + b.add_input<decl::Int>("Seed").default_value(0).min(-10000).max(10000).supports_field(); + + b.add_output<decl::Vector>("Value").dependent_field(); + b.add_output<decl::Float>("Value", "Value_001").dependent_field(); + b.add_output<decl::Int>("Value", "Value_002").dependent_field(); + b.add_output<decl::Bool>("Value", "Value_003").dependent_field(); +} + +static void fn_node_random_value_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiItemR(layout, ptr, "data_type", 0, "", ICON_NONE); +} + +static void fn_node_random_value_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeRandomValue *data = (NodeRandomValue *)MEM_callocN(sizeof(NodeRandomValue), __func__); + data->data_type = CD_PROP_FLOAT; + node->storage = data; +} + +static void fn_node_random_value_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + const NodeRandomValue &storage = *(const NodeRandomValue *)node->storage; + const CustomDataType data_type = static_cast<CustomDataType>(storage.data_type); + + bNodeSocket *sock_min_vector = (bNodeSocket *)node->inputs.first; + bNodeSocket *sock_max_vector = sock_min_vector->next; + bNodeSocket *sock_min_float = sock_max_vector->next; + bNodeSocket *sock_max_float = sock_min_float->next; + bNodeSocket *sock_min_int = sock_max_float->next; + bNodeSocket *sock_max_int = sock_min_int->next; + bNodeSocket *sock_probability = sock_max_int->next; + + bNodeSocket *sock_out_vector = (bNodeSocket *)node->outputs.first; + bNodeSocket *sock_out_float = sock_out_vector->next; + bNodeSocket *sock_out_int = sock_out_float->next; + bNodeSocket *sock_out_bool = sock_out_int->next; + + nodeSetSocketAvailability(sock_min_vector, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(sock_max_vector, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(sock_min_float, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(sock_max_float, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(sock_min_int, data_type == CD_PROP_INT32); + nodeSetSocketAvailability(sock_max_int, data_type == CD_PROP_INT32); + nodeSetSocketAvailability(sock_probability, data_type == CD_PROP_BOOL); + + nodeSetSocketAvailability(sock_out_vector, data_type == CD_PROP_FLOAT3); + nodeSetSocketAvailability(sock_out_float, data_type == CD_PROP_FLOAT); + nodeSetSocketAvailability(sock_out_int, data_type == CD_PROP_INT32); + nodeSetSocketAvailability(sock_out_bool, data_type == CD_PROP_BOOL); +} + +class RandomVectorFunction : public fn::MultiFunction { + public: + RandomVectorFunction() + { + static fn::MFSignature signature = create_signature(); + this->set_signature(&signature); + } + + static fn::MFSignature create_signature() + { + fn::MFSignatureBuilder signature{"Random Value"}; + signature.single_input<float3>("Min"); + signature.single_input<float3>("Max"); + signature.single_input<int>("ID"); + signature.single_input<int>("Seed"); + signature.single_output<float3>("Value"); + return signature.build(); + } + + void call(IndexMask mask, fn::MFParams params, fn::MFContext UNUSED(context)) const override + { + const VArray<float3> &min_values = params.readonly_single_input<float3>(0, "Min"); + const VArray<float3> &max_values = params.readonly_single_input<float3>(1, "Max"); + const VArray<int> &ids = params.readonly_single_input<int>(2, "ID"); + const VArray<int> &seeds = params.readonly_single_input<int>(3, "Seed"); + MutableSpan<float3> values = params.uninitialized_single_output<float3>(4, "Value"); + + for (int64_t i : mask) { + const float3 min_value = min_values[i]; + const float3 max_value = max_values[i]; + const int seed = seeds[i]; + const int id = ids[i]; + + const float x = noise::hash_to_float(seed, id, 0); + const float y = noise::hash_to_float(seed, id, 1); + const float z = noise::hash_to_float(seed, id, 2); + + values[i] = float3(x, y, z) * (max_value - min_value) + min_value; + } + } +}; + +class RandomFloatFunction : public fn::MultiFunction { + public: + RandomFloatFunction() + { + static fn::MFSignature signature = create_signature(); + this->set_signature(&signature); + } + + static fn::MFSignature create_signature() + { + fn::MFSignatureBuilder signature{"Random Value"}; + signature.single_input<float>("Min"); + signature.single_input<float>("Max"); + signature.single_input<int>("ID"); + signature.single_input<int>("Seed"); + signature.single_output<float>("Value"); + return signature.build(); + } + + void call(IndexMask mask, fn::MFParams params, fn::MFContext UNUSED(context)) const override + { + const VArray<float> &min_values = params.readonly_single_input<float>(0, "Min"); + const VArray<float> &max_values = params.readonly_single_input<float>(1, "Max"); + const VArray<int> &ids = params.readonly_single_input<int>(2, "ID"); + const VArray<int> &seeds = params.readonly_single_input<int>(3, "Seed"); + MutableSpan<float> values = params.uninitialized_single_output<float>(4, "Value"); + + for (int64_t i : mask) { + const float min_value = min_values[i]; + const float max_value = max_values[i]; + const int seed = seeds[i]; + const int id = ids[i]; + + const float value = noise::hash_to_float(seed, id); + values[i] = value * (max_value - min_value) + min_value; + } + } +}; + +class RandomIntFunction : public fn::MultiFunction { + public: + RandomIntFunction() + { + static fn::MFSignature signature = create_signature(); + this->set_signature(&signature); + } + + static fn::MFSignature create_signature() + { + fn::MFSignatureBuilder signature{"Random Value"}; + signature.single_input<int>("Min"); + signature.single_input<int>("Max"); + signature.single_input<int>("ID"); + signature.single_input<int>("Seed"); + signature.single_output<int>("Value"); + return signature.build(); + } + + void call(IndexMask mask, fn::MFParams params, fn::MFContext UNUSED(context)) const override + { + const VArray<int> &min_values = params.readonly_single_input<int>(0, "Min"); + const VArray<int> &max_values = params.readonly_single_input<int>(1, "Max"); + const VArray<int> &ids = params.readonly_single_input<int>(2, "ID"); + const VArray<int> &seeds = params.readonly_single_input<int>(3, "Seed"); + MutableSpan<int> values = params.uninitialized_single_output<int>(4, "Value"); + + for (int64_t i : mask) { + const float min_value = min_values[i]; + const float max_value = max_values[i]; + const int seed = seeds[i]; + const int id = ids[i]; + + const float value = noise::hash_to_float(id, seed); + values[i] = round_fl_to_int(value * (max_value - min_value) + min_value); + } + } +}; + +class RandomBoolFunction : public fn::MultiFunction { + public: + RandomBoolFunction() + { + static fn::MFSignature signature = create_signature(); + this->set_signature(&signature); + } + + static fn::MFSignature create_signature() + { + fn::MFSignatureBuilder signature{"Random Value"}; + signature.single_input<float>("Probability"); + signature.single_input<int>("ID"); + signature.single_input<int>("Seed"); + signature.single_output<bool>("Value"); + return signature.build(); + } + + void call(IndexMask mask, fn::MFParams params, fn::MFContext UNUSED(context)) const override + { + const VArray<float> &probabilities = params.readonly_single_input<float>(0, "Probability"); + const VArray<int> &ids = params.readonly_single_input<int>(1, "ID"); + const VArray<int> &seeds = params.readonly_single_input<int>(2, "Seed"); + MutableSpan<bool> values = params.uninitialized_single_output<bool>(3, "Value"); + + for (int64_t i : mask) { + const int seed = seeds[i]; + const int id = ids[i]; + const float probability = probabilities[i]; + values[i] = noise::hash_to_float(id, seed) <= probability; + } + } +}; + +static void fn_node_random_value_build_multi_function(NodeMultiFunctionBuilder &builder) +{ + const NodeRandomValue &storage = *(const NodeRandomValue *)builder.node().storage; + const CustomDataType data_type = static_cast<CustomDataType>(storage.data_type); + + switch (data_type) { + case CD_PROP_FLOAT3: { + static RandomVectorFunction fn; + builder.set_matching_fn(fn); + break; + } + case CD_PROP_FLOAT: { + static RandomFloatFunction fn; + builder.set_matching_fn(fn); + break; + } + case CD_PROP_INT32: { + static RandomIntFunction fn; + builder.set_matching_fn(fn); + break; + } + case CD_PROP_BOOL: { + static RandomBoolFunction fn; + builder.set_matching_fn(fn); + break; + } + default: { + BLI_assert_unreachable(); + break; + } + } +} + +} // namespace blender::nodes + +void register_node_type_fn_random_value() +{ + static bNodeType ntype; + fn_node_type_base(&ntype, FN_NODE_RANDOM_VALUE, "Random Value", NODE_CLASS_CONVERTER, 0); + node_type_init(&ntype, blender::nodes::fn_node_random_value_init); + node_type_update(&ntype, blender::nodes::fn_node_random_value_update); + ntype.draw_buttons = blender::nodes::fn_node_random_value_layout; + ntype.declare = blender::nodes::fn_node_random_value_declare; + ntype.build_multi_function = blender::nodes::fn_node_random_value_build_multi_function; + node_type_storage( + &ntype, "NodeRandomValue", node_free_standard_storage, node_copy_standard_storage); + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/function/nodes/node_fn_rotate_euler.cc b/source/blender/nodes/function/nodes/node_fn_rotate_euler.cc new file mode 100644 index 00000000000..cbae1648663 --- /dev/null +++ b/source/blender/nodes/function/nodes/node_fn_rotate_euler.cc @@ -0,0 +1,138 @@ +/* + * 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_listbase.h" +#include "BLI_string.h" + +#include "RNA_enum_types.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "node_function_util.hh" + +namespace blender::nodes { +static void fn_node_rotate_euler_declare(NodeDeclarationBuilder &b) +{ + b.is_function_node(); + b.add_input<decl::Vector>("Rotation").subtype(PROP_EULER).hide_value(); + b.add_input<decl::Vector>("Rotate By").subtype(PROP_EULER); + b.add_input<decl::Vector>("Axis").default_value({0.0, 0.0, 1.0}).subtype(PROP_XYZ); + b.add_input<decl::Float>("Angle").subtype(PROP_ANGLE); + b.add_output<decl::Vector>("Rotation"); +}; + +static void fn_node_rotate_euler_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + bNodeSocket *rotate_by_socket = static_cast<bNodeSocket *>(BLI_findlink(&node->inputs, 1)); + bNodeSocket *axis_socket = static_cast<bNodeSocket *>(BLI_findlink(&node->inputs, 2)); + bNodeSocket *angle_socket = static_cast<bNodeSocket *>(BLI_findlink(&node->inputs, 3)); + + nodeSetSocketAvailability(rotate_by_socket, + ELEM(node->custom1, FN_NODE_ROTATE_EULER_TYPE_EULER)); + nodeSetSocketAvailability(axis_socket, + ELEM(node->custom1, FN_NODE_ROTATE_EULER_TYPE_AXIS_ANGLE)); + nodeSetSocketAvailability(angle_socket, + ELEM(node->custom1, FN_NODE_ROTATE_EULER_TYPE_AXIS_ANGLE)); +} + +static void fn_node_rotate_euler_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiItemR(layout, ptr, "type", UI_ITEM_R_EXPAND, nullptr, ICON_NONE); + uiItemR(layout, ptr, "space", UI_ITEM_R_EXPAND, nullptr, ICON_NONE); +} + +static const fn::MultiFunction *get_multi_function(bNode &bnode) +{ + static fn::CustomMF_SI_SI_SO<float3, float3, float3> obj_euler_rot{ + "Rotate Euler by Euler/Object", [](const float3 &input, const float3 &rotation) { + float input_mat[3][3]; + eul_to_mat3(input_mat, input); + float rot_mat[3][3]; + eul_to_mat3(rot_mat, rotation); + float mat_res[3][3]; + mul_m3_m3m3(mat_res, rot_mat, input_mat); + float3 result; + mat3_to_eul(result, mat_res); + return result; + }}; + static fn::CustomMF_SI_SI_SI_SO<float3, float3, float, float3> obj_AA_rot{ + "Rotate Euler by AxisAngle/Object", + [](const float3 &input, const float3 &axis, float angle) { + float input_mat[3][3]; + eul_to_mat3(input_mat, input); + float rot_mat[3][3]; + axis_angle_to_mat3(rot_mat, axis, angle); + float mat_res[3][3]; + mul_m3_m3m3(mat_res, rot_mat, input_mat); + float3 result; + mat3_to_eul(result, mat_res); + return result; + }}; + static fn::CustomMF_SI_SI_SO<float3, float3, float3> point_euler_rot{ + "Rotate Euler by Euler/Point", [](const float3 &input, const float3 &rotation) { + float input_mat[3][3]; + eul_to_mat3(input_mat, input); + float rot_mat[3][3]; + eul_to_mat3(rot_mat, rotation); + float mat_res[3][3]; + mul_m3_m3m3(mat_res, input_mat, rot_mat); + float3 result; + mat3_to_eul(result, mat_res); + return result; + }}; + static fn::CustomMF_SI_SI_SI_SO<float3, float3, float, float3> point_AA_rot{ + "Rotate Euler by AxisAngle/Point", [](const float3 &input, const float3 &axis, float angle) { + float input_mat[3][3]; + eul_to_mat3(input_mat, input); + float rot_mat[3][3]; + axis_angle_to_mat3(rot_mat, axis, angle); + float mat_res[3][3]; + mul_m3_m3m3(mat_res, input_mat, rot_mat); + float3 result; + mat3_to_eul(result, mat_res); + return result; + }}; + short type = bnode.custom1; + short space = bnode.custom2; + if (type == FN_NODE_ROTATE_EULER_TYPE_AXIS_ANGLE) { + return space == FN_NODE_ROTATE_EULER_SPACE_OBJECT ? &obj_AA_rot : &point_AA_rot; + } + if (type == FN_NODE_ROTATE_EULER_TYPE_EULER) { + return space == FN_NODE_ROTATE_EULER_SPACE_OBJECT ? &obj_euler_rot : &point_euler_rot; + } + BLI_assert_unreachable(); + return nullptr; +} + +static void fn_node_rotate_euler_build_multi_function(NodeMultiFunctionBuilder &builder) +{ + const fn::MultiFunction *fn = get_multi_function(builder.node()); + builder.set_matching_fn(fn); +} + +} // namespace blender::nodes + +void register_node_type_fn_rotate_euler() +{ + static bNodeType ntype; + fn_node_type_base(&ntype, FN_NODE_ROTATE_EULER, "Rotate Euler", NODE_CLASS_CONVERTER, 0); + ntype.declare = blender::nodes::fn_node_rotate_euler_declare; + ntype.draw_buttons = blender::nodes::fn_node_rotate_euler_layout; + node_type_update(&ntype, blender::nodes::fn_node_rotate_euler_update); + ntype.build_multi_function = blender::nodes::fn_node_rotate_euler_build_multi_function; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/function/nodes/node_fn_string_length.cc b/source/blender/nodes/function/nodes/node_fn_string_length.cc index a0f85dfd2bf..89038629c3c 100644 --- a/source/blender/nodes/function/nodes/node_fn_string_length.cc +++ b/source/blender/nodes/function/nodes/node_fn_string_length.cc @@ -24,6 +24,7 @@ namespace blender::nodes { static void fn_node_string_length_declare(NodeDeclarationBuilder &b) { + b.is_function_node(); b.add_input<decl::String>("String"); b.add_output<decl::Int>("Length"); }; diff --git a/source/blender/nodes/function/nodes/node_fn_string_substring.cc b/source/blender/nodes/function/nodes/node_fn_string_substring.cc index 55a01093ae9..b91171923d6 100644 --- a/source/blender/nodes/function/nodes/node_fn_string_substring.cc +++ b/source/blender/nodes/function/nodes/node_fn_string_substring.cc @@ -22,6 +22,7 @@ namespace blender::nodes { static void fn_node_string_substring_declare(NodeDeclarationBuilder &b) { + b.is_function_node(); b.add_input<decl::String>("String"); b.add_input<decl::Int>("Position"); b.add_input<decl::Int>("Length").min(0); diff --git a/source/blender/nodes/function/nodes/node_fn_value_to_string.cc b/source/blender/nodes/function/nodes/node_fn_value_to_string.cc index c1e6373cb6d..56206af2eb2 100644 --- a/source/blender/nodes/function/nodes/node_fn_value_to_string.cc +++ b/source/blender/nodes/function/nodes/node_fn_value_to_string.cc @@ -21,6 +21,7 @@ namespace blender::nodes { static void fn_node_value_to_string_declare(NodeDeclarationBuilder &b) { + b.is_function_node(); b.add_input<decl::Float>("Value"); b.add_input<decl::Int>("Decimals").min(0); b.add_output<decl::String>("String"); diff --git a/source/blender/nodes/geometry/node_geometry_tree.cc b/source/blender/nodes/geometry/node_geometry_tree.cc index d6b23c38ee4..20b610a4db9 100644 --- a/source/blender/nodes/geometry/node_geometry_tree.cc +++ b/source/blender/nodes/geometry/node_geometry_tree.cc @@ -119,7 +119,7 @@ void register_node_tree_type_geo(void) tt->type = NTREE_GEOMETRY; strcpy(tt->idname, "GeometryNodeTree"); strcpy(tt->ui_name, N_("Geometry Node Editor")); - tt->ui_icon = 0; /* defined in drawnode.c */ + tt->ui_icon = 0; /* Defined in `drawnode.c`. */ strcpy(tt->ui_description, N_("Geometry nodes")); tt->rna_ext.srna = &RNA_GeometryNodeTree; tt->update = geometry_node_tree_update; diff --git a/source/blender/nodes/geometry/node_geometry_util.hh b/source/blender/nodes/geometry/node_geometry_util.hh index 015ac0de002..5896b5bd6cc 100644 --- a/source/blender/nodes/geometry/node_geometry_util.hh +++ b/source/blender/nodes/geometry/node_geometry_util.hh @@ -65,7 +65,9 @@ Mesh *create_grid_mesh(const int verts_x, Mesh *create_cylinder_or_cone_mesh(const float radius_top, const float radius_bottom, const float depth, - const int verts_num, + const int circle_segments, + const int side_segments, + const int fill_segments, const GeometryNodeMeshCircleFillType fill_type); Mesh *create_cuboid_mesh(float3 size, int verts_x, int verts_y, int verts_z); diff --git a/source/blender/nodes/geometry/nodes/node_geo_align_rotation_to_vector.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_align_rotation_to_vector.cc index d0bb906e8af..d0bb906e8af 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_align_rotation_to_vector.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_align_rotation_to_vector.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_clamp.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_clamp.cc index 97070184609..2e931a2da98 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_clamp.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_clamp.cc @@ -269,7 +269,7 @@ void register_node_type_geo_attribute_clamp() static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_LECAGY_ATTRIBUTE_CLAMP, "Attribute Clamp", NODE_CLASS_ATTRIBUTE, 0); + &ntype, GEO_NODE_LEGACY_ATTRIBUTE_CLAMP, "Attribute Clamp", NODE_CLASS_ATTRIBUTE, 0); node_type_init(&ntype, blender::nodes::geo_node_attribute_clamp_init); node_type_update(&ntype, blender::nodes::geo_node_attribute_clamp_update); ntype.declare = blender::nodes::geo_node_attribute_clamp_declare; diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_color_ramp.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_color_ramp.cc index aa054af3acd..aa054af3acd 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_color_ramp.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_color_ramp.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_combine_xyz.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_combine_xyz.cc index 569d5a824ca..569d5a824ca 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_combine_xyz.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_combine_xyz.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_compare.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_compare.cc index 0b9708dae14..0b9708dae14 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_compare.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_compare.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_convert.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_convert.cc index a2382aa9d25..a2382aa9d25 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_convert.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_convert.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_curve_map.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_curve_map.cc index b9621b4ae92..b9621b4ae92 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_curve_map.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_curve_map.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_fill.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_fill.cc index 3c50ae5c837..3c50ae5c837 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_fill.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_fill.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_map_range.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_map_range.cc index 0ea3bbe1e45..0ea3bbe1e45 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_map_range.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_map_range.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_math.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_math.cc index efa09215b45..efa09215b45 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_math.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_math.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_mix.cc index 74e05cb997d..74e05cb997d 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_mix.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_proximity.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_proximity.cc index 0cf411343cf..6120118f611 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_proximity.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_proximity.cc @@ -232,7 +232,7 @@ static void geo_node_attribute_proximity_exec(GeoNodeExecParams params) } // namespace blender::nodes -void register_node_type_geo_attribute_proximity() +void register_node_type_geo_legacy_attribute_proximity() { static bNodeType ntype; diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_randomize.cc index 60b9910399c..2e6ba456725 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_randomize.cc @@ -25,7 +25,7 @@ namespace blender::nodes { -static void geo_node_attribute_randomize_declare(NodeDeclarationBuilder &b) +static void geo_node_legacy_attribute_randomize_declare(NodeDeclarationBuilder &b) { b.add_input<decl::Geometry>("Geometry"); b.add_input<decl::String>("Attribute"); @@ -39,15 +39,15 @@ static void geo_node_attribute_randomize_declare(NodeDeclarationBuilder &b) b.add_output<decl::Geometry>("Geometry"); } -static void geo_node_attribute_random_layout(uiLayout *layout, - bContext *UNUSED(C), - PointerRNA *ptr) +static void geo_node_legacy_attribute_random_layout(uiLayout *layout, + bContext *UNUSED(C), + PointerRNA *ptr) { uiItemR(layout, ptr, "data_type", 0, "", ICON_NONE); uiItemR(layout, ptr, "operation", 0, "", ICON_NONE); } -static void geo_node_attribute_randomize_init(bNodeTree *UNUSED(tree), bNode *node) +static void geo_node_legacy_attribute_randomize_init(bNodeTree *UNUSED(tree), bNode *node) { NodeAttributeRandomize *data = (NodeAttributeRandomize *)MEM_callocN( sizeof(NodeAttributeRandomize), __func__); @@ -57,7 +57,7 @@ static void geo_node_attribute_randomize_init(bNodeTree *UNUSED(tree), bNode *no node->storage = data; } -static void geo_node_attribute_randomize_update(bNodeTree *UNUSED(ntree), bNode *node) +static void geo_node_legacy_attribute_randomize_update(bNodeTree *UNUSED(ntree), bNode *node) { bNodeSocket *sock_min_vector = (bNodeSocket *)BLI_findlink(&node->inputs, 2); bNodeSocket *sock_max_vector = sock_min_vector->next; @@ -280,7 +280,7 @@ static void randomize_attribute_on_component(GeometryComponent &component, attribute.save(); } -static void geo_node_random_attribute_exec(GeoNodeExecParams params) +static void geo_node_legacy_random_attribute_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); const std::string attribute_name = params.get_input<std::string>("Attribute"); @@ -326,18 +326,18 @@ static void geo_node_random_attribute_exec(GeoNodeExecParams params) } // namespace blender::nodes -void register_node_type_geo_attribute_randomize() +void register_node_type_geo_legacy_attribute_randomize() { static bNodeType ntype; geo_node_type_base( &ntype, GEO_NODE_LEGACY_ATTRIBUTE_RANDOMIZE, "Attribute Randomize", NODE_CLASS_ATTRIBUTE, 0); - node_type_init(&ntype, blender::nodes::geo_node_attribute_randomize_init); - node_type_update(&ntype, blender::nodes::geo_node_attribute_randomize_update); + node_type_init(&ntype, blender::nodes::geo_node_legacy_attribute_randomize_init); + node_type_update(&ntype, blender::nodes::geo_node_legacy_attribute_randomize_update); - ntype.declare = blender::nodes::geo_node_attribute_randomize_declare; - ntype.geometry_node_execute = blender::nodes::geo_node_random_attribute_exec; - ntype.draw_buttons = blender::nodes::geo_node_attribute_random_layout; + ntype.declare = blender::nodes::geo_node_legacy_attribute_randomize_declare; + ntype.geometry_node_execute = blender::nodes::geo_node_legacy_random_attribute_exec; + ntype.draw_buttons = blender::nodes::geo_node_legacy_attribute_random_layout; node_type_storage( &ntype, "NodeAttributeRandomize", node_free_standard_storage, node_copy_standard_storage); nodeRegisterType(&ntype); diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_sample_texture.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_sample_texture.cc index 52f97475941..52f97475941 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_sample_texture.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_sample_texture.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_separate_xyz.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_separate_xyz.cc index de0090406c6..de0090406c6 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_separate_xyz.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_separate_xyz.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_transfer.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_transfer.cc index 874350cd714..f187ee39b94 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_transfer.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_transfer.cc @@ -404,7 +404,7 @@ static void transfer_attribute_nearest(const GeometrySet &src_geometry, data_type); for (const int i : IndexRange(tot_samples)) { if (pointcloud_distances_sq[i] < mesh_distances_sq[i]) { - /* Pointcloud point is closer. */ + /* Point-cloud point is closer. */ const int index = pointcloud_indices[i]; pointcloud_src_attribute.varray->get(index, buffer); dst_attribute->set_by_relocate(i, buffer); diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_math.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_vector_math.cc index 59903050f88..59903050f88 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_math.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_vector_math.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_rotate.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_vector_rotate.cc index adaa4de3029..0c515fa63fb 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_vector_rotate.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_attribute_vector_rotate.cc @@ -332,7 +332,7 @@ void register_node_type_geo_attribute_vector_rotate() static bNodeType ntype; geo_node_type_base(&ntype, - GEO_NODE_ATTRIBUTE_VECTOR_ROTATE, + GEO_NODE_LEGACY_ATTRIBUTE_VECTOR_ROTATE, "Attribute Vector Rotate", NODE_CLASS_ATTRIBUTE, 0); diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_endpoints.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_curve_endpoints.cc index 7853c5aa04a..65d22eca39c 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_endpoints.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_curve_endpoints.cc @@ -212,7 +212,8 @@ void register_node_type_geo_curve_endpoints() { static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_CURVE_ENDPOINTS, "Curve Endpoints", NODE_CLASS_GEOMETRY, 0); + geo_node_type_base( + &ntype, GEO_NODE_LEGACY_CURVE_ENDPOINTS, "Curve Endpoints", NODE_CLASS_GEOMETRY, 0); ntype.declare = blender::nodes::geo_node_curve_endpoints_declare; ntype.geometry_node_execute = blender::nodes::geo_node_curve_endpoints_exec; diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_curve_reverse.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_curve_reverse.cc new file mode 100644 index 00000000000..d1c81333c30 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_curve_reverse.cc @@ -0,0 +1,71 @@ +/* + * 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_task.hh" + +#include "BKE_spline.hh" + +#include "node_geometry_util.hh" + +namespace blender::nodes { + +static void geo_node_curve_reverse_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>("Curve"); + b.add_input<decl::String>("Selection"); + b.add_output<decl::Geometry>("Curve"); +} + +static void geo_node_curve_reverse_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"); + geometry_set = bke::geometry_set_realize_instances(geometry_set); + if (!geometry_set.has_curve()) { + params.set_output("Curve", geometry_set); + return; + } + + /* Retrieve data for write access so we can avoid new allocations for the reversed data. */ + CurveComponent &curve_component = geometry_set.get_component_for_write<CurveComponent>(); + CurveEval &curve = *curve_component.get_for_write(); + MutableSpan<SplinePtr> splines = curve.splines(); + + const std::string selection_name = params.extract_input<std::string>("Selection"); + GVArray_Typed<bool> selection = curve_component.attribute_get_for_read( + selection_name, ATTR_DOMAIN_CURVE, true); + + threading::parallel_for(splines.index_range(), 128, [&](IndexRange range) { + for (const int i : range) { + if (selection[i]) { + splines[i]->reverse(); + } + } + }); + + params.set_output("Curve", geometry_set); +} + +} // namespace blender::nodes + +void register_node_type_geo_legacy_curve_reverse() +{ + static bNodeType ntype; + geo_node_type_base( + &ntype, GEO_NODE_LEGACY_CURVE_REVERSE, "Curve Reverse", NODE_CLASS_GEOMETRY, 0); + ntype.declare = blender::nodes::geo_node_curve_reverse_declare; + ntype.geometry_node_execute = blender::nodes::geo_node_curve_reverse_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_select_by_handle_type.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_curve_select_by_handle_type.cc index dfcae2e65b0..dfcae2e65b0 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_select_by_handle_type.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_curve_select_by_handle_type.cc diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_curve_set_handles.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_curve_set_handles.cc new file mode 100644 index 00000000000..339029336d9 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_curve_set_handles.cc @@ -0,0 +1,144 @@ +/* + * 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 "BKE_spline.hh" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +namespace blender::nodes { + +static void geo_node_curve_set_handles_decalre(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>("Curve"); + b.add_input<decl::String>("Selection"); + b.add_output<decl::Geometry>("Curve"); +} + +static void geo_node_curve_set_handles_layout(uiLayout *layout, + bContext *UNUSED(C), + PointerRNA *ptr) +{ + uiItemR(layout, ptr, "mode", UI_ITEM_R_EXPAND, nullptr, ICON_NONE); + uiItemR(layout, ptr, "handle_type", 0, "", ICON_NONE); +} + +static void geo_node_curve_set_handles_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeGeometryCurveSetHandles *data = (NodeGeometryCurveSetHandles *)MEM_callocN( + sizeof(NodeGeometryCurveSetHandles), __func__); + + data->handle_type = GEO_NODE_CURVE_HANDLE_AUTO; + data->mode = GEO_NODE_CURVE_HANDLE_LEFT | GEO_NODE_CURVE_HANDLE_RIGHT; + node->storage = data; +} + +static BezierSpline::HandleType handle_type_from_input_type(GeometryNodeCurveHandleType type) +{ + switch (type) { + case GEO_NODE_CURVE_HANDLE_AUTO: + return BezierSpline::HandleType::Auto; + case GEO_NODE_CURVE_HANDLE_ALIGN: + return BezierSpline::HandleType::Align; + case GEO_NODE_CURVE_HANDLE_FREE: + return BezierSpline::HandleType::Free; + case GEO_NODE_CURVE_HANDLE_VECTOR: + return BezierSpline::HandleType::Vector; + } + BLI_assert_unreachable(); + return BezierSpline::HandleType::Auto; +} + +static void geo_node_curve_set_handles_exec(GeoNodeExecParams params) +{ + const NodeGeometryCurveSetHandles *node_storage = + (NodeGeometryCurveSetHandles *)params.node().storage; + const GeometryNodeCurveHandleType type = (GeometryNodeCurveHandleType)node_storage->handle_type; + const GeometryNodeCurveHandleMode mode = (GeometryNodeCurveHandleMode)node_storage->mode; + + GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"); + geometry_set = bke::geometry_set_realize_instances(geometry_set); + if (!geometry_set.has_curve()) { + params.set_output("Curve", geometry_set); + return; + } + + /* Retrieve data for write access so we can avoid new allocations for the handles data. */ + CurveComponent &curve_component = geometry_set.get_component_for_write<CurveComponent>(); + CurveEval &curve = *curve_component.get_for_write(); + MutableSpan<SplinePtr> splines = curve.splines(); + + const std::string selection_name = params.extract_input<std::string>("Selection"); + GVArray_Typed<bool> selection = curve_component.attribute_get_for_read( + selection_name, ATTR_DOMAIN_POINT, true); + + const BezierSpline::HandleType new_handle_type = handle_type_from_input_type(type); + int point_index = 0; + bool has_bezier_spline = false; + for (SplinePtr &spline : splines) { + if (spline->type() != Spline::Type::Bezier) { + point_index += spline->positions().size(); + continue; + } + + BezierSpline &bezier_spline = static_cast<BezierSpline &>(*spline); + if (ELEM(new_handle_type, BezierSpline::HandleType::Free, BezierSpline::HandleType::Align)) { + /* In this case the automatically calculated handle types need to be "baked", because + * they're possibly changing from a type that is calculated automatically to a type that + * is positioned manually. */ + bezier_spline.ensure_auto_handles(); + } + has_bezier_spline = true; + for (int i_point : IndexRange(bezier_spline.size())) { + if (selection[point_index]) { + if (mode & GEO_NODE_CURVE_HANDLE_LEFT) { + bezier_spline.handle_types_left()[i_point] = new_handle_type; + } + if (mode & GEO_NODE_CURVE_HANDLE_RIGHT) { + bezier_spline.handle_types_right()[i_point] = new_handle_type; + } + } + point_index++; + } + bezier_spline.mark_cache_invalid(); + } + + if (!has_bezier_spline) { + params.error_message_add(NodeWarningType::Info, TIP_("No Bezier splines in input curve")); + } + + params.set_output("Curve", geometry_set); +} +} // namespace blender::nodes + +void register_node_type_geo_legacy_curve_set_handles() +{ + static bNodeType ntype; + geo_node_type_base( + &ntype, GEO_NODE_LEGACY_CURVE_SET_HANDLES, "Set Handle Type", NODE_CLASS_GEOMETRY, 0); + ntype.declare = blender::nodes::geo_node_curve_set_handles_decalre; + ntype.geometry_node_execute = blender::nodes::geo_node_curve_set_handles_exec; + node_type_init(&ntype, blender::nodes::geo_node_curve_set_handles_init); + node_type_storage(&ntype, + "NodeGeometryCurveSetHandles", + node_free_standard_storage, + node_copy_standard_storage); + ntype.draw_buttons = blender::nodes::geo_node_curve_set_handles_layout; + + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_curve_spline_type.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_curve_spline_type.cc new file mode 100644 index 00000000000..44522e990d9 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_curve_spline_type.cc @@ -0,0 +1,302 @@ +/* + * 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 "BKE_spline.hh" + +#include "BLI_task.hh" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +namespace blender::nodes { + +static void geo_node_legacy_curve_spline_type_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>("Curve"); + b.add_input<decl::String>("Selection"); + b.add_output<decl::Geometry>("Curve"); +} + +static void geo_node_legacy_curve_spline_type_layout(uiLayout *layout, + bContext *UNUSED(C), + PointerRNA *ptr) +{ + uiItemR(layout, ptr, "spline_type", 0, "", ICON_NONE); +} + +static void geo_node_legacy_curve_spline_type_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeGeometryCurveSplineType *data = (NodeGeometryCurveSplineType *)MEM_callocN( + sizeof(NodeGeometryCurveSplineType), __func__); + + data->spline_type = GEO_NODE_SPLINE_TYPE_POLY; + node->storage = data; +} + +template<class T> +static void scale_input_assign(const Span<T> input, + const int scale, + const int offset, + const MutableSpan<T> r_output) +{ + for (const int i : IndexRange(r_output.size())) { + r_output[i] = input[i * scale + offset]; + } +} + +template<class T> +static void scale_output_assign(const Span<T> input, + const int scale, + const int offset, + const MutableSpan<T> &r_output) +{ + for (const int i : IndexRange(input.size())) { + r_output[i * scale + offset] = input[i]; + } +} + +template<typename CopyFn> +static void copy_attributes(const Spline &input_spline, Spline &output_spline, CopyFn copy_fn) +{ + input_spline.attributes.foreach_attribute( + [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { + std::optional<GSpan> src = input_spline.attributes.get_for_read(attribute_id); + BLI_assert(src); + if (!output_spline.attributes.create(attribute_id, meta_data.data_type)) { + BLI_assert_unreachable(); + return false; + } + std::optional<GMutableSpan> dst = output_spline.attributes.get_for_write(attribute_id); + if (!dst) { + BLI_assert_unreachable(); + return false; + } + + copy_fn(*src, *dst); + + return true; + }, + ATTR_DOMAIN_POINT); +} + +static SplinePtr convert_to_poly_spline(const Spline &input) +{ + std::unique_ptr<PolySpline> output = std::make_unique<PolySpline>(); + output->resize(input.positions().size()); + output->positions().copy_from(input.positions()); + output->radii().copy_from(input.radii()); + output->tilts().copy_from(input.tilts()); + Spline::copy_base_settings(input, *output); + output->attributes = input.attributes; + return output; +} + +static SplinePtr poly_to_nurbs(const Spline &input) +{ + std::unique_ptr<NURBSpline> output = std::make_unique<NURBSpline>(); + output->resize(input.positions().size()); + output->positions().copy_from(input.positions()); + output->radii().copy_from(input.radii()); + output->tilts().copy_from(input.tilts()); + output->weights().fill(1.0f); + output->set_resolution(12); + output->set_order(4); + Spline::copy_base_settings(input, *output); + output->knots_mode = NURBSpline::KnotsMode::Bezier; + output->attributes = input.attributes; + return output; +} + +static SplinePtr bezier_to_nurbs(const Spline &input) +{ + const BezierSpline &bezier_spline = static_cast<const BezierSpline &>(input); + std::unique_ptr<NURBSpline> output = std::make_unique<NURBSpline>(); + output->resize(input.size() * 3); + + scale_output_assign(bezier_spline.handle_positions_left(), 3, 0, output->positions()); + scale_output_assign(input.radii(), 3, 0, output->radii()); + scale_output_assign(input.tilts(), 3, 0, output->tilts()); + + scale_output_assign(bezier_spline.positions(), 3, 1, output->positions()); + scale_output_assign(input.radii(), 3, 1, output->radii()); + scale_output_assign(input.tilts(), 3, 1, output->tilts()); + + scale_output_assign(bezier_spline.handle_positions_right(), 3, 2, output->positions()); + scale_output_assign(input.radii(), 3, 2, output->radii()); + scale_output_assign(input.tilts(), 3, 2, output->tilts()); + + Spline::copy_base_settings(input, *output); + output->weights().fill(1.0f); + output->set_resolution(12); + output->set_order(4); + output->set_cyclic(input.is_cyclic()); + output->knots_mode = NURBSpline::KnotsMode::Bezier; + output->attributes.reallocate(output->size()); + copy_attributes(input, *output, [](GSpan src, GMutableSpan dst) { + attribute_math::convert_to_static_type(src.type(), [&](auto dummy) { + using T = decltype(dummy); + scale_output_assign<T>(src.typed<T>(), 3, 0, dst.typed<T>()); + scale_output_assign<T>(src.typed<T>(), 3, 1, dst.typed<T>()); + scale_output_assign<T>(src.typed<T>(), 3, 2, dst.typed<T>()); + }); + }); + return output; +} + +static SplinePtr poly_to_bezier(const Spline &input) +{ + std::unique_ptr<BezierSpline> output = std::make_unique<BezierSpline>(); + output->resize(input.size()); + output->positions().copy_from(input.positions()); + output->radii().copy_from(input.radii()); + output->tilts().copy_from(input.tilts()); + output->handle_types_left().fill(BezierSpline::HandleType::Vector); + output->handle_types_right().fill(BezierSpline::HandleType::Vector); + output->set_resolution(12); + Spline::copy_base_settings(input, *output); + output->attributes = input.attributes; + return output; +} + +static SplinePtr nurbs_to_bezier(const Spline &input) +{ + const NURBSpline &nurbs_spline = static_cast<const NURBSpline &>(input); + std::unique_ptr<BezierSpline> output = std::make_unique<BezierSpline>(); + output->resize(input.size() / 3); + scale_input_assign<float3>(input.positions(), 3, 1, output->positions()); + scale_input_assign<float3>(input.positions(), 3, 0, output->handle_positions_left()); + scale_input_assign<float3>(input.positions(), 3, 2, output->handle_positions_right()); + scale_input_assign<float>(input.radii(), 3, 2, output->radii()); + scale_input_assign<float>(input.tilts(), 3, 2, output->tilts()); + output->handle_types_left().fill(BezierSpline::HandleType::Align); + output->handle_types_right().fill(BezierSpline::HandleType::Align); + output->set_resolution(nurbs_spline.resolution()); + Spline::copy_base_settings(input, *output); + output->attributes.reallocate(output->size()); + copy_attributes(input, *output, [](GSpan src, GMutableSpan dst) { + attribute_math::convert_to_static_type(src.type(), [&](auto dummy) { + using T = decltype(dummy); + scale_input_assign<T>(src.typed<T>(), 3, 1, dst.typed<T>()); + }); + }); + return output; +} + +static SplinePtr convert_to_bezier(const Spline &input, GeoNodeExecParams params) +{ + switch (input.type()) { + case Spline::Type::Bezier: + return input.copy(); + case Spline::Type::Poly: + return poly_to_bezier(input); + case Spline::Type::NURBS: + if (input.size() < 6) { + params.error_message_add( + NodeWarningType::Info, + TIP_("NURBS must have minimum of 6 points for Bezier Conversion")); + return input.copy(); + } + else { + if (input.size() % 3 != 0) { + params.error_message_add(NodeWarningType::Info, + TIP_("NURBS must have multiples of 3 points for full Bezier " + "conversion, curve truncated")); + } + return nurbs_to_bezier(input); + } + } + BLI_assert_unreachable(); + return {}; +} + +static SplinePtr convert_to_nurbs(const Spline &input) +{ + switch (input.type()) { + case Spline::Type::NURBS: + return input.copy(); + case Spline::Type::Bezier: + return bezier_to_nurbs(input); + case Spline::Type::Poly: + return poly_to_nurbs(input); + } + BLI_assert_unreachable(); + return {}; +} + +static void geo_node_legacy_curve_spline_type_exec(GeoNodeExecParams params) +{ + const NodeGeometryCurveSplineType *storage = + (const NodeGeometryCurveSplineType *)params.node().storage; + const GeometryNodeSplineType output_type = (const GeometryNodeSplineType)storage->spline_type; + + GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"); + geometry_set = bke::geometry_set_realize_instances(geometry_set); + if (!geometry_set.has_curve()) { + params.set_output("Curve", geometry_set); + return; + } + + const CurveComponent *curve_component = geometry_set.get_component_for_read<CurveComponent>(); + const CurveEval &curve = *curve_component->get_for_read(); + + const std::string selection_name = params.extract_input<std::string>("Selection"); + GVArray_Typed<bool> selection = curve_component->attribute_get_for_read( + selection_name, ATTR_DOMAIN_CURVE, true); + + std::unique_ptr<CurveEval> new_curve = std::make_unique<CurveEval>(); + for (const int i : curve.splines().index_range()) { + if (selection[i]) { + switch (output_type) { + case GEO_NODE_SPLINE_TYPE_POLY: + new_curve->add_spline(convert_to_poly_spline(*curve.splines()[i])); + break; + case GEO_NODE_SPLINE_TYPE_BEZIER: + new_curve->add_spline(convert_to_bezier(*curve.splines()[i], params)); + break; + case GEO_NODE_SPLINE_TYPE_NURBS: + new_curve->add_spline(convert_to_nurbs(*curve.splines()[i])); + break; + } + } + else { + new_curve->add_spline(curve.splines()[i]->copy()); + } + } + + new_curve->attributes = curve.attributes; + params.set_output("Curve", GeometrySet::create_with_curve(new_curve.release())); +} + +} // namespace blender::nodes + +void register_node_type_geo_legacy_curve_spline_type() +{ + static bNodeType ntype; + geo_node_type_base( + &ntype, GEO_NODE_LEGACY_CURVE_SPLINE_TYPE, "Set Spline Type", NODE_CLASS_GEOMETRY, 0); + ntype.declare = blender::nodes::geo_node_legacy_curve_spline_type_declare; + ntype.geometry_node_execute = blender::nodes::geo_node_legacy_curve_spline_type_exec; + node_type_init(&ntype, blender::nodes::geo_node_legacy_curve_spline_type_init); + node_type_storage(&ntype, + "NodeGeometryCurveSplineType", + node_free_standard_storage, + node_copy_standard_storage); + ntype.draw_buttons = blender::nodes::geo_node_legacy_curve_spline_type_layout; + + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/legacy/node_geo_curve_subdivide.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_curve_subdivide.cc new file mode 100644 index 00000000000..f32a68bc042 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_curve_subdivide.cc @@ -0,0 +1,392 @@ +/* + * 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_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; + +namespace blender::nodes { + +static void geo_node_curve_subdivide_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>("Geometry"); + b.add_input<decl::String>("Cuts"); + b.add_input<decl::Int>("Cuts", "Cuts_001").default_value(1).min(0).max(1000); + b.add_output<decl::Geometry>("Geometry"); +} + +static void geo_node_curve_subdivide_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiLayoutSetPropSep(layout, true); + uiLayoutSetPropDecorate(layout, false); + uiItemR(layout, ptr, "cuts_type", 0, IFACE_("Cuts"), ICON_NONE); +} + +static void geo_node_curve_subdivide_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeGeometryCurveSubdivide *data = (NodeGeometryCurveSubdivide *)MEM_callocN( + sizeof(NodeGeometryCurveSubdivide), __func__); + + data->cuts_type = GEO_NODE_ATTRIBUTE_INPUT_INTEGER; + node->storage = data; +} + +static void geo_node_curve_subdivide_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeGeometryPointTranslate &node_storage = *(NodeGeometryPointTranslate *)node->storage; + + update_attribute_input_socket_availabilities( + *node, "Cuts", (GeometryNodeAttributeInputMode)node_storage.input_type); +} + +static Array<int> get_subdivided_offsets(const Spline &spline, + const VArray<int> &cuts, + const int spline_offset) +{ + Array<int> offsets(spline.segments_size() + 1); + int offset = 0; + for (const int i : IndexRange(spline.segments_size())) { + offsets[i] = offset; + offset = offset + std::max(cuts[spline_offset + i], 0) + 1; + } + offsets.last() = offset; + return offsets; +} + +template<typename T> +static void subdivide_attribute(Span<T> src, + const Span<int> offsets, + const bool is_cyclic, + MutableSpan<T> dst) +{ + const int src_size = src.size(); + threading::parallel_for(IndexRange(src_size - 1), 1024, [&](IndexRange range) { + for (const int i : range) { + const int cuts = offsets[i + 1] - offsets[i]; + dst[offsets[i]] = src[i]; + const float factor_delta = 1.0f / (cuts + 1.0f); + for (const int cut : IndexRange(cuts)) { + const float factor = (cut + 1) * factor_delta; + dst[offsets[i] + cut] = attribute_math::mix2(factor, src[i], src[i + 1]); + } + } + }); + + if (is_cyclic) { + const int i = src_size - 1; + const int cuts = offsets[i + 1] - offsets[i]; + dst[offsets[i]] = src.last(); + const float factor_delta = 1.0f / (cuts + 1.0f); + for (const int cut : IndexRange(cuts)) { + const float factor = (cut + 1) * factor_delta; + dst[offsets[i] + cut] = attribute_math::mix2(factor, src.last(), src.first()); + } + } + else { + dst.last() = src.last(); + } +} + +/** + * In order to generate a Bezier spline with the same shape as the input spline, apply the + * De Casteljau algorithm iteratively for the provided number of cuts, constantly updating the + * previous result point's right handle and the left handle at the end of the segment. + * + * \note Non-vector segments in the result spline are given free handles. This could possibly be + * improved with another pass that sets handles to aligned where possible, but currently that does + * not provide much benefit for the increased complexity. + */ +static void subdivide_bezier_segment(const BezierSpline &src, + const int index, + const int offset, + const int result_size, + Span<float3> src_positions, + Span<float3> src_handles_left, + Span<float3> src_handles_right, + MutableSpan<float3> dst_positions, + MutableSpan<float3> dst_handles_left, + MutableSpan<float3> dst_handles_right, + MutableSpan<BezierSpline::HandleType> dst_type_left, + MutableSpan<BezierSpline::HandleType> dst_type_right) +{ + const bool is_last_cyclic_segment = index == (src.size() - 1); + const int next_index = is_last_cyclic_segment ? 0 : index + 1; + + /* The first point in the segment is always copied. */ + dst_positions[offset] = src_positions[index]; + + if (src.segment_is_vector(index)) { + if (is_last_cyclic_segment) { + dst_type_left.first() = BezierSpline::HandleType::Vector; + } + dst_type_left.slice(offset + 1, result_size).fill(BezierSpline::HandleType::Vector); + dst_type_right.slice(offset, result_size).fill(BezierSpline::HandleType::Vector); + + const float factor_delta = 1.0f / result_size; + for (const int cut : IndexRange(result_size)) { + const float factor = cut * factor_delta; + dst_positions[offset + cut] = attribute_math::mix2( + factor, src_positions[index], src_positions[next_index]); + } + } + else { + if (is_last_cyclic_segment) { + dst_type_left.first() = BezierSpline::HandleType::Free; + } + dst_type_left.slice(offset + 1, result_size).fill(BezierSpline::HandleType::Free); + dst_type_right.slice(offset, result_size).fill(BezierSpline::HandleType::Free); + + const int i_segment_last = is_last_cyclic_segment ? 0 : offset + result_size; + + /* Create a Bezier segment to update iteratively for every subdivision + * and references to the meaningful values for ease of use. */ + BezierSpline temp; + temp.resize(2); + float3 &segment_start = temp.positions().first(); + float3 &segment_end = temp.positions().last(); + float3 &handle_prev = temp.handle_positions_right().first(); + float3 &handle_next = temp.handle_positions_left().last(); + segment_start = src_positions[index]; + segment_end = src_positions[next_index]; + handle_prev = src_handles_right[index]; + handle_next = src_handles_left[next_index]; + + for (const int cut : IndexRange(result_size - 1)) { + const float parameter = 1.0f / (result_size - cut); + const BezierSpline::InsertResult insert = temp.calculate_segment_insertion(0, 1, parameter); + + /* Copy relevant temporary data to the result. */ + dst_handles_right[offset + cut] = insert.handle_prev; + dst_handles_left[offset + cut + 1] = insert.left_handle; + dst_positions[offset + cut + 1] = insert.position; + + /* Update the segment to prepare it for the next subdivision. */ + segment_start = insert.position; + handle_prev = insert.right_handle; + handle_next = insert.handle_next; + } + + /* Copy the handles for the last segment from the temporary spline. */ + dst_handles_right[offset + result_size - 1] = handle_prev; + dst_handles_left[i_segment_last] = handle_next; + } +} + +static void subdivide_bezier_spline(const BezierSpline &src, + const Span<int> offsets, + BezierSpline &dst) +{ + Span<float3> src_positions = src.positions(); + Span<float3> src_handles_left = src.handle_positions_left(); + Span<float3> src_handles_right = src.handle_positions_right(); + MutableSpan<float3> dst_positions = dst.positions(); + MutableSpan<float3> dst_handles_left = dst.handle_positions_left(); + MutableSpan<float3> dst_handles_right = dst.handle_positions_right(); + MutableSpan<BezierSpline::HandleType> dst_type_left = dst.handle_types_left(); + MutableSpan<BezierSpline::HandleType> dst_type_right = dst.handle_types_right(); + + threading::parallel_for(IndexRange(src.size() - 1), 512, [&](IndexRange range) { + for (const int i : range) { + subdivide_bezier_segment(src, + i, + offsets[i], + offsets[i + 1] - offsets[i], + src_positions, + src_handles_left, + src_handles_right, + dst_positions, + dst_handles_left, + dst_handles_right, + dst_type_left, + dst_type_right); + } + }); + + if (src.is_cyclic()) { + const int i_last = src.size() - 1; + subdivide_bezier_segment(src, + i_last, + offsets[i_last], + offsets.last() - offsets[i_last], + src_positions, + src_handles_left, + src_handles_right, + dst_positions, + dst_handles_left, + dst_handles_right, + dst_type_left, + dst_type_right); + } + else { + dst_positions.last() = src_positions.last(); + } +} + +static void subdivide_builtin_attributes(const Spline &src_spline, + const Span<int> offsets, + Spline &dst_spline) +{ + const bool is_cyclic = src_spline.is_cyclic(); + subdivide_attribute<float>(src_spline.radii(), offsets, is_cyclic, dst_spline.radii()); + subdivide_attribute<float>(src_spline.tilts(), offsets, is_cyclic, dst_spline.tilts()); + switch (src_spline.type()) { + case Spline::Type::Poly: { + const PolySpline &src = static_cast<const PolySpline &>(src_spline); + PolySpline &dst = static_cast<PolySpline &>(dst_spline); + subdivide_attribute<float3>(src.positions(), offsets, is_cyclic, dst.positions()); + break; + } + case Spline::Type::Bezier: { + const BezierSpline &src = static_cast<const BezierSpline &>(src_spline); + BezierSpline &dst = static_cast<BezierSpline &>(dst_spline); + subdivide_bezier_spline(src, offsets, dst); + dst.mark_cache_invalid(); + break; + } + case Spline::Type::NURBS: { + const NURBSpline &src = static_cast<const NURBSpline &>(src_spline); + NURBSpline &dst = static_cast<NURBSpline &>(dst_spline); + subdivide_attribute<float3>(src.positions(), offsets, is_cyclic, dst.positions()); + subdivide_attribute<float>(src.weights(), offsets, is_cyclic, dst.weights()); + break; + } + } +} + +static void subdivide_dynamic_attributes(const Spline &src_spline, + const Span<int> offsets, + Spline &dst_spline) +{ + const bool is_cyclic = src_spline.is_cyclic(); + src_spline.attributes.foreach_attribute( + [&](const bke::AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { + std::optional<GSpan> src = src_spline.attributes.get_for_read(attribute_id); + BLI_assert(src); + + if (!dst_spline.attributes.create(attribute_id, meta_data.data_type)) { + /* Since the source spline of the same type had the attribute, adding it should work. */ + BLI_assert_unreachable(); + } + + std::optional<GMutableSpan> dst = dst_spline.attributes.get_for_write(attribute_id); + BLI_assert(dst); + + attribute_math::convert_to_static_type(dst->type(), [&](auto dummy) { + using T = decltype(dummy); + subdivide_attribute<T>(src->typed<T>(), offsets, is_cyclic, dst->typed<T>()); + }); + return true; + }, + ATTR_DOMAIN_POINT); +} + +static SplinePtr subdivide_spline(const Spline &spline, + const VArray<int> &cuts, + const int spline_offset) +{ + /* Since we expect to access each value many times, it should be worth it to make sure the + * attribute is a real span (especially considering the note below). Using the offset at each + * point facilitates subdividing in parallel later. */ + Array<int> offsets = get_subdivided_offsets(spline, cuts, spline_offset); + const int result_size = offsets.last() + int(!spline.is_cyclic()); + SplinePtr new_spline = spline.copy_only_settings(); + new_spline->resize(result_size); + subdivide_builtin_attributes(spline, offsets, *new_spline); + subdivide_dynamic_attributes(spline, offsets, *new_spline); + return new_spline; +} + +/** + * \note Passing the virtual array for the entire spline is possibly quite inefficient here when + * the attribute was on the point domain and stored separately for each spline already, and it + * prevents some other optimizations like skipping splines with a single attribute value of < 1. + * However, it allows the node to access builtin attribute easily, so it the makes most sense this + * way until the attribute API is refactored. + */ +static std::unique_ptr<CurveEval> subdivide_curve(const CurveEval &input_curve, + const VArray<int> &cuts) +{ + const Array<int> control_point_offsets = input_curve.control_point_offsets(); + const Span<SplinePtr> input_splines = input_curve.splines(); + + std::unique_ptr<CurveEval> output_curve = std::make_unique<CurveEval>(); + output_curve->resize(input_splines.size()); + output_curve->attributes = input_curve.attributes; + MutableSpan<SplinePtr> output_splines = output_curve->splines(); + + threading::parallel_for(input_splines.index_range(), 128, [&](IndexRange range) { + for (const int i : range) { + output_splines[i] = subdivide_spline(*input_splines[i], cuts, control_point_offsets[i]); + } + }); + + return output_curve; +} + +static void geo_node_subdivide_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", geometry_set); + return; + } + + const CurveComponent &component = *geometry_set.get_component_for_read<CurveComponent>(); + GVArray_Typed<int> cuts = params.get_input_attribute<int>( + "Cuts", component, ATTR_DOMAIN_POINT, 0); + if (cuts->is_single() && cuts->get_internal_single() < 1) { + params.set_output("Geometry", geometry_set); + return; + } + + std::unique_ptr<CurveEval> output_curve = subdivide_curve(*component.get_for_read(), *cuts); + + params.set_output("Geometry", GeometrySet::create_with_curve(output_curve.release())); +} + +} // namespace blender::nodes + +void register_node_type_geo_legacy_curve_subdivide() +{ + static bNodeType ntype; + + geo_node_type_base( + &ntype, GEO_NODE_LEGACY_CURVE_SUBDIVIDE, "Curve Subdivide", NODE_CLASS_GEOMETRY, 0); + ntype.declare = blender::nodes::geo_node_curve_subdivide_declare; + ntype.draw_buttons = blender::nodes::geo_node_curve_subdivide_layout; + node_type_storage(&ntype, + "NodeGeometryCurveSubdivide", + node_free_standard_storage, + node_copy_standard_storage); + node_type_init(&ntype, blender::nodes::geo_node_curve_subdivide_init); + node_type_update(&ntype, blender::nodes::geo_node_curve_subdivide_update); + ntype.geometry_node_execute = blender::nodes::geo_node_subdivide_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_to_points.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_curve_to_points.cc index 1e66b340f5c..0c435d69991 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_to_points.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_curve_to_points.cc @@ -358,7 +358,8 @@ void register_node_type_geo_curve_to_points() { static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_CURVE_TO_POINTS, "Curve to Points", NODE_CLASS_GEOMETRY, 0); + geo_node_type_base( + &ntype, GEO_NODE_LEGACY_CURVE_TO_POINTS, "Curve to Points", NODE_CLASS_GEOMETRY, 0); ntype.declare = blender::nodes::geo_node_curve_to_points_declare; ntype.geometry_node_execute = blender::nodes::geo_node_curve_to_points_exec; ntype.draw_buttons = blender::nodes::geo_node_curve_to_points_layout; diff --git a/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_delete_geometry.cc index 1e2f652cd78..1e2f652cd78 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_delete_geometry.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_edge_split.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_edge_split.cc index 867fecea251..2ea6516996d 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_edge_split.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_edge_split.cc @@ -82,7 +82,7 @@ void register_node_type_geo_edge_split() { static bNodeType ntype; - geo_node_type_base(&ntype, GEO_NODE_EDGE_SPLIT, "Edge Split", NODE_CLASS_GEOMETRY, 0); + geo_node_type_base(&ntype, GEO_NODE_LEGACY_EDGE_SPLIT, "Edge Split", NODE_CLASS_GEOMETRY, 0); ntype.geometry_node_execute = blender::nodes::geo_node_edge_split_exec; ntype.declare = blender::nodes::geo_node_edge_split_declare; nodeRegisterType(&ntype); diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_to_curve.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_mesh_to_curve.cc index 11349dc7d42..11349dc7d42 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_to_curve.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_mesh_to_curve.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_point_distribute.cc index 04b4003daed..f95b0da86ed 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_point_distribute.cc @@ -36,7 +36,6 @@ #include "node_geometry_util.hh" -using blender::bke::AttributeKind; using blender::bke::GeometryInstanceGroup; namespace blender::nodes { diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_point_instance.cc index fb45c22ced4..fb45c22ced4 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_point_instance.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_point_instance.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_rotate.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_point_rotate.cc index 60c82360007..60c82360007 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_point_rotate.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_point_rotate.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_scale.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_point_scale.cc index 99adce149e9..99adce149e9 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_point_scale.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_point_scale.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_separate.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_point_separate.cc index 48b6676c1dd..48b6676c1dd 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_point_separate.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_point_separate.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_translate.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_point_translate.cc index f2fce45c57b..f2fce45c57b 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_point_translate.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_point_translate.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_points_to_volume.cc index d920c8de9f0..d920c8de9f0 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_points_to_volume.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_raycast.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_raycast.cc index 401a478f04c..401a478f04c 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_raycast.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_raycast.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_subdivision_surface.cc b/source/blender/nodes/geometry/nodes/legacy/node_geo_subdivision_surface.cc index 4541bf3569f..07d3f89bdb7 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_subdivision_surface.cc +++ b/source/blender/nodes/geometry/nodes/legacy/node_geo_subdivision_surface.cc @@ -133,7 +133,7 @@ void register_node_type_geo_subdivision_surface() static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_SUBDIVISION_SURFACE, "Subdivision Surface", NODE_CLASS_GEOMETRY, 0); + &ntype, GEO_NODE_LEGACY_SUBDIVISION_SURFACE, "Subdivision Surface", NODE_CLASS_GEOMETRY, 0); ntype.declare = blender::nodes::geo_node_subdivision_surface_declare; ntype.geometry_node_execute = blender::nodes::geo_node_subdivision_surface_exec; ntype.draw_buttons = blender::nodes::geo_node_subdivision_surface_layout; diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc index c8a33205de4..43fb00a482c 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc @@ -26,18 +26,18 @@ namespace blender::nodes { static void geo_node_attribute_capture_declare(NodeDeclarationBuilder &b) { b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::Vector>("Value"); - b.add_input<decl::Float>("Value", "Value_001"); - b.add_input<decl::Color>("Value", "Value_002"); - b.add_input<decl::Bool>("Value", "Value_003"); - b.add_input<decl::Int>("Value", "Value_004"); + b.add_input<decl::Vector>("Value").supports_field(); + b.add_input<decl::Float>("Value", "Value_001").supports_field(); + b.add_input<decl::Color>("Value", "Value_002").supports_field(); + b.add_input<decl::Bool>("Value", "Value_003").supports_field(); + b.add_input<decl::Int>("Value", "Value_004").supports_field(); b.add_output<decl::Geometry>("Geometry"); - b.add_output<decl::Vector>("Attribute"); - b.add_output<decl::Float>("Attribute", "Attribute_001"); - b.add_output<decl::Color>("Attribute", "Attribute_002"); - b.add_output<decl::Bool>("Attribute", "Attribute_003"); - b.add_output<decl::Int>("Attribute", "Attribute_004"); + b.add_output<decl::Vector>("Attribute").field_source(); + b.add_output<decl::Float>("Attribute", "Attribute_001").field_source(); + b.add_output<decl::Color>("Attribute", "Attribute_002").field_source(); + b.add_output<decl::Bool>("Attribute", "Attribute_003").field_source(); + b.add_output<decl::Int>("Attribute", "Attribute_004").field_source(); } static void geo_node_attribute_capture_layout(uiLayout *layout, @@ -117,8 +117,6 @@ static void geo_node_attribute_capture_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); - geometry_set = bke::geometry_set_realize_instances(geometry_set); - const bNode &node = params.node(); const NodeGeometryAttributeCapture &storage = *(const NodeGeometryAttributeCapture *) node.storage; diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_remove.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_remove.cc index 21a9a338857..f93ef6f1db3 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_remove.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_remove.cc @@ -47,8 +47,6 @@ static void geo_node_attribute_remove_exec(GeoNodeExecParams params) GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); Vector<std::string> attribute_names = params.extract_multi_input<std::string>("Attribute"); - geometry_set = geometry_set_realize_instances(geometry_set); - if (geometry_set.has<MeshComponent>()) { remove_attribute( geometry_set.get_component_for_write<MeshComponent>(), params, attribute_names); diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_statistic.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_statistic.cc index 5001034518c..1b7d2fe28a1 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_statistic.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_statistic.cc @@ -29,8 +29,8 @@ namespace blender::nodes { static void geo_node_attribute_statistic_declare(NodeDeclarationBuilder &b) { b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::Float>("Attribute").hide_value(); - b.add_input<decl::Vector>("Attribute", "Attribute_001").hide_value(); + b.add_input<decl::Float>("Attribute").hide_value().supports_field(); + b.add_input<decl::Vector>("Attribute", "Attribute_001").hide_value().supports_field(); b.add_output<decl::Float>("Mean"); b.add_output<decl::Float>("Median"); diff --git a/source/blender/nodes/geometry/nodes/node_geo_boolean.cc b/source/blender/nodes/geometry/nodes/node_geo_boolean.cc index 2a1c43a89fe..21b425c0ed4 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_boolean.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_boolean.cc @@ -83,10 +83,14 @@ static void geo_node_boolean_exec(GeoNodeExecParams params) GeometrySet set_a; if (operation == GEO_NODE_BOOLEAN_DIFFERENCE) { set_a = params.extract_input<GeometrySet>("Geometry 1"); + if (set_a.has_instances()) { + params.error_message_add( + NodeWarningType::Info, + TIP_("Instances are not supported for the first geometry input, and will not be used")); + } /* Note that it technically wouldn't be necessary to realize the instances for the first * geometry input, but the boolean code expects the first shape for the difference operation * to be a single mesh. */ - set_a = geometry_set_realize_instances(set_a); const Mesh *mesh_in_a = set_a.get_mesh_for_read(); if (mesh_in_a != nullptr) { meshes.append(mesh_in_a); 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 f4c295b06fb..d03221703f0 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_collection_info.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_collection_info.cc @@ -21,6 +21,8 @@ #include "UI_interface.h" #include "UI_resources.h" +#include "BKE_collection.h" + #include "node_geometry_util.hh" namespace blender::nodes { @@ -28,6 +30,12 @@ namespace blender::nodes { static void geo_node_collection_info_declare(NodeDeclarationBuilder &b) { b.add_input<decl::Collection>("Collection").hide_label(); + b.add_input<decl::Bool>("Separate Children") + .description("Output each child of the collection as a separate instance"); + b.add_input<decl::Bool>("Reset Children") + .description( + "Reset the transforms of every child instance in the output. Only used when Separate " + "Children is enabled"); b.add_output<decl::Geometry>("Geometry"); } @@ -57,23 +65,66 @@ static void geo_node_collection_info_exec(GeoNodeExecParams params) const bNode &bnode = params.node(); NodeGeometryCollectionInfo *node_storage = (NodeGeometryCollectionInfo *)bnode.storage; - const bool transform_space_relative = (node_storage->transform_space == - GEO_NODE_TRANSFORM_SPACE_RELATIVE); + const bool use_relative_transform = (node_storage->transform_space == + GEO_NODE_TRANSFORM_SPACE_RELATIVE); InstancesComponent &instances = geometry_set_out.get_component_for_write<InstancesComponent>(); - float transform_mat[4][4]; - unit_m4(transform_mat); const Object *self_object = params.self_object(); - if (transform_space_relative) { - copy_v3_v3(transform_mat[3], collection->instance_offset); - - mul_m4_m4_pre(transform_mat, self_object->imat); + const bool separate_children = params.get_input<bool>("Separate Children"); + if (separate_children) { + const bool reset_children = params.get_input<bool>("Reset Children"); + Vector<Collection *> children_collections; + LISTBASE_FOREACH (CollectionChild *, collection_child, &collection->children) { + children_collections.append(collection_child->collection); + } + Vector<Object *> children_objects; + LISTBASE_FOREACH (CollectionObject *, collection_object, &collection->gobject) { + children_objects.append(collection_object->ob); + } + + instances.reserve(children_collections.size() + children_objects.size()); + + for (Collection *child_collection : children_collections) { + float4x4 transform = float4x4::identity(); + if (!reset_children) { + add_v3_v3(transform.values[3], child_collection->instance_offset); + if (use_relative_transform) { + mul_m4_m4_pre(transform.values, self_object->imat); + } + else { + sub_v3_v3(transform.values[3], collection->instance_offset); + } + } + const int handle = instances.add_reference(*child_collection); + instances.add_instance(handle, transform); + } + for (Object *child_object : children_objects) { + const int handle = instances.add_reference(*child_object); + float4x4 transform = float4x4::identity(); + if (!reset_children) { + if (use_relative_transform) { + transform = self_object->imat; + } + else { + sub_v3_v3(transform.values[3], collection->instance_offset); + } + mul_m4_m4_post(transform.values, child_object->obmat); + } + instances.add_instance(handle, transform); + } + } + else { + float4x4 transform = float4x4::identity(); + if (use_relative_transform) { + copy_v3_v3(transform.values[3], collection->instance_offset); + mul_m4_m4_pre(transform.values, self_object->imat); + } + + const int handle = instances.add_reference(*collection); + instances.add_instance(handle, transform); } - - const int handle = instances.add_reference(*collection); - instances.add_instance(handle, transform_mat, -1); params.set_output("Geometry", geometry_set_out); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_convex_hull.cc b/source/blender/nodes/geometry/nodes/node_geo_convex_hull.cc index f1e10e3d276..4377d32210d 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_convex_hull.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_convex_hull.cc @@ -227,9 +227,13 @@ static Mesh *compute_hull(const GeometrySet &geometry_set) return hull_from_bullet(geometry_set.get_mesh_for_read(), positions); } +/* Since only positions are read from the instances, this can be used as an internal optimization + * to avoid the cost of realizing instances before the node. But disable this for now, since + * re-enabling that optimization will be a separate step. */ +# if 0 static void read_positions(const GeometryComponent &component, - Span<float4x4> transforms, - Vector<float3> *r_coords) + Span<float4x4> transforms, + Vector<float3> *r_coords) { GVArray_Typed<float3> positions = component.attribute_get_for_read<float3>( "position", ATTR_DOMAIN_POINT, {0, 0, 0}); @@ -265,6 +269,31 @@ static void read_curve_positions(const CurveEval &curve, } } +static Mesh *convex_hull_from_instances(const GeometrySet &geometry_set) +{ + Vector<GeometryInstanceGroup> set_groups; + bke::geometry_set_gather_instances(geometry_set, set_groups); + + Vector<float3> coords; + + for (const GeometryInstanceGroup &set_group : set_groups) { + const GeometrySet &set = set_group.geometry_set; + Span<float4x4> transforms = set_group.transforms; + + if (set.has_pointcloud()) { + read_positions(*set.get_component_for_read<PointCloudComponent>(), transforms, &coords); + } + if (set.has_mesh()) { + read_positions(*set.get_component_for_read<MeshComponent>(), transforms, &coords); + } + if (set.has_curve()) { + read_curve_positions(*set.get_curve_for_read(), transforms, &coords); + } + } + return hull_from_bullet(nullptr, coords); +} +# endif + #endif /* WITH_BULLET */ static void geo_node_convex_hull_exec(GeoNodeExecParams params) @@ -272,33 +301,14 @@ static void geo_node_convex_hull_exec(GeoNodeExecParams params) GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); #ifdef WITH_BULLET - Mesh *mesh = nullptr; - if (geometry_set.has_instances()) { - Vector<GeometryInstanceGroup> set_groups; - bke::geometry_set_gather_instances(geometry_set, set_groups); - - Vector<float3> coords; - for (const GeometryInstanceGroup &set_group : set_groups) { - const GeometrySet &set = set_group.geometry_set; - Span<float4x4> transforms = set_group.transforms; + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + Mesh *mesh = compute_hull(geometry_set); + geometry_set.replace_mesh(mesh); + geometry_set.keep_only({GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_INSTANCES}); + }); - if (set.has_pointcloud()) { - read_positions(*set.get_component_for_read<PointCloudComponent>(), transforms, &coords); - } - if (set.has_mesh()) { - read_positions(*set.get_component_for_read<MeshComponent>(), transforms, &coords); - } - if (set.has_curve()) { - read_curve_positions(*set.get_curve_for_read(), transforms, &coords); - } - } - mesh = hull_from_bullet(nullptr, coords); - } - else { - mesh = compute_hull(geometry_set); - } - params.set_output("Convex Hull", GeometrySet::create_with_mesh(mesh)); + params.set_output("Convex Hull", std::move(geometry_set)); #else params.error_message_add(NodeWarningType::Error, TIP_("Disabled, Blender was compiled without Bullet")); diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_fill.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_fill.cc index 8de2975f9b0..c30741cf786 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_fill.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_fill.cc @@ -154,23 +154,8 @@ static void geo_node_curve_fill_exec(GeoNodeExecParams params) const NodeGeometryCurveFill &storage = *(const NodeGeometryCurveFill *)params.node().storage; const GeometryNodeCurveFillMode mode = (GeometryNodeCurveFillMode)storage.mode; - if (geometry_set.has_instances()) { - InstancesComponent &instances = geometry_set.get_component_for_write<InstancesComponent>(); - instances.ensure_geometry_instances(); - - threading::parallel_for(IndexRange(instances.references_amount()), 16, [&](IndexRange range) { - for (int i : range) { - GeometrySet &geometry_set = instances.geometry_set_from_reference(i); - geometry_set = bke::geometry_set_realize_instances(geometry_set); - curve_fill_calculate(geometry_set, mode); - } - }); - - params.set_output("Mesh", std::move(geometry_set)); - return; - } - - curve_fill_calculate(geometry_set, mode); + geometry_set.modify_geometry_sets( + [&](GeometrySet &geometry_set) { curve_fill_calculate(geometry_set, mode); }); params.set_output("Mesh", std::move(geometry_set)); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc index 830cfcc8331..67ce20efd9d 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc @@ -563,19 +563,16 @@ static std::unique_ptr<CurveEval> fillet_curve(const CurveEval &input_curve, return output_curve; } -static void geo_node_fillet_exec(GeoNodeExecParams params) +static void calculate_curve_fillet(GeometrySet &geometry_set, + const GeometryNodeCurveFilletMode mode, + const Field<float> &radius_field, + const std::optional<Field<int>> &count_field, + const bool limit_radius) { - GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"); - - geometry_set = bke::geometry_set_realize_instances(geometry_set); - if (!geometry_set.has_curve()) { - params.set_output("Curve", geometry_set); return; } - NodeGeometryCurveFillet &node_storage = *(NodeGeometryCurveFillet *)params.node().storage; - const GeometryNodeCurveFilletMode mode = (GeometryNodeCurveFilletMode)node_storage.mode; FilletParam fillet_param; fillet_param.mode = mode; @@ -584,19 +581,16 @@ static void geo_node_fillet_exec(GeoNodeExecParams params) const int domain_size = component.attribute_domain_size(ATTR_DOMAIN_POINT); fn::FieldEvaluator field_evaluator{field_context, domain_size}; - Field<float> radius_field = params.extract_input<Field<float>>("Radius"); - field_evaluator.add(std::move(radius_field)); + field_evaluator.add(radius_field); if (mode == GEO_NODE_CURVE_FILLET_POLY) { - Field<int> count_field = params.extract_input<Field<int>>("Count"); - field_evaluator.add(std::move(count_field)); + field_evaluator.add(*count_field); } field_evaluator.evaluate(); fillet_param.radii = &field_evaluator.get_evaluated<float>(0); if (fillet_param.radii->is_single() && fillet_param.radii->get_internal_single() < 0.0f) { - params.set_output("Geometry", geometry_set); return; } @@ -604,13 +598,36 @@ static void geo_node_fillet_exec(GeoNodeExecParams params) fillet_param.counts = &field_evaluator.get_evaluated<int>(1); } - fillet_param.limit_radius = params.extract_input<bool>("Limit Radius"); + fillet_param.limit_radius = limit_radius; const CurveEval &input_curve = *geometry_set.get_curve_for_read(); std::unique_ptr<CurveEval> output_curve = fillet_curve(input_curve, fillet_param); - params.set_output("Curve", GeometrySet::create_with_curve(output_curve.release())); + geometry_set.replace_curve(output_curve.release()); +} + +static void geo_node_fillet_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"); + + NodeGeometryCurveFillet &node_storage = *(NodeGeometryCurveFillet *)params.node().storage; + const GeometryNodeCurveFilletMode mode = (GeometryNodeCurveFilletMode)node_storage.mode; + + Field<float> radius_field = params.extract_input<Field<float>>("Radius"); + const bool limit_radius = params.extract_input<bool>("Limit Radius"); + + std::optional<Field<int>> count_field; + if (mode == GEO_NODE_CURVE_FILLET_POLY) { + count_field.emplace(params.extract_input<Field<int>>("Count")); + } + + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + calculate_curve_fillet(geometry_set, mode, radius_field, count_field, limit_radius); + }); + + params.set_output("Curve", std::move(geometry_set)); } + } // namespace blender::nodes void register_node_type_geo_curve_fillet() diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_length.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_length.cc index 8fe054633a1..ac7df35bb72 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_length.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_length.cc @@ -28,7 +28,6 @@ static void geo_node_curve_length_declare(NodeDeclarationBuilder &b) static void geo_node_curve_length_exec(GeoNodeExecParams params) { GeometrySet curve_set = params.extract_input<GeometrySet>("Curve"); - curve_set = bke::geometry_set_realize_instances(curve_set); if (!curve_set.has_curve()) { params.set_output("Length", 0.0f); return; diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_parameter.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_parameter.cc index 2cde198e679..90853387ec7 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_parameter.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_parameter.cc @@ -24,7 +24,7 @@ namespace blender::nodes { static void geo_node_curve_parameter_declare(NodeDeclarationBuilder &b) { - b.add_output<decl::Float>("Factor"); + b.add_output<decl::Float>("Factor").field_source(); } /** diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_spiral.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_spiral.cc index 0803d43e5c3..7292fafc8b0 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_spiral.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_spiral.cc @@ -43,26 +43,18 @@ static std::unique_ptr<CurveEval> create_spiral_curve(const float rotations, const int totalpoints = std::max(int(resolution * rotations), 1); const float delta_radius = (end_radius - start_radius) / (float)totalpoints; - float radius = start_radius; const float delta_height = height / (float)totalpoints; - const float delta_theta = (M_PI * 2 * rotations) / (float)totalpoints; - float theta = 0.0f; + const float delta_theta = (M_PI * 2 * rotations) / (float)totalpoints * + (direction ? 1.0f : -1.0f); for (const int i : IndexRange(totalpoints + 1)) { + const float theta = i * delta_theta; + const float radius = start_radius + i * delta_radius; const float x = radius * cos(theta); const float y = radius * sin(theta); const float z = delta_height * i; spline->add_point(float3(x, y, z), 1.0f, 0.0f); - - radius += delta_radius; - - if (direction) { - theta += delta_theta; - } - else { - theta -= delta_theta; - } } spline->attributes.reallocate(spline->size()); diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc index 208525f17f6..e5be9b7a6f4 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc @@ -35,8 +35,9 @@ namespace blender::nodes { static void geo_node_curve_resample_declare(NodeDeclarationBuilder &b) { b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::Int>("Count").default_value(10).min(1).max(100000); - b.add_input<decl::Float>("Length").default_value(0.1f).min(0.001f).subtype(PROP_DISTANCE); + b.add_input<decl::Int>("Count").default_value(10).min(1).max(100000).supports_field(); + b.add_input<decl::Float>("Length").default_value(0.1f).min(0.001f).supports_field().subtype( + PROP_DISTANCE); b.add_output<decl::Geometry>("Geometry"); } @@ -68,8 +69,8 @@ static void geo_node_curve_resample_update(bNodeTree *UNUSED(ntree), bNode *node struct SampleModeParam { GeometryNodeCurveResampleMode mode; - std::optional<float> length; - std::optional<int> count; + std::optional<Field<float>> length; + std::optional<Field<int>> count; }; static SplinePtr resample_spline(const Spline &src, const int count) @@ -163,28 +164,44 @@ static SplinePtr resample_spline_evaluated(const Spline &src) return dst; } -static std::unique_ptr<CurveEval> resample_curve(const CurveEval &input_curve, +static std::unique_ptr<CurveEval> resample_curve(const CurveComponent *component, const SampleModeParam &mode_param) { - Span<SplinePtr> input_splines = input_curve.splines(); + const CurveEval *input_curve = component->get_for_read(); + GeometryComponentFieldContext field_context{*component, ATTR_DOMAIN_CURVE}; + const int domain_size = component->attribute_domain_size(ATTR_DOMAIN_CURVE); + + fn::FieldEvaluator evaluator{field_context, domain_size}; + + Span<SplinePtr> input_splines = input_curve->splines(); std::unique_ptr<CurveEval> output_curve = std::make_unique<CurveEval>(); output_curve->resize(input_splines.size()); MutableSpan<SplinePtr> output_splines = output_curve->splines(); if (mode_param.mode == GEO_NODE_CURVE_RESAMPLE_COUNT) { + evaluator.add(*mode_param.count); + evaluator.evaluate(); + const VArray<int> &cuts = evaluator.get_evaluated<int>(0); + threading::parallel_for(input_splines.index_range(), 128, [&](IndexRange range) { for (const int i : range) { BLI_assert(mode_param.count); - output_splines[i] = resample_spline(*input_splines[i], *mode_param.count); + output_splines[i] = resample_spline(*input_splines[i], std::max(cuts[i], 1)); } }); } else if (mode_param.mode == GEO_NODE_CURVE_RESAMPLE_LENGTH) { + evaluator.add(*mode_param.length); + evaluator.evaluate(); + const VArray<float> &lengths = evaluator.get_evaluated<float>(0); + threading::parallel_for(input_splines.index_range(), 128, [&](IndexRange range) { for (const int i : range) { - const float length = input_splines[i]->length(); - const int count = std::max(int(length / *mode_param.length) + 1, 1); + /* Don't allow asymptotic count increase for low resolution values. */ + const float divide_length = std::max(lengths[i], 0.0001f); + const float spline_length = input_splines[i]->length(); + const int count = std::max(int(spline_length / divide_length) + 1, 1); output_splines[i] = resample_spline(*input_splines[i], count); } }); @@ -197,29 +214,35 @@ static std::unique_ptr<CurveEval> resample_curve(const CurveEval &input_curve, }); } - output_curve->attributes = input_curve.attributes; + output_curve->attributes = input_curve->attributes; return output_curve; } -static void geo_node_resample_exec(GeoNodeExecParams params) +static void geometry_set_curve_resample(GeometrySet &geometry_set, + const SampleModeParam &mode_param) { - 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(); + std::unique_ptr<CurveEval> output_curve = resample_curve( + geometry_set.get_component_for_read<CurveComponent>(), mode_param); + + geometry_set.replace_curve(output_curve.release()); +} + +static void geo_node_resample_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + NodeGeometryCurveResample &node_storage = *(NodeGeometryCurveResample *)params.node().storage; const GeometryNodeCurveResampleMode mode = (GeometryNodeCurveResampleMode)node_storage.mode; + SampleModeParam mode_param; mode_param.mode = mode; if (mode == GEO_NODE_CURVE_RESAMPLE_COUNT) { - const int count = params.extract_input<int>("Count"); + Field<int> count = params.extract_input<Field<int>>("Count"); if (count < 1) { params.set_output("Geometry", GeometrySet()); return; @@ -227,14 +250,14 @@ static void geo_node_resample_exec(GeoNodeExecParams params) mode_param.count.emplace(count); } else if (mode == GEO_NODE_CURVE_RESAMPLE_LENGTH) { - /* Don't allow asymptotic count increase for low resolution values. */ - const float resolution = std::max(params.extract_input<float>("Length"), 0.0001f); + Field<int> resolution = params.extract_input<Field<int>>("Length"); mode_param.length.emplace(resolution); } - std::unique_ptr<CurveEval> output_curve = resample_curve(input_curve, mode_param); + geometry_set.modify_geometry_sets( + [&](GeometrySet &geometry_set) { geometry_set_curve_resample(geometry_set, mode_param); }); - params.set_output("Geometry", GeometrySet::create_with_curve(output_curve.release())); + params.set_output("Geometry", std::move(geometry_set)); } } // namespace blender::nodes diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_reverse.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_reverse.cc index 32bcbe2c608..b644faabedb 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_reverse.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_reverse.cc @@ -25,37 +25,39 @@ namespace blender::nodes { static void geo_node_curve_reverse_declare(NodeDeclarationBuilder &b) { b.add_input<decl::Geometry>("Curve"); - b.add_input<decl::String>("Selection"); + b.add_input<decl::Bool>("Selection").default_value(true).hide_value().supports_field(); b.add_output<decl::Geometry>("Curve"); } static void geo_node_curve_reverse_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"); - geometry_set = bke::geometry_set_realize_instances(geometry_set); - if (!geometry_set.has_curve()) { - params.set_output("Curve", geometry_set); - return; - } - /* Retrieve data for write access so we can avoid new allocations for the reversed data. */ - CurveComponent &curve_component = geometry_set.get_component_for_write<CurveComponent>(); - CurveEval &curve = *curve_component.get_for_write(); - MutableSpan<SplinePtr> splines = curve.splines(); + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + if (!geometry_set.has_curve()) { + return; + } + + Field<bool> selection_field = params.extract_input<Field<bool>>("Selection"); + CurveComponent &component = geometry_set.get_component_for_write<CurveComponent>(); + GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_CURVE}; + const int domain_size = component.attribute_domain_size(ATTR_DOMAIN_CURVE); - const std::string selection_name = params.extract_input<std::string>("Selection"); - GVArray_Typed<bool> selection = curve_component.attribute_get_for_read( - selection_name, ATTR_DOMAIN_CURVE, true); + fn::FieldEvaluator selection_evaluator{field_context, domain_size}; + selection_evaluator.add(selection_field); + selection_evaluator.evaluate(); + const IndexMask selection = selection_evaluator.get_evaluated_as_mask(0); - threading::parallel_for(splines.index_range(), 128, [&](IndexRange range) { - for (const int i : range) { - if (selection[i]) { - splines[i]->reverse(); + CurveEval &curve = *component.get_for_write(); + MutableSpan<SplinePtr> splines = curve.splines(); + threading::parallel_for(selection.index_range(), 128, [&](IndexRange range) { + for (const int i : range) { + splines[selection[i]]->reverse(); } - } + }); }); - params.set_output("Curve", geometry_set); + params.set_output("Curve", std::move(geometry_set)); } } // namespace blender::nodes @@ -63,8 +65,7 @@ static void geo_node_curve_reverse_exec(GeoNodeExecParams params) void register_node_type_geo_curve_reverse() { static bNodeType ntype; - geo_node_type_base( - &ntype, GEO_NODE_LEGACY_CURVE_REVERSE, "Curve Reverse", NODE_CLASS_GEOMETRY, 0); + geo_node_type_base(&ntype, GEO_NODE_CURVE_REVERSE, "Curve Reverse", NODE_CLASS_GEOMETRY, 0); ntype.declare = blender::nodes::geo_node_curve_reverse_declare; ntype.geometry_node_execute = blender::nodes::geo_node_curve_reverse_exec; nodeRegisterType(&ntype); diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_sample.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_sample.cc index ac0cd510ffa..1266f525861 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_sample.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_sample.cc @@ -28,12 +28,12 @@ namespace blender::nodes { static void geo_node_curve_sample_declare(NodeDeclarationBuilder &b) { b.add_input<decl::Geometry>("Curve"); - b.add_input<decl::Float>("Factor").min(0.0f).max(1.0f).subtype(PROP_FACTOR); - b.add_input<decl::Float>("Length").min(0.0f).subtype(PROP_DISTANCE); + b.add_input<decl::Float>("Factor").min(0.0f).max(1.0f).subtype(PROP_FACTOR).supports_field(); + b.add_input<decl::Float>("Length").min(0.0f).subtype(PROP_DISTANCE).supports_field(); - b.add_output<decl::Vector>("Position"); - b.add_output<decl::Vector>("Tangent"); - b.add_output<decl::Vector>("Normal"); + b.add_output<decl::Vector>("Position").dependent_field(); + b.add_output<decl::Vector>("Tangent").dependent_field(); + b.add_output<decl::Vector>("Normal").dependent_field(); } static void geo_node_curve_sample_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_set_handles.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_set_handles.cc index 31c13134f79..9e7ac60c29d 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_set_handles.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_set_handles.cc @@ -23,10 +23,10 @@ namespace blender::nodes { -static void geo_node_curve_set_handles_decalre(NodeDeclarationBuilder &b) +static void geo_node_curve_set_handles_declare(NodeDeclarationBuilder &b) { b.add_input<decl::Geometry>("Curve"); - b.add_input<decl::String>("Selection"); + b.add_input<decl::Bool>("Selection").default_value(true).hide_value().supports_field(); b.add_output<decl::Geometry>("Curve"); } @@ -44,7 +44,7 @@ static void geo_node_curve_set_handles_init(bNodeTree *UNUSED(tree), bNode *node sizeof(NodeGeometryCurveSetHandles), __func__); data->handle_type = GEO_NODE_CURVE_HANDLE_AUTO; - data->mode = GEO_NODE_CURVE_HANDLE_LEFT | GEO_NODE_CURVE_HANDLE_RIGHT; + data->mode = GEO_NODE_CURVE_HANDLE_LEFT; node->storage = data; } @@ -72,57 +72,63 @@ static void geo_node_curve_set_handles_exec(GeoNodeExecParams params) const GeometryNodeCurveHandleMode mode = (GeometryNodeCurveHandleMode)node_storage->mode; GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"); - geometry_set = bke::geometry_set_realize_instances(geometry_set); - if (!geometry_set.has_curve()) { - params.set_output("Curve", geometry_set); - return; - } - - /* Retrieve data for write access so we can avoid new allocations for the handles data. */ - CurveComponent &curve_component = geometry_set.get_component_for_write<CurveComponent>(); - CurveEval &curve = *curve_component.get_for_write(); - MutableSpan<SplinePtr> splines = curve.splines(); + Field<bool> selection_field = params.extract_input<Field<bool>>("Selection"); - const std::string selection_name = params.extract_input<std::string>("Selection"); - GVArray_Typed<bool> selection = curve_component.attribute_get_for_read( - selection_name, ATTR_DOMAIN_POINT, true); - - const BezierSpline::HandleType new_handle_type = handle_type_from_input_type(type); - int point_index = 0; bool has_bezier_spline = false; - for (SplinePtr &spline : splines) { - if (spline->type() != Spline::Type::Bezier) { - point_index += spline->positions().size(); - continue; + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + if (!geometry_set.has_curve()) { + return; } - BezierSpline &bezier_spline = static_cast<BezierSpline &>(*spline); - if (ELEM(new_handle_type, BezierSpline::HandleType::Free, BezierSpline::HandleType::Align)) { - /* In this case the automatically calculated handle types need to be "baked", because - * they're possibly changing from a type that is calculated automatically to a type that - * is positioned manually. */ - bezier_spline.ensure_auto_handles(); - } - has_bezier_spline = true; - for (int i_point : IndexRange(bezier_spline.size())) { - if (selection[point_index]) { - if (mode & GEO_NODE_CURVE_HANDLE_LEFT) { - bezier_spline.handle_types_left()[i_point] = new_handle_type; - } - if (mode & GEO_NODE_CURVE_HANDLE_RIGHT) { - bezier_spline.handle_types_right()[i_point] = new_handle_type; + /* Retrieve data for write access so we can avoid new allocations for the handles data. */ + CurveComponent &curve_component = geometry_set.get_component_for_write<CurveComponent>(); + CurveEval &curve = *curve_component.get_for_write(); + MutableSpan<SplinePtr> splines = curve.splines(); + + GeometryComponentFieldContext field_context{curve_component, ATTR_DOMAIN_POINT}; + const int domain_size = curve_component.attribute_domain_size(ATTR_DOMAIN_POINT); + + fn::FieldEvaluator selection_evaluator{field_context, domain_size}; + selection_evaluator.add(selection_field); + selection_evaluator.evaluate(); + const VArray<bool> &selection = selection_evaluator.get_evaluated<bool>(0); + + const BezierSpline::HandleType new_handle_type = handle_type_from_input_type(type); + int point_index = 0; + + for (SplinePtr &spline : splines) { + if (spline->type() != Spline::Type::Bezier) { + point_index += spline->positions().size(); + continue; + } + + has_bezier_spline = true; + BezierSpline &bezier_spline = static_cast<BezierSpline &>(*spline); + if (ELEM(new_handle_type, BezierSpline::HandleType::Free, BezierSpline::HandleType::Align)) { + /* In this case the automatically calculated handle types need to be "baked", because + * they're possibly changing from a type that is calculated automatically to a type that + * is positioned manually. */ + bezier_spline.ensure_auto_handles(); + } + + for (int i_point : IndexRange(bezier_spline.size())) { + if (selection[point_index]) { + if (mode & GEO_NODE_CURVE_HANDLE_LEFT) { + bezier_spline.handle_types_left()[i_point] = new_handle_type; + } + if (mode & GEO_NODE_CURVE_HANDLE_RIGHT) { + bezier_spline.handle_types_right()[i_point] = new_handle_type; + } } + point_index++; } - point_index++; + bezier_spline.mark_cache_invalid(); } - bezier_spline.mark_cache_invalid(); - } - + }); if (!has_bezier_spline) { params.error_message_add(NodeWarningType::Info, TIP_("No Bezier splines in input curve")); } - - params.set_output("Curve", geometry_set); + params.set_output("Curve", std::move(geometry_set)); } } // namespace blender::nodes @@ -130,8 +136,8 @@ void register_node_type_geo_curve_set_handles() { static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_LEGACY_CURVE_SET_HANDLES, "Set Handle Type", NODE_CLASS_GEOMETRY, 0); - ntype.declare = blender::nodes::geo_node_curve_set_handles_decalre; + &ntype, GEO_NODE_CURVE_SET_HANDLES, "Set Handle Type", NODE_CLASS_GEOMETRY, 0); + ntype.declare = blender::nodes::geo_node_curve_set_handles_declare; ntype.geometry_node_execute = blender::nodes::geo_node_curve_set_handles_exec; node_type_init(&ntype, blender::nodes::geo_node_curve_set_handles_init); node_type_storage(&ntype, diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc index 0ef107fd8a4..ec72154db13 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc @@ -28,7 +28,7 @@ namespace blender::nodes { static void geo_node_curve_spline_type_declare(NodeDeclarationBuilder &b) { b.add_input<decl::Geometry>("Curve"); - b.add_input<decl::String>("Selection"); + b.add_input<decl::Bool>("Selection").default_value(true).hide_value().supports_field(); b.add_output<decl::Geometry>("Curve"); } @@ -245,41 +245,47 @@ static void geo_node_curve_spline_type_exec(GeoNodeExecParams params) const GeometryNodeSplineType output_type = (const GeometryNodeSplineType)storage->spline_type; GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"); - geometry_set = bke::geometry_set_realize_instances(geometry_set); - if (!geometry_set.has_curve()) { - params.set_output("Curve", geometry_set); - return; - } + Field<bool> selection_field = params.extract_input<Field<bool>>("Selection"); - const CurveComponent *curve_component = geometry_set.get_component_for_read<CurveComponent>(); - const CurveEval &curve = *curve_component->get_for_read(); - - const std::string selection_name = params.extract_input<std::string>("Selection"); - GVArray_Typed<bool> selection = curve_component->attribute_get_for_read( - selection_name, ATTR_DOMAIN_CURVE, true); - - std::unique_ptr<CurveEval> new_curve = std::make_unique<CurveEval>(); - for (const int i : curve.splines().index_range()) { - if (selection[i]) { - switch (output_type) { - case GEO_NODE_SPLINE_TYPE_POLY: - new_curve->add_spline(convert_to_poly_spline(*curve.splines()[i])); - break; - case GEO_NODE_SPLINE_TYPE_BEZIER: - new_curve->add_spline(convert_to_bezier(*curve.splines()[i], params)); - break; - case GEO_NODE_SPLINE_TYPE_NURBS: - new_curve->add_spline(convert_to_nurbs(*curve.splines()[i])); - break; - } + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + if (!geometry_set.has_curve()) { + return; } - else { - new_curve->add_spline(curve.splines()[i]->copy()); + + const CurveComponent *curve_component = geometry_set.get_component_for_read<CurveComponent>(); + const CurveEval &curve = *curve_component->get_for_read(); + GeometryComponentFieldContext field_context{*curve_component, ATTR_DOMAIN_CURVE}; + const int domain_size = curve_component->attribute_domain_size(ATTR_DOMAIN_CURVE); + + fn::FieldEvaluator selection_evaluator{field_context, domain_size}; + selection_evaluator.add(selection_field); + selection_evaluator.evaluate(); + const VArray<bool> &selection = selection_evaluator.get_evaluated<bool>(0); + + std::unique_ptr<CurveEval> new_curve = std::make_unique<CurveEval>(); + for (const int i : curve.splines().index_range()) { + if (selection[i]) { + switch (output_type) { + case GEO_NODE_SPLINE_TYPE_POLY: + new_curve->add_spline(convert_to_poly_spline(*curve.splines()[i])); + break; + case GEO_NODE_SPLINE_TYPE_BEZIER: + new_curve->add_spline(convert_to_bezier(*curve.splines()[i], params)); + break; + case GEO_NODE_SPLINE_TYPE_NURBS: + new_curve->add_spline(convert_to_nurbs(*curve.splines()[i])); + break; + } + } + else { + new_curve->add_spline(curve.splines()[i]->copy()); + } } - } + new_curve->attributes = curve.attributes; + geometry_set.replace_curve(new_curve.release()); + }); - new_curve->attributes = curve.attributes; - params.set_output("Curve", GeometrySet::create_with_curve(new_curve.release())); + params.set_output("Curve", std::move(geometry_set)); } } // namespace blender::nodes @@ -288,7 +294,7 @@ void register_node_type_geo_curve_spline_type() { static bNodeType ntype; geo_node_type_base( - &ntype, GEO_NODE_LEGACY_CURVE_SPLINE_TYPE, "Set Spline Type", NODE_CLASS_GEOMETRY, 0); + &ntype, GEO_NODE_CURVE_SPLINE_TYPE, "Set Spline Type", NODE_CLASS_GEOMETRY, 0); ntype.declare = blender::nodes::geo_node_curve_spline_type_declare; ntype.geometry_node_execute = blender::nodes::geo_node_curve_spline_type_exec; node_type_init(&ntype, blender::nodes::geo_node_curve_spline_type_init); diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_subdivide.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_subdivide.cc index 0522f2b8981..34997c66cbb 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_subdivide.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_subdivide.cc @@ -34,35 +34,10 @@ namespace blender::nodes { static void geo_node_curve_subdivide_declare(NodeDeclarationBuilder &b) { b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::String>("Cuts"); - b.add_input<decl::Int>("Cuts", "Cuts_001").default_value(1).min(0).max(1000); + b.add_input<decl::Int>("Cuts").default_value(1).min(0).max(1000).supports_field(); b.add_output<decl::Geometry>("Geometry"); } -static void geo_node_curve_subdivide_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) -{ - uiLayoutSetPropSep(layout, true); - uiLayoutSetPropDecorate(layout, false); - uiItemR(layout, ptr, "cuts_type", 0, IFACE_("Cuts"), ICON_NONE); -} - -static void geo_node_curve_subdivide_init(bNodeTree *UNUSED(tree), bNode *node) -{ - NodeGeometryCurveSubdivide *data = (NodeGeometryCurveSubdivide *)MEM_callocN( - sizeof(NodeGeometryCurveSubdivide), __func__); - - data->cuts_type = GEO_NODE_ATTRIBUTE_INPUT_INTEGER; - node->storage = data; -} - -static void geo_node_curve_subdivide_update(bNodeTree *UNUSED(ntree), bNode *node) -{ - NodeGeometryPointTranslate &node_storage = *(NodeGeometryPointTranslate *)node->storage; - - update_attribute_input_socket_availabilities( - *node, "Cuts", (GeometryNodeAttributeInputMode)node_storage.input_type); -} - static Array<int> get_subdivided_offsets(const Spline &spline, const VArray<int> &cuts, const int spline_offset) @@ -350,25 +325,30 @@ static std::unique_ptr<CurveEval> subdivide_curve(const CurveEval &input_curve, static void geo_node_subdivide_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + Field<int> cuts_field = params.extract_input<Field<int>>("Cuts"); - geometry_set = bke::geometry_set_realize_instances(geometry_set); + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + if (!geometry_set.has_curve()) { + return; + } - if (!geometry_set.has_curve()) { - params.set_output("Geometry", geometry_set); - return; - } + const CurveComponent &component = *geometry_set.get_component_for_read<CurveComponent>(); + GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_POINT}; + const int domain_size = component.attribute_domain_size(ATTR_DOMAIN_POINT); - const CurveComponent &component = *geometry_set.get_component_for_read<CurveComponent>(); - GVArray_Typed<int> cuts = params.get_input_attribute<int>( - "Cuts", component, ATTR_DOMAIN_POINT, 0); - if (cuts->is_single() && cuts->get_internal_single() < 1) { - params.set_output("Geometry", geometry_set); - return; - } + fn::FieldEvaluator evaluator{field_context, domain_size}; + evaluator.add(cuts_field); + evaluator.evaluate(); + const VArray<int> &cuts = evaluator.get_evaluated<int>(0); - std::unique_ptr<CurveEval> output_curve = subdivide_curve(*component.get_for_read(), *cuts); + if (cuts.is_single() && cuts.get_internal_single() < 1) { + return; + } - params.set_output("Geometry", GeometrySet::create_with_curve(output_curve.release())); + std::unique_ptr<CurveEval> output_curve = subdivide_curve(*component.get_for_read(), cuts); + geometry_set.replace_curve(output_curve.release()); + }); + params.set_output("Geometry", geometry_set); } } // namespace blender::nodes @@ -377,16 +357,8 @@ void register_node_type_geo_curve_subdivide() { static bNodeType ntype; - geo_node_type_base( - &ntype, GEO_NODE_LEGACY_CURVE_SUBDIVIDE, "Curve Subdivide", NODE_CLASS_GEOMETRY, 0); + geo_node_type_base(&ntype, GEO_NODE_CURVE_SUBDIVIDE, "Curve Subdivide", NODE_CLASS_GEOMETRY, 0); ntype.declare = blender::nodes::geo_node_curve_subdivide_declare; - ntype.draw_buttons = blender::nodes::geo_node_curve_subdivide_layout; - node_type_storage(&ntype, - "NodeGeometryCurveSubdivide", - node_free_standard_storage, - node_copy_standard_storage); - node_type_init(&ntype, blender::nodes::geo_node_curve_subdivide_init); - node_type_update(&ntype, blender::nodes::geo_node_curve_subdivide_update); ntype.geometry_node_execute = blender::nodes::geo_node_subdivide_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 89ba635ff4b..00451946af9 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 @@ -32,39 +32,52 @@ static void geo_node_curve_to_mesh_declare(NodeDeclarationBuilder &b) b.add_output<decl::Geometry>("Mesh"); } +static void geometry_set_curve_to_mesh(GeometrySet &geometry_set, const GeometrySet &profile_set) +{ + const CurveEval *profile_curve = profile_set.get_curve_for_read(); + + if (profile_curve == nullptr) { + Mesh *mesh = bke::curve_to_wire_mesh(*geometry_set.get_curve_for_read()); + geometry_set.replace_mesh(mesh); + } + else { + Mesh *mesh = bke::curve_to_mesh_sweep(*geometry_set.get_curve_for_read(), *profile_curve); + geometry_set.replace_mesh(mesh); + } +} + static void geo_node_curve_to_mesh_exec(GeoNodeExecParams params) { GeometrySet curve_set = params.extract_input<GeometrySet>("Curve"); GeometrySet profile_set = params.extract_input<GeometrySet>("Profile Curve"); - curve_set = bke::geometry_set_realize_instances(curve_set); - profile_set = bke::geometry_set_realize_instances(profile_set); + if (profile_set.has_instances()) { + params.error_message_add(NodeWarningType::Error, + TIP_("Instances are not supported in the profile input")); + params.set_output("Mesh", GeometrySet()); + return; + } - /* NOTE: Theoretically an "is empty" check would be more correct for errors. */ - if (profile_set.has_mesh() && !profile_set.has_curve()) { + if (!profile_set.has_curve() && !profile_set.is_empty()) { params.error_message_add(NodeWarningType::Warning, - TIP_("No curve data available in profile input")); + TIP_("No curve data available in the profile input")); } - if (!curve_set.has_curve()) { - if (curve_set.has_mesh()) { - params.error_message_add(NodeWarningType::Warning, - TIP_("No curve data available in curve input")); + bool has_curve = false; + curve_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + if (geometry_set.has_curve()) { + has_curve = true; + geometry_set_curve_to_mesh(geometry_set, profile_set); } - params.set_output("Mesh", GeometrySet()); - return; - } - - const CurveEval *profile_curve = profile_set.get_curve_for_read(); + geometry_set.keep_only({GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_INSTANCES}); + }); - if (profile_curve == nullptr) { - Mesh *mesh = bke::curve_to_wire_mesh(*curve_set.get_curve_for_read()); - params.set_output("Mesh", GeometrySet::create_with_mesh(mesh)); - } - else { - Mesh *mesh = bke::curve_to_mesh_sweep(*curve_set.get_curve_for_read(), *profile_curve); - params.set_output("Mesh", GeometrySet::create_with_mesh(mesh)); + if (!has_curve && !curve_set.is_empty()) { + params.error_message_add(NodeWarningType::Warning, + TIP_("No curve data available in curve input")); } + + params.set_output("Mesh", std::move(curve_set)); } } // namespace blender::nodes diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc index 2b6d25b6bf3..97043980899 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc @@ -320,28 +320,18 @@ static void trim_bezier_spline(Spline &spline, bezier_spline.resize(size); } -static void geo_node_curve_trim_exec(GeoNodeExecParams params) +static void geometry_set_curve_trim(GeometrySet &geometry_set, + const GeometryNodeCurveSampleMode mode, + const float start, + const float end) { - const NodeGeometryCurveTrim &node_storage = *(NodeGeometryCurveTrim *)params.node().storage; - const GeometryNodeCurveSampleMode mode = (GeometryNodeCurveSampleMode)node_storage.mode; - - GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"); - geometry_set = bke::geometry_set_realize_instances(geometry_set); if (!geometry_set.has_curve()) { - params.set_output("Curve", std::move(geometry_set)); return; } - CurveComponent &curve_component = geometry_set.get_component_for_write<CurveComponent>(); - CurveEval &curve = *curve_component.get_for_write(); + CurveEval &curve = *geometry_set.get_curve_for_write(); MutableSpan<SplinePtr> splines = curve.splines(); - const float start = mode == GEO_NODE_CURVE_SAMPLE_FACTOR ? - params.extract_input<float>("Start") : - params.extract_input<float>("Start_001"); - const float end = mode == GEO_NODE_CURVE_SAMPLE_FACTOR ? params.extract_input<float>("End") : - params.extract_input<float>("End_001"); - threading::parallel_for(splines.index_range(), 128, [&](IndexRange range) { for (const int i : range) { Spline &spline = *splines[i]; @@ -382,6 +372,29 @@ static void geo_node_curve_trim_exec(GeoNodeExecParams params) splines[i]->mark_cache_invalid(); } }); +} + +static void geo_node_curve_trim_exec(GeoNodeExecParams params) +{ + const NodeGeometryCurveTrim &node_storage = *(NodeGeometryCurveTrim *)params.node().storage; + const GeometryNodeCurveSampleMode mode = (GeometryNodeCurveSampleMode)node_storage.mode; + + GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve"); + + if (mode == GEO_NODE_CURVE_SAMPLE_FACTOR) { + const float start = params.extract_input<float>("Start"); + const float end = params.extract_input<float>("End"); + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + geometry_set_curve_trim(geometry_set, mode, start, end); + }); + } + else if (mode == GEO_NODE_CURVE_SAMPLE_LENGTH) { + const float start = params.extract_input<float>("Start_001"); + const float end = params.extract_input<float>("End_001"); + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + geometry_set_curve_trim(geometry_set, mode, start, end); + }); + } params.set_output("Curve", std::move(geometry_set)); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_distribute_points_on_faces.cc b/source/blender/nodes/geometry/nodes/node_geo_distribute_points_on_faces.cc new file mode 100644 index 00000000000..1a4c5d84dbf --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_distribute_points_on_faces.cc @@ -0,0 +1,594 @@ +/* + * 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_kdtree.h" +#include "BLI_noise.hh" +#include "BLI_rand.hh" +#include "BLI_task.hh" +#include "BLI_timeit.hh" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_pointcloud_types.h" + +#include "BKE_attribute_math.hh" +#include "BKE_bvhutils.h" +#include "BKE_mesh.h" +#include "BKE_mesh_runtime.h" +#include "BKE_mesh_sample.hh" +#include "BKE_pointcloud.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +using blender::bke::GeometryInstanceGroup; + +namespace blender::nodes { + +static void geo_node_point_distribute_points_on_faces_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>("Geometry"); + b.add_input<decl::Float>("Distance Min").min(0.0f).subtype(PROP_DISTANCE); + b.add_input<decl::Float>("Density Max").default_value(10.0f).min(0.0f); + b.add_input<decl::Float>("Density").default_value(10.0f).supports_field(); + b.add_input<decl::Float>("Density Factor") + .default_value(1.0f) + .min(0.0f) + .max(1.0f) + .subtype(PROP_FACTOR) + .supports_field(); + b.add_input<decl::Int>("Seed"); + b.add_input<decl::Bool>("Selection").default_value(true).hide_value().supports_field(); + + b.add_output<decl::Geometry>("Points"); + b.add_output<decl::Vector>("Normal").field_source(); + b.add_output<decl::Vector>("Rotation").subtype(PROP_EULER).field_source(); + b.add_output<decl::Int>("Stable ID").field_source(); +} + +static void geo_node_point_distribute_points_on_faces_layout(uiLayout *layout, + bContext *UNUSED(C), + PointerRNA *ptr) +{ + uiItemR(layout, ptr, "distribute_method", 0, "", ICON_NONE); +} + +static void node_point_distribute_points_on_faces_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + bNodeSocket *sock_distance_min = (bNodeSocket *)BLI_findlink(&node->inputs, 1); + bNodeSocket *sock_density_max = (bNodeSocket *)sock_distance_min->next; + bNodeSocket *sock_density = sock_density_max->next; + bNodeSocket *sock_density_factor = sock_density->next; + nodeSetSocketAvailability(sock_distance_min, + node->custom1 == GEO_NODE_POINT_DISTRIBUTE_POINTS_ON_FACES_POISSON); + nodeSetSocketAvailability(sock_density_max, + node->custom1 == GEO_NODE_POINT_DISTRIBUTE_POINTS_ON_FACES_POISSON); + nodeSetSocketAvailability(sock_density, + node->custom1 == GEO_NODE_POINT_DISTRIBUTE_POINTS_ON_FACES_RANDOM); + nodeSetSocketAvailability(sock_density_factor, + node->custom1 == GEO_NODE_POINT_DISTRIBUTE_POINTS_ON_FACES_POISSON); +} + +/** + * Use an arbitrary choice of axes for a usable rotation attribute directly out of this node. + */ +static float3 normal_to_euler_rotation(const float3 normal) +{ + float quat[4]; + vec_to_quat(quat, normal, OB_NEGZ, OB_POSY); + float3 rotation; + quat_to_eul(rotation, quat); + return rotation; +} + +static void sample_mesh_surface(const Mesh &mesh, + const float base_density, + const Span<float> density_factors, + const int seed, + Vector<float3> &r_positions, + Vector<float3> &r_bary_coords, + Vector<int> &r_looptri_indices) +{ + const Span<MLoopTri> looptris{BKE_mesh_runtime_looptri_ensure(&mesh), + BKE_mesh_runtime_looptri_len(&mesh)}; + + for (const int looptri_index : looptris.index_range()) { + const MLoopTri &looptri = looptris[looptri_index]; + const int v0_loop = looptri.tri[0]; + const int v1_loop = looptri.tri[1]; + const int v2_loop = looptri.tri[2]; + const int v0_index = mesh.mloop[v0_loop].v; + const int v1_index = mesh.mloop[v1_loop].v; + const int v2_index = mesh.mloop[v2_loop].v; + const float3 v0_pos = float3(mesh.mvert[v0_index].co); + const float3 v1_pos = float3(mesh.mvert[v1_index].co); + const float3 v2_pos = float3(mesh.mvert[v2_index].co); + + float looptri_density_factor = 1.0f; + if (!density_factors.is_empty()) { + const float v0_density_factor = std::max(0.0f, density_factors[v0_loop]); + const float v1_density_factor = std::max(0.0f, density_factors[v1_loop]); + const float v2_density_factor = std::max(0.0f, density_factors[v2_loop]); + looptri_density_factor = (v0_density_factor + v1_density_factor + v2_density_factor) / 3.0f; + } + const float area = area_tri_v3(v0_pos, v1_pos, v2_pos); + + const int looptri_seed = noise::hash(looptri_index, seed); + RandomNumberGenerator looptri_rng(looptri_seed); + + const float points_amount_fl = area * base_density * looptri_density_factor; + const float add_point_probability = fractf(points_amount_fl); + const bool add_point = add_point_probability > looptri_rng.get_float(); + const int point_amount = (int)points_amount_fl + (int)add_point; + + for (int i = 0; i < point_amount; i++) { + const float3 bary_coord = looptri_rng.get_barycentric_coordinates(); + float3 point_pos; + interp_v3_v3v3v3(point_pos, v0_pos, v1_pos, v2_pos, bary_coord); + r_positions.append(point_pos); + r_bary_coords.append(bary_coord); + r_looptri_indices.append(looptri_index); + } + } +} + +BLI_NOINLINE static KDTree_3d *build_kdtree(Span<float3> positions) +{ + KDTree_3d *kdtree = BLI_kdtree_3d_new(positions.size()); + + int i_point = 0; + for (const float3 position : positions) { + BLI_kdtree_3d_insert(kdtree, i_point, position); + i_point++; + } + + BLI_kdtree_3d_balance(kdtree); + return kdtree; +} + +BLI_NOINLINE static void update_elimination_mask_for_close_points( + Span<float3> positions, const float minimum_distance, MutableSpan<bool> elimination_mask) +{ + if (minimum_distance <= 0.0f) { + return; + } + + KDTree_3d *kdtree = build_kdtree(positions); + + for (const int i : positions.index_range()) { + if (elimination_mask[i]) { + continue; + } + + struct CallbackData { + int index; + MutableSpan<bool> elimination_mask; + } callback_data = {i, elimination_mask}; + + BLI_kdtree_3d_range_search_cb( + kdtree, + positions[i], + minimum_distance, + [](void *user_data, int index, const float *UNUSED(co), float UNUSED(dist_sq)) { + CallbackData &callback_data = *static_cast<CallbackData *>(user_data); + if (index != callback_data.index) { + callback_data.elimination_mask[index] = true; + } + return true; + }, + &callback_data); + } + + BLI_kdtree_3d_free(kdtree); +} + +BLI_NOINLINE static void update_elimination_mask_based_on_density_factors( + const Mesh &mesh, + const Span<float> density_factors, + const Span<float3> bary_coords, + const Span<int> looptri_indices, + const MutableSpan<bool> elimination_mask) +{ + const Span<MLoopTri> looptris{BKE_mesh_runtime_looptri_ensure(&mesh), + BKE_mesh_runtime_looptri_len(&mesh)}; + for (const int i : bary_coords.index_range()) { + if (elimination_mask[i]) { + continue; + } + + const MLoopTri &looptri = looptris[looptri_indices[i]]; + const float3 bary_coord = bary_coords[i]; + + const int v0_loop = looptri.tri[0]; + const int v1_loop = looptri.tri[1]; + const int v2_loop = looptri.tri[2]; + + const float v0_density_factor = std::max(0.0f, density_factors[v0_loop]); + const float v1_density_factor = std::max(0.0f, density_factors[v1_loop]); + const float v2_density_factor = std::max(0.0f, density_factors[v2_loop]); + + const float probablity = v0_density_factor * bary_coord.x + v1_density_factor * bary_coord.y + + v2_density_factor * bary_coord.z; + + const float hash = noise::hash_float_to_float(bary_coord); + if (hash > probablity) { + elimination_mask[i] = true; + } + } +} + +BLI_NOINLINE static void eliminate_points_based_on_mask(const Span<bool> elimination_mask, + Vector<float3> &positions, + Vector<float3> &bary_coords, + Vector<int> &looptri_indices) +{ + for (int i = positions.size() - 1; i >= 0; i--) { + if (elimination_mask[i]) { + positions.remove_and_reorder(i); + bary_coords.remove_and_reorder(i); + looptri_indices.remove_and_reorder(i); + } + } +} + +BLI_NOINLINE static void interpolate_attribute(const Mesh &mesh, + const Span<float3> bary_coords, + const Span<int> looptri_indices, + const AttributeDomain source_domain, + const GVArray &source_data, + GMutableSpan output_data) +{ + switch (source_domain) { + case ATTR_DOMAIN_POINT: { + bke::mesh_surface_sample::sample_point_attribute( + mesh, looptri_indices, bary_coords, source_data, output_data); + break; + } + case ATTR_DOMAIN_CORNER: { + bke::mesh_surface_sample::sample_corner_attribute( + mesh, looptri_indices, bary_coords, source_data, output_data); + break; + } + case ATTR_DOMAIN_FACE: { + bke::mesh_surface_sample::sample_face_attribute( + mesh, looptri_indices, source_data, output_data); + break; + } + default: { + /* Not supported currently. */ + return; + } + } +} + +BLI_NOINLINE static void propagate_existing_attributes( + const MeshComponent &mesh_component, + const Map<AttributeIDRef, AttributeKind> &attributes, + GeometryComponent &point_component, + const Span<float3> bary_coords, + const Span<int> looptri_indices) +{ + const Mesh &mesh = *mesh_component.get_for_read(); + + for (Map<AttributeIDRef, AttributeKind>::Item entry : attributes.items()) { + const AttributeIDRef attribute_id = entry.key; + const CustomDataType output_data_type = entry.value.data_type; + /* The output domain is always #ATTR_DOMAIN_POINT, since we are creating a point cloud. */ + OutputAttribute attribute_out = point_component.attribute_try_get_for_output_only( + attribute_id, ATTR_DOMAIN_POINT, output_data_type); + if (!attribute_out) { + continue; + } + + GMutableSpan out_span = attribute_out.as_span(); + + std::optional<AttributeMetaData> attribute_info = point_component.attribute_get_meta_data( + attribute_id); + if (!attribute_info) { + continue; + } + + const AttributeDomain source_domain = attribute_info->domain; + GVArrayPtr source_attribute = mesh_component.attribute_get_for_read( + attribute_id, source_domain, output_data_type, nullptr); + if (!source_attribute) { + continue; + } + + interpolate_attribute( + mesh, bary_coords, looptri_indices, source_domain, *source_attribute, out_span); + + attribute_out.save(); + } +} + +namespace { +struct AttributeOutputs { + StrongAnonymousAttributeID normal_id; + StrongAnonymousAttributeID rotation_id; + StrongAnonymousAttributeID stable_id_id; +}; +} // namespace + +BLI_NOINLINE static void compute_attribute_outputs(const MeshComponent &mesh_component, + PointCloudComponent &point_component, + const Span<float3> bary_coords, + const Span<int> looptri_indices, + const AttributeOutputs &attribute_outputs) +{ + std::optional<OutputAttribute_Typed<int>> id_attribute; + std::optional<OutputAttribute_Typed<float3>> normal_attribute; + std::optional<OutputAttribute_Typed<float3>> rotation_attribute; + + MutableSpan<int> ids; + MutableSpan<float3> normals; + MutableSpan<float3> rotations; + + if (attribute_outputs.stable_id_id) { + id_attribute.emplace(point_component.attribute_try_get_for_output_only<int>( + attribute_outputs.stable_id_id.get(), ATTR_DOMAIN_POINT)); + ids = id_attribute->as_span(); + } + if (attribute_outputs.normal_id) { + normal_attribute.emplace(point_component.attribute_try_get_for_output_only<float3>( + attribute_outputs.normal_id.get(), ATTR_DOMAIN_POINT)); + normals = normal_attribute->as_span(); + } + if (attribute_outputs.rotation_id) { + rotation_attribute.emplace(point_component.attribute_try_get_for_output_only<float3>( + attribute_outputs.rotation_id.get(), ATTR_DOMAIN_POINT)); + rotations = rotation_attribute->as_span(); + } + + const Mesh &mesh = *mesh_component.get_for_read(); + const Span<MLoopTri> looptris{BKE_mesh_runtime_looptri_ensure(&mesh), + BKE_mesh_runtime_looptri_len(&mesh)}; + + for (const int i : bary_coords.index_range()) { + const int looptri_index = looptri_indices[i]; + const MLoopTri &looptri = looptris[looptri_index]; + const float3 &bary_coord = bary_coords[i]; + + const int v0_index = mesh.mloop[looptri.tri[0]].v; + const int v1_index = mesh.mloop[looptri.tri[1]].v; + const int v2_index = mesh.mloop[looptri.tri[2]].v; + const float3 v0_pos = float3(mesh.mvert[v0_index].co); + const float3 v1_pos = float3(mesh.mvert[v1_index].co); + const float3 v2_pos = float3(mesh.mvert[v2_index].co); + + if (!ids.is_empty()) { + ids[i] = noise::hash(noise::hash_float(bary_coord), looptri_index); + } + float3 normal; + if (!normals.is_empty() || !rotations.is_empty()) { + normal_tri_v3(normal, v0_pos, v1_pos, v2_pos); + } + if (!normals.is_empty()) { + normals[i] = normal; + } + if (!rotations.is_empty()) { + rotations[i] = normal_to_euler_rotation(normal); + } + } + + if (id_attribute) { + id_attribute->save(); + } + if (normal_attribute) { + normal_attribute->save(); + } + if (rotation_attribute) { + rotation_attribute->save(); + } +} + +static Array<float> calc_full_density_factors_with_selection(const MeshComponent &component, + const Field<float> &density_field, + const Field<bool> &selection_field) +{ + const AttributeDomain attribute_domain = ATTR_DOMAIN_CORNER; + GeometryComponentFieldContext field_context{component, attribute_domain}; + const int domain_size = component.attribute_domain_size(attribute_domain); + + fn::FieldEvaluator selection_evaluator{field_context, domain_size}; + selection_evaluator.add(selection_field); + selection_evaluator.evaluate(); + const IndexMask selection_mask = selection_evaluator.get_evaluated_as_mask(0); + + Array<float> densities(domain_size, 0.0f); + + fn::FieldEvaluator density_evaluator{field_context, &selection_mask}; + density_evaluator.add_with_destination(density_field, densities.as_mutable_span()); + density_evaluator.evaluate(); + return densities; +} + +static void distribute_points_random(const MeshComponent &component, + const Field<float> &density_field, + const Field<bool> &selection_field, + const int seed, + Vector<float3> &positions, + Vector<float3> &bary_coords, + Vector<int> &looptri_indices) +{ + const Array<float> densities = calc_full_density_factors_with_selection( + component, density_field, selection_field); + const Mesh &mesh = *component.get_for_read(); + sample_mesh_surface(mesh, 1.0f, densities, seed, positions, bary_coords, looptri_indices); +} + +static void distribute_points_poisson_disk(const MeshComponent &mesh_component, + const float minimum_distance, + const float max_density, + const Field<float> &density_factor_field, + const Field<bool> &selection_field, + const int seed, + Vector<float3> &positions, + Vector<float3> &bary_coords, + Vector<int> &looptri_indices) +{ + const Mesh &mesh = *mesh_component.get_for_read(); + sample_mesh_surface(mesh, max_density, {}, seed, positions, bary_coords, looptri_indices); + + Array<bool> elimination_mask(positions.size(), false); + update_elimination_mask_for_close_points(positions, minimum_distance, elimination_mask); + + const Array<float> density_factors = calc_full_density_factors_with_selection( + mesh_component, density_factor_field, selection_field); + + update_elimination_mask_based_on_density_factors( + mesh, density_factors, bary_coords, looptri_indices, elimination_mask.as_mutable_span()); + + eliminate_points_based_on_mask( + elimination_mask.as_span(), positions, bary_coords, looptri_indices); +} + +static void point_distribution_calculate(GeometrySet &geometry_set, + const Field<bool> selection_field, + const GeometryNodeDistributePointsOnFacesMode method, + const int seed, + const AttributeOutputs &attribute_outputs, + const GeoNodeExecParams ¶ms) +{ + if (!geometry_set.has_mesh()) { + return; + } + + const MeshComponent &mesh_component = *geometry_set.get_component_for_read<MeshComponent>(); + + Vector<float3> positions; + Vector<float3> bary_coords; + Vector<int> looptri_indices; + + switch (method) { + case GEO_NODE_POINT_DISTRIBUTE_POINTS_ON_FACES_RANDOM: { + const Field<float> density_field = params.get_input<Field<float>>("Density"); + distribute_points_random(mesh_component, + density_field, + selection_field, + seed, + positions, + bary_coords, + looptri_indices); + break; + } + case GEO_NODE_POINT_DISTRIBUTE_POINTS_ON_FACES_POISSON: { + const float minimum_distance = params.get_input<float>("Distance Min"); + const float density_max = params.get_input<float>("Density Max"); + const Field<float> density_factors_field = params.get_input<Field<float>>("Density Factor"); + distribute_points_poisson_disk(mesh_component, + minimum_distance, + density_max, + density_factors_field, + selection_field, + seed, + positions, + bary_coords, + looptri_indices); + break; + } + } + + PointCloud *pointcloud = BKE_pointcloud_new_nomain(positions.size()); + memcpy(pointcloud->co, positions.data(), sizeof(float3) * positions.size()); + uninitialized_fill_n(pointcloud->radius, pointcloud->totpoint, 0.05f); + geometry_set.replace_pointcloud(pointcloud); + + PointCloudComponent &point_component = + geometry_set.get_component_for_write<PointCloudComponent>(); + + Map<AttributeIDRef, AttributeKind> attributes; + geometry_set.gather_attributes_for_propagation( + {GEO_COMPONENT_TYPE_MESH}, GEO_COMPONENT_TYPE_POINT_CLOUD, false, attributes); + + /* Position is set separately. */ + attributes.remove("position"); + + propagate_existing_attributes( + mesh_component, attributes, point_component, bary_coords, looptri_indices); + + compute_attribute_outputs( + mesh_component, point_component, bary_coords, looptri_indices, attribute_outputs); +} + +static void geo_node_point_distribute_points_on_faces_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + + const GeometryNodeDistributePointsOnFacesMode method = + static_cast<GeometryNodeDistributePointsOnFacesMode>(params.node().custom1); + + const int seed = params.get_input<int>("Seed") * 5383843; + const Field<bool> selection_field = params.extract_input<Field<bool>>("Selection"); + + AttributeOutputs attribute_outputs; + if (params.output_is_required("Normal")) { + attribute_outputs.normal_id = StrongAnonymousAttributeID("normal"); + } + if (params.output_is_required("Rotation")) { + attribute_outputs.rotation_id = StrongAnonymousAttributeID("rotation"); + } + if (params.output_is_required("Stable ID")) { + attribute_outputs.stable_id_id = StrongAnonymousAttributeID("stable id"); + } + + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + point_distribution_calculate( + geometry_set, selection_field, method, seed, attribute_outputs, params); + /* Keep instances because the original geometry set may contain instances that are processed as + * well. */ + geometry_set.keep_only({GEO_COMPONENT_TYPE_POINT_CLOUD, GEO_COMPONENT_TYPE_INSTANCES}); + }); + + params.set_output("Points", std::move(geometry_set)); + + if (attribute_outputs.normal_id) { + params.set_output( + "Normal", + AnonymousAttributeFieldInput::Create<float3>(std::move(attribute_outputs.normal_id))); + } + if (attribute_outputs.rotation_id) { + params.set_output( + "Rotation", + AnonymousAttributeFieldInput::Create<float3>(std::move(attribute_outputs.rotation_id))); + } + if (attribute_outputs.stable_id_id) { + params.set_output( + "Stable ID", + AnonymousAttributeFieldInput::Create<int>(std::move(attribute_outputs.stable_id_id))); + } +} + +} // namespace blender::nodes + +void register_node_type_geo_distribute_points_on_faces() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, + GEO_NODE_DISTRIBUTE_POINTS_ON_FACES, + "Distribute Points on Faces", + NODE_CLASS_GEOMETRY, + 0); + node_type_update(&ntype, blender::nodes::node_point_distribute_points_on_faces_update); + node_type_size(&ntype, 170, 100, 320); + ntype.declare = blender::nodes::geo_node_point_distribute_points_on_faces_declare; + ntype.geometry_node_execute = blender::nodes::geo_node_point_distribute_points_on_faces_exec; + ntype.draw_buttons = blender::nodes::geo_node_point_distribute_points_on_faces_layout; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_index.cc b/source/blender/nodes/geometry/nodes/node_geo_input_index.cc index c52ff3d448e..7fcbaf429dd 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_input_index.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_input_index.cc @@ -20,30 +20,12 @@ namespace blender::nodes { static void geo_node_input_index_declare(NodeDeclarationBuilder &b) { - b.add_output<decl::Int>("Index"); + b.add_output<decl::Int>("Index").field_source(); } -class IndexFieldInput final : public fn::FieldInput { - public: - IndexFieldInput() : FieldInput(CPPType::get<int>(), "Index") - { - } - - const GVArray *get_varray_for_context(const fn::FieldContext &UNUSED(context), - IndexMask mask, - ResourceScope &scope) const final - { - /* TODO: Investigate a similar method to IndexRange::as_span() */ - auto index_func = [](int i) { return i; }; - return &scope.construct< - fn::GVArray_For_EmbeddedVArray<int, VArray_For_Func<int, decltype(index_func)>>>( - mask.min_array_size(), mask.min_array_size(), index_func); - } -}; - static void geo_node_input_index_exec(GeoNodeExecParams params) { - Field<int> index_field{std::make_shared<IndexFieldInput>()}; + Field<int> index_field{std::make_shared<fn::IndexFieldInput>()}; params.set_output("Index", std::move(index_field)); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_normal.cc b/source/blender/nodes/geometry/nodes/node_geo_input_normal.cc index f92086acdf0..5a2495afb9e 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_input_normal.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_input_normal.cc @@ -28,7 +28,7 @@ namespace blender::nodes { static void geo_node_input_normal_declare(NodeDeclarationBuilder &b) { - b.add_output<decl::Vector>("Normal"); + b.add_output<decl::Vector>("Normal").field_source(); } static GVArrayPtr mesh_face_normals(const Mesh &mesh, diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_position.cc b/source/blender/nodes/geometry/nodes/node_geo_input_position.cc index 3f3457a3acb..44874259e20 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_input_position.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_input_position.cc @@ -20,13 +20,12 @@ namespace blender::nodes { static void geo_node_input_position_declare(NodeDeclarationBuilder &b) { - b.add_output<decl::Vector>("Position"); + b.add_output<decl::Vector>("Position").field_source(); } static void geo_node_input_position_exec(GeoNodeExecParams params) { - Field<float3> position_field{ - std::make_shared<AttributeFieldInput>("position", CPPType::get<float3>())}; + Field<float3> position_field{AttributeFieldInput::Create<float3>("position")}; params.set_output("Position", std::move(position_field)); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_spline_length.cc b/source/blender/nodes/geometry/nodes/node_geo_input_spline_length.cc new file mode 100644 index 00000000000..b5f3e1b0c28 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_input_spline_length.cc @@ -0,0 +1,109 @@ +/* + * 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 "BKE_spline.hh" + +namespace blender::nodes { + +static void geo_node_input_spline_length_declare(NodeDeclarationBuilder &b) +{ + b.add_output<decl::Float>("Length").field_source(); +} + +static const GVArray *construct_spline_length_gvarray(const CurveComponent &component, + const AttributeDomain domain, + ResourceScope &scope) +{ + const CurveEval *curve = component.get_for_read(); + if (curve == nullptr) { + return nullptr; + } + + Span<SplinePtr> splines = curve->splines(); + auto length_fn = [splines](int i) { return splines[i]->length(); }; + + if (domain == ATTR_DOMAIN_CURVE) { + return &scope.construct< + fn::GVArray_For_EmbeddedVArray<float, VArray_For_Func<float, decltype(length_fn)>>>( + splines.size(), splines.size(), length_fn); + } + if (domain == ATTR_DOMAIN_POINT) { + GVArrayPtr length = std::make_unique< + fn::GVArray_For_EmbeddedVArray<float, VArray_For_Func<float, decltype(length_fn)>>>( + splines.size(), splines.size(), length_fn); + return scope + .add_value(component.attribute_try_adapt_domain( + std::move(length), ATTR_DOMAIN_CURVE, ATTR_DOMAIN_POINT)) + .get(); + } + + return nullptr; +} + +class SplineLengthFieldInput final : public fn::FieldInput { + public: + SplineLengthFieldInput() : fn::FieldInput(CPPType::get<float>(), "Spline Length") + { + } + + const GVArray *get_varray_for_context(const fn::FieldContext &context, + IndexMask UNUSED(mask), + ResourceScope &scope) const final + { + if (const GeometryComponentFieldContext *geometry_context = + dynamic_cast<const GeometryComponentFieldContext *>(&context)) { + + const GeometryComponent &component = geometry_context->geometry_component(); + const AttributeDomain domain = geometry_context->domain(); + if (component.type() == GEO_COMPONENT_TYPE_CURVE) { + const CurveComponent &curve_component = static_cast<const CurveComponent &>(component); + return construct_spline_length_gvarray(curve_component, domain, scope); + } + } + return nullptr; + } + + uint64_t hash() const override + { + /* Some random constant hash. */ + return 3549623580; + } + + bool is_equal_to(const fn::FieldNode &other) const override + { + return dynamic_cast<const SplineLengthFieldInput *>(&other) != nullptr; + } +}; + +static void geo_node_input_spline_length_exec(GeoNodeExecParams params) +{ + Field<float> length_field{std::make_shared<SplineLengthFieldInput>()}; + params.set_output("Length", std::move(length_field)); +} + +} // namespace blender::nodes + +void register_node_type_geo_input_spline_length() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_INPUT_SPLINE_LENGTH, "Spline Length", NODE_CLASS_INPUT, 0); + ntype.geometry_node_execute = blender::nodes::geo_node_input_spline_length_exec; + ntype.declare = blender::nodes::geo_node_input_spline_length_declare; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_tangent.cc b/source/blender/nodes/geometry/nodes/node_geo_input_tangent.cc index 68788709f1e..d690642373a 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_input_tangent.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_input_tangent.cc @@ -24,7 +24,7 @@ namespace blender::nodes { static void geo_node_input_tangent_declare(NodeDeclarationBuilder &b) { - b.add_output<decl::Vector>("Tangent"); + b.add_output<decl::Vector>("Tangent").field_source(); } static void calculate_bezier_tangents(const BezierSpline &spline, MutableSpan<float3> tangents) diff --git a/source/blender/nodes/geometry/nodes/node_geo_instance_on_points.cc b/source/blender/nodes/geometry/nodes/node_geo_instance_on_points.cc new file mode 100644 index 00000000000..8c0c0763be8 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_instance_on_points.cc @@ -0,0 +1,207 @@ +/* + * 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 "DNA_collection_types.h" + +#include "BLI_hash.h" +#include "BLI_task.hh" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +namespace blender::nodes { + +static void geo_node_instance_on_points_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>("Points").description("Points to instance on"); + b.add_input<decl::Geometry>("Instance").description("Geometry that is instanced on the points"); + b.add_input<decl::Bool>("Pick Instance") + .supports_field() + .description("Place different instances on different points"); + b.add_input<decl::Int>("Instance Index") + .implicit_field() + .description( + "Index of the instance that used for each point. This is only used when Pick Instances " + "is on. By default the point index is used"); + b.add_input<decl::Vector>("Rotation") + .subtype(PROP_EULER) + .supports_field() + .description("Rotation of the instances"); + b.add_input<decl::Vector>("Scale") + .default_value({1.0f, 1.0f, 1.0f}) + .supports_field() + .description("Scale of the instances"); + b.add_input<decl::Int>("Stable ID") + .supports_field() + .description( + "ID for every instance that is used to identify it over time even when the number of " + "instances changes. Used for example for motion blur"); + + b.add_output<decl::Geometry>("Instances"); +} + +static void add_instances_from_component(InstancesComponent &dst_component, + const GeometryComponent &src_component, + const GeometrySet &instance, + const GeoNodeExecParams ¶ms) +{ + const AttributeDomain domain = ATTR_DOMAIN_POINT; + const int domain_size = src_component.attribute_domain_size(domain); + + /* The initial size of the component might be non-zero when this function is called for multiple + * component types. */ + const int start_len = dst_component.instances_amount(); + dst_component.resize(start_len + domain_size); + MutableSpan<int> dst_handles = dst_component.instance_reference_handles().slice(start_len, + domain_size); + MutableSpan<float4x4> dst_transforms = dst_component.instance_transforms().slice(start_len, + domain_size); + MutableSpan<int> dst_stable_ids = dst_component.instance_ids().slice(start_len, domain_size); + + GeometryComponentFieldContext field_context{src_component, domain}; + FieldEvaluator field_evaluator{field_context, domain_size}; + + const VArray<bool> *pick_instance = nullptr; + const VArray<int> *indices = nullptr; + const VArray<float3> *rotations = nullptr; + const VArray<float3> *scales = nullptr; + field_evaluator.add(params.get_input<Field<bool>>("Pick Instance"), &pick_instance); + field_evaluator.add(params.get_input<Field<int>>("Instance Index"), &indices); + field_evaluator.add(params.get_input<Field<float3>>("Rotation"), &rotations); + field_evaluator.add(params.get_input<Field<float3>>("Scale"), &scales); + field_evaluator.add_with_destination(params.get_input<Field<int>>("Stable ID"), dst_stable_ids); + field_evaluator.evaluate(); + + GVArray_Typed<float3> positions = src_component.attribute_get_for_read<float3>( + "position", domain, {0, 0, 0}); + + const InstancesComponent *src_instances = instance.get_component_for_read<InstancesComponent>(); + + /* Maps handles from the source instances to handles on the new instance. */ + Array<int> handle_mapping; + /* Only fill #handle_mapping when it may be used below. */ + if (src_instances != nullptr && + (!pick_instance->is_single() || pick_instance->get_internal_single())) { + Span<InstanceReference> src_references = src_instances->references(); + handle_mapping.reinitialize(src_references.size()); + for (const int src_instance_handle : src_references.index_range()) { + const InstanceReference &reference = src_references[src_instance_handle]; + const int dst_instance_handle = dst_component.add_reference(reference); + handle_mapping[src_instance_handle] = dst_instance_handle; + } + } + + const int full_instance_handle = dst_component.add_reference(instance); + /* Add this reference last, because it is the most likely one to be removed later on. */ + const int empty_reference_handle = dst_component.add_reference(InstanceReference()); + + threading::parallel_for(IndexRange(domain_size), 1024, [&](IndexRange range) { + for (const int i : range) { + /* Compute base transform for every instances. */ + float4x4 &dst_transform = dst_transforms[i]; + dst_transform = float4x4::from_loc_eul_scale( + positions[i], rotations->get(i), scales->get(i)); + + /* Reference that will be used by this new instance. */ + int dst_handle = empty_reference_handle; + + const bool use_individual_instance = pick_instance->get(i); + if (use_individual_instance) { + if (src_instances != nullptr) { + const int src_instances_amount = src_instances->instances_amount(); + const int original_index = indices->get(i); + /* Use #mod_i instead of `%` to get the desirable wrap around behavior where -1 refers to + * the last element. */ + const int index = mod_i(original_index, std::max(src_instances_amount, 1)); + if (index < src_instances_amount) { + /* Get the reference to the source instance. */ + const int src_handle = src_instances->instance_reference_handles()[index]; + dst_handle = handle_mapping[src_handle]; + + /* Take transforms of the source instance into account. */ + mul_m4_m4_post(dst_transform.values, + src_instances->instance_transforms()[index].values); + } + } + } + else { + /* Use entire source geometry as instance. */ + dst_handle = full_instance_handle; + } + /* Set properties of new instance. */ + dst_handles[i] = dst_handle; + } + }); + + if (pick_instance->is_single()) { + if (pick_instance->get_internal_single()) { + if (instance.has_realized_data()) { + params.error_message_add( + NodeWarningType::Info, + TIP_("Realized geometry is not used when pick instances is true")); + } + } + } +} + +static void geo_node_instance_on_points_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Points"); + GeometrySet instance = params.get_input<GeometrySet>("Instance"); + instance.ensure_owns_direct_data(); + + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + InstancesComponent &instances = geometry_set.get_component_for_write<InstancesComponent>(); + + if (geometry_set.has<MeshComponent>()) { + add_instances_from_component( + instances, *geometry_set.get_component_for_read<MeshComponent>(), instance, params); + geometry_set.remove(GEO_COMPONENT_TYPE_MESH); + } + if (geometry_set.has<PointCloudComponent>()) { + add_instances_from_component(instances, + *geometry_set.get_component_for_read<PointCloudComponent>(), + instance, + params); + geometry_set.remove(GEO_COMPONENT_TYPE_POINT_CLOUD); + } + if (geometry_set.has<CurveComponent>()) { + add_instances_from_component( + instances, *geometry_set.get_component_for_read<CurveComponent>(), instance, params); + geometry_set.remove(GEO_COMPONENT_TYPE_CURVE); + } + /* Unused references may have been added above. Remove those now so that other nodes don't + * process them needlessly. */ + instances.remove_unused_references(); + }); + + params.set_output("Instances", std::move(geometry_set)); +} + +} // namespace blender::nodes + +void register_node_type_geo_instance_on_points() +{ + static bNodeType ntype; + + geo_node_type_base( + &ntype, GEO_NODE_INSTANCE_ON_POINTS, "Instance on Points", NODE_CLASS_GEOMETRY, 0); + ntype.declare = blender::nodes::geo_node_instance_on_points_declare; + ntype.geometry_node_execute = blender::nodes::geo_node_instance_on_points_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 93643298f92..3e9b615f478 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc @@ -437,7 +437,7 @@ static void join_curve_components(MutableSpan<GeometrySet> src_geometry_sets, Ge /* Retrieve attribute info before moving the splines out of the input components. */ const Map<AttributeIDRef, AttributeMetaData> info = get_final_attribute_info( {(const GeometryComponent **)src_components.data(), src_components.size()}, - {"position", "radius", "tilt", "cyclic", "resolution"}); + {"position", "radius", "tilt", "handle_left", "handle_right", "cyclic", "resolution"}); CurveComponent &dst_component = result.get_component_for_write<CurveComponent>(); CurveEval *dst_curve = new CurveEval(); diff --git a/source/blender/nodes/geometry/nodes/node_geo_material_assign.cc b/source/blender/nodes/geometry/nodes/node_geo_material_assign.cc index 43818947272..780994996ae 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_material_assign.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_material_assign.cc @@ -30,7 +30,7 @@ static void geo_node_material_assign_declare(NodeDeclarationBuilder &b) { b.add_input<decl::Geometry>("Geometry"); b.add_input<decl::Material>("Material").hide_label(); - b.add_input<decl::Bool>("Selection").default_value(true).hide_value(); + b.add_input<decl::Bool>("Selection").default_value(true).hide_value().supports_field(); b.add_output<decl::Geometry>("Geometry"); } @@ -64,23 +64,22 @@ static void geo_node_material_assign_exec(GeoNodeExecParams params) GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); - geometry_set = geometry_set_realize_instances(geometry_set); + geometry_set.modify_geometry_sets([&](GeometrySet &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) { + GeometryComponentFieldContext field_context{mesh_component, ATTR_DOMAIN_FACE}; - 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) { + fn::FieldEvaluator selection_evaluator{field_context, mesh->totpoly}; + selection_evaluator.add(selection_field); + selection_evaluator.evaluate(); + const IndexMask selection = selection_evaluator.get_evaluated_as_mask(0); - GeometryComponentFieldContext field_context{mesh_component, ATTR_DOMAIN_FACE}; - - fn::FieldEvaluator selection_evaluator{field_context, mesh->totpoly}; - selection_evaluator.add(selection_field); - selection_evaluator.evaluate(); - const IndexMask selection = selection_evaluator.get_evaluated_as_mask(0); - - assign_material_to_faces(*mesh, selection, material); + assign_material_to_faces(*mesh, selection, material); + } } - } + }); params.set_output("Geometry", std::move(geometry_set)); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_material_replace.cc b/source/blender/nodes/geometry/nodes/node_geo_material_replace.cc index a9c3bfc6ce0..a917434fa00 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_material_replace.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_material_replace.cc @@ -40,11 +40,9 @@ static void geo_node_material_replace_exec(GeoNodeExecParams params) Material *new_material = params.extract_input<Material *>("New"); 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(); + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + Mesh *mesh = geometry_set.get_mesh_for_write(); if (mesh != nullptr) { for (const int i : IndexRange(mesh->totcol)) { if (mesh->mat[i] == old_material) { @@ -52,7 +50,7 @@ static void geo_node_material_replace_exec(GeoNodeExecParams params) } } } - } + }); params.set_output("Geometry", std::move(geometry_set)); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_material_selection.cc b/source/blender/nodes/geometry/nodes/node_geo_material_selection.cc index 22c24e34314..9d4533b9bda 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_material_selection.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_material_selection.cc @@ -31,7 +31,7 @@ namespace blender::nodes { static void geo_node_material_selection_declare(NodeDeclarationBuilder &b) { b.add_input<decl::Material>("Material").hide_label(true); - b.add_output<decl::Bool>("Selection"); + b.add_output<decl::Bool>("Selection").field_source(); } static void select_mesh_by_material(const Mesh &mesh, @@ -100,13 +100,16 @@ class MaterialSelectionFieldInput final : public fn::FieldInput { uint64_t hash() const override { - /* Some random constant hash. */ - return 91619626; + return get_default_hash(material_); } bool is_equal_to(const fn::FieldNode &other) const override { - return dynamic_cast<const MaterialSelectionFieldInput *>(&other) != nullptr; + if (const MaterialSelectionFieldInput *other_material_selection = + dynamic_cast<const MaterialSelectionFieldInput *>(&other)) { + return material_ == other_material_selection->material_; + } + return false; } }; diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cone.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cone.cc index 0d58476fc58..059e6e8680c 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cone.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cone.cc @@ -29,13 +29,38 @@ namespace blender::nodes { static void geo_node_mesh_primitive_cone_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Int>("Vertices").default_value(32).min(3); + b.add_input<decl::Int>("Vertices").default_value(32).min(3).max(512); + b.add_input<decl::Int>("Side Segments").default_value(1).min(1).max(512); + b.add_input<decl::Int>("Fill Segments").default_value(1).min(1).max(512); b.add_input<decl::Float>("Radius Top").min(0.0f).subtype(PROP_DISTANCE); b.add_input<decl::Float>("Radius Bottom").default_value(1.0f).min(0.0f).subtype(PROP_DISTANCE); b.add_input<decl::Float>("Depth").default_value(2.0f).min(0.0f).subtype(PROP_DISTANCE); b.add_output<decl::Geometry>("Geometry"); } +static void geo_node_mesh_primitive_cone_init(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeGeometryMeshCone *node_storage = (NodeGeometryMeshCone *)MEM_callocN( + sizeof(NodeGeometryMeshCone), __func__); + + node_storage->fill_type = GEO_NODE_MESH_CIRCLE_FILL_NGON; + + node->storage = node_storage; +} + +static void geo_node_mesh_primitive_cone_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + bNodeSocket *vertices_socket = (bNodeSocket *)node->inputs.first; + bNodeSocket *rings_socket = vertices_socket->next; + bNodeSocket *fill_subdiv_socket = rings_socket->next; + + const NodeGeometryMeshCone &storage = *(const NodeGeometryMeshCone *)node->storage; + const GeometryNodeMeshCircleFillType fill_type = + static_cast<const GeometryNodeMeshCircleFillType>(storage.fill_type); + const bool has_fill = fill_type != GEO_NODE_MESH_CIRCLE_FILL_NONE; + nodeSetSocketAvailability(fill_subdiv_socket, has_fill); +} + static void geo_node_mesh_primitive_cone_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) @@ -45,493 +70,632 @@ static void geo_node_mesh_primitive_cone_layout(uiLayout *layout, uiItemR(layout, ptr, "fill_type", 0, nullptr, ICON_NONE); } -static void geo_node_mesh_primitive_cone_init(bNodeTree *UNUSED(ntree), bNode *node) +struct ConeConfig { + float radius_top; + float radius_bottom; + float height; + int circle_segments; + int side_segments; + int fill_segments; + GeometryNodeMeshCircleFillType fill_type; + + bool top_is_point; + bool bottom_is_point; + /* The cone tip and a triangle fan filling are topologically identical. + * This simplifies the logic in some cases. */ + bool top_has_center_vert; + bool bottom_has_center_vert; + + /* Helpful quantities. */ + int tot_quad_rings; + int tot_edge_rings; + int tot_verts; + int tot_edges; + + /* Helpful vertex indices. */ + int first_vert; + int first_ring_verts_start; + int last_ring_verts_start; + int last_vert; + + /* Helpful edge indices. */ + int first_ring_edges_start; + int last_ring_edges_start; + int last_fan_edges_start; + int last_edge; + + ConeConfig(float radius_top, + float radius_bottom, + float depth, + int circle_segments, + int side_segments, + int fill_segments, + GeometryNodeMeshCircleFillType fill_type) + : radius_top(radius_top), + radius_bottom(radius_bottom), + height(0.5f * depth), + circle_segments(circle_segments), + side_segments(side_segments), + fill_segments(fill_segments), + fill_type(fill_type) + { + this->top_is_point = this->radius_top == 0.0f; + this->bottom_is_point = this->radius_bottom == 0.0f; + this->top_has_center_vert = this->top_is_point || + this->fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN; + this->bottom_has_center_vert = this->bottom_is_point || + this->fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN; + + this->tot_quad_rings = this->calculate_total_quad_rings(); + this->tot_edge_rings = this->calculate_total_edge_rings(); + this->tot_verts = this->calculate_total_verts(); + this->tot_edges = this->calculate_total_edges(); + + this->first_vert = 0; + this->first_ring_verts_start = this->top_has_center_vert ? 1 : first_vert; + this->last_vert = this->tot_verts - 1; + this->last_ring_verts_start = this->last_vert - this->circle_segments; + + this->first_ring_edges_start = this->top_has_center_vert ? this->circle_segments : 0; + this->last_ring_edges_start = this->first_ring_edges_start + + this->tot_quad_rings * this->circle_segments * 2; + this->last_fan_edges_start = this->tot_edges - this->circle_segments; + this->last_edge = this->tot_edges - 1; + } + + private: + int calculate_total_quad_rings(); + int calculate_total_edge_rings(); + int calculate_total_verts(); + int calculate_total_edges(); + + public: + int get_tot_corners() const; + int get_tot_faces() const; +}; + +int ConeConfig::calculate_total_quad_rings() { - NodeGeometryMeshCone *node_storage = (NodeGeometryMeshCone *)MEM_callocN( - sizeof(NodeGeometryMeshCone), __func__); + if (top_is_point && bottom_is_point) { + return 0; + } - node_storage->fill_type = GEO_NODE_MESH_CIRCLE_FILL_NGON; + int quad_rings = 0; - node->storage = node_storage; + if (!top_is_point) { + quad_rings += fill_segments - 1; + } + + quad_rings += (!top_is_point && !bottom_is_point) ? side_segments : (side_segments - 1); + + if (!bottom_is_point) { + quad_rings += fill_segments - 1; + } + + return quad_rings; } -static int vert_total(const GeometryNodeMeshCircleFillType fill_type, - const int verts_num, - const bool top_is_point, - const bool bottom_is_point) +int ConeConfig::calculate_total_edge_rings() { - int vert_total = 0; + if (top_is_point && bottom_is_point) { + return 0; + } + + int edge_rings = 0; + if (!top_is_point) { - vert_total += verts_num; - if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { - vert_total++; - } + edge_rings += fill_segments; } - else { + + edge_rings += side_segments - 1; + + if (!bottom_is_point) { + edge_rings += fill_segments; + } + + return edge_rings; +} + +int ConeConfig::calculate_total_verts() +{ + if (top_is_point && bottom_is_point) { + return side_segments + 1; + } + + int vert_total = 0; + + if (top_has_center_vert) { vert_total++; } + + if (!top_is_point) { + vert_total += circle_segments * fill_segments; + } + + vert_total += circle_segments * (side_segments - 1); + if (!bottom_is_point) { - vert_total += verts_num; - if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { - vert_total++; - } + vert_total += circle_segments * fill_segments; } - else { + + if (bottom_has_center_vert) { vert_total++; } return vert_total; } -static int edge_total(const GeometryNodeMeshCircleFillType fill_type, - const int verts_num, - const bool top_is_point, - const bool bottom_is_point) +int ConeConfig::calculate_total_edges() { if (top_is_point && bottom_is_point) { - return 1; + return side_segments; } int edge_total = 0; - if (!top_is_point) { - edge_total += verts_num; - if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { - edge_total += verts_num; - } + if (top_has_center_vert) { + edge_total += circle_segments; } - edge_total += verts_num; + edge_total += circle_segments * (tot_quad_rings * 2 + 1); - if (!bottom_is_point) { - edge_total += verts_num; - if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { - edge_total += verts_num; - } + if (bottom_has_center_vert) { + edge_total += circle_segments; } return edge_total; } -static int corner_total(const GeometryNodeMeshCircleFillType fill_type, - const int verts_num, - const bool top_is_point, - const bool bottom_is_point) +int ConeConfig::get_tot_corners() const { if (top_is_point && bottom_is_point) { return 0; } int corner_total = 0; - if (!top_is_point) { - if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { - corner_total += verts_num; - } - else if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { - corner_total += verts_num * 3; - } - } - if (!top_is_point && !bottom_is_point) { - corner_total += verts_num * 4; + if (top_has_center_vert) { + corner_total += (circle_segments * 3); } - else { - corner_total += verts_num * 3; + else if (!top_is_point && fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { + corner_total += circle_segments; } - if (!bottom_is_point) { - if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { - corner_total += verts_num; - } - else if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { - corner_total += verts_num * 3; - } + corner_total += tot_quad_rings * (circle_segments * 4); + + if (bottom_has_center_vert) { + corner_total += (circle_segments * 3); + } + else if (!bottom_is_point && fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { + corner_total += circle_segments; } return corner_total; } -static int face_total(const GeometryNodeMeshCircleFillType fill_type, - const int verts_num, - const bool top_is_point, - const bool bottom_is_point) +int ConeConfig::get_tot_faces() const { if (top_is_point && bottom_is_point) { return 0; } int face_total = 0; - if (!top_is_point) { - if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { - face_total++; - } - else if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { - face_total += verts_num; - } + if (top_has_center_vert) { + face_total += circle_segments; + } + else if (!top_is_point && fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { + face_total++; } - face_total += verts_num; + face_total += tot_quad_rings * circle_segments; - if (!bottom_is_point) { - if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { - face_total++; - } - else if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { - face_total += verts_num; - } + if (bottom_has_center_vert) { + face_total += circle_segments; + } + else if (!bottom_is_point && fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { + face_total++; } return face_total; } -static void calculate_uvs(Mesh *mesh, - const bool top_is_point, - const bool bottom_is_point, - const int verts_num, - const GeometryNodeMeshCircleFillType fill_type) +static void calculate_cone_vertices(const MutableSpan<MVert> &verts, const ConeConfig &config) { - MeshComponent mesh_component; - mesh_component.replace(mesh, GeometryOwnershipType::Editable); - OutputAttribute_Typed<float2> uv_attribute = - mesh_component.attribute_try_get_for_output_only<float2>("uv_map", ATTR_DOMAIN_CORNER); - MutableSpan<float2> uvs = uv_attribute.as_span(); - - Array<float2> circle(verts_num); + Array<float2> circle(config.circle_segments); + const float angle_delta = 2.0f * (M_PI / static_cast<float>(config.circle_segments)); float angle = 0.0f; - const float angle_delta = 2.0f * M_PI / static_cast<float>(verts_num); - for (const int i : IndexRange(verts_num)) { - circle[i].x = std::cos(angle) * 0.225f + 0.25f; - circle[i].y = std::sin(angle) * 0.225f + 0.25f; + for (const int i : IndexRange(config.circle_segments)) { + circle[i].x = std::cos(angle); + circle[i].y = std::sin(angle); angle += angle_delta; } - int loop_index = 0; - if (!top_is_point) { - if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { - for (const int i : IndexRange(verts_num)) { - uvs[loop_index++] = circle[i]; - } - } - else if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { - for (const int i : IndexRange(verts_num)) { - uvs[loop_index++] = circle[i]; - uvs[loop_index++] = circle[(i + 1) % verts_num]; - uvs[loop_index++] = float2(0.25f, 0.25f); - } - } - } + int vert_index = 0; - /* Create side corners and faces. */ - if (!top_is_point && !bottom_is_point) { - const float bottom = (fill_type == GEO_NODE_MESH_CIRCLE_FILL_NONE) ? 0.0f : 0.5f; - /* Quads connect the top and bottom. */ - for (const int i : IndexRange(verts_num)) { - const float vert = static_cast<float>(i); - uvs[loop_index++] = float2(vert / verts_num, bottom); - uvs[loop_index++] = float2(vert / verts_num, 1.0f); - uvs[loop_index++] = float2((vert + 1.0f) / verts_num, 1.0f); - uvs[loop_index++] = float2((vert + 1.0f) / verts_num, bottom); - } + /* Top cone tip or triangle fan center. */ + if (config.top_has_center_vert) { + copy_v3_fl3(verts[vert_index++].co, 0.0f, 0.0f, config.height); } - else { - /* Triangles connect the top and bottom section. */ - if (!top_is_point) { - for (const int i : IndexRange(verts_num)) { - uvs[loop_index++] = circle[i] + float2(0.5f, 0.0f); - uvs[loop_index++] = float2(0.75f, 0.25f); - uvs[loop_index++] = circle[(i + 1) % verts_num] + float2(0.5f, 0.0f); - } - } - else { - BLI_assert(!bottom_is_point); - for (const int i : IndexRange(verts_num)) { - uvs[loop_index++] = circle[i]; - uvs[loop_index++] = circle[(i + 1) % verts_num]; - uvs[loop_index++] = float2(0.25f, 0.25f); + + /* Top fill including the outer edge of the fill. */ + if (!config.top_is_point) { + const float top_fill_radius_delta = config.radius_top / + static_cast<float>(config.fill_segments); + for (const int i : IndexRange(config.fill_segments)) { + const float top_fill_radius = top_fill_radius_delta * (i + 1); + for (const int j : IndexRange(config.circle_segments)) { + const float x = circle[j].x * top_fill_radius; + const float y = circle[j].y * top_fill_radius; + copy_v3_fl3(verts[vert_index++].co, x, y, config.height); } } } - /* Create bottom corners and faces. */ - if (!bottom_is_point) { - if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { - for (const int i : IndexRange(verts_num)) { - /* Go backwards because of reversed face normal. */ - uvs[loop_index++] = circle[verts_num - 1 - i] + float2(0.5f, 0.0f); - } + /* Rings along the side. */ + const float side_radius_delta = (config.radius_bottom - config.radius_top) / + static_cast<float>(config.side_segments); + const float height_delta = 2.0f * config.height / static_cast<float>(config.side_segments); + for (const int i : IndexRange(config.side_segments - 1)) { + const float ring_radius = config.radius_top + (side_radius_delta * (i + 1)); + const float ring_height = config.height - (height_delta * (i + 1)); + for (const int j : IndexRange(config.circle_segments)) { + const float x = circle[j].x * ring_radius; + const float y = circle[j].y * ring_radius; + copy_v3_fl3(verts[vert_index++].co, x, y, ring_height); } - else if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { - for (const int i : IndexRange(verts_num)) { - uvs[loop_index++] = circle[i] + float2(0.5f, 0.0f); - uvs[loop_index++] = float2(0.75f, 0.25f); - uvs[loop_index++] = circle[(i + 1) % verts_num] + float2(0.5f, 0.0f); + } + + /* Bottom fill including the outer edge of the fill. */ + if (!config.bottom_is_point) { + const float bottom_fill_radius_delta = config.radius_bottom / + static_cast<float>(config.fill_segments); + for (const int i : IndexRange(config.fill_segments)) { + const float bottom_fill_radius = config.radius_bottom - (i * bottom_fill_radius_delta); + for (const int j : IndexRange(config.circle_segments)) { + const float x = circle[j].x * bottom_fill_radius; + const float y = circle[j].y * bottom_fill_radius; + copy_v3_fl3(verts[vert_index++].co, x, y, -config.height); } } } - uv_attribute.save(); + /* Bottom cone tip or triangle fan center. */ + if (config.bottom_has_center_vert) { + copy_v3_fl3(verts[vert_index++].co, 0.0f, 0.0f, -config.height); + } } -Mesh *create_cylinder_or_cone_mesh(const float radius_top, - const float radius_bottom, - const float depth, - const int verts_num, - const GeometryNodeMeshCircleFillType fill_type) +static void calculate_cone_edges(const MutableSpan<MEdge> &edges, const ConeConfig &config) { - const bool top_is_point = radius_top == 0.0f; - const bool bottom_is_point = radius_bottom == 0.0f; - const float height = depth * 0.5f; - /* Handle the case of a line / single point before everything else to avoid - * the need to check for it later. */ - if (top_is_point && bottom_is_point) { - const bool single_vertex = height == 0.0f; - Mesh *mesh = BKE_mesh_new_nomain(single_vertex ? 1 : 2, single_vertex ? 0 : 1, 0, 0, 0); - copy_v3_v3(mesh->mvert[0].co, float3(0.0f, 0.0f, height)); - if (single_vertex) { - const short up[3] = {0, 0, SHRT_MAX}; - copy_v3_v3_short(mesh->mvert[0].no, up); - return mesh; - } - copy_v3_v3(mesh->mvert[1].co, float3(0.0f, 0.0f, -height)); - mesh->medge[0].v1 = 0; - mesh->medge[0].v2 = 1; - mesh->medge[0].flag |= ME_LOOSEEDGE; - BKE_mesh_normals_tag_dirty(mesh); - return mesh; - } - - Mesh *mesh = BKE_mesh_new_nomain( - vert_total(fill_type, verts_num, top_is_point, bottom_is_point), - edge_total(fill_type, verts_num, top_is_point, bottom_is_point), - 0, - corner_total(fill_type, verts_num, top_is_point, bottom_is_point), - face_total(fill_type, verts_num, top_is_point, bottom_is_point)); - BKE_id_material_eval_ensure_default_slot(&mesh->id); - MutableSpan<MVert> verts{mesh->mvert, mesh->totvert}; - MutableSpan<MLoop> loops{mesh->mloop, mesh->totloop}; - MutableSpan<MEdge> edges{mesh->medge, mesh->totedge}; - MutableSpan<MPoly> polys{mesh->mpoly, mesh->totpoly}; - - /* Calculate vertex positions. */ - const int top_verts_start = 0; - const int bottom_verts_start = top_verts_start + (!top_is_point ? verts_num : 1); - const float angle_delta = 2.0f * (M_PI / static_cast<float>(verts_num)); - for (const int i : IndexRange(verts_num)) { - const float angle = i * angle_delta; - const float x = std::cos(angle); - const float y = std::sin(angle); - if (!top_is_point) { - copy_v3_v3(verts[top_verts_start + i].co, float3(x * radius_top, y * radius_top, height)); - } - if (!bottom_is_point) { - copy_v3_v3(verts[bottom_verts_start + i].co, - float3(x * radius_bottom, y * radius_bottom, -height)); + int edge_index = 0; + + /* Edges for top cone tip or triangle fan */ + if (config.top_has_center_vert) { + for (const int i : IndexRange(config.circle_segments)) { + MEdge &edge = edges[edge_index++]; + edge.v1 = config.first_vert; + edge.v2 = config.first_ring_verts_start + i; + edge.flag = ME_EDGEDRAW | ME_EDGERENDER; } } - if (top_is_point) { - copy_v3_v3(verts[top_verts_start].co, float3(0.0f, 0.0f, height)); - } - if (bottom_is_point) { - copy_v3_v3(verts[bottom_verts_start].co, float3(0.0f, 0.0f, -height)); - } - /* Add center vertices for the triangle fans at the end. */ - const int top_center_vert_index = bottom_verts_start + (bottom_is_point ? 1 : verts_num); - const int bottom_center_vert_index = top_center_vert_index + (top_is_point ? 0 : 1); - if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { - if (!top_is_point) { - copy_v3_v3(verts[top_center_vert_index].co, float3(0.0f, 0.0f, height)); + /* Rings and connecting edges between the rings. */ + for (const int i : IndexRange(config.tot_edge_rings)) { + const int this_ring_vert_start = config.first_ring_verts_start + (i * config.circle_segments); + const int next_ring_vert_start = this_ring_vert_start + config.circle_segments; + /* Edge rings. */ + for (const int j : IndexRange(config.circle_segments)) { + MEdge &edge = edges[edge_index++]; + edge.v1 = this_ring_vert_start + j; + edge.v2 = this_ring_vert_start + ((j + 1) % config.circle_segments); + edge.flag = ME_EDGEDRAW | ME_EDGERENDER; } - if (!bottom_is_point) { - copy_v3_v3(verts[bottom_center_vert_index].co, float3(0.0f, 0.0f, -height)); + if (i == config.tot_edge_rings - 1) { + /* There is one fewer ring of connecting edges. */ + break; } - } - - /* Create top edges. */ - const int top_edges_start = 0; - const int top_fan_edges_start = (!top_is_point && - fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) ? - top_edges_start + verts_num : - top_edges_start; - if (!top_is_point) { - for (const int i : IndexRange(verts_num)) { - MEdge &edge = edges[top_edges_start + i]; - edge.v1 = top_verts_start + i; - edge.v2 = top_verts_start + (i + 1) % verts_num; + /* Connecting edges. */ + for (const int j : IndexRange(config.circle_segments)) { + MEdge &edge = edges[edge_index++]; + edge.v1 = this_ring_vert_start + j; + edge.v2 = next_ring_vert_start + j; edge.flag = ME_EDGEDRAW | ME_EDGERENDER; } - if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { - for (const int i : IndexRange(verts_num)) { - MEdge &edge = edges[top_fan_edges_start + i]; - edge.v1 = top_center_vert_index; - edge.v2 = top_verts_start + i; - edge.flag = ME_EDGEDRAW | ME_EDGERENDER; - } - } } - /* Create connecting edges. */ - const int connecting_edges_start = top_fan_edges_start + (!top_is_point ? verts_num : 0); - for (const int i : IndexRange(verts_num)) { - MEdge &edge = edges[connecting_edges_start + i]; - edge.v1 = top_verts_start + (!top_is_point ? i : 0); - edge.v2 = bottom_verts_start + (!bottom_is_point ? i : 0); - edge.flag = ME_EDGEDRAW | ME_EDGERENDER; - } - - /* Create bottom edges. */ - const int bottom_edges_start = connecting_edges_start + verts_num; - const int bottom_fan_edges_start = (!bottom_is_point && - fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) ? - bottom_edges_start + verts_num : - bottom_edges_start; - if (!bottom_is_point) { - for (const int i : IndexRange(verts_num)) { - MEdge &edge = edges[bottom_edges_start + i]; - edge.v1 = bottom_verts_start + i; - edge.v2 = bottom_verts_start + (i + 1) % verts_num; + /* Edges for bottom triangle fan or tip. */ + if (config.bottom_has_center_vert) { + for (const int i : IndexRange(config.circle_segments)) { + MEdge &edge = edges[edge_index++]; + edge.v1 = config.last_ring_verts_start + i; + edge.v2 = config.last_vert; edge.flag = ME_EDGEDRAW | ME_EDGERENDER; } - if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { - for (const int i : IndexRange(verts_num)) { - MEdge &edge = edges[bottom_fan_edges_start + i]; - edge.v1 = bottom_center_vert_index; - edge.v2 = bottom_verts_start + i; - edge.flag = ME_EDGEDRAW | ME_EDGERENDER; - } - } } +} - /* Create top corners and faces. */ +static void calculate_cone_faces(const MutableSpan<MLoop> &loops, + const MutableSpan<MPoly> &polys, + const ConeConfig &config) +{ int loop_index = 0; int poly_index = 0; - if (!top_is_point) { - if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { + + if (config.top_has_center_vert) { + /* Top cone tip or center triangle fan in the fill. */ + const int top_center_vert = 0; + const int top_fan_edges_start = 0; + + for (const int i : IndexRange(config.circle_segments)) { MPoly &poly = polys[poly_index++]; poly.loopstart = loop_index; - poly.totloop = verts_num; + poly.totloop = 3; - for (const int i : IndexRange(verts_num)) { - MLoop &loop = loops[loop_index++]; - loop.v = top_verts_start + i; - loop.e = top_edges_start + i; - } + MLoop &loop_a = loops[loop_index++]; + loop_a.v = config.first_ring_verts_start + i; + loop_a.e = config.first_ring_edges_start + i; + MLoop &loop_b = loops[loop_index++]; + loop_b.v = config.first_ring_verts_start + ((i + 1) % config.circle_segments); + loop_b.e = top_fan_edges_start + ((i + 1) % config.circle_segments); + MLoop &loop_c = loops[loop_index++]; + loop_c.v = top_center_vert; + loop_c.e = top_fan_edges_start + i; } - else if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { - for (const int i : IndexRange(verts_num)) { + } + else if (config.fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { + /* Center n-gon in the fill. */ + MPoly &poly = polys[poly_index++]; + poly.loopstart = loop_index; + poly.totloop = config.circle_segments; + for (const int i : IndexRange(config.circle_segments)) { + MLoop &loop = loops[loop_index++]; + loop.v = i; + loop.e = i; + } + } + + /* Quads connect one edge ring to the next one. */ + if (config.tot_quad_rings > 0) { + for (const int i : IndexRange(config.tot_quad_rings)) { + const int this_ring_vert_start = config.first_ring_verts_start + + (i * config.circle_segments); + const int next_ring_vert_start = this_ring_vert_start + config.circle_segments; + + const int this_ring_edges_start = config.first_ring_edges_start + + (i * 2 * config.circle_segments); + const int next_ring_edges_start = this_ring_edges_start + (2 * config.circle_segments); + const int ring_connections_start = this_ring_edges_start + config.circle_segments; + + for (const int j : IndexRange(config.circle_segments)) { MPoly &poly = polys[poly_index++]; poly.loopstart = loop_index; - poly.totloop = 3; + poly.totloop = 4; MLoop &loop_a = loops[loop_index++]; - loop_a.v = top_verts_start + i; - loop_a.e = top_edges_start + i; + loop_a.v = this_ring_vert_start + j; + loop_a.e = ring_connections_start + j; MLoop &loop_b = loops[loop_index++]; - loop_b.v = top_verts_start + (i + 1) % verts_num; - loop_b.e = top_fan_edges_start + (i + 1) % verts_num; + loop_b.v = next_ring_vert_start + j; + loop_b.e = next_ring_edges_start + j; MLoop &loop_c = loops[loop_index++]; - loop_c.v = top_center_vert_index; - loop_c.e = top_fan_edges_start + i; + loop_c.v = next_ring_vert_start + ((j + 1) % config.circle_segments); + loop_c.e = ring_connections_start + ((j + 1) % config.circle_segments); + MLoop &loop_d = loops[loop_index++]; + loop_d.v = this_ring_vert_start + ((j + 1) % config.circle_segments); + loop_d.e = this_ring_edges_start + j; } } } - /* Create side corners and faces. */ - if (!top_is_point && !bottom_is_point) { - /* Quads connect the top and bottom. */ - for (const int i : IndexRange(verts_num)) { + if (config.bottom_has_center_vert) { + /* Bottom cone tip or center triangle fan in the fill. */ + for (const int i : IndexRange(config.circle_segments)) { MPoly &poly = polys[poly_index++]; poly.loopstart = loop_index; - poly.totloop = 4; + poly.totloop = 3; MLoop &loop_a = loops[loop_index++]; - loop_a.v = top_verts_start + i; - loop_a.e = connecting_edges_start + i; + loop_a.v = config.last_ring_verts_start + i; + loop_a.e = config.last_fan_edges_start + i; MLoop &loop_b = loops[loop_index++]; - loop_b.v = bottom_verts_start + i; - loop_b.e = bottom_edges_start + i; + loop_b.v = config.last_vert; + loop_b.e = config.last_fan_edges_start + (i + 1) % config.circle_segments; MLoop &loop_c = loops[loop_index++]; - loop_c.v = bottom_verts_start + (i + 1) % verts_num; - loop_c.e = connecting_edges_start + (i + 1) % verts_num; - MLoop &loop_d = loops[loop_index++]; - loop_d.v = top_verts_start + (i + 1) % verts_num; - loop_d.e = top_edges_start + i; + loop_c.v = config.last_ring_verts_start + (i + 1) % config.circle_segments; + loop_c.e = config.last_ring_edges_start + i; } } - else { - /* Triangles connect the top and bottom section. */ - if (!top_is_point) { - for (const int i : IndexRange(verts_num)) { - MPoly &poly = polys[poly_index++]; - poly.loopstart = loop_index; - poly.totloop = 3; + else if (config.fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { + /* Center n-gon in the fill. */ + MPoly &poly = polys[poly_index++]; + poly.loopstart = loop_index; + poly.totloop = config.circle_segments; - MLoop &loop_a = loops[loop_index++]; - loop_a.v = top_verts_start + i; - loop_a.e = connecting_edges_start + i; - MLoop &loop_b = loops[loop_index++]; - loop_b.v = bottom_verts_start; - loop_b.e = connecting_edges_start + (i + 1) % verts_num; - MLoop &loop_c = loops[loop_index++]; - loop_c.v = top_verts_start + (i + 1) % verts_num; - loop_c.e = top_edges_start + i; - } + for (const int i : IndexRange(config.circle_segments)) { + /* Go backwards to reverse surface normal. */ + MLoop &loop = loops[loop_index++]; + loop.v = config.last_vert - i; + loop.e = config.last_edge - ((i + 1) % config.circle_segments); } - else { - BLI_assert(!bottom_is_point); - for (const int i : IndexRange(verts_num)) { - MPoly &poly = polys[poly_index++]; - poly.loopstart = loop_index; - poly.totloop = 3; + } +} - MLoop &loop_a = loops[loop_index++]; - loop_a.v = bottom_verts_start + i; - loop_a.e = bottom_edges_start + i; - MLoop &loop_b = loops[loop_index++]; - loop_b.v = bottom_verts_start + (i + 1) % verts_num; - loop_b.e = connecting_edges_start + (i + 1) % verts_num; - MLoop &loop_c = loops[loop_index++]; - loop_c.v = top_verts_start; - loop_c.e = connecting_edges_start + i; +/** + * If the top is the cone tip or has a fill, it is unwrapped into a circle in the + * lower left quadrant of the UV. + * Likewise, if the bottom is the cone tip or has a fill, it is unwrapped into a circle + * in the lower right quadrant of the UV. + * If the mesh is a truncated cone or a cylinder, the side faces are unwrapped into + * a rectangle that fills the top half of the UV (or the entire UV, if there are no fills). + */ +static void calculate_cone_uvs(Mesh *mesh, const ConeConfig &config) +{ + MeshComponent mesh_component; + mesh_component.replace(mesh, GeometryOwnershipType::Editable); + OutputAttribute_Typed<float2> uv_attribute = + mesh_component.attribute_try_get_for_output_only<float2>("uv_map", ATTR_DOMAIN_CORNER); + MutableSpan<float2> uvs = uv_attribute.as_span(); + + Array<float2> circle(config.circle_segments); + float angle = 0.0f; + const float angle_delta = 2.0f * M_PI / static_cast<float>(config.circle_segments); + for (const int i : IndexRange(config.circle_segments)) { + circle[i].x = std::cos(angle) * 0.225f; + circle[i].y = std::sin(angle) * 0.225f; + angle += angle_delta; + } + + int loop_index = 0; + + /* Left circle of the UV representing the top fill or top cone tip. */ + if (config.top_is_point || config.fill_type != GEO_NODE_MESH_CIRCLE_FILL_NONE) { + const float2 center_left(0.25f, 0.25f); + const float radius_factor_delta = 1.0f / (config.top_is_point ? + static_cast<float>(config.side_segments) : + static_cast<float>(config.fill_segments)); + const int left_circle_segment_count = config.top_is_point ? config.side_segments : + config.fill_segments; + + if (config.top_has_center_vert) { + /* Cone tip itself or triangle fan center of the fill. */ + for (const int i : IndexRange(config.circle_segments)) { + uvs[loop_index++] = radius_factor_delta * circle[i] + center_left; + uvs[loop_index++] = radius_factor_delta * circle[(i + 1) % config.circle_segments] + + center_left; + uvs[loop_index++] = center_left; + } + } + else if (!config.top_is_point && config.fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { + /* N-gon at the center of the fill. */ + for (const int i : IndexRange(config.circle_segments)) { + uvs[loop_index++] = radius_factor_delta * circle[i] + center_left; + } + } + /* The rest of the top fill is made out of quad rings. */ + for (const int i : IndexRange(1, left_circle_segment_count - 1)) { + const float inner_radius_factor = i * radius_factor_delta; + const float outer_radius_factor = (i + 1) * radius_factor_delta; + for (const int j : IndexRange(config.circle_segments)) { + uvs[loop_index++] = inner_radius_factor * circle[j] + center_left; + uvs[loop_index++] = outer_radius_factor * circle[j] + center_left; + uvs[loop_index++] = outer_radius_factor * circle[(j + 1) % config.circle_segments] + + center_left; + uvs[loop_index++] = inner_radius_factor * circle[(j + 1) % config.circle_segments] + + center_left; } } } - /* Create bottom corners and faces. */ - if (!bottom_is_point) { - if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { - MPoly &poly = polys[poly_index++]; - poly.loopstart = loop_index; - poly.totloop = verts_num; + if (!config.top_is_point && !config.bottom_is_point) { + /* Mesh is a truncated cone or cylinder. The sides are unwrapped into a rectangle. */ + const float bottom = (config.fill_type == GEO_NODE_MESH_CIRCLE_FILL_NONE) ? 0.0f : 0.5f; + const float x_delta = 1.0f / static_cast<float>(config.circle_segments); + const float y_delta = (1.0f - bottom) / static_cast<float>(config.side_segments); - for (const int i : IndexRange(verts_num)) { - /* Go backwards to reverse surface normal. */ - MLoop &loop = loops[loop_index++]; - loop.v = bottom_verts_start + verts_num - 1 - i; - loop.e = bottom_edges_start + verts_num - 1 - (i + 1) % verts_num; + for (const int i : IndexRange(config.side_segments)) { + for (const int j : IndexRange(config.circle_segments)) { + uvs[loop_index++] = float2(j * x_delta, i * y_delta + bottom); + uvs[loop_index++] = float2(j * x_delta, (i + 1) * y_delta + bottom); + uvs[loop_index++] = float2((j + 1) * x_delta, (i + 1) * y_delta + bottom); + uvs[loop_index++] = float2((j + 1) * x_delta, i * y_delta + bottom); } } - else if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { - for (const int i : IndexRange(verts_num)) { - MPoly &poly = polys[poly_index++]; - poly.loopstart = loop_index; - poly.totloop = 3; + } - MLoop &loop_a = loops[loop_index++]; - loop_a.v = bottom_verts_start + i; - loop_a.e = bottom_fan_edges_start + i; - MLoop &loop_b = loops[loop_index++]; - loop_b.v = bottom_center_vert_index; - loop_b.e = bottom_fan_edges_start + (i + 1) % verts_num; - MLoop &loop_c = loops[loop_index++]; - loop_c.v = bottom_verts_start + (i + 1) % verts_num; - loop_c.e = bottom_edges_start + i; + /* Right circle of the UV representing the bottom fill or bottom cone tip. */ + if (config.bottom_is_point || config.fill_type != GEO_NODE_MESH_CIRCLE_FILL_NONE) { + const float2 center_right(0.75f, 0.25f); + const float radius_factor_delta = 1.0f / (config.bottom_is_point ? + static_cast<float>(config.side_segments) : + static_cast<float>(config.fill_segments)); + const int right_circle_segment_count = config.bottom_is_point ? config.side_segments : + config.fill_segments; + + /* The bottom circle has to be created outside in to match the loop order. */ + for (const int i : IndexRange(right_circle_segment_count - 1)) { + const float outer_radius_factor = 1.0f - i * radius_factor_delta; + const float inner_radius_factor = 1.0f - (i + 1) * radius_factor_delta; + for (const int j : IndexRange(config.circle_segments)) { + uvs[loop_index++] = outer_radius_factor * circle[j] + center_right; + uvs[loop_index++] = inner_radius_factor * circle[j] + center_right; + uvs[loop_index++] = inner_radius_factor * circle[(j + 1) % config.circle_segments] + + center_right; + uvs[loop_index++] = outer_radius_factor * circle[(j + 1) % config.circle_segments] + + center_right; + } + } + + if (config.bottom_has_center_vert) { + /* Cone tip itself or triangle fan center of the fill. */ + for (const int i : IndexRange(config.circle_segments)) { + uvs[loop_index++] = radius_factor_delta * circle[i] + center_right; + uvs[loop_index++] = center_right; + uvs[loop_index++] = radius_factor_delta * circle[(i + 1) % config.circle_segments] + + center_right; + } + } + else if (!config.bottom_is_point && config.fill_type == GEO_NODE_MESH_CIRCLE_FILL_NGON) { + /* N-gon at the center of the fill. */ + for (const int i : IndexRange(config.circle_segments)) { + /* Go backwards because of reversed face normal. */ + uvs[loop_index++] = radius_factor_delta * circle[config.circle_segments - 1 - i] + + center_right; } } } + uv_attribute.save(); +} + +static Mesh *create_vertex_mesh() +{ + /* Returns a mesh with a single vertex at the origin. */ + Mesh *mesh = BKE_mesh_new_nomain(1, 0, 0, 0, 0); + copy_v3_fl3(mesh->mvert[0].co, 0.0f, 0.0f, 0.0f); + const short up[3] = {0, 0, SHRT_MAX}; + copy_v3_v3_short(mesh->mvert[0].no, up); + return mesh; +} + +Mesh *create_cylinder_or_cone_mesh(const float radius_top, + const float radius_bottom, + const float depth, + const int circle_segments, + const int side_segments, + const int fill_segments, + const GeometryNodeMeshCircleFillType fill_type) +{ + const ConeConfig config( + radius_top, radius_bottom, depth, circle_segments, side_segments, fill_segments, fill_type); + + /* Handle the case of a line / single point before everything else to avoid + * the need to check for it later. */ + if (config.top_is_point && config.bottom_is_point) { + if (config.height == 0.0f) { + return create_vertex_mesh(); + } + const float z_delta = -2.0f * config.height / static_cast<float>(config.side_segments); + const float3 start(0.0f, 0.0f, config.height); + const float3 delta(0.0f, 0.0f, z_delta); + return create_line_mesh(start, delta, config.tot_verts); + } + + Mesh *mesh = BKE_mesh_new_nomain( + config.tot_verts, config.tot_edges, 0, config.get_tot_corners(), config.get_tot_faces()); + BKE_id_material_eval_ensure_default_slot(&mesh->id); + + MutableSpan<MVert> verts{mesh->mvert, mesh->totvert}; + MutableSpan<MLoop> loops{mesh->mloop, mesh->totloop}; + MutableSpan<MEdge> edges{mesh->medge, mesh->totedge}; + MutableSpan<MPoly> polys{mesh->mpoly, mesh->totpoly}; + + calculate_cone_vertices(verts, config); + calculate_cone_edges(edges, config); + calculate_cone_faces(loops, polys, config); + calculate_cone_uvs(mesh, config); + BKE_mesh_normals_tag_dirty(mesh); - calculate_uvs(mesh, top_is_point, bottom_is_point, verts_num, fill_type); + calculate_cone_uvs(mesh, config); return mesh; } @@ -540,23 +704,37 @@ static void geo_node_mesh_primitive_cone_exec(GeoNodeExecParams params) { const bNode &node = params.node(); const NodeGeometryMeshCone &storage = *(const NodeGeometryMeshCone *)node.storage; - const GeometryNodeMeshCircleFillType fill_type = (const GeometryNodeMeshCircleFillType) storage.fill_type; - const int verts_num = params.extract_input<int>("Vertices"); - if (verts_num < 3) { + const int circle_segments = params.extract_input<int>("Vertices"); + if (circle_segments < 3) { params.error_message_add(NodeWarningType::Info, TIP_("Vertices must be at least 3")); params.set_output("Geometry", GeometrySet()); return; } + const int side_segments = params.extract_input<int>("Side Segments"); + if (side_segments < 1) { + params.error_message_add(NodeWarningType::Info, TIP_("Side Segments must be at least 1")); + params.set_output("Geometry", GeometrySet()); + return; + } + + const bool no_fill = fill_type == GEO_NODE_MESH_CIRCLE_FILL_NONE; + const int fill_segments = no_fill ? 1 : params.extract_input<int>("Fill Segments"); + if (fill_segments < 1) { + params.error_message_add(NodeWarningType::Info, TIP_("Fill Segments must be at least 1")); + params.set_output("Geometry", GeometrySet()); + return; + } + const float radius_top = params.extract_input<float>("Radius Top"); const float radius_bottom = params.extract_input<float>("Radius Bottom"); const float depth = params.extract_input<float>("Depth"); Mesh *mesh = create_cylinder_or_cone_mesh( - radius_top, radius_bottom, depth, verts_num, fill_type); + radius_top, radius_bottom, depth, circle_segments, side_segments, fill_segments, fill_type); /* Transform the mesh so that the base of the cone is at the origin. */ BKE_mesh_translate(mesh, float3(0.0f, 0.0f, depth * 0.5f), false); @@ -572,6 +750,7 @@ void register_node_type_geo_mesh_primitive_cone() geo_node_type_base(&ntype, GEO_NODE_MESH_PRIMITIVE_CONE, "Cone", NODE_CLASS_GEOMETRY, 0); node_type_init(&ntype, blender::nodes::geo_node_mesh_primitive_cone_init); + node_type_update(&ntype, blender::nodes::geo_node_mesh_primitive_cone_update); node_type_storage( &ntype, "NodeGeometryMeshCone", node_free_standard_storage, node_copy_standard_storage); ntype.geometry_node_execute = blender::nodes::geo_node_mesh_primitive_cone_exec; diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cylinder.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cylinder.cc index ed701c921ca..287ea896ade 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cylinder.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cylinder.cc @@ -29,9 +29,31 @@ namespace blender::nodes { static void geo_node_mesh_primitive_cylinder_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Int>("Vertices").default_value(32).min(3).max(4096); - b.add_input<decl::Float>("Radius").default_value(1.0f).min(0.0f).subtype(PROP_DISTANCE); - b.add_input<decl::Float>("Depth").default_value(2.0f).min(0.0f).subtype(PROP_DISTANCE); + b.add_input<decl::Int>("Vertices") + .default_value(32) + .min(3) + .max(512) + .description("The number of vertices around the circumference"); + b.add_input<decl::Int>("Side Segments") + .default_value(1) + .min(1) + .max(512) + .description("The number of segments along the side"); + b.add_input<decl::Int>("Fill Segments") + .default_value(1) + .min(1) + .max(512) + .description("The number of concentric segments of the fill"); + b.add_input<decl::Float>("Radius") + .default_value(1.0f) + .min(0.0f) + .subtype(PROP_DISTANCE) + .description("The radius of the cylinder"); + b.add_input<decl::Float>("Depth") + .default_value(2.0f) + .min(0.0f) + .subtype(PROP_DISTANCE) + .description("The height of the cylinder on the Z axis"); b.add_output<decl::Geometry>("Geometry"); } @@ -54,6 +76,19 @@ static void geo_node_mesh_primitive_cylinder_init(bNodeTree *UNUSED(ntree), bNod node->storage = node_storage; } +static void geo_node_mesh_primitive_cylinder_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + bNodeSocket *vertices_socket = (bNodeSocket *)node->inputs.first; + bNodeSocket *rings_socket = vertices_socket->next; + bNodeSocket *fill_subdiv_socket = rings_socket->next; + + const NodeGeometryMeshCone &storage = *(const NodeGeometryMeshCone *)node->storage; + const GeometryNodeMeshCircleFillType fill_type = + static_cast<const GeometryNodeMeshCircleFillType>(storage.fill_type); + const bool has_fill = fill_type != GEO_NODE_MESH_CIRCLE_FILL_NONE; + nodeSetSocketAvailability(fill_subdiv_socket, has_fill); +} + static void geo_node_mesh_primitive_cylinder_exec(GeoNodeExecParams params) { const bNode &node = params.node(); @@ -64,15 +99,31 @@ static void geo_node_mesh_primitive_cylinder_exec(GeoNodeExecParams params) const float radius = params.extract_input<float>("Radius"); const float depth = params.extract_input<float>("Depth"); - const int verts_num = params.extract_input<int>("Vertices"); - if (verts_num < 3) { + const int circle_segments = params.extract_input<int>("Vertices"); + if (circle_segments < 3) { params.error_message_add(NodeWarningType::Info, TIP_("Vertices must be at least 3")); params.set_output("Geometry", GeometrySet()); return; } + const int side_segments = params.extract_input<int>("Side Segments"); + if (side_segments < 1) { + params.error_message_add(NodeWarningType::Info, TIP_("Side Segments must be at least 1")); + params.set_output("Geometry", GeometrySet()); + return; + } + + const bool no_fill = fill_type == GEO_NODE_MESH_CIRCLE_FILL_NONE; + const int fill_segments = no_fill ? 1 : params.extract_input<int>("Fill Segments"); + if (fill_segments < 1) { + params.error_message_add(NodeWarningType::Info, TIP_("Fill Segments must be at least 1")); + params.set_output("Geometry", GeometrySet()); + return; + } + /* The cylinder is a special case of the cone mesh where the top and bottom radius are equal. */ - Mesh *mesh = create_cylinder_or_cone_mesh(radius, radius, depth, verts_num, fill_type); + Mesh *mesh = create_cylinder_or_cone_mesh( + radius, radius, depth, circle_segments, side_segments, fill_segments, fill_type); params.set_output("Geometry", GeometrySet::create_with_mesh(mesh)); } @@ -84,6 +135,7 @@ void register_node_type_geo_mesh_primitive_cylinder() static bNodeType ntype; geo_node_type_base(&ntype, GEO_NODE_MESH_PRIMITIVE_CYLINDER, "Cylinder", NODE_CLASS_GEOMETRY, 0); node_type_init(&ntype, blender::nodes::geo_node_mesh_primitive_cylinder_init); + node_type_update(&ntype, blender::nodes::geo_node_mesh_primitive_cylinder_update); node_type_storage( &ntype, "NodeGeometryMeshCylinder", node_free_standard_storage, node_copy_standard_storage); ntype.declare = blender::nodes::geo_node_mesh_primitive_cylinder_declare; diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_subdivide.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_subdivide.cc index 9ca74fed9a7..c436f5bd480 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_subdivide.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_subdivide.cc @@ -32,28 +32,9 @@ static void geo_node_mesh_subdivide_declare(NodeDeclarationBuilder &b) b.add_output<decl::Geometry>("Geometry"); } -static void geo_node_mesh_subdivide_exec(GeoNodeExecParams params) +static void geometry_set_mesh_subdivide(GeometrySet &geometry_set, const int level) { - GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); - geometry_set = geometry_set_realize_instances(geometry_set); - if (!geometry_set.has_mesh()) { - params.set_output("Geometry", geometry_set); - return; - } - -#ifndef WITH_OPENSUBDIV - params.error_message_add(NodeWarningType::Error, - TIP_("Disabled, Blender was compiled without OpenSubdiv")); - params.set_output("Geometry", std::move(geometry_set)); - return; -#endif - - /* See CCGSUBSURF_LEVEL_MAX for max limit. */ - const int subdiv_level = clamp_i(params.extract_input<int>("Level"), 0, 11); - - if (subdiv_level == 0) { - params.set_output("Geometry", std::move(geometry_set)); return; } @@ -61,7 +42,7 @@ static void geo_node_mesh_subdivide_exec(GeoNodeExecParams params) /* Initialize mesh settings. */ SubdivToMeshSettings mesh_settings; - mesh_settings.resolution = (1 << subdiv_level) + 1; + mesh_settings.resolution = (1 << level) + 1; mesh_settings.use_optimal_display = false; /* Initialize subdivision settings. */ @@ -79,7 +60,6 @@ static void geo_node_mesh_subdivide_exec(GeoNodeExecParams params) /* In case of bad topology, skip to input mesh. */ if (subdiv == nullptr) { - params.set_output("Geometry", std::move(geometry_set)); return; } @@ -90,6 +70,29 @@ static void geo_node_mesh_subdivide_exec(GeoNodeExecParams params) mesh_component.replace(mesh_out); BKE_subdiv_free(subdiv); +} + +static void geo_node_mesh_subdivide_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); + +#ifndef WITH_OPENSUBDIV + params.error_message_add(NodeWarningType::Error, + TIP_("Disabled, Blender was compiled without OpenSubdiv")); + params.set_output("Geometry", std::move(geometry_set)); + return; +#endif + + /* See CCGSUBSURF_LEVEL_MAX for max limit. */ + const int subdiv_level = clamp_i(params.extract_input<int>("Level"), 0, 11); + + if (subdiv_level == 0) { + params.set_output("Geometry", std::move(geometry_set)); + return; + } + + geometry_set.modify_geometry_sets( + [&](GeometrySet &geometry_set) { geometry_set_mesh_subdivide(geometry_set, subdiv_level); }); params.set_output("Geometry", std::move(geometry_set)); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_to_points.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_to_points.cc new file mode 100644 index 00000000000..6863f685eae --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_to_points.cc @@ -0,0 +1,189 @@ +/* + * 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 "DNA_pointcloud_types.h" + +#include "BKE_attribute_math.hh" +#include "BKE_pointcloud.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +using blender::Array; + +namespace blender::nodes { + +static void geo_node_mesh_to_points_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>("Mesh"); + b.add_input<decl::Vector>("Position").implicit_field(); + b.add_input<decl::Float>("Radius") + .default_value(0.05f) + .min(0.0f) + .subtype(PROP_DISTANCE) + .supports_field(); + b.add_input<decl::Bool>("Selection").default_value(true).supports_field().hide_value(); + b.add_output<decl::Geometry>("Points"); +} + +static void geo_node_mesh_to_points_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiItemR(layout, ptr, "mode", 0, "", ICON_NONE); +} + +static void geo_node_mesh_to_points_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeGeometryMeshToPoints *data = (NodeGeometryMeshToPoints *)MEM_callocN( + sizeof(NodeGeometryMeshToPoints), __func__); + data->mode = GEO_NODE_MESH_TO_POINTS_VERTICES; + node->storage = data; +} + +template<typename T> +static void copy_attribute_to_points(const VArray<T> &src, + const IndexMask mask, + MutableSpan<T> dst) +{ + for (const int i : mask.index_range()) { + dst[i] = src[mask[i]]; + } +} + +static void geometry_set_mesh_to_points(GeometrySet &geometry_set, + Field<float3> &position_field, + Field<float> &radius_field, + Field<bool> &selection_field, + const AttributeDomain domain) +{ + const MeshComponent *mesh_component = geometry_set.get_component_for_read<MeshComponent>(); + if (mesh_component == nullptr) { + geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES}); + return; + } + GeometryComponentFieldContext field_context{*mesh_component, domain}; + const int domain_size = mesh_component->attribute_domain_size(domain); + if (domain_size == 0) { + geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES}); + return; + } + fn::FieldEvaluator selection_evaluator{field_context, domain_size}; + selection_evaluator.add(selection_field); + selection_evaluator.evaluate(); + const IndexMask selection = selection_evaluator.get_evaluated_as_mask(0); + + PointCloud *pointcloud = BKE_pointcloud_new_nomain(selection.size()); + uninitialized_fill_n(pointcloud->radius, pointcloud->totpoint, 0.05f); + geometry_set.replace_pointcloud(pointcloud); + PointCloudComponent &point_component = + geometry_set.get_component_for_write<PointCloudComponent>(); + + /* Evaluating directly into the point cloud doesn't work because we are not using the full + * "min_array_size" array but compressing the selected elements into the final array with no + * gaps. */ + fn::FieldEvaluator evaluator{field_context, &selection}; + evaluator.add(position_field); + evaluator.add(radius_field); + evaluator.evaluate(); + copy_attribute_to_points(evaluator.get_evaluated<float3>(0), + selection, + {(float3 *)pointcloud->co, pointcloud->totpoint}); + copy_attribute_to_points( + evaluator.get_evaluated<float>(1), selection, {pointcloud->radius, pointcloud->totpoint}); + + Map<AttributeIDRef, AttributeKind> attributes; + geometry_set.gather_attributes_for_propagation( + {GEO_COMPONENT_TYPE_MESH}, GEO_COMPONENT_TYPE_POINT_CLOUD, false, attributes); + attributes.remove("position"); + + for (Map<AttributeIDRef, AttributeKind>::Item entry : attributes.items()) { + const AttributeIDRef attribute_id = entry.key; + const CustomDataType data_type = entry.value.data_type; + GVArrayPtr src = mesh_component->attribute_get_for_read(attribute_id, domain, data_type); + OutputAttribute dst = point_component.attribute_try_get_for_output_only( + attribute_id, ATTR_DOMAIN_POINT, data_type); + if (dst && src) { + attribute_math::convert_to_static_type(data_type, [&](auto dummy) { + using T = decltype(dummy); + GVArray_Typed<T> src_typed{*src}; + copy_attribute_to_points(*src_typed, selection, dst.as_span().typed<T>()); + }); + dst.save(); + } + } + + geometry_set.keep_only({GEO_COMPONENT_TYPE_POINT_CLOUD, GEO_COMPONENT_TYPE_INSTANCES}); +} + +static void geo_node_mesh_to_points_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Mesh"); + Field<float3> position = params.extract_input<Field<float3>>("Position"); + Field<float> radius = params.extract_input<Field<float>>("Radius"); + Field<bool> selection = params.extract_input<Field<bool>>("Selection"); + + /* Use another multi-function operation to make sure the input radius is greater than zero. + * TODO: Use mutable multi-function once that is supported. */ + static fn::CustomMF_SI_SO<float, float> max_zero_fn( + __func__, [](float value) { return std::max(0.0f, value); }); + auto max_zero_op = std::make_shared<FieldOperation>( + FieldOperation(max_zero_fn, {std::move(radius)})); + Field<float> positive_radius(std::move(max_zero_op), 0); + + const NodeGeometryMeshToPoints &storage = + *(const NodeGeometryMeshToPoints *)params.node().storage; + const GeometryNodeMeshToPointsMode mode = (GeometryNodeMeshToPointsMode)storage.mode; + + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + switch (mode) { + case GEO_NODE_MESH_TO_POINTS_VERTICES: + geometry_set_mesh_to_points( + geometry_set, position, positive_radius, selection, ATTR_DOMAIN_POINT); + break; + case GEO_NODE_MESH_TO_POINTS_EDGES: + geometry_set_mesh_to_points( + geometry_set, position, positive_radius, selection, ATTR_DOMAIN_EDGE); + break; + case GEO_NODE_MESH_TO_POINTS_FACES: + geometry_set_mesh_to_points( + geometry_set, position, positive_radius, selection, ATTR_DOMAIN_FACE); + break; + case GEO_NODE_MESH_TO_POINTS_CORNERS: + geometry_set_mesh_to_points( + geometry_set, position, positive_radius, selection, ATTR_DOMAIN_CORNER); + break; + } + }); + + params.set_output("Points", std::move(geometry_set)); +} + +} // namespace blender::nodes + +void register_node_type_geo_mesh_to_points() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_MESH_TO_POINTS, "Mesh to Points", NODE_CLASS_GEOMETRY, 0); + ntype.declare = blender::nodes::geo_node_mesh_to_points_declare; + ntype.geometry_node_execute = blender::nodes::geo_node_mesh_to_points_exec; + node_type_init(&ntype, blender::nodes::geo_node_mesh_to_points_init); + ntype.draw_buttons = blender::nodes::geo_node_mesh_to_points_layout; + node_type_storage( + &ntype, "NodeGeometryMeshToPoints", node_free_standard_storage, node_copy_standard_storage); + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_points_to_vertices.cc b/source/blender/nodes/geometry/nodes/node_geo_points_to_vertices.cc new file mode 100644 index 00000000000..afd0ced6360 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_points_to_vertices.cc @@ -0,0 +1,118 @@ +/* + * 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_task.hh" + +#include "BKE_attribute_math.hh" +#include "BKE_mesh.h" + +#include "node_geometry_util.hh" + +using blender::Array; + +namespace blender::nodes { + +static void geo_node_points_to_vertices_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>("Points"); + b.add_input<decl::Bool>("Selection").default_value(true).supports_field().hide_value(); + b.add_output<decl::Geometry>("Mesh"); +} + +template<typename T> +static void copy_attribute_to_vertices(const Span<T> src, const IndexMask mask, MutableSpan<T> dst) +{ + for (const int i : mask.index_range()) { + dst[i] = src[mask[i]]; + } +} + +/* One improvement would be to move the attribute arrays directly to the mesh when possible. */ +static void geometry_set_points_to_vertices(GeometrySet &geometry_set, + Field<bool> &selection_field) +{ + const PointCloudComponent *point_component = + geometry_set.get_component_for_read<PointCloudComponent>(); + if (point_component == nullptr) { + geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES}); + return; + } + + GeometryComponentFieldContext field_context{*point_component, ATTR_DOMAIN_POINT}; + const int domain_size = point_component->attribute_domain_size(ATTR_DOMAIN_POINT); + if (domain_size == 0) { + geometry_set.keep_only({GEO_COMPONENT_TYPE_INSTANCES}); + return; + } + + fn::FieldEvaluator selection_evaluator{field_context, domain_size}; + selection_evaluator.add(selection_field); + selection_evaluator.evaluate(); + const IndexMask selection = selection_evaluator.get_evaluated_as_mask(0); + + Map<AttributeIDRef, AttributeKind> attributes; + geometry_set.gather_attributes_for_propagation( + {GEO_COMPONENT_TYPE_POINT_CLOUD}, GEO_COMPONENT_TYPE_MESH, false, attributes); + + Mesh *mesh = BKE_mesh_new_nomain(selection.size(), 0, 0, 0, 0); + geometry_set.replace_mesh(mesh); + MeshComponent &mesh_component = geometry_set.get_component_for_write<MeshComponent>(); + + for (Map<AttributeIDRef, AttributeKind>::Item entry : attributes.items()) { + const AttributeIDRef attribute_id = entry.key; + const CustomDataType data_type = entry.value.data_type; + GVArrayPtr src = point_component->attribute_get_for_read( + attribute_id, ATTR_DOMAIN_POINT, data_type); + OutputAttribute dst = mesh_component.attribute_try_get_for_output_only( + attribute_id, ATTR_DOMAIN_POINT, data_type); + if (dst && src) { + attribute_math::convert_to_static_type(data_type, [&](auto dummy) { + using T = decltype(dummy); + GVArray_Typed<T> src_typed{*src}; + VArray_Span<T> src_typed_span{*src_typed}; + copy_attribute_to_vertices(src_typed_span, selection, dst.as_span().typed<T>()); + }); + dst.save(); + } + } + + geometry_set.keep_only({GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_INSTANCES}); +} + +static void geo_node_points_to_vertices_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set = params.extract_input<GeometrySet>("Points"); + Field<bool> selection_field = params.extract_input<Field<bool>>("Selection"); + + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + geometry_set_points_to_vertices(geometry_set, selection_field); + }); + + params.set_output("Mesh", std::move(geometry_set)); +} + +} // namespace blender::nodes + +void register_node_type_geo_points_to_vertices() +{ + static bNodeType ntype; + + geo_node_type_base( + &ntype, GEO_NODE_POINTS_TO_VERTICES, "Points to Vertices", NODE_CLASS_GEOMETRY, 0); + ntype.declare = blender::nodes::geo_node_points_to_vertices_declare; + ntype.geometry_node_execute = blender::nodes::geo_node_points_to_vertices_exec; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_proximity.cc b/source/blender/nodes/geometry/nodes/node_geo_proximity.cc new file mode 100644 index 00000000000..7062deff2f1 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_proximity.cc @@ -0,0 +1,235 @@ +/* + * 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_task.hh" +#include "BLI_timeit.hh" + +#include "DNA_mesh_types.h" + +#include "BKE_bvhutils.h" +#include "BKE_geometry_set.hh" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +namespace blender::nodes { + +static void geo_node_proximity_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Geometry>("Target"); + b.add_input<decl::Vector>("Source Position").implicit_field(); + b.add_output<decl::Vector>("Position").dependent_field(); + b.add_output<decl::Float>("Distance").dependent_field(); +} + +static void geo_node_proximity_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiItemR(layout, ptr, "target_element", 0, "", ICON_NONE); +} + +static void geo_proximity_init(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeGeometryProximity *node_storage = (NodeGeometryProximity *)MEM_callocN( + sizeof(NodeGeometryProximity), __func__); + node_storage->target_element = GEO_NODE_PROX_TARGET_FACES; + node->storage = node_storage; +} + +static void calculate_mesh_proximity(const VArray<float3> &positions, + const IndexMask mask, + const Mesh &mesh, + const GeometryNodeProximityTargetType type, + const MutableSpan<float> r_distances, + const MutableSpan<float3> r_locations) +{ + BVHTreeFromMesh bvh_data; + switch (type) { + case GEO_NODE_PROX_TARGET_POINTS: + BKE_bvhtree_from_mesh_get(&bvh_data, &mesh, BVHTREE_FROM_VERTS, 2); + break; + case GEO_NODE_PROX_TARGET_EDGES: + BKE_bvhtree_from_mesh_get(&bvh_data, &mesh, BVHTREE_FROM_EDGES, 2); + break; + case GEO_NODE_PROX_TARGET_FACES: + BKE_bvhtree_from_mesh_get(&bvh_data, &mesh, BVHTREE_FROM_LOOPTRI, 2); + break; + } + + if (bvh_data.tree == nullptr) { + return; + } + + threading::parallel_for(mask.index_range(), 512, [&](IndexRange range) { + BVHTreeNearest nearest; + copy_v3_fl(nearest.co, FLT_MAX); + nearest.index = -1; + + for (int i : range) { + const int index = mask[i]; + /* Use the distance to the last found point as upper bound to speedup the bvh lookup. */ + nearest.dist_sq = float3::distance_squared(nearest.co, positions[index]); + + BLI_bvhtree_find_nearest( + bvh_data.tree, positions[index], &nearest, bvh_data.nearest_callback, &bvh_data); + + if (nearest.dist_sq < r_distances[index]) { + r_distances[index] = nearest.dist_sq; + if (!r_locations.is_empty()) { + r_locations[index] = nearest.co; + } + } + } + }); + + free_bvhtree_from_mesh(&bvh_data); +} + +static void calculate_pointcloud_proximity(const VArray<float3> &positions, + const IndexMask mask, + const PointCloud &pointcloud, + const MutableSpan<float> r_distances, + const MutableSpan<float3> r_locations) +{ + BVHTreeFromPointCloud bvh_data; + BKE_bvhtree_from_pointcloud_get(&bvh_data, &pointcloud, 2); + if (bvh_data.tree == nullptr) { + return; + } + + threading::parallel_for(mask.index_range(), 512, [&](IndexRange range) { + BVHTreeNearest nearest; + copy_v3_fl(nearest.co, FLT_MAX); + nearest.index = -1; + + for (int i : range) { + const int index = mask[i]; + /* Use the distance to the closest point in the mesh to speedup the pointcloud bvh lookup. + * This is ok because we only need to find the closest point in the pointcloud if it's + * closer than the mesh. */ + nearest.dist_sq = r_distances[index]; + + BLI_bvhtree_find_nearest( + bvh_data.tree, positions[index], &nearest, bvh_data.nearest_callback, &bvh_data); + + if (nearest.dist_sq < r_distances[index]) { + r_distances[index] = nearest.dist_sq; + if (!r_locations.is_empty()) { + r_locations[index] = nearest.co; + } + } + } + }); + + free_bvhtree_from_pointcloud(&bvh_data); +} + +class ProximityFunction : public fn::MultiFunction { + private: + GeometrySet target_; + GeometryNodeProximityTargetType type_; + + public: + ProximityFunction(GeometrySet target, GeometryNodeProximityTargetType type) + : target_(std::move(target)), type_(type) + { + static fn::MFSignature signature = create_signature(); + this->set_signature(&signature); + } + + static fn::MFSignature create_signature() + { + blender::fn::MFSignatureBuilder signature{"Geometry Proximity"}; + signature.single_input<float3>("Source Position"); + signature.single_output<float3>("Position"); + signature.single_output<float>("Distance"); + return signature.build(); + } + + void call(IndexMask mask, fn::MFParams params, fn::MFContext UNUSED(context)) const override + { + const VArray<float3> &src_positions = params.readonly_single_input<float3>(0, + "Source Position"); + MutableSpan<float3> positions = params.uninitialized_single_output_if_required<float3>( + 1, "Position"); + /* Make sure there is a distance array, used for finding the smaller distance when there are + * multiple components. Theoretically it would be possible to avoid using the distance array + * when there is only one component. However, this only adds an allocation and a single float + * comparison per vertex, so it's likely not worth it. */ + MutableSpan<float> distances = params.uninitialized_single_output<float>(2, "Distance"); + + distances.fill(FLT_MAX); + + if (target_.has_mesh()) { + calculate_mesh_proximity( + src_positions, mask, *target_.get_mesh_for_read(), type_, distances, positions); + } + + if (target_.has_pointcloud() && type_ == GEO_NODE_PROX_TARGET_POINTS) { + calculate_pointcloud_proximity( + src_positions, mask, *target_.get_pointcloud_for_read(), distances, positions); + } + + if (params.single_output_is_required(2, "Distance")) { + threading::parallel_for(mask.index_range(), 2048, [&](IndexRange range) { + for (const int i : range) { + const int j = mask[i]; + distances[j] = std::sqrt(distances[j]); + } + }); + } + } +}; + +static void geo_node_proximity_exec(GeoNodeExecParams params) +{ + GeometrySet geometry_set_target = params.extract_input<GeometrySet>("Target"); + + if (!geometry_set_target.has_mesh() && !geometry_set_target.has_pointcloud()) { + params.set_output("Position", fn::make_constant_field<float3>({0.0f, 0.0f, 0.0f})); + params.set_output("Distance", fn::make_constant_field<float>({0.0f})); + return; + } + + const NodeGeometryProximity &storage = *(const NodeGeometryProximity *)params.node().storage; + Field<float3> position_field = params.extract_input<Field<float3>>("Source Position"); + + auto proximity_fn = std::make_unique<ProximityFunction>( + std::move(geometry_set_target), + static_cast<GeometryNodeProximityTargetType>(storage.target_element)); + auto proximity_op = std::make_shared<FieldOperation>( + FieldOperation(std::move(proximity_fn), {std::move(position_field)})); + + params.set_output("Position", Field<float3>(proximity_op, 0)); + params.set_output("Distance", Field<float>(proximity_op, 1)); +} + +} // namespace blender::nodes + +void register_node_type_geo_proximity() +{ + static bNodeType ntype; + + geo_node_type_base(&ntype, GEO_NODE_PROXIMITY, "Geometry Proximity", NODE_CLASS_GEOMETRY, 0); + node_type_init(&ntype, blender::nodes::geo_proximity_init); + node_type_storage( + &ntype, "NodeGeometryProximity", node_free_standard_storage, node_copy_standard_storage); + ntype.declare = blender::nodes::geo_node_proximity_declare; + ntype.geometry_node_execute = blender::nodes::geo_node_proximity_exec; + ntype.draw_buttons = blender::nodes::geo_node_proximity_layout; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_separate_components.cc b/source/blender/nodes/geometry/nodes/node_geo_separate_components.cc index c63e4ec49d9..dafd10cee2d 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_separate_components.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_separate_components.cc @@ -25,20 +25,18 @@ static void geo_node_join_geometry_declare(NodeDeclarationBuilder &b) b.add_output<decl::Geometry>("Point Cloud"); b.add_output<decl::Geometry>("Curve"); b.add_output<decl::Geometry>("Volume"); + b.add_output<decl::Geometry>("Instances"); } static void geo_node_separate_components_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry"); - /* Note that it will be possible to skip realizing instances here when instancing - * geometry directly is supported by creating corresponding geometry instances. */ - geometry_set = bke::geometry_set_realize_instances(geometry_set); - GeometrySet meshes; GeometrySet point_clouds; GeometrySet volumes; GeometrySet curves; + GeometrySet instances; if (geometry_set.has<MeshComponent>()) { meshes.add(*geometry_set.get_component_for_read<MeshComponent>()); @@ -52,11 +50,15 @@ static void geo_node_separate_components_exec(GeoNodeExecParams params) if (geometry_set.has<VolumeComponent>()) { volumes.add(*geometry_set.get_component_for_read<VolumeComponent>()); } + if (geometry_set.has<InstancesComponent>()) { + instances.add(*geometry_set.get_component_for_read<InstancesComponent>()); + } params.set_output("Mesh", meshes); params.set_output("Point Cloud", point_clouds); params.set_output("Curve", curves); params.set_output("Volume", volumes); + params.set_output("Instances", instances); } } // namespace blender::nodes diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_position.cc b/source/blender/nodes/geometry/nodes/node_geo_set_position.cc index c5e10b788ac..8caf961fc04 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_set_position.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_set_position.cc @@ -23,8 +23,8 @@ namespace blender::nodes { static void geo_node_set_position_declare(NodeDeclarationBuilder &b) { b.add_input<decl::Geometry>("Geometry"); - b.add_input<decl::Vector>("Position").hide_value(); - b.add_input<decl::Bool>("Selection").default_value(true).hide_value(); + b.add_input<decl::Vector>("Position").implicit_field(); + b.add_input<decl::Bool>("Selection").default_value(true).hide_value().supports_field(); b.add_output<decl::Geometry>("Geometry"); } @@ -34,6 +34,9 @@ static void set_position_in_component(GeometryComponent &component, { GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_POINT}; const int domain_size = component.attribute_domain_size(ATTR_DOMAIN_POINT); + if (domain_size == 0) { + return; + } fn::FieldEvaluator selection_evaluator{field_context, domain_size}; selection_evaluator.add(selection_field); diff --git a/source/blender/nodes/geometry/nodes/node_geo_string_to_curves.cc b/source/blender/nodes/geometry/nodes/node_geo_string_to_curves.cc new file mode 100644 index 00000000000..5e2f03806c3 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_string_to_curves.cc @@ -0,0 +1,306 @@ +/* + * 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 "DNA_curve_types.h" +#include "DNA_vfont_types.h" + +#include "BKE_curve.h" +#include "BKE_font.h" +#include "BKE_spline.hh" + +#include "BLI_hash.h" +#include "BLI_string_utf8.h" +#include "BLI_task.hh" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +namespace blender::nodes { + +static void geo_node_string_to_curves_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::String>("String"); + b.add_input<decl::Float>("Size").default_value(1.0f).min(0.0f).subtype(PROP_DISTANCE); + b.add_input<decl::Float>("Character Spacing") + .default_value(1.0f) + .min(0.0f) + .subtype(PROP_DISTANCE); + b.add_input<decl::Float>("Word Spacing").default_value(1.0f).min(0.0f).subtype(PROP_DISTANCE); + b.add_input<decl::Float>("Line Spacing").default_value(1.0f).min(0.0f).subtype(PROP_DISTANCE); + b.add_input<decl::Float>("Text Box Width").default_value(0.0f).min(0.0f).subtype(PROP_DISTANCE); + b.add_input<decl::Float>("Text Box Height").default_value(0.0f).min(0.0f).subtype(PROP_DISTANCE); + b.add_output<decl::Geometry>("Curves"); + b.add_output<decl::String>("Remainder"); +} + +static void geo_node_string_to_curves_layout(uiLayout *layout, struct bContext *C, PointerRNA *ptr) +{ + uiLayoutSetPropSep(layout, true); + uiLayoutSetPropDecorate(layout, false); + uiTemplateID(layout, + C, + ptr, + "font", + nullptr, + "FONT_OT_open", + "FONT_OT_unlink", + UI_TEMPLATE_ID_FILTER_ALL, + false, + nullptr); + uiItemR(layout, ptr, "overflow", 0, "", ICON_NONE); + uiItemR(layout, ptr, "align_x", 0, "", ICON_NONE); + uiItemR(layout, ptr, "align_y", 0, "", ICON_NONE); +} + +static void geo_node_string_to_curves_init(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeGeometryStringToCurves *data = (NodeGeometryStringToCurves *)MEM_callocN( + sizeof(NodeGeometryStringToCurves), __func__); + + data->overflow = GEO_NODE_STRING_TO_CURVES_MODE_OVERFLOW; + data->align_x = GEO_NODE_STRING_TO_CURVES_ALIGN_X_LEFT; + data->align_y = GEO_NODE_STRING_TO_CURVES_ALIGN_Y_TOP_BASELINE; + node->storage = data; + node->id = (ID *)BKE_vfont_builtin_get(); +} + +static void geo_node_string_to_curves_update(bNodeTree *UNUSED(ntree), bNode *node) +{ + const NodeGeometryStringToCurves *storage = (const NodeGeometryStringToCurves *)node->storage; + const GeometryNodeStringToCurvesOverflowMode overflow = (GeometryNodeStringToCurvesOverflowMode) + storage->overflow; + bNodeSocket *socket_remainder = ((bNodeSocket *)node->outputs.first)->next; + nodeSetSocketAvailability(socket_remainder, overflow == GEO_NODE_STRING_TO_CURVES_MODE_TRUNCATE); + + bNodeSocket *height_socket = (bNodeSocket *)node->inputs.last; + bNodeSocket *width_socket = height_socket->prev; + nodeSetSocketAvailability(height_socket, overflow != GEO_NODE_STRING_TO_CURVES_MODE_OVERFLOW); + node_sock_label(width_socket, + overflow == GEO_NODE_STRING_TO_CURVES_MODE_OVERFLOW ? N_("Max Width") : + N_("Text Box Width")); +} + +struct TextLayout { + /* Position of each character. */ + Vector<float2> positions; + + /* The text that fit into the text box, with newline character sequences replaced. */ + std::string text; + + /* The text that didn't fit into the text box in 'Truncate' mode. May be empty. */ + std::string truncated_text; + + /* Font size could be modified if in 'Scale to fit'-mode. */ + float final_font_size; +}; + +static TextLayout get_text_layout(GeoNodeExecParams ¶ms) +{ + TextLayout layout; + layout.text = params.extract_input<std::string>("String"); + if (layout.text.empty()) { + return {}; + } + + const NodeGeometryStringToCurves &storage = + *(const NodeGeometryStringToCurves *)params.node().storage; + const GeometryNodeStringToCurvesOverflowMode overflow = (GeometryNodeStringToCurvesOverflowMode) + storage.overflow; + const GeometryNodeStringToCurvesAlignXMode align_x = (GeometryNodeStringToCurvesAlignXMode) + storage.align_x; + const GeometryNodeStringToCurvesAlignYMode align_y = (GeometryNodeStringToCurvesAlignYMode) + storage.align_y; + + const float font_size = std::max(params.extract_input<float>("Size"), 0.0f); + const float char_spacing = params.extract_input<float>("Character Spacing"); + const float word_spacing = params.extract_input<float>("Word Spacing"); + const float line_spacing = params.extract_input<float>("Line Spacing"); + const float textbox_w = params.extract_input<float>("Text Box Width"); + const float textbox_h = overflow == GEO_NODE_STRING_TO_CURVES_MODE_OVERFLOW ? + 0.0f : + params.extract_input<float>("Text Box Height"); + VFont *vfont = (VFont *)params.node().id; + + Curve cu = {nullptr}; + cu.type = OB_FONT; + /* Set defaults */ + cu.resolu = 12; + cu.smallcaps_scale = 0.75f; + cu.wordspace = 1.0f; + /* Set values from inputs */ + cu.spacemode = align_x; + cu.align_y = align_y; + cu.fsize = font_size; + cu.spacing = char_spacing; + cu.wordspace = word_spacing; + cu.linedist = line_spacing; + cu.vfont = vfont; + cu.overflow = overflow; + cu.tb = (TextBox *)MEM_calloc_arrayN(MAXTEXTBOX, sizeof(TextBox), __func__); + cu.tb->w = textbox_w; + cu.tb->h = textbox_h; + cu.totbox = 1; + size_t len_bytes; + size_t len_chars = BLI_strlen_utf8_ex(layout.text.c_str(), &len_bytes); + cu.len_char32 = len_chars; + cu.len = len_bytes; + cu.pos = len_chars; + /* The reason for the additional character here is unknown, but reflects other code elsewhere. */ + cu.str = (char *)MEM_mallocN(len_bytes + sizeof(char32_t), __func__); + cu.strinfo = (CharInfo *)MEM_callocN((len_chars + 1) * sizeof(CharInfo), __func__); + BLI_strncpy(cu.str, layout.text.c_str(), len_bytes + 1); + + struct CharTrans *chartransdata = nullptr; + int text_len; + bool text_free; + const char32_t *r_text = nullptr; + /* Mode FO_DUPLI used because it doesn't create curve splines. */ + BKE_vfont_to_curve_ex( + nullptr, &cu, FO_DUPLI, nullptr, &r_text, &text_len, &text_free, &chartransdata); + + if (text_free) { + MEM_freeN((void *)r_text); + } + + Span<CharInfo> info{cu.strinfo, text_len}; + layout.final_font_size = cu.fsize_realtime; + layout.positions.reserve(text_len); + + for (const int i : IndexRange(text_len)) { + CharTrans &ct = chartransdata[i]; + layout.positions.append(float2(ct.xof, ct.yof) * layout.final_font_size); + + if ((info[i].flag & CU_CHINFO_OVERFLOW) && (cu.overflow == CU_OVERFLOW_TRUNCATE)) { + const int offset = BLI_str_utf8_offset_from_index(layout.text.c_str(), i + 1); + layout.truncated_text = layout.text.substr(offset); + layout.text = layout.text.substr(0, offset); + break; + } + } + + MEM_SAFE_FREE(chartransdata); + MEM_SAFE_FREE(cu.str); + MEM_SAFE_FREE(cu.strinfo); + MEM_SAFE_FREE(cu.tb); + + return layout; +} + +/* Returns a mapping of UTF-32 character code to instance handle. */ +static Map<int, int> create_curve_instances(GeoNodeExecParams ¶ms, + const float fontsize, + const Span<char32_t> charcodes, + InstancesComponent &instance_component) +{ + VFont *vfont = (VFont *)params.node().id; + Map<int, int> handles; + + for (int i : charcodes.index_range()) { + if (handles.contains(charcodes[i])) { + continue; + } + Curve cu = {nullptr}; + cu.type = OB_FONT; + cu.resolu = 12; + cu.vfont = vfont; + CharInfo charinfo = {0}; + charinfo.mat_nr = 1; + + BKE_vfont_build_char(&cu, &cu.nurb, charcodes[i], &charinfo, 0, 0, 0, i, 1); + std::unique_ptr<CurveEval> curve_eval = curve_eval_from_dna_curve(cu); + BKE_nurbList_free(&cu.nurb); + float4x4 size_matrix = float4x4::identity(); + size_matrix.apply_scale(fontsize); + curve_eval->transform(size_matrix); + + GeometrySet geometry_set_curve = GeometrySet::create_with_curve(curve_eval.release()); + handles.add_new(charcodes[i], instance_component.add_reference(std::move(geometry_set_curve))); + } + return handles; +} + +static void add_instances_from_handles(InstancesComponent &instances, + const Map<int, int> &char_handles, + const Span<char32_t> charcodes, + const Span<float2> positions) +{ + instances.resize(positions.size()); + MutableSpan<int> handles = instances.instance_reference_handles(); + MutableSpan<float4x4> transforms = instances.instance_transforms(); + MutableSpan<int> instance_ids = instances.instance_ids(); + + threading::parallel_for(IndexRange(positions.size()), 256, [&](IndexRange range) { + for (const int i : range) { + handles[i] = char_handles.lookup(charcodes[i]); + transforms[i] = float4x4::from_location({positions[i].x, positions[i].y, 0}); + instance_ids[i] = i; + } + }); +} + +static void geo_node_string_to_curves_exec(GeoNodeExecParams params) +{ + TextLayout layout = get_text_layout(params); + + const NodeGeometryStringToCurves &storage = + *(const NodeGeometryStringToCurves *)params.node().storage; + if (storage.overflow == GEO_NODE_STRING_TO_CURVES_MODE_TRUNCATE) { + params.set_output("Remainder", std::move(layout.truncated_text)); + } + + if (layout.positions.size() == 0) { + params.set_output("Curves", GeometrySet()); + return; + } + + /* Convert UTF-8 encoded string to UTF-32. */ + size_t len_bytes; + size_t len_chars = BLI_strlen_utf8_ex(layout.text.c_str(), &len_bytes); + Array<char32_t> char_codes(len_chars + 1); + BLI_str_utf8_as_utf32(char_codes.data(), layout.text.c_str(), len_chars + 1); + + /* Create and add instances. */ + GeometrySet geometry_set_out; + InstancesComponent &instances = geometry_set_out.get_component_for_write<InstancesComponent>(); + Map<int, int> char_handles = create_curve_instances( + params, layout.final_font_size, char_codes, instances); + add_instances_from_handles(instances, char_handles, char_codes, layout.positions); + + params.set_output("Curves", std::move(geometry_set_out)); +} + +} // namespace blender::nodes + +void register_node_type_geo_string_to_curves() +{ + static bNodeType ntype; + + geo_node_type_base( + &ntype, GEO_NODE_STRING_TO_CURVES, "String to Curves", NODE_CLASS_GEOMETRY, 0); + ntype.declare = blender::nodes::geo_node_string_to_curves_declare; + ntype.geometry_node_execute = blender::nodes::geo_node_string_to_curves_exec; + node_type_init(&ntype, blender::nodes::geo_node_string_to_curves_init); + node_type_update(&ntype, blender::nodes::geo_node_string_to_curves_update); + node_type_size(&ntype, 190, 120, 700); + node_type_storage(&ntype, + "NodeGeometryStringToCurves", + node_free_standard_storage, + node_copy_standard_storage); + ntype.draw_buttons = blender::nodes::geo_node_string_to_curves_layout; + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_switch.cc b/source/blender/nodes/geometry/nodes/node_geo_switch.cc index ca857c4d2e3..05df927fb39 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_switch.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_switch.cc @@ -19,24 +19,37 @@ #include "UI_interface.h" #include "UI_resources.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "BKE_material.h" + +#include "FN_multi_function_signature.hh" + namespace blender::nodes { static void geo_node_switch_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Bool>("Switch"); - - b.add_input<decl::Float>("False"); - b.add_input<decl::Float>("True"); - b.add_input<decl::Int>("False", "False_001").min(-100000).max(100000); - b.add_input<decl::Int>("True", "True_001").min(-100000).max(100000); - b.add_input<decl::Bool>("False", "False_002"); - b.add_input<decl::Bool>("True", "True_002"); - b.add_input<decl::Vector>("False", "False_003"); - b.add_input<decl::Vector>("True", "True_003"); - b.add_input<decl::Color>("False", "False_004").default_value({0.8f, 0.8f, 0.8f, 1.0f}); - b.add_input<decl::Color>("True", "True_004").default_value({0.8f, 0.8f, 0.8f, 1.0f}); - b.add_input<decl::String>("False", "False_005"); - b.add_input<decl::String>("True", "True_005"); + b.add_input<decl::Bool>("Switch").default_value(false).supports_field(); + b.add_input<decl::Bool>("Switch", "Switch_001").default_value(false); + + b.add_input<decl::Float>("False").supports_field(); + b.add_input<decl::Float>("True").supports_field(); + b.add_input<decl::Int>("False", "False_001").min(-100000).max(100000).supports_field(); + b.add_input<decl::Int>("True", "True_001").min(-100000).max(100000).supports_field(); + b.add_input<decl::Bool>("False", "False_002").default_value(false).hide_value().supports_field(); + b.add_input<decl::Bool>("True", "True_002").default_value(true).hide_value().supports_field(); + b.add_input<decl::Vector>("False", "False_003").supports_field(); + b.add_input<decl::Vector>("True", "True_003").supports_field(); + b.add_input<decl::Color>("False", "False_004") + .default_value({0.8f, 0.8f, 0.8f, 1.0f}) + .supports_field(); + b.add_input<decl::Color>("True", "True_004") + .default_value({0.8f, 0.8f, 0.8f, 1.0f}) + .supports_field(); + b.add_input<decl::String>("False", "False_005").supports_field(); + b.add_input<decl::String>("True", "True_005").supports_field(); + b.add_input<decl::Geometry>("False", "False_006"); b.add_input<decl::Geometry>("True", "True_006"); b.add_input<decl::Object>("False", "False_007"); @@ -48,12 +61,12 @@ static void geo_node_switch_declare(NodeDeclarationBuilder &b) b.add_input<decl::Material>("False", "False_010"); b.add_input<decl::Material>("True", "True_010"); - b.add_output<decl::Float>("Output"); - b.add_output<decl::Int>("Output", "Output_001"); - b.add_output<decl::Bool>("Output", "Output_002"); - b.add_output<decl::Vector>("Output", "Output_003"); - b.add_output<decl::Color>("Output", "Output_004"); - b.add_output<decl::String>("Output", "Output_005"); + b.add_output<decl::Float>("Output").dependent_field(); + b.add_output<decl::Int>("Output", "Output_001").dependent_field(); + b.add_output<decl::Bool>("Output", "Output_002").dependent_field(); + b.add_output<decl::Vector>("Output", "Output_003").dependent_field(); + b.add_output<decl::Color>("Output", "Output_004").dependent_field(); + b.add_output<decl::String>("Output", "Output_005").dependent_field(); b.add_output<decl::Geometry>("Output", "Output_006"); b.add_output<decl::Object>("Output", "Output_007"); b.add_output<decl::Collection>("Output", "Output_008"); @@ -77,91 +90,168 @@ static void geo_node_switch_update(bNodeTree *UNUSED(ntree), bNode *node) { NodeSwitch *node_storage = (NodeSwitch *)node->storage; int index = 0; - LISTBASE_FOREACH (bNodeSocket *, socket, &node->inputs) { - nodeSetSocketAvailability( - socket, index == 0 || socket->type == (eNodeSocketDatatype)node_storage->input_type); - index++; + bNodeSocket *field_switch = (bNodeSocket *)node->inputs.first; + bNodeSocket *non_field_switch = (bNodeSocket *)field_switch->next; + + const bool fields_type = ELEM((eNodeSocketDatatype)node_storage->input_type, + SOCK_FLOAT, + SOCK_INT, + SOCK_BOOLEAN, + SOCK_VECTOR, + SOCK_RGBA, + SOCK_STRING); + + nodeSetSocketAvailability(field_switch, fields_type); + nodeSetSocketAvailability(non_field_switch, !fields_type); + + LISTBASE_FOREACH_INDEX (bNodeSocket *, socket, &node->inputs, index) { + if (index <= 1) { + continue; + } + nodeSetSocketAvailability(socket, + socket->type == (eNodeSocketDatatype)node_storage->input_type); } + LISTBASE_FOREACH (bNodeSocket *, socket, &node->outputs) { nodeSetSocketAvailability(socket, socket->type == (eNodeSocketDatatype)node_storage->input_type); } } -template<typename T> -static void output_input(GeoNodeExecParams ¶ms, - const bool input, - const StringRef input_suffix, - const StringRef output_identifier) +template<typename T> class SwitchFieldsFunction : public fn::MultiFunction { + public: + SwitchFieldsFunction() + { + static fn::MFSignature signature = create_signature(); + this->set_signature(&signature); + } + static fn::MFSignature create_signature() + { + fn::MFSignatureBuilder signature{"Switch"}; + signature.single_input<bool>("Switch"); + signature.single_input<T>("False"); + signature.single_input<T>("True"); + signature.single_output<T>("Output"); + return signature.build(); + } + + void call(IndexMask mask, fn::MFParams params, fn::MFContext UNUSED(context)) const override + { + const VArray<bool> &switches = params.readonly_single_input<bool>(0, "Switch"); + const VArray<T> &falses = params.readonly_single_input<T>(1, "False"); + const VArray<T> &trues = params.readonly_single_input<T>(2, "True"); + MutableSpan<T> values = params.uninitialized_single_output_if_required<T>(3, "Output"); + for (int64_t i : mask) { + new (&values[i]) T(switches[i] ? trues[i] : falses[i]); + } + } +}; + +template<typename T> void switch_fields(GeoNodeExecParams ¶ms, const StringRef suffix) +{ + if (params.lazy_require_input("Switch")) { + return; + } + + const std::string name_false = "False" + suffix; + const std::string name_true = "True" + suffix; + const std::string name_output = "Output" + suffix; + + /* TODO: Allow for Laziness when the switch field is constant. */ + const bool require_false = params.lazy_require_input(name_false); + const bool require_true = params.lazy_require_input(name_true); + if (require_false | require_true) { + return; + } + + Field<bool> switches_field = params.extract_input<Field<bool>>("Switch"); + Field<T> falses_field = params.extract_input<Field<T>>(name_false); + Field<T> trues_field = params.extract_input<Field<T>>(name_true); + + auto switch_fn = std::make_unique<SwitchFieldsFunction<T>>(); + auto switch_op = std::make_shared<FieldOperation>(FieldOperation( + std::move(switch_fn), + {std::move(switches_field), std::move(falses_field), std::move(trues_field)})); + + params.set_output(name_output, Field<T>(switch_op, 0)); +} + +template<typename T> void switch_no_fields(GeoNodeExecParams ¶ms, const StringRef suffix) { - const std::string name_a = "False" + input_suffix; - const std::string name_b = "True" + input_suffix; - if (input) { - params.set_input_unused(name_a); - if (params.lazy_require_input(name_b)) { + if (params.lazy_require_input("Switch_001")) { + return; + } + bool switch_value = params.get_input<bool>("Switch_001"); + + const std::string name_false = "False" + suffix; + const std::string name_true = "True" + suffix; + const std::string name_output = "Output" + suffix; + + if (switch_value) { + params.set_input_unused(name_false); + if (params.lazy_require_input(name_true)) { return; } - params.set_output(output_identifier, params.extract_input<T>(name_b)); + params.set_output(name_output, params.extract_input<T>(name_true)); } else { - params.set_input_unused(name_b); - if (params.lazy_require_input(name_a)) { + params.set_input_unused(name_true); + if (params.lazy_require_input(name_false)) { return; } - params.set_output(output_identifier, params.extract_input<T>(name_a)); + params.set_output(name_output, params.extract_input<T>(name_false)); } } 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.get_input<bool>("Switch"); - switch ((eNodeSocketDatatype)storage.input_type) { + const eNodeSocketDatatype data_type = static_cast<eNodeSocketDatatype>(storage.input_type); + + switch (data_type) { + case SOCK_FLOAT: { - output_input<float>(params, input, "", "Output"); + switch_fields<float>(params, ""); break; } case SOCK_INT: { - output_input<int>(params, input, "_001", "Output_001"); + switch_fields<int>(params, "_001"); break; } case SOCK_BOOLEAN: { - output_input<bool>(params, input, "_002", "Output_002"); + switch_fields<bool>(params, "_002"); break; } case SOCK_VECTOR: { - output_input<float3>(params, input, "_003", "Output_003"); + switch_fields<float3>(params, "_003"); break; } case SOCK_RGBA: { - output_input<ColorGeometry4f>(params, input, "_004", "Output_004"); + switch_fields<ColorGeometry4f>(params, "_004"); break; } case SOCK_STRING: { - output_input<std::string>(params, input, "_005", "Output_005"); + switch_fields<std::string>(params, "_005"); break; } case SOCK_GEOMETRY: { - output_input<GeometrySet>(params, input, "_006", "Output_006"); + switch_no_fields<GeometrySet>(params, "_006"); break; } case SOCK_OBJECT: { - output_input<Object *>(params, input, "_007", "Output_007"); + switch_no_fields<Object *>(params, "_007"); break; } case SOCK_COLLECTION: { - output_input<Collection *>(params, input, "_008", "Output_008"); + switch_no_fields<Collection *>(params, "_008"); break; } case SOCK_TEXTURE: { - output_input<Tex *>(params, input, "_009", "Output_009"); + switch_no_fields<Tex *>(params, "_009"); break; } case SOCK_MATERIAL: { - output_input<Material *>(params, input, "_010", "Output_010"); + switch_no_fields<Material *>(params, "_010"); break; } default: diff --git a/source/blender/nodes/geometry/nodes/node_geo_triangulate.cc b/source/blender/nodes/geometry/nodes/node_geo_triangulate.cc index 8886c7194db..7ef0913622c 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_triangulate.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_triangulate.cc @@ -58,14 +58,14 @@ static void geo_node_triangulate_exec(GeoNodeExecParams params) GeometryNodeTriangulateNGons ngon_method = static_cast<GeometryNodeTriangulateNGons>( params.node().custom2); - geometry_set = geometry_set_realize_instances(geometry_set); - - /* #triangulate_mesh might modify the input mesh currently. */ - Mesh *mesh_in = geometry_set.get_mesh_for_write(); - if (mesh_in != nullptr) { - Mesh *mesh_out = triangulate_mesh(mesh_in, quad_method, ngon_method, min_vertices, 0); - geometry_set.replace_mesh(mesh_out); - } + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + /* #triangulate_mesh might modify the input mesh currently. */ + Mesh *mesh_in = geometry_set.get_mesh_for_write(); + if (mesh_in != nullptr) { + Mesh *mesh_out = triangulate_mesh(mesh_in, quad_method, ngon_method, min_vertices, 0); + geometry_set.replace_mesh(mesh_out); + } + }); params.set_output("Geometry", std::move(geometry_set)); } diff --git a/source/blender/nodes/intern/geometry_nodes_eval_log.cc b/source/blender/nodes/intern/geometry_nodes_eval_log.cc index 3b3b643d0ae..fa9bf09d8d9 100644 --- a/source/blender/nodes/intern/geometry_nodes_eval_log.cc +++ b/source/blender/nodes/intern/geometry_nodes_eval_log.cc @@ -159,15 +159,22 @@ const SocketLog *NodeLog::lookup_socket_log(const bNode &node, const bNodeSocket GeometryValueLog::GeometryValueLog(const GeometrySet &geometry_set, bool log_full_geometry) { - bke::geometry_set_instances_attribute_foreach( - geometry_set, - [&](const bke::AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { + static std::array all_component_types = {GEO_COMPONENT_TYPE_CURVE, + GEO_COMPONENT_TYPE_INSTANCES, + GEO_COMPONENT_TYPE_MESH, + GEO_COMPONENT_TYPE_POINT_CLOUD, + GEO_COMPONENT_TYPE_VOLUME}; + geometry_set.attribute_foreach( + all_component_types, + true, + [&](const bke::AttributeIDRef &attribute_id, + const AttributeMetaData &meta_data, + const GeometryComponent &UNUSED(component)) { if (attribute_id.is_named()) { this->attributes_.append({attribute_id.name(), meta_data.domain, meta_data.data_type}); } - return true; - }, - 8); + }); + for (const GeometryComponent *component : geometry_set.get_components_for_read()) { component_types_.append(component->type()); switch (component->type()) { diff --git a/source/blender/nodes/intern/node_common.c b/source/blender/nodes/intern/node_common.cc index b8c89d1db37..f5e6e7640ad 100644 --- a/source/blender/nodes/intern/node_common.c +++ b/source/blender/nodes/intern/node_common.cc @@ -21,12 +21,16 @@ * \ingroup nodes */ -#include <stddef.h> -#include <string.h> +#include <cstddef> +#include <cstring> #include "DNA_node_types.h" #include "BLI_listbase.h" +#include "BLI_map.hh" +#include "BLI_multi_value_map.hh" +#include "BLI_set.hh" +#include "BLI_stack.hh" #include "BLI_string.h" #include "BLI_utildefines.h" @@ -42,10 +46,10 @@ #include "node_common.h" #include "node_util.h" -enum { - REFINE_FORWARD = 1 << 0, - REFINE_BACKWARD = 1 << 1, -}; +using blender::Map; +using blender::MultiValueMap; +using blender::Set; +using blender::Stack; /* -------------------------------------------------------------------- */ /** \name Node Group @@ -53,24 +57,22 @@ enum { bNodeSocket *node_group_find_input_socket(bNode *groupnode, const char *identifier) { - bNodeSocket *sock; - for (sock = groupnode->inputs.first; sock; sock = sock->next) { + LISTBASE_FOREACH (bNodeSocket *, sock, &groupnode->inputs) { if (STREQ(sock->identifier, identifier)) { return sock; } } - return NULL; + return nullptr; } bNodeSocket *node_group_find_output_socket(bNode *groupnode, const char *identifier) { - bNodeSocket *sock; - for (sock = groupnode->outputs.first; sock; sock = sock->next) { + LISTBASE_FOREACH (bNodeSocket *, sock, &groupnode->outputs) { if (STREQ(sock->identifier, identifier)) { return sock; } } - return NULL; + return nullptr; } /* groups display their internal tree name as label */ @@ -95,13 +97,12 @@ bool node_group_poll_instance(bNode *node, bNodeTree *nodetree, const char **dis bool nodeGroupPoll(bNodeTree *nodetree, bNodeTree *grouptree, const char **r_disabled_hint) { - bNode *node; bool valid = true; /* unspecified node group, generally allowed * (if anything, should be avoided on operator level) */ - if (grouptree == NULL) { + if (grouptree == nullptr) { return true; } @@ -110,7 +111,7 @@ bool nodeGroupPoll(bNodeTree *nodetree, bNodeTree *grouptree, const char **r_dis return false; } - for (node = grouptree->nodes.first; node; node = node->next) { + LISTBASE_FOREACH (bNode *, node, &grouptree->nodes) { if (node->typeinfo->poll_instance && !node->typeinfo->poll_instance(node, nodetree, r_disabled_hint)) { valid = false; @@ -121,12 +122,15 @@ bool nodeGroupPoll(bNodeTree *nodetree, bNodeTree *grouptree, const char **r_dis } /* used for both group nodes and interface nodes */ -static bNodeSocket *group_verify_socket( - bNodeTree *ntree, bNode *gnode, bNodeSocket *iosock, ListBase *verify_lb, int in_out) +static bNodeSocket *group_verify_socket(bNodeTree *ntree, + bNode *gnode, + bNodeSocket *iosock, + ListBase *verify_lb, + eNodeSocketInOut in_out) { bNodeSocket *sock; - for (sock = verify_lb->first; sock; sock = sock->next) { + for (sock = (bNodeSocket *)verify_lb->first; sock; sock = sock->next) { if (STREQ(sock->identifier, iosock->identifier)) { break; } @@ -163,29 +167,32 @@ static bNodeSocket *group_verify_socket( } /* used for both group nodes and interface nodes */ -static void group_verify_socket_list( - bNodeTree *ntree, bNode *gnode, ListBase *iosock_lb, ListBase *verify_lb, int in_out) +static void group_verify_socket_list(bNodeTree *ntree, + bNode *gnode, + ListBase *iosock_lb, + ListBase *verify_lb, + eNodeSocketInOut in_out) { - bNodeSocket *iosock, *sock, *nextsock; + bNodeSocket *sock, *nextsock; /* step by step compare */ - iosock = iosock_lb->first; + bNodeSocket *iosock = (bNodeSocket *)iosock_lb->first; for (; iosock; iosock = iosock->next) { /* abusing new_sock pointer for verification here! only used inside this function */ iosock->new_sock = group_verify_socket(ntree, gnode, iosock, verify_lb, in_out); } /* leftovers are removed */ - for (sock = verify_lb->first; sock; sock = nextsock) { + for (sock = (bNodeSocket *)verify_lb->first; sock; sock = nextsock) { nextsock = sock->next; nodeRemoveSocket(ntree, gnode, sock); } /* and we put back the verified sockets */ - iosock = iosock_lb->first; + iosock = (bNodeSocket *)iosock_lb->first; for (; iosock; iosock = iosock->next) { if (iosock->new_sock) { BLI_addtail(verify_lb, iosock->new_sock); - iosock->new_sock = NULL; + iosock->new_sock = nullptr; } } } @@ -194,11 +201,11 @@ static void group_verify_socket_list( void node_group_update(struct bNodeTree *ntree, struct bNode *node) { /* check inputs and outputs, and remove or insert them */ - if (node->id == NULL) { + if (node->id == nullptr) { nodeRemoveAllSockets(ntree, node); } else if ((ID_IS_LINKED(node->id) && (node->id->tag & LIB_TAG_MISSING))) { - /* Missing datablock, leave sockets unchanged so that when it comes back + /* Missing data-block, leave sockets unchanged so that when it comes back * the links remain valid. */ } else { @@ -227,7 +234,7 @@ static void node_frame_init(bNodeTree *UNUSED(ntree), bNode *node) void register_node_type_frame(void) { /* frame type is used for all tree types, needs dynamic allocation */ - bNodeType *ntype = MEM_callocN(sizeof(bNodeType), "frame node type"); + bNodeType *ntype = (bNodeType *)MEM_callocN(sizeof(bNodeType), "frame node type"); ntype->free_self = (void (*)(bNodeType *))MEM_freeN; node_type_base(ntype, NODE_FRAME, "Frame", NODE_CLASS_LAYOUT, NODE_BACKGROUND); @@ -254,11 +261,11 @@ static void node_reroute_update_internal_links(bNodeTree *ntree, bNode *node) return; } - link = MEM_callocN(sizeof(bNodeLink), "internal node link"); + link = (bNodeLink *)MEM_callocN(sizeof(bNodeLink), "internal node link"); link->fromnode = node; - link->fromsock = node->inputs.first; + link->fromsock = (bNodeSocket *)node->inputs.first; link->tonode = node; - link->tosock = node->outputs.first; + link->tosock = (bNodeSocket *)node->outputs.first; /* internal link is always valid */ link->flag |= NODE_LINK_VALID; BLI_addtail(&node->internal_links, link); @@ -276,7 +283,7 @@ static void node_reroute_init(bNodeTree *ntree, bNode *node) void register_node_type_reroute(void) { /* frame type is used for all tree types, needs dynamic allocation */ - bNodeType *ntype = MEM_callocN(sizeof(bNodeType), "frame node type"); + bNodeType *ntype = (bNodeType *)MEM_callocN(sizeof(bNodeType), "frame node type"); ntype->free_self = (void (*)(bNodeType *))MEM_freeN; node_type_base(ntype, NODE_REROUTE, "Reroute", NODE_CLASS_LAYOUT, 0); @@ -286,76 +293,37 @@ void register_node_type_reroute(void) nodeRegisterType(ntype); } -static void node_reroute_inherit_type_recursive(bNodeTree *ntree, bNode *node, int flag) +static void propagate_reroute_type_from_start_socket( + bNodeSocket *start_socket, + const MultiValueMap<bNodeSocket *, bNodeLink *> &links_map, + Map<bNode *, const bNodeSocketType *> &r_reroute_types) { - bNodeSocket *input = node->inputs.first; - bNodeSocket *output = node->outputs.first; - bNodeLink *link; - int type = SOCK_FLOAT; - const char *type_idname = nodeStaticSocketType(type, PROP_NONE); - - /* XXX it would be a little bit more efficient to restrict actual updates - * to reroute nodes connected to an updated node, but there's no reliable flag - * to indicate updated nodes (node->update is not set on linking). - */ - - node->done = 1; - - /* recursive update */ - for (link = ntree->links.first; link; link = link->next) { - bNode *fromnode = link->fromnode; - bNode *tonode = link->tonode; - if (!tonode || !fromnode) { - continue; + Stack<bNode *> nodes_to_check; + for (bNodeLink *link : links_map.lookup(start_socket)) { + if (link->tonode->type == NODE_REROUTE) { + nodes_to_check.push(link->tonode); } - if (nodeLinkIsHidden(link)) { - continue; - } - - if (flag & REFINE_FORWARD) { - if (tonode == node && fromnode->type == NODE_REROUTE && !fromnode->done) { - node_reroute_inherit_type_recursive(ntree, fromnode, REFINE_FORWARD); - } + if (link->fromnode->type == NODE_REROUTE) { + nodes_to_check.push(link->fromnode); } - if (flag & REFINE_BACKWARD) { - if (fromnode == node && tonode->type == NODE_REROUTE && !tonode->done) { - node_reroute_inherit_type_recursive(ntree, tonode, REFINE_BACKWARD); - } - } - } - - /* determine socket type from unambiguous input/output connection if possible */ - if (nodeSocketLinkLimit(input) == 1 && input->link) { - type = input->link->fromsock->type; - type_idname = nodeStaticSocketType(type, PROP_NONE); - } - else if (nodeSocketLinkLimit(output) == 1 && output->link) { - type = output->link->tosock->type; - type_idname = nodeStaticSocketType(type, PROP_NONE); } - - if (input->type != type) { - bNodeSocket *ninput = nodeAddSocket(ntree, node, SOCK_IN, type_idname, "input", "Input"); - for (link = ntree->links.first; link; link = link->next) { - if (link->tosock == input) { - link->tosock = ninput; - ninput->link = link; + const bNodeSocketType *current_type = start_socket->typeinfo; + while (!nodes_to_check.is_empty()) { + bNode *reroute_node = nodes_to_check.pop(); + BLI_assert(reroute_node->type == NODE_REROUTE); + if (r_reroute_types.add(reroute_node, current_type)) { + for (bNodeLink *link : links_map.lookup((bNodeSocket *)reroute_node->inputs.first)) { + if (link->fromnode->type == NODE_REROUTE) { + nodes_to_check.push(link->fromnode); + } } - } - nodeRemoveSocket(ntree, node, input); - } - - if (output->type != type) { - bNodeSocket *noutput = nodeAddSocket(ntree, node, SOCK_OUT, type_idname, "output", "Output"); - for (link = ntree->links.first; link; link = link->next) { - if (link->fromsock == output) { - link->fromsock = noutput; + for (bNodeLink *link : links_map.lookup((bNodeSocket *)reroute_node->outputs.first)) { + if (link->tonode->type == NODE_REROUTE) { + nodes_to_check.push(link->tonode); + } } } - nodeRemoveSocket(ntree, node, output); } - - nodeUpdateInternalLinks(ntree, node); } /* Global update function for Reroute node types. @@ -363,16 +331,58 @@ static void node_reroute_inherit_type_recursive(bNodeTree *ntree, bNode *node, i */ void ntree_update_reroute_nodes(bNodeTree *ntree) { - bNode *node; + /* Contains nodes that are linked to at least one reroute node. */ + Set<bNode *> nodes_linked_with_reroutes; + /* Contains all links that are linked to at least one reroute node. */ + MultiValueMap<bNodeSocket *, bNodeLink *> links_map; + /* Build acceleration data structures for the algorithm below. */ + LISTBASE_FOREACH (bNodeLink *, link, &ntree->links) { + if (link->fromsock == nullptr || link->tosock == nullptr) { + continue; + } + if (link->fromnode->type != NODE_REROUTE && link->tonode->type != NODE_REROUTE) { + continue; + } + if (link->fromnode->type != NODE_REROUTE) { + nodes_linked_with_reroutes.add(link->fromnode); + } + if (link->tonode->type != NODE_REROUTE) { + nodes_linked_with_reroutes.add(link->tonode); + } + links_map.add(link->fromsock, link); + links_map.add(link->tosock, link); + } + + /* Will contain the socket type for every linked reroute node. */ + Map<bNode *, const bNodeSocketType *> reroute_types; + + /* Propagate socket types from left to right. */ + for (bNode *start_node : nodes_linked_with_reroutes) { + LISTBASE_FOREACH (bNodeSocket *, output_socket, &start_node->outputs) { + propagate_reroute_type_from_start_socket(output_socket, links_map, reroute_types); + } + } - /* clear tags */ - for (node = ntree->nodes.first; node; node = node->next) { - node->done = 0; + /* Propagate socket types from right to left. This affects reroute nodes that haven't been + * changed in the the loop above. */ + for (bNode *start_node : nodes_linked_with_reroutes) { + LISTBASE_FOREACH (bNodeSocket *, input_socket, &start_node->inputs) { + propagate_reroute_type_from_start_socket(input_socket, links_map, reroute_types); + } } - for (node = ntree->nodes.first; node; node = node->next) { - if (node->type == NODE_REROUTE && !node->done) { - node_reroute_inherit_type_recursive(ntree, node, REFINE_FORWARD | REFINE_BACKWARD); + /* Actually update reroute nodes with changed types. */ + for (const auto &item : reroute_types.items()) { + bNode *reroute_node = item.key; + const bNodeSocketType *socket_type = item.value; + bNodeSocket *input_socket = (bNodeSocket *)reroute_node->inputs.first; + bNodeSocket *output_socket = (bNodeSocket *)reroute_node->outputs.first; + + if (input_socket->typeinfo != socket_type) { + nodeModifySocketType(ntree, reroute_node, input_socket, socket_type->idname); + } + if (output_socket->typeinfo != socket_type) { + nodeModifySocketType(ntree, reroute_node, output_socket, socket_type->idname); } } } @@ -393,7 +403,7 @@ static bool node_is_connected_to_output_recursive(bNodeTree *ntree, bNode *node) } /* test all connected nodes, first positive find is sufficient to return true */ - for (link = ntree->links.first; link; link = link->next) { + for (link = (bNodeLink *)ntree->links.first; link; link = link->next) { if (link->fromnode == node) { if (node_is_connected_to_output_recursive(ntree, link->tonode)) { return true; @@ -408,7 +418,7 @@ bool BKE_node_is_connected_to_output(bNodeTree *ntree, bNode *node) bNode *tnode; /* clear flags */ - for (tnode = ntree->nodes.first; tnode; tnode = tnode->next) { + for (tnode = (bNode *)ntree->nodes.first; tnode; tnode = tnode->next) { tnode->done = 0; } @@ -419,9 +429,9 @@ void BKE_node_tree_unlink_id(ID *id, struct bNodeTree *ntree) { bNode *node; - for (node = ntree->nodes.first; node; node = node->next) { + for (node = (bNode *)ntree->nodes.first; node; node = node->next) { if (node->id == id) { - node->id = NULL; + node->id = nullptr; } } } @@ -440,17 +450,17 @@ static void node_group_input_init(bNodeTree *ntree, bNode *node) bNodeSocket *node_group_input_find_socket(bNode *node, const char *identifier) { bNodeSocket *sock; - for (sock = node->outputs.first; sock; sock = sock->next) { + for (sock = (bNodeSocket *)node->outputs.first; sock; sock = sock->next) { if (STREQ(sock->identifier, identifier)) { return sock; } } - return NULL; + return nullptr; } void node_group_input_update(bNodeTree *ntree, bNode *node) { - bNodeSocket *extsock = node->outputs.last; + bNodeSocket *extsock = (bNodeSocket *)node->outputs.last; bNodeLink *link, *linknext, *exposelink; /* Adding a tree socket and verifying will remove the extension socket! * This list caches the existing links from the extension socket @@ -460,14 +470,14 @@ void node_group_input_update(bNodeTree *ntree, bNode *node) /* find links from the extension socket and store them */ BLI_listbase_clear(&tmplinks); - for (link = ntree->links.first; link; link = linknext) { + for (link = (bNodeLink *)ntree->links.first; link; link = linknext) { linknext = link->next; if (nodeLinkIsHidden(link)) { continue; } if (link->fromsock == extsock) { - bNodeLink *tlink = MEM_callocN(sizeof(bNodeLink), "temporary link"); + bNodeLink *tlink = (bNodeLink *)MEM_callocN(sizeof(bNodeLink), "temporary link"); *tlink = *link; BLI_addtail(&tmplinks, tlink); @@ -476,8 +486,8 @@ void node_group_input_update(bNodeTree *ntree, bNode *node) } /* find valid link to expose */ - exposelink = NULL; - for (link = tmplinks.first; link; link = link->next) { + exposelink = nullptr; + for (link = (bNodeLink *)tmplinks.first; link; link = link->next) { /* XXX Multiple sockets can be connected to the extension socket at once, * in that case the arbitrary first link determines name and type. * This could be improved by choosing the "best" type among all links, @@ -498,7 +508,7 @@ void node_group_input_update(bNodeTree *ntree, bNode *node) newsock = node_group_input_find_socket(node, gsock->identifier); /* redirect links from the extension socket */ - for (link = tmplinks.first; link; link = link->next) { + for (link = (bNodeLink *)tmplinks.first; link; link = link->next) { nodeAddLink(ntree, node, newsock, link->tonode, link->tosock); } } @@ -518,7 +528,7 @@ void node_group_input_update(bNodeTree *ntree, bNode *node) void register_node_type_group_input(void) { /* used for all tree types, needs dynamic allocation */ - bNodeType *ntype = MEM_callocN(sizeof(bNodeType), "node type"); + bNodeType *ntype = (bNodeType *)MEM_callocN(sizeof(bNodeType), "node type"); ntype->free_self = (void (*)(bNodeType *))MEM_freeN; node_type_base(ntype, NODE_GROUP_INPUT, "Group Input", NODE_CLASS_INTERFACE, 0); @@ -537,17 +547,17 @@ static void node_group_output_init(bNodeTree *ntree, bNode *node) bNodeSocket *node_group_output_find_socket(bNode *node, const char *identifier) { bNodeSocket *sock; - for (sock = node->inputs.first; sock; sock = sock->next) { + for (sock = (bNodeSocket *)node->inputs.first; sock; sock = sock->next) { if (STREQ(sock->identifier, identifier)) { return sock; } } - return NULL; + return nullptr; } void node_group_output_update(bNodeTree *ntree, bNode *node) { - bNodeSocket *extsock = node->inputs.last; + bNodeSocket *extsock = (bNodeSocket *)node->inputs.last; bNodeLink *link, *linknext, *exposelink; /* Adding a tree socket and verifying will remove the extension socket! * This list caches the existing links to the extension socket @@ -557,14 +567,14 @@ void node_group_output_update(bNodeTree *ntree, bNode *node) /* find links to the extension socket and store them */ BLI_listbase_clear(&tmplinks); - for (link = ntree->links.first; link; link = linknext) { + for (link = (bNodeLink *)ntree->links.first; link; link = linknext) { linknext = link->next; if (nodeLinkIsHidden(link)) { continue; } if (link->tosock == extsock) { - bNodeLink *tlink = MEM_callocN(sizeof(bNodeLink), "temporary link"); + bNodeLink *tlink = (bNodeLink *)MEM_callocN(sizeof(bNodeLink), "temporary link"); *tlink = *link; BLI_addtail(&tmplinks, tlink); @@ -573,8 +583,8 @@ void node_group_output_update(bNodeTree *ntree, bNode *node) } /* find valid link to expose */ - exposelink = NULL; - for (link = tmplinks.first; link; link = link->next) { + exposelink = nullptr; + for (link = (bNodeLink *)tmplinks.first; link; link = link->next) { /* XXX Multiple sockets can be connected to the extension socket at once, * in that case the arbitrary first link determines name and type. * This could be improved by choosing the "best" type among all links, @@ -596,7 +606,7 @@ void node_group_output_update(bNodeTree *ntree, bNode *node) newsock = node_group_output_find_socket(node, gsock->identifier); /* redirect links to the extension socket */ - for (link = tmplinks.first; link; link = link->next) { + for (link = (bNodeLink *)tmplinks.first; link; link = link->next) { nodeAddLink(ntree, link->fromnode, link->fromsock, node, newsock); } } @@ -616,7 +626,7 @@ void node_group_output_update(bNodeTree *ntree, bNode *node) void register_node_type_group_output(void) { /* used for all tree types, needs dynamic allocation */ - bNodeType *ntype = MEM_callocN(sizeof(bNodeType), "node type"); + bNodeType *ntype = (bNodeType *)MEM_callocN(sizeof(bNodeType), "node type"); ntype->free_self = (void (*)(bNodeType *))MEM_freeN; node_type_base(ntype, NODE_GROUP_OUTPUT, "Group Output", NODE_CLASS_INTERFACE, 0); diff --git a/source/blender/nodes/intern/node_socket.cc b/source/blender/nodes/intern/node_socket.cc index 31260f95242..4334f1b5030 100644 --- a/source/blender/nodes/intern/node_socket.cc +++ b/source/blender/nodes/intern/node_socket.cc @@ -872,7 +872,7 @@ static bNodeSocketType *make_socket_type_material() void register_standard_node_socket_types(void) { - /* draw callbacks are set in drawnode.c to avoid bad-level calls */ + /* Draw callbacks are set in `drawnode.c` to avoid bad-level calls. */ nodeRegisterSocketType(make_socket_type_float(PROP_NONE)); nodeRegisterSocketType(make_socket_type_float(PROP_UNSIGNED)); diff --git a/source/blender/nodes/intern/node_tree_ref.cc b/source/blender/nodes/intern/node_tree_ref.cc index 641d02af902..43c7fbd2599 100644 --- a/source/blender/nodes/intern/node_tree_ref.cc +++ b/source/blender/nodes/intern/node_tree_ref.cc @@ -19,6 +19,7 @@ #include "NOD_node_tree_ref.hh" #include "BLI_dot_export.hh" +#include "BLI_stack.hh" namespace blender::nodes { @@ -473,6 +474,108 @@ bool NodeTreeRef::has_undefined_nodes_or_sockets() const return false; } +bool NodeRef::any_input_is_directly_linked() const +{ + for (const SocketRef *socket : inputs_) { + if (!socket->directly_linked_sockets().is_empty()) { + return true; + } + } + return false; +} + +bool NodeRef::any_output_is_directly_linked() const +{ + for (const SocketRef *socket : outputs_) { + if (!socket->directly_linked_sockets().is_empty()) { + return true; + } + } + return false; +} + +bool NodeRef::any_socket_is_directly_linked(eNodeSocketInOut in_out) const +{ + if (in_out == SOCK_IN) { + return this->any_input_is_directly_linked(); + } + return this->any_output_is_directly_linked(); +} + +/** + * Sort nodes topologically from left to right or right to left. + * In the future the result if this could be cached on #NodeTreeRef. + */ +Vector<const NodeRef *> NodeTreeRef::toposort(const ToposortDirection direction) const +{ + struct Item { + const NodeRef *node; + /* Index of the next socket that is checked in the depth-first search. */ + int socket_index = 0; + /* Link index in the next socket that is checked in the depth-first search. */ + int link_index = 0; + }; + + Vector<const NodeRef *> toposort; + toposort.reserve(nodes_by_id_.size()); + Array<bool> node_is_done_by_id(nodes_by_id_.size(), false); + Stack<Item> nodes_to_check; + + for (const NodeRef *start_node : nodes_by_id_) { + if (node_is_done_by_id[start_node->id()]) { + /* Ignore nodes that are done already. */ + continue; + } + if (start_node->any_socket_is_directly_linked( + direction == ToposortDirection::LeftToRight ? SOCK_OUT : SOCK_IN)) { + /* Ignore non-start nodes. */ + continue; + } + + /* Do a depth-first search to sort nodes topologically. */ + nodes_to_check.push({start_node}); + while (!nodes_to_check.is_empty()) { + Item &item = nodes_to_check.peek(); + const NodeRef &node = *item.node; + const Span<const SocketRef *> sockets = node.sockets( + direction == ToposortDirection::LeftToRight ? SOCK_IN : SOCK_OUT); + + while (true) { + if (item.socket_index == sockets.size()) { + /* All sockets have already been visited. */ + break; + } + const SocketRef &socket = *sockets[item.socket_index]; + const Span<const SocketRef *> linked_sockets = socket.directly_linked_sockets(); + if (item.link_index == linked_sockets.size()) { + /* All links connected to this socket have already been visited. */ + item.socket_index++; + item.link_index = 0; + continue; + } + const SocketRef &linked_socket = *linked_sockets[item.link_index]; + const NodeRef &linked_node = linked_socket.node(); + if (node_is_done_by_id[linked_node.id()]) { + /* The linked node has already been visited. */ + item.link_index++; + continue; + } + nodes_to_check.push({&linked_node}); + break; + } + + /* If no other element has been pushed, the current node can be pushed to the sorted list. */ + if (&item == &nodes_to_check.peek()) { + node_is_done_by_id[node.id()] = true; + toposort.append(&node); + nodes_to_check.pop(); + } + } + } + + return toposort; +} + std::string NodeTreeRef::to_dot() const { dot::DirectedGraph digraph; diff --git a/source/blender/nodes/shader/node_shader_tree.c b/source/blender/nodes/shader/node_shader_tree.c index c3a675fcd20..83ee0c2f411 100644 --- a/source/blender/nodes/shader/node_shader_tree.c +++ b/source/blender/nodes/shader/node_shader_tree.c @@ -201,7 +201,7 @@ void register_node_tree_type_sh(void) tt->type = NTREE_SHADER; strcpy(tt->idname, "ShaderNodeTree"); strcpy(tt->ui_name, N_("Shader Editor")); - tt->ui_icon = 0; /* defined in drawnode.c */ + tt->ui_icon = 0; /* Defined in `drawnode.c`. */ strcpy(tt->ui_description, N_("Shader nodes")); tt->foreach_nodeclass = foreach_nodeclass; diff --git a/source/blender/nodes/shader/nodes/node_shader_brightness.c b/source/blender/nodes/shader/nodes/node_shader_brightness.c index d8f560277f2..4f375c666de 100644 --- a/source/blender/nodes/shader/nodes/node_shader_brightness.c +++ b/source/blender/nodes/shader/nodes/node_shader_brightness.c @@ -19,7 +19,7 @@ #include "node_shader_util.h" -/* **************** Brigh and contrsast ******************** */ +/* **************** Bright and contrast ******************** */ static bNodeSocketTemplate sh_node_brightcontrast_in[] = { {SOCK_RGBA, N_("Color"), 1.0f, 1.0f, 1.0f, 1.0f}, diff --git a/source/blender/nodes/shader/nodes/node_shader_clamp.cc b/source/blender/nodes/shader/nodes/node_shader_clamp.cc index 31d8f8ef15c..e8d4239937f 100644 --- a/source/blender/nodes/shader/nodes/node_shader_clamp.cc +++ b/source/blender/nodes/shader/nodes/node_shader_clamp.cc @@ -27,6 +27,7 @@ namespace blender::nodes { static void sh_node_clamp_declare(NodeDeclarationBuilder &b) { + b.is_function_node(); b.add_input<decl::Float>("Value").min(0.0f).max(1.0f).default_value(1.0f); b.add_input<decl::Float>("Min").default_value(0.0f).min(-10000.0f).max(10000.0f); b.add_input<decl::Float>("Max").default_value(1.0f).min(-10000.0f).max(10000.0f); diff --git a/source/blender/nodes/shader/nodes/node_shader_curves.cc b/source/blender/nodes/shader/nodes/node_shader_curves.cc index e4ada06133e..8657d9e517d 100644 --- a/source/blender/nodes/shader/nodes/node_shader_curves.cc +++ b/source/blender/nodes/shader/nodes/node_shader_curves.cc @@ -27,6 +27,7 @@ namespace blender::nodes { static void sh_node_curve_vec_declare(NodeDeclarationBuilder &b) { + b.is_function_node(); b.add_input<decl::Float>("Fac").min(0.0f).max(1.0f).default_value(1.0f).subtype(PROP_FACTOR); b.add_input<decl::Vector>("Vector").min(-1.0f).max(1.0f); b.add_output<decl::Vector>("Vector"); @@ -342,3 +343,142 @@ void register_node_type_sh_curve_rgb(void) nodeRegisterType(&ntype); } + +/* **************** CURVE FLOAT ******************** */ + +namespace blender::nodes { + +static void sh_node_curve_float_declare(NodeDeclarationBuilder &b) +{ + b.add_input<decl::Float>("Factor").min(0.0f).max(1.0f).default_value(1.0f).subtype(PROP_FACTOR); + b.add_input<decl::Float>("Value").default_value(1.0f); + b.add_output<decl::Float>("Value"); +}; + +} // namespace blender::nodes + +static void node_shader_exec_curve_float(void *UNUSED(data), + int UNUSED(thread), + bNode *node, + bNodeExecData *UNUSED(execdata), + bNodeStack **in, + bNodeStack **out) +{ + float value; + float fac; + + nodestack_get_vec(&fac, SOCK_FLOAT, in[0]); + nodestack_get_vec(&value, SOCK_FLOAT, in[1]); + out[0]->vec[0] = BKE_curvemapping_evaluateF((CurveMapping *)node->storage, 0, value); + if (fac != 1.0f) { + out[0]->vec[0] = (1.0f - fac) * value + fac * out[0]->vec[0]; + } +} + +static void node_shader_init_curve_float(bNodeTree *UNUSED(ntree), bNode *node) +{ + node->storage = BKE_curvemapping_add(1, 0.0f, 0.0f, 1.0f, 1.0f); +} + +static int gpu_shader_curve_float(GPUMaterial *mat, + bNode *node, + bNodeExecData *UNUSED(execdata), + GPUNodeStack *in, + GPUNodeStack *out) +{ + float *array, layer; + int size; + + CurveMapping *cumap = (CurveMapping *)node->storage; + + BKE_curvemapping_table_F(cumap, &array, &size); + GPUNodeLink *tex = GPU_color_band(mat, size, array, &layer); + + float ext_xyz[4]; + float range_x; + + const CurveMap *cm = &cumap->cm[0]; + ext_xyz[0] = cm->mintable; + ext_xyz[2] = cm->maxtable; + range_x = 1.0f / max_ff(1e-8f, cm->maxtable - cm->mintable); + /* Compute extrapolation gradients. */ + if ((cumap->flag & CUMA_EXTEND_EXTRAPOLATE) != 0) { + ext_xyz[1] = (cm->ext_in[0] != 0.0f) ? (cm->ext_in[1] / (cm->ext_in[0] * range_x)) : 1e8f; + ext_xyz[3] = (cm->ext_out[0] != 0.0f) ? (cm->ext_out[1] / (cm->ext_out[0] * range_x)) : 1e8f; + } + else { + ext_xyz[1] = 0.0f; + ext_xyz[3] = 0.0f; + } + return GPU_stack_link(mat, + node, + "curve_float", + in, + out, + tex, + GPU_constant(&layer), + GPU_uniform(&range_x), + GPU_uniform(ext_xyz)); +} + +class CurveFloatFunction : public blender::fn::MultiFunction { + private: + const CurveMapping &cumap_; + + public: + CurveFloatFunction(const CurveMapping &cumap) : cumap_(cumap) + { + static blender::fn::MFSignature signature = create_signature(); + this->set_signature(&signature); + } + + static blender::fn::MFSignature create_signature() + { + blender::fn::MFSignatureBuilder signature{"Curve Float"}; + signature.single_input<float>("Factor"); + signature.single_input<float>("Value"); + signature.single_output<float>("Value"); + return signature.build(); + } + + void call(blender::IndexMask mask, + blender::fn::MFParams params, + blender::fn::MFContext UNUSED(context)) const override + { + const blender::VArray<float> &fac = params.readonly_single_input<float>(0, "Factor"); + const blender::VArray<float> &val_in = params.readonly_single_input<float>(1, "Value"); + blender::MutableSpan<float> val_out = params.uninitialized_single_output<float>(2, "Value"); + + for (int64_t i : mask) { + val_out[i] = BKE_curvemapping_evaluateF(&cumap_, 0, val_in[i]); + if (fac[i] != 1.0f) { + val_out[i] = (1.0f - fac[i]) * val_in[i] + fac[i] * val_out[i]; + } + } + } +}; + +static void sh_node_curve_float_build_multi_function( + blender::nodes::NodeMultiFunctionBuilder &builder) +{ + bNode &bnode = builder.node(); + CurveMapping *cumap = (CurveMapping *)bnode.storage; + BKE_curvemapping_init(cumap); + builder.construct_and_set_matching_fn<CurveFloatFunction>(*cumap); +} + +void register_node_type_sh_curve_float(void) +{ + static bNodeType ntype; + + sh_fn_node_type_base(&ntype, SH_NODE_CURVE_FLOAT, "Float Curve", NODE_CLASS_CONVERTER, 0); + ntype.declare = blender::nodes::sh_node_curve_float_declare; + node_type_init(&ntype, node_shader_init_curve_float); + node_type_size_preset(&ntype, NODE_SIZE_LARGE); + node_type_storage(&ntype, "CurveMapping", node_free_curves, node_copy_curves); + node_type_exec(&ntype, node_initexec_curves, nullptr, node_shader_exec_curve_float); + node_type_gpu(&ntype, gpu_shader_curve_float); + ntype.build_multi_function = sh_node_curve_float_build_multi_function; + + nodeRegisterType(&ntype); +} diff --git a/source/blender/nodes/shader/nodes/node_shader_hair_info.c b/source/blender/nodes/shader/nodes/node_shader_hair_info.c index 843185befb6..c721fb9c77a 100644 --- a/source/blender/nodes/shader/nodes/node_shader_hair_info.c +++ b/source/blender/nodes/shader/nodes/node_shader_hair_info.c @@ -22,6 +22,7 @@ static bNodeSocketTemplate outputs[] = { {SOCK_FLOAT, N_("Is Strand"), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}, {SOCK_FLOAT, N_("Intercept"), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}, + {SOCK_FLOAT, N_("Length"), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}, {SOCK_FLOAT, N_("Thickness"), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}, {SOCK_VECTOR, N_("Tangent Normal"), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}, // { SOCK_FLOAT, 0, N_("Fade"), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}, @@ -35,7 +36,11 @@ static int node_shader_gpu_hair_info(GPUMaterial *mat, GPUNodeStack *in, GPUNodeStack *out) { - return GPU_stack_link(mat, node, "node_hair_info", in, out); + /* Length: don't request length if not needed. */ + static const float zero = 0; + GPUNodeLink *length_link = (!out[2].hasoutput) ? GPU_constant(&zero) : + GPU_attribute(mat, CD_HAIRLENGTH, ""); + return GPU_stack_link(mat, node, "node_hair_info", in, out, length_link); } /* node type definition */ diff --git a/source/blender/nodes/shader/nodes/node_shader_map_range.cc b/source/blender/nodes/shader/nodes/node_shader_map_range.cc index a5f9a24a728..5ea194ddc83 100644 --- a/source/blender/nodes/shader/nodes/node_shader_map_range.cc +++ b/source/blender/nodes/shader/nodes/node_shader_map_range.cc @@ -29,6 +29,7 @@ namespace blender::nodes { static void sh_node_map_range_declare(NodeDeclarationBuilder &b) { + b.is_function_node(); b.add_input<decl::Float>("Value").min(-10000.0f).max(10000.0f).default_value(1.0f); b.add_input<decl::Float>("From Min").min(-10000.0f).max(10000.0f); b.add_input<decl::Float>("From Max").min(-10000.0f).max(10000.0f).default_value(1.0f); diff --git a/source/blender/nodes/shader/nodes/node_shader_math.cc b/source/blender/nodes/shader/nodes/node_shader_math.cc index 80a27b8e6a1..96d1be49c04 100644 --- a/source/blender/nodes/shader/nodes/node_shader_math.cc +++ b/source/blender/nodes/shader/nodes/node_shader_math.cc @@ -31,6 +31,7 @@ namespace blender::nodes { static void sh_node_math_declare(NodeDeclarationBuilder &b) { + b.is_function_node(); b.add_input<decl::Float>("Value").default_value(0.5f).min(-10000.0f).max(10000.0f); b.add_input<decl::Float>("Value", "Value_001").default_value(0.5f).min(-10000.0f).max(10000.0f); b.add_input<decl::Float>("Value", "Value_002").default_value(0.5f).min(-10000.0f).max(10000.0f); diff --git a/source/blender/nodes/shader/nodes/node_shader_mixRgb.cc b/source/blender/nodes/shader/nodes/node_shader_mixRgb.cc index 860cc260d5d..d4d02e80ada 100644 --- a/source/blender/nodes/shader/nodes/node_shader_mixRgb.cc +++ b/source/blender/nodes/shader/nodes/node_shader_mixRgb.cc @@ -27,6 +27,7 @@ namespace blender::nodes { static void sh_node_mix_rgb_declare(NodeDeclarationBuilder &b) { + b.is_function_node(); b.add_input<decl::Float>("Fac").default_value(0.5f).min(0.0f).max(1.0f).subtype(PROP_FACTOR); b.add_input<decl::Color>("Color1").default_value({0.5f, 0.5f, 0.5f, 1.0f}); b.add_input<decl::Color>("Color2").default_value({0.5f, 0.5f, 0.5f, 1.0f}); diff --git a/source/blender/nodes/shader/nodes/node_shader_sepcombRGB.cc b/source/blender/nodes/shader/nodes/node_shader_sepcombRGB.cc index 63be399366f..24c5dcf7ba3 100644 --- a/source/blender/nodes/shader/nodes/node_shader_sepcombRGB.cc +++ b/source/blender/nodes/shader/nodes/node_shader_sepcombRGB.cc @@ -27,6 +27,7 @@ namespace blender::nodes { static void sh_node_seprgb_declare(NodeDeclarationBuilder &b) { + b.is_function_node(); b.add_input<decl::Color>("Image").default_value({0.8f, 0.8f, 0.8f, 1.0f}); b.add_output<decl::Float>("R"); b.add_output<decl::Float>("G"); @@ -119,6 +120,7 @@ namespace blender::nodes { static void sh_node_combrgb_declare(NodeDeclarationBuilder &b) { + b.is_function_node(); b.add_input<decl::Float>("R").min(0.0f).max(1.0f); b.add_input<decl::Float>("G").min(0.0f).max(1.0f); b.add_input<decl::Float>("B").min(0.0f).max(1.0f); diff --git a/source/blender/nodes/shader/nodes/node_shader_sepcombXYZ.cc b/source/blender/nodes/shader/nodes/node_shader_sepcombXYZ.cc index b4b3c48482f..8ca8fc19521 100644 --- a/source/blender/nodes/shader/nodes/node_shader_sepcombXYZ.cc +++ b/source/blender/nodes/shader/nodes/node_shader_sepcombXYZ.cc @@ -27,6 +27,7 @@ namespace blender::nodes { static void sh_node_sepxyz_declare(NodeDeclarationBuilder &b) { + b.is_function_node(); b.add_input<decl::Vector>("Vector").min(-10000.0f).max(10000.0f); b.add_output<decl::Float>("X"); b.add_output<decl::Float>("Y"); @@ -103,6 +104,7 @@ namespace blender::nodes { static void sh_node_combxyz_declare(NodeDeclarationBuilder &b) { + b.is_function_node(); b.add_input<decl::Float>("X").min(-10000.0f).max(10000.0f); b.add_input<decl::Float>("Y").min(-10000.0f).max(10000.0f); b.add_input<decl::Float>("Z").min(-10000.0f).max(10000.0f); diff --git a/source/blender/nodes/shader/nodes/node_shader_tex_musgrave.cc b/source/blender/nodes/shader/nodes/node_shader_tex_musgrave.cc index d33d92f25fd..23f150d8135 100644 --- a/source/blender/nodes/shader/nodes/node_shader_tex_musgrave.cc +++ b/source/blender/nodes/shader/nodes/node_shader_tex_musgrave.cc @@ -23,6 +23,7 @@ namespace blender::nodes { static void sh_node_tex_musgrave_declare(NodeDeclarationBuilder &b) { + b.is_function_node(); b.add_input<decl::Vector>("Vector").hide_value(); b.add_input<decl::Float>("W").min(-1000.0f).max(1000.0f); b.add_input<decl::Float>("Scale").min(-1000.0f).max(1000.0f).default_value(5.0f); diff --git a/source/blender/nodes/shader/nodes/node_shader_tex_noise.cc b/source/blender/nodes/shader/nodes/node_shader_tex_noise.cc index 1ae6b3a616c..6ffc8979815 100644 --- a/source/blender/nodes/shader/nodes/node_shader_tex_noise.cc +++ b/source/blender/nodes/shader/nodes/node_shader_tex_noise.cc @@ -25,7 +25,8 @@ namespace blender::nodes { static void sh_node_tex_noise_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Vector>("Vector").hide_value(); + b.is_function_node(); + b.add_input<decl::Vector>("Vector").implicit_field(); b.add_input<decl::Float>("W").min(-1000.0f).max(1000.0f); b.add_input<decl::Float>("Scale").min(-1000.0f).max(1000.0f).default_value(5.0f); b.add_input<decl::Float>("Detail").min(0.0f).max(16.0f).default_value(2.0f); diff --git a/source/blender/nodes/shader/nodes/node_shader_tex_voronoi.cc b/source/blender/nodes/shader/nodes/node_shader_tex_voronoi.cc index cea7af247c1..e12e5724e8e 100644 --- a/source/blender/nodes/shader/nodes/node_shader_tex_voronoi.cc +++ b/source/blender/nodes/shader/nodes/node_shader_tex_voronoi.cc @@ -23,6 +23,7 @@ namespace blender::nodes { static void sh_node_tex_voronoi_declare(NodeDeclarationBuilder &b) { + b.is_function_node(); b.add_input<decl::Vector>("Vector").hide_value(); b.add_input<decl::Float>("W").min(-1000.0f).max(1000.0f); b.add_input<decl::Float>("Scale").min(-1000.0f).max(1000.0f).default_value(5.0f); diff --git a/source/blender/nodes/shader/nodes/node_shader_tex_white_noise.cc b/source/blender/nodes/shader/nodes/node_shader_tex_white_noise.cc index bae16e10120..03543e5f7fe 100644 --- a/source/blender/nodes/shader/nodes/node_shader_tex_white_noise.cc +++ b/source/blender/nodes/shader/nodes/node_shader_tex_white_noise.cc @@ -23,6 +23,7 @@ namespace blender::nodes { static void sh_node_tex_white_noise_declare(NodeDeclarationBuilder &b) { + b.is_function_node(); b.add_input<decl::Vector>("Vector").min(-10000.0f).max(10000.0f); b.add_input<decl::Float>("W").min(-10000.0f).max(10000.0f); b.add_output<decl::Float>("Value"); diff --git a/source/blender/nodes/shader/nodes/node_shader_valToRgb.cc b/source/blender/nodes/shader/nodes/node_shader_valToRgb.cc index 1870caffbb1..d4d08be5d49 100644 --- a/source/blender/nodes/shader/nodes/node_shader_valToRgb.cc +++ b/source/blender/nodes/shader/nodes/node_shader_valToRgb.cc @@ -33,6 +33,7 @@ namespace blender::nodes { static void sh_node_valtorgb_declare(NodeDeclarationBuilder &b) { + b.is_function_node(); b.add_input<decl::Float>("Fac").default_value(0.5f).min(0.0f).max(1.0f).subtype(PROP_FACTOR); b.add_output<decl::Color>("Color"); b.add_output<decl::Float>("Alpha"); diff --git a/source/blender/nodes/shader/nodes/node_shader_vector_math.cc b/source/blender/nodes/shader/nodes/node_shader_vector_math.cc index 5b24e8bb72d..f49ff06cef1 100644 --- a/source/blender/nodes/shader/nodes/node_shader_vector_math.cc +++ b/source/blender/nodes/shader/nodes/node_shader_vector_math.cc @@ -29,6 +29,7 @@ namespace blender::nodes { static void sh_node_vector_math_declare(NodeDeclarationBuilder &b) { + b.is_function_node(); b.add_input<decl::Vector>("Vector").min(-10000.0f).max(10000.0f); b.add_input<decl::Vector>("Vector", "Vector_001").min(-10000.0f).max(10000.0f); b.add_input<decl::Vector>("Vector", "Vector_002").min(-10000.0f).max(10000.0f); diff --git a/source/blender/nodes/shader/nodes/node_shader_vector_rotate.cc b/source/blender/nodes/shader/nodes/node_shader_vector_rotate.cc index e9fd6c4f31e..c9b26fa5199 100644 --- a/source/blender/nodes/shader/nodes/node_shader_vector_rotate.cc +++ b/source/blender/nodes/shader/nodes/node_shader_vector_rotate.cc @@ -27,12 +27,13 @@ namespace blender::nodes { static void sh_node_vector_rotate_declare(NodeDeclarationBuilder &b) { + b.is_function_node(); b.add_input<decl::Vector>("Vector").min(0.0f).max(1.0f).hide_value(); - b.add_input<decl::Vector>("Vector"); + b.add_input<decl::Vector>("Center"); b.add_input<decl::Vector>("Axis").min(-1.0f).max(1.0f).default_value({0.0f, 0.0f, 1.0f}); b.add_input<decl::Float>("Angle").subtype(PROP_ANGLE); b.add_input<decl::Vector>("Rotation").subtype(PROP_EULER); - b.add_output<decl::Vector>("Value"); + b.add_output<decl::Vector>("Vector"); }; } // namespace blender::nodes diff --git a/source/blender/nodes/texture/node_texture_tree.c b/source/blender/nodes/texture/node_texture_tree.c index 7452007639c..14597050524 100644 --- a/source/blender/nodes/texture/node_texture_tree.c +++ b/source/blender/nodes/texture/node_texture_tree.c @@ -169,7 +169,7 @@ void register_node_tree_type_tex(void) tt->type = NTREE_TEXTURE; strcpy(tt->idname, "TextureNodeTree"); strcpy(tt->ui_name, N_("Texture Node Editor")); - tt->ui_icon = 0; /* defined in drawnode.c */ + tt->ui_icon = 0; /* Defined in `drawnode.c`. */ strcpy(tt->ui_description, N_("Texture nodes")); tt->foreach_nodeclass = foreach_nodeclass; diff --git a/source/blender/nodes/texture/nodes/node_texture_curves.c b/source/blender/nodes/texture/nodes/node_texture_curves.c index 70f7e731720..f61e3f36db5 100644 --- a/source/blender/nodes/texture/nodes/node_texture_curves.c +++ b/source/blender/nodes/texture/nodes/node_texture_curves.c @@ -26,7 +26,7 @@ /* **************** CURVE Time ******************** */ -/* custom1 = sfra, custom2 = efra */ +/* custom1 = start-frame, custom2 = end-frame. */ static bNodeSocketTemplate time_outputs[] = {{SOCK_FLOAT, N_("Value")}, {-1, ""}}; static void time_colorfn( diff --git a/source/blender/python/bmesh/bmesh_py_types.c b/source/blender/python/bmesh/bmesh_py_types.c index e5e601e0eb6..cbebe4746e9 100644 --- a/source/blender/python/bmesh/bmesh_py_types.c +++ b/source/blender/python/bmesh/bmesh_py_types.c @@ -1950,7 +1950,7 @@ static PyObject *bpy_bmface_calc_tangent_edge_diagonal(BPy_BMFace *self) PyDoc_STRVAR(bpy_bmface_calc_tangent_vert_diagonal_doc, ".. method:: calc_tangent_vert_diagonal()\n" "\n" - " Return face tangent based on the two most distent vertices.\n" + " Return face tangent based on the two most distant vertices.\n" "\n" " :return: a normalized vector.\n" " :rtype: :class:`mathutils.Vector`\n"); @@ -3464,7 +3464,7 @@ PyDoc_STRVAR(bpy_bmelemseq_doc, ":class:`BMVert`, :class:`BMEdge`, :class:`BMFace`, :class:`BMLoop`.\n" "\n" "When accessed via :class:`BMesh.verts`, :class:`BMesh.edges`, :class:`BMesh.faces`\n" - "there are also functions to create/remomove items.\n"); + "there are also functions to create/remove items.\n"); PyDoc_STRVAR(bpy_bmiter_doc, "Internal BMesh type for looping over verts/faces/edges,\n" "used for iterating over :class:`BMElemSeq` types.\n"); diff --git a/source/blender/python/intern/bpy_interface.c b/source/blender/python/intern/bpy_interface.c index 7a93a076621..95affa9dba9 100644 --- a/source/blender/python/intern/bpy_interface.c +++ b/source/blender/python/intern/bpy_interface.c @@ -246,7 +246,7 @@ void BPY_modules_update(void) #if 0 /* slow, this runs all the time poll, draw etc 100's of time a sec. */ PyObject *mod = PyImport_ImportModuleLevel("bpy", NULL, NULL, NULL, 0); PyModule_AddObject(mod, "data", BPY_rna_module()); - PyModule_AddObject(mod, "types", BPY_rna_types()); /* atm this does not need updating */ + PyModule_AddObject(mod, "types", BPY_rna_types()); /* This does not need updating. */ #endif /* refreshes the main struct */ diff --git a/source/blender/python/intern/bpy_props.c b/source/blender/python/intern/bpy_props.c index ac624d365fa..6d384ed9358 100644 --- a/source/blender/python/intern/bpy_props.c +++ b/source/blender/python/intern/bpy_props.c @@ -3750,7 +3750,7 @@ PyDoc_STRVAR( " (e.g. returned by :class:`bpy.types.UILayout.icon`)\n" " :number: Unique value used as the identifier for this item (stored in file data).\n" " Use when the identifier may need to change. If the *ENUM_FLAG* option is used,\n" - " the values are bitmasks and should be powers of two.\n" + " the values are bit-masks and should be powers of two.\n" "\n" " When an item only contains 4 items they define ``(identifier, name, description, " "number)``.\n" diff --git a/source/blender/python/mathutils/mathutils_geometry.c b/source/blender/python/mathutils/mathutils_geometry.c index 5868c76b28f..d47b59d0c76 100644 --- a/source/blender/python/mathutils/mathutils_geometry.c +++ b/source/blender/python/mathutils/mathutils_geometry.c @@ -1210,7 +1210,7 @@ PyDoc_STRVAR(M_Geometry_tessellate_polygon_doc, "\n" " :arg veclist_list: list of polylines\n" " :rtype: list\n"); -/* PolyFill function, uses Blenders scanfill to fill multiple poly lines */ +/* PolyFill function, uses Blenders scan-fill to fill multiple poly lines. */ static PyObject *M_Geometry_tessellate_polygon(PyObject *UNUSED(self), PyObject *polyLineSeq) { PyObject *tri_list; /* Return this list of tri's */ diff --git a/source/blender/render/RE_pipeline.h b/source/blender/render/RE_pipeline.h index 3237772dd80..0d2d93ae026 100644 --- a/source/blender/render/RE_pipeline.h +++ b/source/blender/render/RE_pipeline.h @@ -233,7 +233,8 @@ void RE_create_render_pass(struct RenderResult *rr, int channels, const char *chan_id, const char *layername, - const char *viewname); + const char *viewname, + const bool allocate); /* obligatory initialize call, disprect is optional */ void RE_InitState(struct Render *re, diff --git a/source/blender/render/intern/engine.c b/source/blender/render/intern/engine.c index 389b821ca35..790c46dad0f 100644 --- a/source/blender/render/intern/engine.c +++ b/source/blender/render/intern/engine.c @@ -207,11 +207,10 @@ static RenderResult *render_result_from_bake(RenderEngine *engine, int x, int y, /* Add render passes. */ RenderPass *result_pass = render_layer_add_pass( - rr, rl, engine->bake.depth, RE_PASSNAME_COMBINED, "", "RGBA"); - RenderPass *primitive_pass = render_layer_add_pass(rr, rl, 4, "BakePrimitive", "", "RGBA"); - RenderPass *differential_pass = render_layer_add_pass(rr, rl, 4, "BakeDifferential", "", "RGBA"); - - render_result_passes_allocated_ensure(rr); + rr, rl, engine->bake.depth, RE_PASSNAME_COMBINED, "", "RGBA", true); + RenderPass *primitive_pass = render_layer_add_pass(rr, rl, 4, "BakePrimitive", "", "RGBA", true); + RenderPass *differential_pass = render_layer_add_pass( + rr, rl, 4, "BakeDifferential", "", "RGBA", true); /* Fill render passes from bake pixel array, to be read by the render engine. */ for (int ty = 0; ty < h; ty++) { @@ -414,7 +413,7 @@ void RE_engine_add_pass(RenderEngine *engine, return; } - RE_create_render_pass(re->result, name, channels, chan_id, layername, NULL); + RE_create_render_pass(re->result, name, channels, chan_id, layername, NULL, false); } void RE_engine_end_result( diff --git a/source/blender/render/intern/pipeline.c b/source/blender/render/intern/pipeline.c index 72ff920561d..7c5259a1c5c 100644 --- a/source/blender/render/intern/pipeline.c +++ b/source/blender/render/intern/pipeline.c @@ -1015,10 +1015,10 @@ static void render_result_uncrop(Render *re) render_result_disprect_to_full_resolution(re); rres = render_result_new(re, &re->disprect, RR_ALL_LAYERS, RR_ALL_VIEWS); - render_result_passes_allocated_ensure(rres); rres->stamp_data = BKE_stamp_data_copy(re->result->stamp_data); render_result_clone_passes(re, rres, NULL); + render_result_passes_allocated_ensure(rres); render_result_merge(rres, re->result); render_result_free(re->result); @@ -2817,7 +2817,7 @@ RenderPass *RE_create_gp_pass(RenderResult *rr, const char *layername, const cha BLI_freelinkN(&rl->passes, rp); } /* create a totally new pass */ - return render_layer_add_pass(rr, rl, 4, RE_PASSNAME_COMBINED, viewname, "RGBA"); + return render_layer_add_pass(rr, rl, 4, RE_PASSNAME_COMBINED, viewname, "RGBA", true); } bool RE_allow_render_generic_object(Object *ob) diff --git a/source/blender/render/intern/render_result.c b/source/blender/render/intern/render_result.c index c308147fc5b..0681bcd9aa5 100644 --- a/source/blender/render/intern/render_result.c +++ b/source/blender/render/intern/render_result.c @@ -213,12 +213,37 @@ static void set_pass_full_name( /********************************** New **************************************/ +static void render_layer_allocate_pass(RenderResult *rr, RenderPass *rp) +{ + if (rp->rect != NULL) { + return; + } + + const size_t rectsize = ((size_t)rr->rectx) * rr->recty * rp->channels; + rp->rect = MEM_callocN(sizeof(float) * rectsize, rp->name); + + if (STREQ(rp->name, RE_PASSNAME_VECTOR)) { + /* initialize to max speed */ + float *rect = rp->rect; + for (int x = rectsize - 1; x >= 0; x--) { + rect[x] = PASS_VECTOR_MAX; + } + } + else if (STREQ(rp->name, RE_PASSNAME_Z)) { + float *rect = rp->rect; + for (int x = rectsize - 1; x >= 0; x--) { + rect[x] = 10e10; + } + } +} + RenderPass *render_layer_add_pass(RenderResult *rr, RenderLayer *rl, int channels, const char *name, const char *viewname, - const char *chan_id) + const char *chan_id, + const bool allocate) { const int view_id = BLI_findstringindex(&rr->views, viewname, offsetof(RenderView, name)); RenderPass *rpass = MEM_callocN(sizeof(RenderPass), name); @@ -250,8 +275,13 @@ RenderPass *render_layer_add_pass(RenderResult *rr, BLI_addtail(&rl->passes, rpass); - /* The result contains non-allocated pass now, so tag it as such. */ - rr->passes_allocated = false; + if (allocate) { + render_layer_allocate_pass(rr, rpass); + } + else { + /* The result contains non-allocated pass now, so tag it as such. */ + rr->passes_allocated = false; + } return rpass; } @@ -323,14 +353,14 @@ RenderResult *render_result_new(Render *re, #define RENDER_LAYER_ADD_PASS_SAFE(rr, rl, channels, name, viewname, chan_id) \ do { \ - if (render_layer_add_pass(rr, rl, channels, name, viewname, chan_id) == NULL) { \ + if (render_layer_add_pass(rr, rl, channels, name, viewname, chan_id, false) == NULL) { \ render_result_free(rr); \ return NULL; \ } \ } while (false) /* A renderlayer should always have a Combined pass. */ - render_layer_add_pass(rr, rl, 4, "Combined", view, "RGBA"); + render_layer_add_pass(rr, rl, 4, "Combined", view, "RGBA", false); if (view_layer->passflag & SCE_PASS_Z) { RENDER_LAYER_ADD_PASS_SAFE(rr, rl, 1, RE_PASSNAME_Z, view, "Z"); @@ -427,7 +457,7 @@ RenderResult *render_result_new(Render *re, } /* a renderlayer should always have a Combined pass */ - render_layer_add_pass(rr, rl, 4, RE_PASSNAME_COMBINED, view, "RGBA"); + render_layer_add_pass(rr, rl, 4, RE_PASSNAME_COMBINED, view, "RGBA", false); } /* NOTE: this has to be in sync with `scene.c`. */ @@ -453,26 +483,7 @@ void render_result_passes_allocated_ensure(RenderResult *rr) continue; } - if (rp->rect != NULL) { - continue; - } - - const size_t rectsize = ((size_t)rr->rectx) * rr->recty * rp->channels; - rp->rect = MEM_callocN(sizeof(float) * rectsize, rp->name); - - if (STREQ(rp->name, RE_PASSNAME_VECTOR)) { - /* initialize to max speed */ - float *rect = rp->rect; - for (int x = rectsize - 1; x >= 0; x--) { - rect[x] = PASS_VECTOR_MAX; - } - } - else if (STREQ(rp->name, RE_PASSNAME_Z)) { - float *rect = rp->rect; - for (int x = rectsize - 1; x >= 0; x--) { - rect[x] = 10e10; - } - } + render_layer_allocate_pass(rr, rp); } } @@ -501,7 +512,7 @@ void render_result_clone_passes(Render *re, RenderResult *rr, const char *viewna &rl->passes, main_rp->fullname, offsetof(RenderPass, fullname)); if (!rp) { render_layer_add_pass( - rr, rl, main_rp->channels, main_rp->name, main_rp->view, main_rp->chan_id); + rr, rl, main_rp->channels, main_rp->name, main_rp->view, main_rp->chan_id, false); } } } @@ -512,7 +523,8 @@ void RE_create_render_pass(RenderResult *rr, int channels, const char *chan_id, const char *layername, - const char *viewname) + const char *viewname, + const bool allocate) { RenderLayer *rl; RenderPass *rp; @@ -542,7 +554,7 @@ void RE_create_render_pass(RenderResult *rr, } if (!rp) { - render_layer_add_pass(rr, rl, channels, name, view, chan_id); + render_layer_add_pass(rr, rl, channels, name, view, chan_id, allocate); } } } @@ -1083,7 +1095,7 @@ int render_result_exr_file_read_path(RenderResult *rr, void *exrhandle = IMB_exr_get_handle(); int rectx, recty; - if (IMB_exr_begin_read(exrhandle, filepath, &rectx, &recty) == 0) { + if (!IMB_exr_begin_read(exrhandle, filepath, &rectx, &recty, false)) { printf("failed being read %s\n", filepath); IMB_exr_close(exrhandle); return 0; @@ -1175,20 +1187,32 @@ void render_result_exr_file_cache_write(Render *re) /* For cache, makes exact copy of render result */ bool render_result_exr_file_cache_read(Render *re) { - char str[FILE_MAXFILE + MAX_ID_NAME + MAX_ID_NAME + 100] = ""; + /* File path to cache. */ + char filepath[FILE_MAXFILE + MAX_ID_NAME + MAX_ID_NAME + 100] = ""; char *root = U.render_cachedir; + render_result_exr_file_cache_path(re->scene, root, filepath); - RE_FreeRenderResult(re->result); - re->result = render_result_new(re, &re->disprect, RR_ALL_LAYERS, RR_ALL_VIEWS); + printf("read exr cache file: %s\n", filepath); - /* First try cache. */ - render_result_exr_file_cache_path(re->scene, root, str); + /* Try opening the file. */ + void *exrhandle = IMB_exr_get_handle(); + int rectx, recty; - printf("read exr cache file: %s\n", str); - if (!render_result_exr_file_read_path(re->result, NULL, str)) { - printf("cannot read: %s\n", str); + if (!IMB_exr_begin_read(exrhandle, filepath, &rectx, &recty, true)) { + printf("cannot read: %s\n", filepath); + IMB_exr_close(exrhandle); return false; } + + /* Read file contents into render result. */ + const char *colorspace = IMB_colormanagement_role_colorspace_name_get(COLOR_ROLE_SCENE_LINEAR); + RE_FreeRenderResult(re->result); + + IMB_exr_read_channels(exrhandle); + re->result = render_result_new_from_exr(exrhandle, colorspace, false, rectx, recty); + + IMB_exr_close(exrhandle); + return true; } diff --git a/source/blender/render/intern/render_result.h b/source/blender/render/intern/render_result.h index 4145bb3b8ab..34b8143869c 100644 --- a/source/blender/render/intern/render_result.h +++ b/source/blender/render/intern/render_result.h @@ -83,7 +83,8 @@ struct RenderPass *render_layer_add_pass(struct RenderResult *rr, int channels, const char *name, const char *viewname, - const char *chan_id); + const char *chan_id, + const bool allocate); int render_result_exr_file_read_path(struct RenderResult *rr, struct RenderLayer *rl_single, diff --git a/source/blender/render/intern/texture_image.c b/source/blender/render/intern/texture_image.c index 62aee564626..edfa284242c 100644 --- a/source/blender/render/intern/texture_image.c +++ b/source/blender/render/intern/texture_image.c @@ -1958,13 +1958,11 @@ int imagewraposa(Tex *tex, } if (texres->nor && (tex->imaflag & TEX_NORMALMAP)) { - /* qdn: normal from color - * The invert of the red channel is to make - * the normal map compliant with the outside world. - * It needs to be done because in Blender - * the normal used in the renderer points inward. It is generated - * this way in calc_vertexnormals(). Should this ever change - * this negate must be removed. */ + /* Normal from color: + * The invert of the red channel is to make the normal map compliant with the outside world. + * It needs to be done because in Blender the normal used in the renderer points inward. + * It is generated this way in #calc_vertexnormals(). + * Should this ever change this negate must be removed. */ texres->nor[0] = -2.0f * (texres->tr - 0.5f); texres->nor[1] = 2.0f * (texres->tg - 0.5f); texres->nor[2] = 2.0f * (texres->tb - 0.5f); diff --git a/source/blender/sequencer/SEQ_sequencer.h b/source/blender/sequencer/SEQ_sequencer.h index 7e733817630..1b8982da0d2 100644 --- a/source/blender/sequencer/SEQ_sequencer.h +++ b/source/blender/sequencer/SEQ_sequencer.h @@ -89,6 +89,7 @@ void SEQ_sequence_base_dupli_recursive(const struct Scene *scene_src, const struct ListBase *seqbase, int dupe_flag, const int flag); +bool SEQ_valid_strip_channel(struct Sequence *seq); /* Read and Write functions for .blend file data */ void SEQ_blend_write(struct BlendWriter *writer, struct ListBase *seqbase); diff --git a/source/blender/sequencer/intern/effects.c b/source/blender/sequencer/intern/effects.c index 4448db013fe..427a8835879 100644 --- a/source/blender/sequencer/intern/effects.c +++ b/source/blender/sequencer/intern/effects.c @@ -3154,6 +3154,7 @@ void seq_effect_speed_rebuild_map(Scene *scene, Sequence *seq) float target_frame = 0; for (int frame_index = 1; frame_index < effect_strip_length; frame_index++) { target_frame += evaluate_fcurve(fcu, seq->startdisp + frame_index); + CLAMP(target_frame, 0, seq->seq1->len); v->frameMap[frame_index] = target_frame; } } diff --git a/source/blender/sequencer/intern/modifier.c b/source/blender/sequencer/intern/modifier.c index 07d09f4ae17..1a63f4c4655 100644 --- a/source/blender/sequencer/intern/modifier.c +++ b/source/blender/sequencer/intern/modifier.c @@ -216,7 +216,7 @@ static void modifier_apply_threaded(ImBuf *ibuf, /** \name Color Balance Modifier * \{ */ -static StripColorBalance calc_cb(StripColorBalance *cb_) +static StripColorBalance calc_cb_lgg(StripColorBalance *cb_) { StripColorBalance cb = *cb_; int c; @@ -262,8 +262,52 @@ static StripColorBalance calc_cb(StripColorBalance *cb_) return cb; } +static StripColorBalance calc_cb_sop(StripColorBalance *cb_) +{ + StripColorBalance cb = *cb_; + int c; + + for (c = 0; c < 3; c++) { + if (cb.flag & SEQ_COLOR_BALANCE_INVERSE_SLOPE) { + if (cb.slope[c] != 0.0f) { + cb.slope[c] = 1.0f / cb.slope[c]; + } + else { + cb.slope[c] = 1000000; + } + } + + if (cb.flag & SEQ_COLOR_BALANCE_INVERSE_OFFSET) { + cb.offset[c] = -1.0f * (cb.offset[c] - 1.0f); + } + else { + cb.offset[c] = cb.offset[c] - 1.0f; + } + + if (!(cb.flag & SEQ_COLOR_BALANCE_INVERSE_POWER)) { + if (cb.power[c] != 0.0f) { + cb.power[c] = 1.0f / cb.power[c]; + } + else { + cb.power[c] = 1000000; + } + } + } + + return cb; +} + +static StripColorBalance calc_cb(StripColorBalance *cb_) +{ + if (cb_->method == SEQ_COLOR_BALANCE_METHOD_LIFTGAMMAGAIN) { + return calc_cb_lgg(cb_); + } + /* `cb_->method == SEQ_COLOR_BALANCE_METHOD_SLOPEOFFSETPOWER`. */ + return calc_cb_sop(cb_); +} + /* NOTE: lift is actually 2-lift. */ -MINLINE float color_balance_fl( +MINLINE float color_balance_fl_lgg( float in, const float lift, const float gain, const float gamma, const float mul) { float x = (((in - 1.0f) * lift) + 1.0f) * gain; @@ -278,12 +322,40 @@ MINLINE float color_balance_fl( return x; } -static void make_cb_table_float(float lift, float gain, float gamma, float *table, float mul) +MINLINE float color_balance_fl_sop(float in, + const float slope, + const float offset, + const float power, + const float pivot, + float mul) { - int y; + float x = in * slope + offset; + + /* prevent NaN */ + if (x < 0.0f) { + x = 0.0f; + } - for (y = 0; y < 256; y++) { - float v = color_balance_fl((float)y * (1.0f / 255.0f), lift, gain, gamma, mul); + x = powf(x / pivot, power) * pivot; + x *= mul; + CLAMP(x, FLT_MIN, FLT_MAX); + return x; +} + +static void make_cb_table_float_lgg(float lift, float gain, float gamma, float *table, float mul) +{ + for (int y = 0; y < 256; y++) { + float v = color_balance_fl_lgg((float)y * (1.0f / 255.0f), lift, gain, gamma, mul); + + table[y] = v; + } +} + +static void make_cb_table_float_sop( + float slope, float offset, float power, float pivot, float *table, float mul) +{ + for (int y = 0; y < 256; y++) { + float v = color_balance_fl_sop((float)y * (1.0f / 255.0f), slope, offset, power, pivot, mul); table[y] = v; } @@ -310,7 +382,13 @@ static void color_balance_byte_byte(StripColorBalance *cb_, straight_uchar_to_premul_float(p, cp); for (c = 0; c < 3; c++) { - float t = color_balance_fl(p[c], cb.lift[c], cb.gain[c], cb.gamma[c], mul); + float t; + if (cb.method == SEQ_COLOR_BALANCE_METHOD_LIFTGAMMAGAIN) { + t = color_balance_fl_lgg(p[c], cb.lift[c], cb.gain[c], cb.gamma[c], mul); + } + else { + t = color_balance_fl_sop(p[c], cb.slope[c], cb.offset[c], cb.power[c], 1.0, mul); + } if (m) { float m_normal = (float)m[c] / 255.0f; @@ -352,7 +430,12 @@ static void color_balance_byte_float(StripColorBalance *cb_, cb = calc_cb(cb_); for (c = 0; c < 3; c++) { - make_cb_table_float(cb.lift[c], cb.gain[c], cb.gamma[c], cb_tab[c], mul); + if (cb.method == SEQ_COLOR_BALANCE_METHOD_LIFTGAMMAGAIN) { + make_cb_table_float_lgg(cb.lift[c], cb.gain[c], cb.gamma[c], cb_tab[c], mul); + } + else { + make_cb_table_float_sop(cb.slope[c], cb.offset[c], cb.power[c], 1.0, cb_tab[c], mul); + } } for (i = 0; i < 256; i++) { @@ -397,7 +480,13 @@ static void color_balance_float_float(StripColorBalance *cb_, while (p < e) { int c; for (c = 0; c < 3; c++) { - float t = color_balance_fl(p[c], cb.lift[c], cb.gain[c], cb.gamma[c], mul); + float t; + if (cb_->method == SEQ_COLOR_BALANCE_METHOD_LIFTGAMMAGAIN) { + t = color_balance_fl_lgg(p[c], cb.lift[c], cb.gain[c], cb.gamma[c], mul); + } + else { + t = color_balance_fl_sop(p[c], cb.slope[c], cb.offset[c], cb.power[c], 1.0, mul); + } if (m) { p[c] = p[c] * (1.0f - m[c]) + t * m[c]; @@ -507,11 +596,15 @@ static void colorBalance_init_data(SequenceModifierData *smd) int c; cbmd->color_multiply = 1.0f; + cbmd->color_balance.method = 0; for (c = 0; c < 3; c++) { cbmd->color_balance.lift[c] = 1.0f; cbmd->color_balance.gamma[c] = 1.0f; cbmd->color_balance.gain[c] = 1.0f; + cbmd->color_balance.slope[c] = 1.0f; + cbmd->color_balance.offset[c] = 1.0f; + cbmd->color_balance.power[c] = 1.0f; } } diff --git a/source/blender/sequencer/intern/sequencer.c b/source/blender/sequencer/intern/sequencer.c index 382bd51aae1..3478c2d4f97 100644 --- a/source/blender/sequencer/intern/sequencer.c +++ b/source/blender/sequencer/intern/sequencer.c @@ -144,6 +144,8 @@ Sequence *SEQ_sequence_alloc(ListBase *lb, int timeline_frame, int machine, int seq->strip = seq_strip_alloc(type); seq->stereo3d_format = MEM_callocN(sizeof(Stereo3dFormat), "Sequence Stereo Format"); + seq->color_tag = SEQUENCE_COLOR_NONE; + SEQ_relations_session_uuid_generate(seq); return seq; @@ -636,6 +638,18 @@ void SEQ_sequence_base_dupli_recursive(const Scene *scene_src, seq_new_fix_links_recursive(seq); } } + +bool SEQ_valid_strip_channel(Sequence *seq) +{ + if (seq->machine < 1) { + return false; + } + if (seq->machine > MAXSEQ) { + return false; + } + return true; +} + /* r_prefix + [" + escaped_name + "] + \0 */ #define SEQ_RNAPATH_MAXSTR ((30 + 2 + (SEQ_NAME_MAXSTR * 2) + 2) + 1) diff --git a/source/blender/sequencer/intern/strip_relations.c b/source/blender/sequencer/intern/strip_relations.c index 46fdd2c3d14..9822bfe38f9 100644 --- a/source/blender/sequencer/intern/strip_relations.c +++ b/source/blender/sequencer/intern/strip_relations.c @@ -526,4 +526,4 @@ struct Sequence *SEQ_find_metastrip_by_sequence(ListBase *seqbase, Sequence *met } return NULL; -}
\ No newline at end of file +} diff --git a/source/blender/sequencer/intern/strip_transform.c b/source/blender/sequencer/intern/strip_transform.c index d5ff455c694..54ca4ef487f 100644 --- a/source/blender/sequencer/intern/strip_transform.c +++ b/source/blender/sequencer/intern/strip_transform.c @@ -278,7 +278,7 @@ bool SEQ_transform_seqbase_shuffle_ex(ListBase *seqbasep, SEQ_time_update_sequence(evil_scene, test); } - if ((test->machine < 1) || (test->machine > MAXSEQ)) { + if (!SEQ_valid_strip_channel(test)) { /* Blender 2.4x would remove the strip. * nicer to move it to the end */ diff --git a/source/blender/windowmanager/WM_api.h b/source/blender/windowmanager/WM_api.h index 987fef570cb..cf90179f129 100644 --- a/source/blender/windowmanager/WM_api.h +++ b/source/blender/windowmanager/WM_api.h @@ -208,14 +208,16 @@ struct ID *WM_file_link_datablock(struct Main *bmain, struct View3D *v3d, const char *filepath, const short id_code, - const char *id_name); + const char *id_name, + int flag); struct ID *WM_file_append_datablock(struct Main *bmain, struct Scene *scene, struct ViewLayer *view_layer, struct View3D *v3d, const char *filepath, const short id_code, - const char *id_name); + const char *id_name, + int flag); void WM_lib_reload(struct Library *lib, struct bContext *C, struct ReportList *reports); /* mouse cursors */ @@ -906,6 +908,7 @@ int WM_event_modifier_flag(const struct wmEvent *event); bool WM_event_is_modal_tweak_exit(const struct wmEvent *event, int tweak_event); bool WM_event_is_last_mousemove(const struct wmEvent *event); bool WM_event_is_mouse_drag(const struct wmEvent *event); +bool WM_event_is_mouse_drag_or_press(const wmEvent *event); int WM_event_drag_threshold(const struct wmEvent *event); bool WM_event_drag_test(const struct wmEvent *event, const int prev_xy[2]); diff --git a/source/blender/windowmanager/WM_types.h b/source/blender/windowmanager/WM_types.h index 1b6e9bd2c1d..2f98834703c 100644 --- a/source/blender/windowmanager/WM_types.h +++ b/source/blender/windowmanager/WM_types.h @@ -998,6 +998,13 @@ typedef struct wmDragAsset { const char *path; int id_type; int import_type; /* eFileAssetImportType */ + + /* FIXME: This is temporary evil solution to get scene/view-layer/etc in the copy callback of the + * #wmDropBox. + * TODO: Handle link/append in operator called at the end of the drop process, and NOT in its + * copy callback. + * */ + struct bContext *evil_C; } wmDragAsset; typedef char *(*WMDropboxTooltipFunc)(struct bContext *, diff --git a/source/blender/windowmanager/intern/wm_dragdrop.c b/source/blender/windowmanager/intern/wm_dragdrop.c index 6585349c83c..c5a89e3ad9f 100644 --- a/source/blender/windowmanager/intern/wm_dragdrop.c +++ b/source/blender/windowmanager/intern/wm_dragdrop.c @@ -42,6 +42,9 @@ #include "BKE_global.h" #include "BKE_idtype.h" #include "BKE_lib_id.h" +#include "BKE_main.h" + +#include "BLO_readfile.h" #include "GPU_shader.h" #include "GPU_state.h" @@ -390,11 +393,43 @@ static ID *wm_drag_asset_id_import(wmDragAsset *asset_drag) const char *name = asset_drag->name; ID_Type idtype = asset_drag->id_type; + /* FIXME: Link/Append should happens in the operator called at the end of drop process, not from + * here. */ + + Main *bmain = CTX_data_main(asset_drag->evil_C); + Scene *scene = CTX_data_scene(asset_drag->evil_C); + ViewLayer *view_layer = CTX_data_view_layer(asset_drag->evil_C); + View3D *view3d = CTX_wm_view3d(asset_drag->evil_C); + switch ((eFileAssetImportType)asset_drag->import_type) { case FILE_ASSET_IMPORT_LINK: - return WM_file_link_datablock(G_MAIN, NULL, NULL, NULL, asset_drag->path, idtype, name); + return WM_file_link_datablock(bmain, + scene, + view_layer, + view3d, + asset_drag->path, + idtype, + name, + FILE_ACTIVE_COLLECTION); case FILE_ASSET_IMPORT_APPEND: - return WM_file_append_datablock(G_MAIN, NULL, NULL, NULL, asset_drag->path, idtype, name); + return WM_file_append_datablock(bmain, + scene, + view_layer, + view3d, + asset_drag->path, + idtype, + name, + BLO_LIBLINK_APPEND_RECURSIVE | FILE_ACTIVE_COLLECTION); + case FILE_ASSET_IMPORT_APPEND_REUSE: + return WM_file_append_datablock(G_MAIN, + scene, + view_layer, + view3d, + asset_drag->path, + idtype, + name, + BLO_LIBLINK_APPEND_RECURSIVE | FILE_ACTIVE_COLLECTION | + BLO_LIBLINK_APPEND_LOCAL_ID_REUSE); } BLI_assert_unreachable(); diff --git a/source/blender/windowmanager/intern/wm_event_query.c b/source/blender/windowmanager/intern/wm_event_query.c index 562a1f83473..364aab9482a 100644 --- a/source/blender/windowmanager/intern/wm_event_query.c +++ b/source/blender/windowmanager/intern/wm_event_query.c @@ -270,6 +270,12 @@ bool WM_event_is_mouse_drag(const wmEvent *event) return ISTWEAK(event->type) || (ISMOUSE_BUTTON(event->type) && (event->val == KM_CLICK_DRAG)); } +bool WM_event_is_mouse_drag_or_press(const wmEvent *event) +{ + return WM_event_is_mouse_drag(event) || + (ISMOUSE_BUTTON(event->type) && (event->val == KM_PRESS)); +} + /** \} */ /* -------------------------------------------------------------------- */ diff --git a/source/blender/windowmanager/intern/wm_event_system.c b/source/blender/windowmanager/intern/wm_event_system.c index 3b92d858c71..f025fd43b49 100644 --- a/source/blender/windowmanager/intern/wm_event_system.c +++ b/source/blender/windowmanager/intern/wm_event_system.c @@ -3046,7 +3046,7 @@ static int wm_handlers_do_intern(bContext *C, wmEvent *event, ListBase *handlers /* Other drop custom types allowed. */ if (event->custom == EVT_DATA_DRAGDROP) { ListBase *lb = (ListBase *)event->customdata; - LISTBASE_FOREACH (wmDrag *, drag, lb) { + LISTBASE_FOREACH_MUTABLE (wmDrag *, drag, lb) { if (drop->poll(C, drag, event)) { /* Optionally copy drag information to operator properties. Don't call it if the * operator fails anyway, it might do more than just set properties (e.g. @@ -3057,7 +3057,8 @@ static int wm_handlers_do_intern(bContext *C, wmEvent *event, ListBase *handlers /* Pass single matched wmDrag onto the operator. */ BLI_remlink(lb, drag); - ListBase single_lb = {drag, drag}; + ListBase single_lb = {0}; + BLI_addtail(&single_lb, drag); event->customdata = &single_lb; int op_retval = wm_operator_call_internal( diff --git a/source/blender/windowmanager/intern/wm_files.c b/source/blender/windowmanager/intern/wm_files.c index 126f8abc69a..24a3b58fc26 100644 --- a/source/blender/windowmanager/intern/wm_files.c +++ b/source/blender/windowmanager/intern/wm_files.c @@ -869,6 +869,28 @@ static void file_read_reports_finalize(BlendFileReadReport *bf_reports) duration_lib_override_recursive_resync_seconds); } + if (bf_reports->count.linked_proxies != 0 || + bf_reports->count.proxies_to_lib_overrides_success != 0 || + bf_reports->count.proxies_to_lib_overrides_failures != 0) { + BKE_reportf(bf_reports->reports, + RPT_WARNING, + "Proxies are deprecated (%d proxies were automatically converted to library " + "overrides, %d proxies could not be converted and %d linked proxies were kept " + "untouched). If you need to keep proxies for the time being, please disable the " + "`Proxy to Override Auto Conversion` in Experimental user preferences", + bf_reports->count.proxies_to_lib_overrides_success, + bf_reports->count.proxies_to_lib_overrides_failures, + bf_reports->count.linked_proxies); + } + + if (bf_reports->count.vse_strips_skipped != 0) { + BKE_reportf(bf_reports->reports, + RPT_ERROR, + "%d sequence strips were not read because they were in a channel larger than %d", + bf_reports->count.vse_strips_skipped, + MAXSEQ); + } + BLI_linklist_free(bf_reports->resynced_lib_overrides_libraries, NULL); bf_reports->resynced_lib_overrides_libraries = NULL; } diff --git a/source/blender/windowmanager/intern/wm_files_link.c b/source/blender/windowmanager/intern/wm_files_link.c index 09567eca17f..a73bea31669 100644 --- a/source/blender/windowmanager/intern/wm_files_link.c +++ b/source/blender/windowmanager/intern/wm_files_link.c @@ -127,10 +127,10 @@ static int wm_link_append_invoke(bContext *C, wmOperator *op, const wmEvent *UNU return OPERATOR_RUNNING_MODAL; } -static short wm_link_append_flag(wmOperator *op) +static int wm_link_append_flag(wmOperator *op) { PropertyRNA *prop; - short flag = 0; + int flag = 0; if (RNA_boolean_get(op->ptr, "autoselect")) { flag |= FILE_AUTOSELECT; @@ -147,17 +147,20 @@ static short wm_link_append_flag(wmOperator *op) } else { if (RNA_boolean_get(op->ptr, "use_recursive")) { - flag |= FILE_APPEND_RECURSIVE; + flag |= BLO_LIBLINK_APPEND_RECURSIVE; } if (RNA_boolean_get(op->ptr, "set_fake")) { - flag |= FILE_APPEND_SET_FAKEUSER; + flag |= BLO_LIBLINK_APPEND_SET_FAKEUSER; + } + if (RNA_boolean_get(op->ptr, "do_reuse_local_id")) { + flag |= BLO_LIBLINK_APPEND_LOCAL_ID_REUSE; } } if (RNA_boolean_get(op->ptr, "instance_collections")) { - flag |= FILE_COLLECTION_INSTANCE; + flag |= BLO_LIBLINK_COLLECTION_INSTANCE; } if (RNA_boolean_get(op->ptr, "instance_object_data")) { - flag |= FILE_OBDATA_INSTANCE; + flag |= BLO_LIBLINK_OBDATA_INSTANCE; } return flag; @@ -396,7 +399,7 @@ static void wm_append_loose_data_instantiate(WMLinkAppendData *lapp_data, LinkNode *itemlink; Collection *active_collection = NULL; - const bool do_obdata = (lapp_data->flag & FILE_OBDATA_INSTANCE) != 0; + const bool do_obdata = (lapp_data->flag & BLO_LIBLINK_OBDATA_INSTANCE) != 0; const bool object_set_selected = (lapp_data->flag & FILE_AUTOSELECT) != 0; /* Do NOT make base active here! screws up GUI stuff, @@ -472,7 +475,7 @@ static void wm_append_loose_data_instantiate(WMLinkAppendData *lapp_data, /* In case user requested instantiation of collections as empties, we do so for the one they * explicitly selected (originally directly linked IDs). */ - if ((lapp_data->flag & FILE_COLLECTION_INSTANCE) != 0 && + if ((lapp_data->flag & BLO_LIBLINK_COLLECTION_INSTANCE) != 0 && (item->append_tag & WM_APPEND_TAG_INDIRECT) == 0) { /* BKE_object_add(...) messes with the selection. */ Object *ob = BKE_object_add_only_object(bmain, OB_EMPTY, collection->id.name + 2); @@ -573,6 +576,8 @@ static void wm_append_loose_data_instantiate(WMLinkAppendData *lapp_data, ob, object_set_selected, object_set_active, view_layer, v3d); copy_v3_v3(ob->loc, scene->cursor.location); + + id->tag &= ~LIB_TAG_DOIT; } } @@ -592,6 +597,11 @@ static int foreach_libblock_append_callback(LibraryIDLinkCallbackData *cb_data) } if (!BKE_idtype_idcode_is_linkable(GS(id->name))) { + /* While we do not want to add non-linkable ID (shape keys...) to the list of linked items, + * unfortunately they can use fully linkable valid IDs too, like actions. Those need to be + * processed, so we need to recursively deal with them here. */ + BKE_library_foreach_ID_link( + cb_data->bmain, id, foreach_libblock_append_callback, data, IDWALK_NOP); return IDWALK_RET_NOP; } @@ -626,8 +636,9 @@ static void wm_append_do(WMLinkAppendData *lapp_data, { BLI_assert((lapp_data->flag & FILE_LINK) == 0); - const bool do_recursive = (lapp_data->flag & FILE_APPEND_RECURSIVE) != 0; - const bool set_fakeuser = (lapp_data->flag & FILE_APPEND_SET_FAKEUSER) != 0; + const bool do_recursive = (lapp_data->flag & BLO_LIBLINK_APPEND_RECURSIVE) != 0; + const bool set_fakeuser = (lapp_data->flag & BLO_LIBLINK_APPEND_SET_FAKEUSER) != 0; + const bool do_reuse_local_id = (lapp_data->flag & BLO_LIBLINK_APPEND_LOCAL_ID_REUSE) != 0; LinkNode *itemlink; @@ -642,7 +653,6 @@ static void wm_append_do(WMLinkAppendData *lapp_data, BLI_ghash_insert(lapp_data->new_id_to_item, id, item); } - const bool do_reuse_existing_id = false; lapp_data->library_weak_reference_mapping = BKE_main_library_weak_reference_create(bmain); /* NOTE: Since we append items for IDs not already listed (i.e. implicitly linked indirect @@ -656,8 +666,8 @@ static void wm_append_do(WMLinkAppendData *lapp_data, } BLI_assert(item->customdata == NULL); - /* Clear tag previously used to mark IDs needing post-processing (instantiation of loose - * objects etc.). */ + /* In Append case linked IDs should never be marked as needing post-processing (instantiation + * of loose objects etc.). */ BLI_assert((id->tag & LIB_TAG_DOIT) == 0); ID *existing_local_id = BKE_idtype_idcode_append_is_reusable(GS(id->name)) ? @@ -674,10 +684,7 @@ static void wm_append_do(WMLinkAppendData *lapp_data, CLOG_INFO(&LOG, 3, "Appended ID '%s' is proxified, keeping it linked...", id->name); item->append_action = WM_APPEND_ACT_KEEP_LINKED; } - /* Only re-use existing local ID for indirectly linked data, the ID explicitely selected by the - * user we always fully append. */ - else if (do_reuse_existing_id && existing_local_id != NULL && - (item->append_tag & WM_APPEND_TAG_INDIRECT) != 0) { + else if (do_reuse_local_id && existing_local_id != NULL) { CLOG_INFO(&LOG, 3, "Appended ID '%s' as a matching local one, re-using it...", id->name); item->append_action = WM_APPEND_ACT_REUSE_LOCAL; item->customdata = existing_local_id; @@ -806,6 +813,27 @@ static void wm_append_do(WMLinkAppendData *lapp_data, BKE_libblock_relink_to_newid_new(bmain, id); } + /* Remove linked IDs when a local existing data has been reused instead. */ + BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false); + for (itemlink = lapp_data->items.list; itemlink; itemlink = itemlink->next) { + WMLinkAppendDataItem *item = itemlink->link; + + if (item->append_action != WM_APPEND_ACT_REUSE_LOCAL) { + continue; + } + + ID *id = item->new_id; + if (id == NULL) { + continue; + } + BLI_assert(ID_IS_LINKED(id)); + BLI_assert(id->newid != NULL); + + id->tag |= LIB_TAG_DOIT; + item->new_id = id->newid; + } + BKE_id_multi_tagged_delete(bmain); + /* Instantiate newly created (duplicated) IDs as needed. */ wm_append_loose_data_instantiate(lapp_data, bmain, scene, view_layer, v3d); @@ -1057,7 +1085,7 @@ static int wm_link_append_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } - short flag = wm_link_append_flag(op); + int flag = wm_link_append_flag(op); const bool do_append = (flag & FILE_LINK) == 0; /* sanity checks for flag */ @@ -1066,12 +1094,10 @@ static int wm_link_append_exec(bContext *C, wmOperator *op) RPT_WARNING, "Scene '%s' is linked, instantiation of objects is disabled", scene->id.name + 2); - flag &= ~(FILE_COLLECTION_INSTANCE | FILE_OBDATA_INSTANCE); + flag &= ~(BLO_LIBLINK_COLLECTION_INSTANCE | BLO_LIBLINK_OBDATA_INSTANCE); scene = NULL; } - /* We need to add nothing from #eBLOLibLinkFlags to flag here. */ - /* from here down, no error returns */ if (view_layer && RNA_boolean_get(op->ptr, "autoselect")) { @@ -1201,14 +1227,25 @@ static void wm_link_append_properties_common(wmOperatorType *ot, bool is_link) prop = RNA_def_boolean( ot->srna, "link", is_link, "Link", "Link the objects or data-blocks rather than appending"); RNA_def_property_flag(prop, PROP_SKIP_SAVE | PROP_HIDDEN); + + prop = RNA_def_boolean( + ot->srna, + "do_reuse_local_id", + false, + "Re-Use Local Data", + "Try to re-use previously matching appended data-blocks instead of appending a new copy"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE | PROP_HIDDEN); + prop = RNA_def_boolean(ot->srna, "autoselect", true, "Select", "Select new objects"); RNA_def_property_flag(prop, PROP_SKIP_SAVE); + prop = RNA_def_boolean(ot->srna, "active_collection", true, "Active Collection", "Put new objects on the active collection"); RNA_def_property_flag(prop, PROP_SKIP_SAVE); + prop = RNA_def_boolean( ot->srna, "instance_collections", @@ -1299,13 +1336,14 @@ static ID *wm_file_link_append_datablock_ex(Main *bmain, const char *filepath, const short id_code, const char *id_name, - const bool do_append) + const int flag) { + const bool do_append = (flag & FILE_LINK) == 0; /* Tag everything so we can make local only the new datablock. */ BKE_main_id_tag_all(bmain, LIB_TAG_PRE_EXISTING, true); /* Define working data, with just the one item we want to link. */ - WMLinkAppendData *lapp_data = wm_link_append_data_new(do_append ? FILE_APPEND_RECURSIVE : 0); + WMLinkAppendData *lapp_data = wm_link_append_data_new(flag); wm_link_append_data_library_add(lapp_data, filepath); WMLinkAppendDataItem *item = wm_link_append_data_item_add(lapp_data, id_name, id_code, NULL); @@ -1314,13 +1352,13 @@ static ID *wm_file_link_append_datablock_ex(Main *bmain, /* Link datablock. */ wm_link_do(lapp_data, NULL, bmain, scene, view_layer, v3d); - /* Get linked datablock and free working data. */ - ID *id = item->new_id; - if (do_append) { wm_append_do(lapp_data, NULL, bmain, scene, view_layer, v3d); } + /* Get linked datablock and free working data. */ + ID *id = item->new_id; + wm_link_append_data_free(lapp_data); BKE_main_id_tag_all(bmain, LIB_TAG_PRE_EXISTING, false); @@ -1338,10 +1376,12 @@ ID *WM_file_link_datablock(Main *bmain, View3D *v3d, const char *filepath, const short id_code, - const char *id_name) + const char *id_name, + int flag) { + flag |= FILE_LINK; return wm_file_link_append_datablock_ex( - bmain, scene, view_layer, v3d, filepath, id_code, id_name, false); + bmain, scene, view_layer, v3d, filepath, id_code, id_name, flag); } /* @@ -1354,10 +1394,12 @@ ID *WM_file_append_datablock(Main *bmain, View3D *v3d, const char *filepath, const short id_code, - const char *id_name) + const char *id_name, + int flag) { + BLI_assert((flag & FILE_LINK) == 0); ID *id = wm_file_link_append_datablock_ex( - bmain, scene, view_layer, v3d, filepath, id_code, id_name, true); + bmain, scene, view_layer, v3d, filepath, id_code, id_name, flag); return id; } diff --git a/source/blender/windowmanager/intern/wm_gesture_ops.c b/source/blender/windowmanager/intern/wm_gesture_ops.c index 06d5c16108f..b087bd9d668 100644 --- a/source/blender/windowmanager/intern/wm_gesture_ops.c +++ b/source/blender/windowmanager/intern/wm_gesture_ops.c @@ -184,7 +184,8 @@ int WM_gesture_box_invoke(bContext *C, wmOperator *op, const wmEvent *event) { wmWindow *win = CTX_wm_window(C); const ARegion *region = CTX_wm_region(C); - const bool wait_for_input = !ISTWEAK(event->type) && RNA_boolean_get(op->ptr, "wait_for_input"); + const bool wait_for_input = !WM_event_is_mouse_drag_or_press(event) && + RNA_boolean_get(op->ptr, "wait_for_input"); if (wait_for_input) { op->customdata = WM_gesture_new(win, region, event, WM_GESTURE_CROSS_RECT); @@ -438,7 +439,8 @@ static void gesture_circle_apply(bContext *C, wmOperator *op); int WM_gesture_circle_invoke(bContext *C, wmOperator *op, const wmEvent *event) { wmWindow *win = CTX_wm_window(C); - const bool wait_for_input = !ISTWEAK(event->type) && RNA_boolean_get(op->ptr, "wait_for_input"); + const bool wait_for_input = !WM_event_is_mouse_drag_or_press(event) && + RNA_boolean_get(op->ptr, "wait_for_input"); op->customdata = WM_gesture_new(win, CTX_wm_region(C), event, WM_GESTURE_CIRCLE); wmGesture *gesture = op->customdata; @@ -1009,7 +1011,7 @@ int WM_gesture_straightline_invoke(bContext *C, wmOperator *op, const wmEvent *e op->customdata = WM_gesture_new(win, CTX_wm_region(C), event, WM_GESTURE_STRAIGHTLINE); - if (ISTWEAK(event->type)) { + if (WM_event_is_mouse_drag_or_press(event)) { wmGesture *gesture = op->customdata; gesture->is_active = true; } diff --git a/source/blender/windowmanager/intern/wm_init_exit.c b/source/blender/windowmanager/intern/wm_init_exit.c index a8d2e000108..ad04416613a 100644 --- a/source/blender/windowmanager/intern/wm_init_exit.c +++ b/source/blender/windowmanager/intern/wm_init_exit.c @@ -373,13 +373,6 @@ void WM_init(bContext *C, int argc, const char **argv) BLI_strncpy(G.lib, BKE_main_blendfile_path_from_global(), sizeof(G.lib)); -#ifdef WITH_COMPOSITOR - if (1) { - extern void *COM_linker_hack; - COM_linker_hack = COM_execute; - } -#endif - wm_homefile_read_post(C, params_file_read_post); } diff --git a/source/creator/creator_signals.c b/source/creator/creator_signals.c index 29e12a96fe1..5604fb4c58d 100644 --- a/source/creator/creator_signals.c +++ b/source/creator/creator_signals.c @@ -79,7 +79,7 @@ static void sig_handle_fpe(int UNUSED(sig)) } # endif -/* handling ctrl-c event in console */ +/* Handling `Ctrl-C` event in the console. */ # if !defined(WITH_HEADLESS) static void sig_handle_blender_esc(int sig) { diff --git a/source/tools b/source/tools -Subproject 723b24841df1ed8478949bca20c73878fab00dc +Subproject 01f51a0e551ab730f0934dc6488613690ac4bf8 diff --git a/tests/performance/api/device.py b/tests/performance/api/device.py index b61ae42be36..1e930a12352 100644 --- a/tests/performance/api/device.py +++ b/tests/performance/api/device.py @@ -11,7 +11,7 @@ def get_cpu_name() -> str: return platform.processor() elif platform.system() == "Darwin": cmd = ['/usr/sbin/sysctl', "-n", "machdep.cpu.brand_string"] - return subprocess.check_output(cmd).strip().decode('utf-8') + return subprocess.check_output(cmd).strip().decode('utf-8', 'ignore') else: with open('/proc/cpuinfo') as f: for line in f: diff --git a/tests/python/CMakeLists.txt b/tests/python/CMakeLists.txt index 75f00c3c5cc..2b31b6362e9 100644 --- a/tests/python/CMakeLists.txt +++ b/tests/python/CMakeLists.txt @@ -749,10 +749,26 @@ set(geo_node_tests points utilities vector - volume - ) +if(WITH_GMP) + list(APPEND geo_node_tests mesh/boolean) +else() + MESSAGE(STATUS "Disabling mesh/boolean tests because WITH_GMP is off.") +endif() + +if(WITH_OPENVDB) + list(APPEND geo_node_tests volume) +else() + MESSAGE(STATUS "Disabling volume tests because WITH_OPENVDB is off.") +endif() + +if(WITH_OPENSUBDIV) + list(APPEND geo_node_tests mesh/subdivision_tests) +else() + MESSAGE(STATUS "Disabling mesh/subdivision_tests because WITH_OPENSUBDIV is off.") +endif() + foreach(geo_node_test ${geo_node_tests}) if(EXISTS "${TEST_SRC_DIR}/modeling/geometry_nodes/${geo_node_test}/") file(GLOB files "${TEST_SRC_DIR}/modeling/geometry_nodes/${geo_node_test}/*.blend") diff --git a/tests/python/bl_blendfile_liblink.py b/tests/python/bl_blendfile_liblink.py index 992bf6b89d9..4545e0b846a 100644 --- a/tests/python/bl_blendfile_liblink.py +++ b/tests/python/bl_blendfile_liblink.py @@ -10,7 +10,7 @@ from bl_blendfile_utils import TestHelper class TestBlendLibLinkHelper(TestHelper): - + def __init__(self, args): self.args = args @@ -165,7 +165,7 @@ class TestBlendLibAppendBasic(TestBlendLibLinkHelper): link_dir = os.path.join(output_lib_path, "Mesh") bpy.ops.wm.append(directory=link_dir, filename="LibMesh", - instance_object_data=False, set_fake=False, use_recursive=False) + instance_object_data=False, set_fake=False, use_recursive=False, do_reuse_local_id=False) assert(len(bpy.data.meshes) == 1) assert(bpy.data.meshes[0].library is None) @@ -179,7 +179,7 @@ class TestBlendLibAppendBasic(TestBlendLibLinkHelper): link_dir = os.path.join(output_lib_path, "Mesh") bpy.ops.wm.append(directory=link_dir, filename="LibMesh", - instance_object_data=True, set_fake=False, use_recursive=False) + instance_object_data=True, set_fake=False, use_recursive=False, do_reuse_local_id=False) assert(len(bpy.data.meshes) == 1) assert(bpy.data.meshes[0].library is None) @@ -194,7 +194,7 @@ class TestBlendLibAppendBasic(TestBlendLibLinkHelper): link_dir = os.path.join(output_lib_path, "Mesh") bpy.ops.wm.append(directory=link_dir, filename="LibMesh", - instance_object_data=False, set_fake=True, use_recursive=False) + instance_object_data=False, set_fake=True, use_recursive=False, do_reuse_local_id=False) assert(len(bpy.data.meshes) == 1) assert(bpy.data.meshes[0].library is None) @@ -208,7 +208,7 @@ class TestBlendLibAppendBasic(TestBlendLibLinkHelper): link_dir = os.path.join(output_lib_path, "Object") bpy.ops.wm.append(directory=link_dir, filename="LibMesh", - instance_object_data=False, set_fake=False, use_recursive=False) + instance_object_data=False, set_fake=False, use_recursive=False, do_reuse_local_id=False) assert(len(bpy.data.meshes) == 1) # This one fails currently, for unclear reasons. @@ -224,7 +224,7 @@ class TestBlendLibAppendBasic(TestBlendLibLinkHelper): link_dir = os.path.join(output_lib_path, "Object") bpy.ops.wm.append(directory=link_dir, filename="LibMesh", - instance_object_data=False, set_fake=False, use_recursive=True) + instance_object_data=False, set_fake=False, use_recursive=True, do_reuse_local_id=False) assert(len(bpy.data.meshes) == 1) assert(bpy.data.meshes[0].library is None) @@ -239,7 +239,7 @@ class TestBlendLibAppendBasic(TestBlendLibLinkHelper): link_dir = os.path.join(output_lib_path, "Collection") bpy.ops.wm.append(directory=link_dir, filename="LibMesh", - instance_object_data=False, set_fake=False, use_recursive=True) + instance_object_data=False, set_fake=False, use_recursive=True, do_reuse_local_id=False) assert(bpy.data.meshes[0].library is None) assert(bpy.data.meshes[0].users == 1) @@ -251,9 +251,73 @@ class TestBlendLibAppendBasic(TestBlendLibLinkHelper): assert(bpy.data.collections[0].users == 1) +class TestBlendLibAppendReuseID(TestBlendLibLinkHelper): + + def __init__(self, args): + self.args = args + + def test_append(self): + output_dir = self.args.output_dir + output_lib_path = self.init_lib_data_basic() + + # Append of a single Object, and then append it again. + self.reset_blender() + + link_dir = os.path.join(output_lib_path, "Object") + bpy.ops.wm.append(directory=link_dir, filename="LibMesh", + instance_object_data=False, set_fake=False, use_recursive=True, do_reuse_local_id=False) + + assert(len(bpy.data.meshes) == 1) + assert(bpy.data.meshes[0].library is None) + assert(bpy.data.meshes[0].use_fake_user is False) + assert(bpy.data.meshes[0].users == 1) + assert(bpy.data.meshes[0].library_weak_reference is not None) + assert(bpy.data.meshes[0].library_weak_reference.filepath == output_lib_path) + assert(bpy.data.meshes[0].library_weak_reference.id_name == "MELibMesh") + assert(len(bpy.data.objects) == 1) + for ob in bpy.data.objects: + assert(ob.library is None) + assert(ob.library_weak_reference is None) + assert(len(bpy.data.collections) == 0) # Scene's master collection is not listed here + + bpy.ops.wm.append(directory=link_dir, filename="LibMesh", + instance_object_data=False, set_fake=False, use_recursive=True, do_reuse_local_id=True) + + assert(len(bpy.data.meshes) == 1) + assert(bpy.data.meshes[0].library is None) + assert(bpy.data.meshes[0].use_fake_user is False) + assert(bpy.data.meshes[0].users == 2) + assert(bpy.data.meshes[0].library_weak_reference is not None) + assert(bpy.data.meshes[0].library_weak_reference.filepath == output_lib_path) + assert(bpy.data.meshes[0].library_weak_reference.id_name == "MELibMesh") + assert(len(bpy.data.objects) == 2) + for ob in bpy.data.objects: + assert(ob.library is None) + assert(ob.library_weak_reference is None) + assert(len(bpy.data.collections) == 0) # Scene's master collection is not listed here + + bpy.ops.wm.append(directory=link_dir, filename="LibMesh", + instance_object_data=False, set_fake=False, use_recursive=True, do_reuse_local_id=False) + + assert(len(bpy.data.meshes) == 2) + assert(bpy.data.meshes[0].library_weak_reference is None) + assert(bpy.data.meshes[1].library is None) + assert(bpy.data.meshes[1].use_fake_user is False) + assert(bpy.data.meshes[1].users == 1) + assert(bpy.data.meshes[1].library_weak_reference is not None) + assert(bpy.data.meshes[1].library_weak_reference.filepath == output_lib_path) + assert(bpy.data.meshes[1].library_weak_reference.id_name == "MELibMesh") + assert(len(bpy.data.objects) == 3) + for ob in bpy.data.objects: + assert(ob.library is None) + assert(ob.library_weak_reference is None) + assert(len(bpy.data.collections) == 0) # Scene's master collection is not listed here + + TESTS = ( TestBlendLibLinkSaveLoadBasic, TestBlendLibAppendBasic, + TestBlendLibAppendReuseID, ) diff --git a/tests/python/modules/render_report.py b/tests/python/modules/render_report.py index 560f8e33585..90f16dc80fb 100755 --- a/tests/python/modules/render_report.py +++ b/tests/python/modules/render_report.py @@ -410,7 +410,7 @@ class Report: failed = False except subprocess.CalledProcessError as e: if self.verbose: - print_message(e.output.decode("utf-8")) + print_message(e.output.decode("utf-8", 'ignore')) failed = e.returncode != 1 else: if not self.update: @@ -437,7 +437,7 @@ class Report: subprocess.check_output(command) except subprocess.CalledProcessError as e: if self.verbose: - print_message(e.output.decode("utf-8")) + print_message(e.output.decode("utf-8", 'ignore')) return not failed @@ -488,7 +488,7 @@ class Report: if verbose: print(" ".join(command)) if (verbose or crash) and output: - print(output.decode("utf-8")) + print(output.decode("utf-8", 'ignore')) # Detect missing filepaths and consider those errors for filepath, output_filepath in zip(remaining_filepaths[:], output_filepaths): |