From 39107b3133afab0b850f1c7e4b9186f3b5b85df3 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Mon, 30 Oct 2023 21:38:10 +0100 Subject: [PATCH] Revert changes from main commits that were merged into blender-v4.0-release The last good commit was 8474716abb0db3b06838a57f7217bc945638d8df. After this commits from main were pushed to blender-v4.0-release. These are being reverted. Commits a4880576dc from to b26f176d1a that happend afterwards were meant for 4.0, and their contents is preserved. --- CMakeLists.txt | 116 +- .../build_environment/cmake/download.cmake | 2 +- build_files/cmake/Modules/FindWebP.cmake | 2 +- build_files/cmake/macros.cmake | 22 +- .../cmake/platform/platform_apple.cmake | 10 +- .../cmake/platform/platform_unix.cmake | 38 +- .../cmake/platform/platform_win32.cmake | 4 +- build_files/config/pipeline_config.yaml | 2 +- doc/doxygen/Doxyfile | 2 +- extern/CMakeLists.txt | 8 - extern/vulkan_memory_allocator/CMakeLists.txt | 10 +- extern/wcwidth/wcwidth.h | 8 - intern/cycles/blender/addon/properties.py | 7 +- intern/cycles/blender/addon/ui.py | 1 - intern/cycles/blender/mesh.cpp | 10 +- intern/cycles/blender/shader.cpp | 1 - intern/cycles/blender/util.h | 8 +- intern/cycles/cmake/macros.cmake | 2 +- intern/cycles/device/oneapi/device_impl.cpp | 6 +- intern/cycles/integrator/tile.cpp | 2 - intern/cycles/kernel/CMakeLists.txt | 29 +- intern/cycles/kernel/closure/bsdf.h | 4 +- intern/cycles/kernel/closure/bsdf_util.h | 3 - intern/cycles/kernel/device/oneapi/compat.h | 4 +- .../cycles/kernel/integrator/shade_surface.h | 2 +- intern/cycles/kernel/light/area.h | 6 +- intern/cycles/kernel/types.h | 2 - intern/cycles/scene/shader.cpp | 4 - intern/cycles/scene/shader.h | 1 - intern/ghost/CMakeLists.txt | 65 +- intern/ghost/GHOST_IEvent.hh | 8 +- intern/ghost/GHOST_IEventConsumer.hh | 2 +- intern/ghost/GHOST_Types.h | 19 +- .../intern/GHOST_CallbackEventConsumer.cc | 2 +- .../intern/GHOST_CallbackEventConsumer.hh | 2 +- intern/ghost/intern/GHOST_Context.cc | 6 +- intern/ghost/intern/GHOST_Context.hh | 2 - intern/ghost/intern/GHOST_ContextCGL.hh | 2 + intern/ghost/intern/GHOST_ContextCGL.mm | 3 + intern/ghost/intern/GHOST_ContextD3D.cc | 4 +- intern/ghost/intern/GHOST_ContextEGL.hh | 4 - intern/ghost/intern/GHOST_ContextVK.cc | 73 +- intern/ghost/intern/GHOST_ContextVK.hh | 11 - intern/ghost/intern/GHOST_DropTargetWin32.cc | 4 +- intern/ghost/intern/GHOST_Event.hh | 8 +- intern/ghost/intern/GHOST_EventDragnDrop.hh | 2 +- intern/ghost/intern/GHOST_EventManager.cc | 10 +- intern/ghost/intern/GHOST_EventManager.hh | 10 +- intern/ghost/intern/GHOST_EventPrinter.cc | 52 +- intern/ghost/intern/GHOST_EventPrinter.hh | 2 +- intern/ghost/intern/GHOST_EventString.hh | 2 +- intern/ghost/intern/GHOST_ImeWin32.cc | 2 +- intern/ghost/intern/GHOST_System.cc | 2 +- intern/ghost/intern/GHOST_System.hh | 2 +- intern/ghost/intern/GHOST_SystemCocoa.mm | 32 +- intern/ghost/intern/GHOST_SystemHeadless.hh | 6 +- intern/ghost/intern/GHOST_SystemPathsWin32.cc | 2 +- intern/ghost/intern/GHOST_SystemSDL.cc | 4 +- intern/ghost/intern/GHOST_SystemWayland.cc | 575 +----- intern/ghost/intern/GHOST_SystemWayland.hh | 4 - intern/ghost/intern/GHOST_SystemWin32.cc | 4 +- intern/ghost/intern/GHOST_SystemX11.cc | 16 +- .../intern/GHOST_WaylandCursorSettings.hh | 131 ++ intern/ghost/intern/GHOST_WindowWayland.cc | 131 +- intern/ghost/intern/GHOST_WindowWayland.hh | 9 +- intern/ghost/intern/GHOST_WindowWin32.cc | 4 +- intern/ghost/intern/GHOST_WindowX11.cc | 1 - intern/ghost/test/gears/GHOST_Test.cpp | 2 +- intern/utfconv/CMakeLists.txt | 8 +- .../utfconv/{utf_winfunc.cc => utf_winfunc.c} | 4 +- .../utfconv/{utf_winfunc.hh => utf_winfunc.h} | 0 intern/utfconv/{utfconv.cc => utfconv.c} | 2 +- intern/utfconv/{utfconv.hh => utfconv.h} | 8 + intern/wayland_dynload/CMakeLists.txt | 15 +- .../extern/wayland_dynload_API.h | 2 - release/datafiles/colormanagement/config.ocio | 4 +- release/datafiles/splash.png | Bin 730581 -> 1153018 bytes .../bl_i18n_utils/bl_extract_messages.py | 4 +- scripts/modules/bpy_extras/anim_utils.py | 224 +-- scripts/modules/rna_xml.py | 4 +- .../keyconfig/keymap_data/blender_default.py | 39 - scripts/startup/bl_operators/anim.py | 40 +- .../startup/bl_operators/geometry_nodes.py | 188 +- scripts/startup/bl_operators/wm.py | 29 - .../startup/bl_ui/node_add_menu_geometry.py | 3 - .../startup/bl_ui/properties_data_gpencil.py | 2 +- .../bl_ui/properties_data_lightprobe.py | 42 +- scripts/startup/bl_ui/properties_data_mesh.py | 28 + .../startup/bl_ui/properties_data_modifier.py | 3 +- .../bl_ui/properties_grease_pencil_common.py | 5 +- scripts/startup/bl_ui/properties_material.py | 87 +- scripts/startup/bl_ui/properties_object.py | 1 - .../startup/bl_ui/properties_paint_common.py | 7 - scripts/startup/bl_ui/properties_render.py | 193 +- .../startup/bl_ui/properties_view_layer.py | 40 +- scripts/startup/bl_ui/properties_world.py | 2 +- scripts/startup/bl_ui/space_dopesheet.py | 2 +- scripts/startup/bl_ui/space_node.py | 27 +- .../startup/bl_ui/space_toolsystem_toolbar.py | 32 +- scripts/startup/bl_ui/space_topbar.py | 2 +- scripts/startup/bl_ui/space_userpref.py | 97 +- scripts/startup/bl_ui/space_view3d.py | 184 +- source/blender/animrig/ANIM_fcurve.hh | 18 - source/blender/animrig/ANIM_keyframing.hh | 150 -- source/blender/animrig/ANIM_visualkey.hh | 21 - source/blender/animrig/CMakeLists.txt | 13 - .../animrig/intern/bone_collections.cc | 2 +- source/blender/animrig/intern/fcurve.cc | 42 - source/blender/animrig/intern/keyframing.cc | 1006 ---------- .../blender/animrig/intern/keyframing_auto.cc | 342 ---- source/blender/animrig/intern/visualkey.cc | 288 --- .../blendthumb/src/blendthumb_extract.cc | 4 +- source/blender/blenfont/BLF_api.h | 9 +- source/blender/blenfont/intern/blf.cc | 22 +- source/blender/blenfont/intern/blf_font.cc | 259 +-- source/blender/blenfont/intern/blf_glyph.cc | 220 +-- .../blenfont/intern/blf_internal_types.h | 83 +- source/blender/blenkernel/BKE_animsys.h | 9 +- source/blender/blenkernel/BKE_attribute.h | 8 +- source/blender/blenkernel/BKE_attribute.hh | 63 +- .../blender/blenkernel/BKE_blender_version.h | 6 +- .../blenkernel/BKE_compute_contexts.hh | 5 - source/blender/blenkernel/BKE_crazyspace.hh | 5 +- source/blender/blenkernel/BKE_curves.h | 3 +- source/blender/blenkernel/BKE_curves.hh | 14 +- source/blender/blenkernel/BKE_curves_utils.hh | 25 + source/blender/blenkernel/BKE_customdata.h | 5 +- source/blender/blenkernel/BKE_editmesh.h | 6 +- .../blender/blenkernel/BKE_geometry_fields.hh | 124 +- source/blender/blenkernel/BKE_geometry_set.hh | 19 +- source/blender/blenkernel/BKE_global.h | 4 +- .../blenkernel/BKE_gpencil_modifier_legacy.h | 2 +- .../blender/blenkernel/BKE_grease_pencil.hh | 141 +- source/blender/blenkernel/BKE_instances.hh | 55 +- source/blender/blenkernel/BKE_mesh.h | 28 +- source/blender/blenkernel/BKE_mesh.hh | 2 + .../blenkernel/BKE_mesh_legacy_convert.hh | 3 - source/blender/blenkernel/BKE_mesh_remap.hh | 14 +- source/blender/blenkernel/BKE_mesh_runtime.hh | 3 +- source/blender/blenkernel/BKE_mesh_types.hh | 28 +- source/blender/blenkernel/BKE_modifier.h | 2 +- source/blender/blenkernel/BKE_node.h | 25 +- source/blender/blenkernel/BKE_node.hh | 3 - source/blender/blenkernel/BKE_node_runtime.hh | 24 +- .../BKE_node_tree_anonymous_attributes.hh | 1 + .../blender/blenkernel/BKE_node_tree_update.h | 1 + source/blender/blenkernel/BKE_object.hh | 472 ++--- source/blender/blenkernel/BKE_pbvh_api.hh | 69 +- source/blender/blenkernel/BKE_pointcloud.h | 4 +- source/blender/blenkernel/BKE_report.h | 34 +- source/blender/blenkernel/BKE_screen.hh | 27 - source/blender/blenkernel/BKE_shader_fx.h | 2 +- .../blender/blenkernel/BKE_subdiv_modifier.hh | 1 + .../blender/blenkernel/intern/DerivedMesh.cc | 124 +- source/blender/blenkernel/intern/action.cc | 2 +- .../blenkernel/intern/action_mirror.cc | 2 +- source/blender/blenkernel/intern/anim_sys.cc | 14 +- source/blender/blenkernel/intern/appdir.cc | 6 +- source/blender/blenkernel/intern/armature.cc | 10 +- source/blender/blenkernel/intern/asset.cc | 2 +- source/blender/blenkernel/intern/attribute.cc | 20 +- .../blenkernel/intern/attribute_access.cc | 195 +- .../intern/attribute_access_intern.hh | 4 +- .../intern/bake_geometry_nodes_modifier.cc | 2 +- .../blenkernel/intern/bake_items_paths.cc | 2 +- source/blender/blenkernel/intern/blender.cc | 2 +- .../blender/blenkernel/intern/constraint.cc | 2 +- .../blender/blenkernel/intern/crazyspace.cc | 77 +- .../blender/blenkernel/intern/curve_bezier.cc | 54 +- .../intern/curve_to_mesh_convert.cc | 2 + source/blender/blenkernel/intern/curves.cc | 23 +- .../blenkernel/intern/curves_geometry.cc | 2 +- .../blender/blenkernel/intern/curves_utils.cc | 14 + .../blender/blenkernel/intern/customdata.cc | 34 +- .../blenkernel/intern/data_transfer.cc | 73 +- source/blender/blenkernel/intern/deform.cc | 2 +- source/blender/blenkernel/intern/displist.cc | 3 + .../blender/blenkernel/intern/dynamicpaint.cc | 2 +- source/blender/blenkernel/intern/editmesh.cc | 28 +- source/blender/blenkernel/intern/fcurve.cc | 10 +- .../blenkernel/intern/fcurve_driver.cc | 2 +- source/blender/blenkernel/intern/freestyle.cc | 2 +- .../intern/geometry_component_edit_data.cc | 70 +- .../geometry_component_grease_pencil.cc | 103 -- .../intern/geometry_component_instances.cc | 4 +- .../intern/geometry_component_mesh.cc | 11 +- .../blenkernel/intern/geometry_fields.cc | 261 +-- .../blender/blenkernel/intern/geometry_set.cc | 30 - .../blenkernel/intern/gpencil_geom_legacy.cc | 2 +- .../blenkernel/intern/gpencil_legacy.cc | 2 +- .../intern/gpencil_modifier_legacy.cc | 17 +- .../blenkernel/intern/grease_pencil.cc | 672 ++----- .../intern/grease_pencil_convert_legacy.cc | 15 +- .../blenkernel/intern/grease_pencil_test.cc | 66 +- source/blender/blenkernel/intern/image.cc | 2 +- .../blender/blenkernel/intern/image_save.cc | 7 +- source/blender/blenkernel/intern/instances.cc | 99 +- source/blender/blenkernel/intern/ipo.cc | 2 +- source/blender/blenkernel/intern/key.cc | 4 +- source/blender/blenkernel/intern/layer.cc | 2 +- source/blender/blenkernel/intern/lib_id.cc | 2 +- .../blender/blenkernel/intern/lightprobe.cc | 6 +- source/blender/blenkernel/intern/linestyle.cc | 2 +- .../blender/blenkernel/intern/main_namemap.cc | 2 +- source/blender/blenkernel/intern/mask.cc | 2 +- source/blender/blenkernel/intern/material.cc | 4 - source/blender/blenkernel/intern/mball.cc | 2 +- .../blenkernel/intern/mball_tessellate.cc | 2 +- source/blender/blenkernel/intern/mesh.cc | 134 +- source/blender/blenkernel/intern/mesh_fair.cc | 6 +- .../blenkernel/intern/mesh_iterators.cc | 8 +- .../blenkernel/intern/mesh_legacy_convert.cc | 181 +- .../blender/blenkernel/intern/mesh_mirror.cc | 7 +- .../blender/blenkernel/intern/mesh_normals.cc | 208 +-- .../blender/blenkernel/intern/mesh_remap.cc | 78 +- .../blender/blenkernel/intern/mesh_runtime.cc | 7 - .../blender/blenkernel/intern/mesh_tangent.cc | 12 +- .../blender/blenkernel/intern/mesh_wrapper.cc | 26 +- source/blender/blenkernel/intern/modifier.cc | 7 +- source/blender/blenkernel/intern/movieclip.cc | 5 - source/blender/blenkernel/intern/nla.cc | 2 +- source/blender/blenkernel/intern/node.cc | 117 +- .../intern/node_tree_anonymous_attributes.cc | 14 +- .../intern/node_tree_field_inferencing.cc | 24 +- .../blenkernel/intern/node_tree_update.cc | 17 +- source/blender/blenkernel/intern/object.cc | 59 +- .../blenkernel/intern/object_deform.cc | 2 +- .../blender/blenkernel/intern/object_dupli.cc | 6 - .../blenkernel/intern/object_update.cc | 6 +- source/blender/blenkernel/intern/paint.cc | 2 - .../blenkernel/intern/particle_system.cc | 5 +- source/blender/blenkernel/intern/pbvh.cc | 103 +- .../blender/blenkernel/intern/pbvh_bmesh.cc | 821 +++++---- .../blender/blenkernel/intern/pbvh_colors.cc | 23 +- .../blender/blenkernel/intern/pbvh_intern.hh | 11 +- .../blender/blenkernel/intern/pointcloud.cc | 15 +- .../blender/blenkernel/intern/preferences.cc | 2 +- source/blender/blenkernel/intern/report.cc | 61 +- source/blender/blenkernel/intern/rigidbody.cc | 5 +- source/blender/blenkernel/intern/scene.cc | 2 +- source/blender/blenkernel/intern/screen.cc | 40 +- source/blender/blenkernel/intern/shader_fx.cc | 7 +- .../blender/blenkernel/intern/shrinkwrap.cc | 6 +- .../blender/blenkernel/intern/studiolight.cc | 2 +- .../blenkernel/intern/subdiv_modifier.cc | 17 +- source/blender/blenkernel/intern/tracking.cc | 2 +- .../blenkernel/intern/tracking_util.cc | 2 +- source/blender/blenkernel/intern/unit.cc | 1438 ++------------- .../blenkernel/intern/volume_render.cc | 8 +- source/blender/blenkernel/intern/workspace.cc | 2 +- source/blender/blenlib/BLI_array_utils.hh | 35 - source/blender/blenlib/BLI_compiler_compat.h | 2 - source/blender/blenlib/BLI_listbase.h | 5 +- source/blender/blenlib/BLI_math_matrix.hh | 88 - source/blender/blenlib/BLI_math_vector.hh | 48 - source/blender/blenlib/BLI_mmap.h | 1 - .../blender/blenlib/BLI_string_utf8_symbols.h | 4 - ...BLI_string_utils.hh => BLI_string_utils.h} | 32 +- source/blender/blenlib/BLI_vector.hh | 2 +- source/blender/blenlib/BLI_virtual_array.hh | 14 - source/blender/blenlib/CMakeLists.txt | 22 +- .../{BLI_filelist.cc => BLI_filelist.c} | 63 +- source/blender/blenlib/intern/BLI_mmap.c | 5 - source/blender/blenlib/intern/array_utils.cc | 53 - .../blenlib/intern/{dynlib.cc => dynlib.c} | 14 +- .../intern/{fileops_c.cc => fileops.c} | 93 +- source/blender/blenlib/intern/fileops.cc | 2 +- .../blenlib/intern/filereader_memory.c | 2 +- source/blender/blenlib/intern/listbase.cc | 15 +- source/blender/blenlib/intern/math_matrix.cc | 2 - source/blender/blenlib/intern/noise.cc | 5 +- .../intern/{path_util.cc => path_util.c} | 36 +- .../blenlib/intern/{storage.cc => storage.c} | 70 +- .../intern/{string_utf8.cc => string_utf8.c} | 56 +- .../{string_utils.cc => string_utils.c} | 121 +- source/blender/blenlib/intern/vector.cc | 2 +- .../intern/{winstuff.cc => winstuff.c} | 22 +- .../{winstuff_dir.cc => winstuff_dir.c} | 4 +- .../blenlib/tests/BLI_math_matrix_test.cc | 9 - .../blender/blenlib/tests/BLI_memiter_test.cc | 2 +- .../blenlib/tests/BLI_path_util_test.cc | 2 +- .../blender/blenlib/tests/BLI_stack_test.cc | 1 + .../blender/blenlib/tests/BLI_string_test.cc | 2 +- .../blenlib/tests/BLI_string_utils_test.cc | 81 - .../blenloader/intern/versioning_260.cc | 4 +- .../blenloader/intern/versioning_270.cc | 14 +- .../blenloader/intern/versioning_300.cc | 2 +- .../blenloader/intern/versioning_400.cc | 52 +- .../blenloader/intern/versioning_common.cc | 5 - .../blenloader/intern/versioning_defaults.cc | 4 +- .../blenloader/intern/versioning_legacy.cc | 2 +- .../blenloader/intern/versioning_userdef.cc | 11 +- source/blender/blenloader/intern/writefile.cc | 274 ++- .../tests/blendfile_loading_base_test.cc | 6 +- source/blender/blentranslation/CMakeLists.txt | 6 +- source/blender/bmesh/intern/bmesh_core.cc | 45 +- source/blender/bmesh/intern/bmesh_core.h | 4 +- .../bmesh/intern/bmesh_mesh_normals.cc | 19 +- .../blender/bmesh/intern/bmesh_mesh_normals.h | 1 + .../bmesh/intern/bmesh_mesh_validate.cc | 23 - source/blender/bmesh/intern/bmesh_mods.cc | 101 +- source/blender/bmesh/intern/bmesh_mods.h | 2 + .../blender/bmesh/intern/bmesh_opdefines.cc | 1 + source/blender/bmesh/intern/bmesh_query.cc | 2 +- source/blender/bmesh/intern/bmesh_query.h | 2 +- source/blender/bmesh/operators/bmo_bevel.cc | 2 + .../blender/bmesh/operators/bmo_dissolve.cc | 63 +- source/blender/bmesh/operators/bmo_edgenet.cc | 66 +- source/blender/bmesh/operators/bmo_extrude.cc | 2 +- source/blender/bmesh/operators/bmo_hull.cc | 15 +- .../blender/bmesh/operators/bmo_subdivide.cc | 63 +- source/blender/bmesh/tools/bmesh_bevel.cc | 150 +- source/blender/bmesh/tools/bmesh_bevel.h | 1 + source/blender/bmesh/tools/bmesh_boolean.cc | 2 +- .../bmesh/tools/bmesh_decimate_collapse.cc | 3 +- source/blender/compositor/CMakeLists.txt | 2 +- .../{COM_compositor.hh => COM_compositor.h} | 18 +- .../compositor/intern/COM_ExecutionGroup.h | 6 +- .../intern/COM_MultiThreadedRowOperation.h | 2 + .../compositor/intern/COM_NodeOperation.h | 2 +- .../compositor/intern/COM_compositor.cc | 2 +- .../compositor/nodes/COM_CryptomatteNode.cc | 5 +- .../compositor/nodes/COM_KeyingScreenNode.cc | 1 - .../compositor/nodes/COM_KuwaharaNode.cc | 6 +- .../operations/COM_KeyingScreenOperation.cc | 298 ++- .../operations/COM_KeyingScreenOperation.h | 34 +- .../COM_KuwaharaAnisotropicOperation.cc | 25 +- .../COM_KuwaharaAnisotropicOperation.h | 1 - .../COM_KuwaharaClassicOperation.cc | 30 +- .../operations/COM_KuwaharaClassicOperation.h | 6 +- .../operations/COM_WriteBufferOperation.cc | 8 +- .../realtime_compositor/CMakeLists.txt | 12 +- .../realtime_compositor/COM_context.hh | 7 +- .../COM_input_descriptor.hh | 8 + .../COM_realize_on_domain_operation.hh | 34 + .../COM_static_cache_manager.hh | 2 - .../algorithms/COM_algorithm_jump_flooding.hh | 16 +- .../COM_algorithm_realize_on_domain.hh | 28 - .../algorithms/COM_algorithm_transform.hh | 33 - .../algorithms/intern/jump_flooding.cc | 8 +- .../algorithms/intern/realize_on_domain.cc | 113 -- .../algorithms/intern/transform.cc | 87 - .../cached_resources/COM_keying_screen.hh | 87 - .../cached_resources/intern/keying_screen.cc | 255 --- .../realtime_compositor/intern/operation.cc | 6 + .../intern/realize_on_domain_operation.cc | 169 +- .../realtime_compositor/intern/result.cc | 1 + .../intern/static_cache_manager.cc | 1 - .../realtime_compositor/intern/utilities.cc | 6 + ...tor_double_edge_mask_compute_boundary.glsl | 8 +- ...tor_double_edge_mask_compute_gradient.glsl | 10 +- .../compositor_inpaint_compute_boundary.glsl | 6 +- .../compositor_inpaint_compute_region.glsl | 12 +- .../shaders/compositor_jump_flooding.glsl | 31 +- .../shaders/compositor_keying_screen.glsl | 52 - .../compositor_kuwahara_anisotropic.glsl | 6 - .../shaders/compositor_kuwahara_classic.glsl | 6 - ...d_input.glsl => compositor_read_pass.glsl} | 4 +- .../shaders/compositor_split_viewer.glsl | 2 +- .../shaders/compositor_write_output.glsl | 2 +- .../shaders/infos/compositor_convert_info.hh | 18 + .../infos/compositor_double_edge_mask_info.hh | 8 +- .../shaders/infos/compositor_inpaint_info.hh | 8 +- .../infos/compositor_jump_flooding_info.hh | 4 +- .../infos/compositor_keying_screen_info.hh | 15 - .../shaders/infos/compositor_kuwahara_info.hh | 47 +- .../infos/compositor_read_input_info.hh | 35 - .../infos/compositor_read_pass_info.hh | 35 + .../infos/compositor_split_viewer_info.hh | 2 +- .../infos/compositor_write_output_info.hh | 2 +- ...u_shader_compositor_jump_flooding_lib.glsl | 37 +- ...u_shader_compositor_texture_utilities.glsl | 23 - source/blender/datatoc/CMakeLists.txt | 6 +- source/blender/draw/CMakeLists.txt | 29 +- source/blender/draw/DRW_engine.h | 6 +- source/blender/draw/DRW_pbvh.hh | 14 +- ...W_select_buffer.hh => DRW_select_buffer.h} | 87 +- .../engines/compositor/compositor_engine.cc | 2 +- .../engines/eevee/eevee_depth_of_field.cc | 6 +- .../draw/engines/eevee/eevee_engine.cc | 2 +- .../draw/engines/eevee/eevee_lightcache.cc | 16 +- .../draw/engines/eevee/eevee_lightcache.h | 3 +- .../draw/engines/eevee/eevee_lightprobes.cc | 18 +- .../draw/engines/eevee/eevee_lut_gen.cc | 2 +- .../draw/engines/eevee/eevee_materials.cc | 2 +- .../blender/draw/engines/eevee/eevee_mist.cc | 2 +- .../draw/engines/eevee/eevee_motion_blur.cc | 2 +- .../draw/engines/eevee/eevee_occlusion.cc | 2 +- .../draw/engines/eevee/eevee_renderpasses.cc | 2 +- .../engines/eevee/eevee_screen_raytrace.cc | 2 +- .../draw/engines/eevee/eevee_shaders.cc | 2 +- .../draw/engines/eevee/eevee_shadows.cc | 6 +- .../draw/engines/eevee/eevee_subsurface.cc | 2 +- .../draw/engines/eevee/eevee_volumes.cc | 2 +- .../eevee/shaders/bsdf_common_lib.glsl | 2 +- .../eevee/shaders/effect_dof_bokeh_frag.glsl | 2 +- .../eevee_next/eevee_ambient_occlusion.cc | 4 +- .../engines/eevee_next/eevee_cryptomatte.hh | 4 +- .../draw/engines/eevee_next/eevee_defines.hh | 45 +- .../eevee_next/eevee_depth_of_field.cc | 8 +- .../eevee_next/eevee_depth_of_field.hh | 2 +- .../draw/engines/eevee_next/eevee_engine.cc | 2 +- .../draw/engines/eevee_next/eevee_gbuffer.hh | 8 +- .../engines/eevee_next/eevee_hizbuffer.cc | 73 +- .../engines/eevee_next/eevee_hizbuffer.hh | 19 +- .../draw/engines/eevee_next/eevee_instance.cc | 39 +- .../draw/engines/eevee_next/eevee_instance.hh | 6 +- .../eevee_next/eevee_irradiance_cache.cc | 47 +- .../eevee_next/eevee_irradiance_cache.hh | 17 +- .../draw/engines/eevee_next/eevee_light.cc | 10 +- .../draw/engines/eevee_next/eevee_light.hh | 10 +- .../engines/eevee_next/eevee_lightcache.cc | 12 +- .../engines/eevee_next/eevee_lightcache.hh | 5 +- .../engines/eevee_next/eevee_lightprobe.cc | 6 +- .../draw/engines/eevee_next/eevee_lookdev.cc | 16 +- .../draw/engines/eevee_next/eevee_lookdev.hh | 1 - .../draw/engines/eevee_next/eevee_material.cc | 153 +- .../draw/engines/eevee_next/eevee_material.hh | 76 +- .../engines/eevee_next/eevee_motion_blur.cc | 4 +- .../draw/engines/eevee_next/eevee_pipeline.cc | 520 +----- .../draw/engines/eevee_next/eevee_pipeline.hh | 253 +-- .../engines/eevee_next/eevee_planar_probes.cc | 152 -- .../engines/eevee_next/eevee_planar_probes.hh | 127 -- .../draw/engines/eevee_next/eevee_raytrace.cc | 46 +- .../eevee_next/eevee_reflection_probes.cc | 736 ++++---- .../eevee_next/eevee_reflection_probes.hh | 104 +- .../draw/engines/eevee_next/eevee_sampling.hh | 4 +- .../draw/engines/eevee_next/eevee_shader.cc | 99 +- .../draw/engines/eevee_next/eevee_shader.hh | 7 - .../engines/eevee_next/eevee_shader_shared.hh | 152 +- .../draw/engines/eevee_next/eevee_shadow.cc | 154 +- .../draw/engines/eevee_next/eevee_shadow.hh | 46 +- .../engines/eevee_next/eevee_subsurface.cc | 4 +- .../engines/eevee_next/eevee_subsurface.hh | 4 +- .../draw/engines/eevee_next/eevee_sync.cc | 148 +- .../draw/engines/eevee_next/eevee_sync.hh | 104 +- .../draw/engines/eevee_next/eevee_velocity.cc | 6 +- .../draw/engines/eevee_next/eevee_velocity.hh | 18 +- .../draw/engines/eevee_next/eevee_view.cc | 15 +- .../draw/engines/eevee_next/eevee_view.hh | 18 - .../draw/engines/eevee_next/eevee_volume.cc | 183 +- .../draw/engines/eevee_next/eevee_volume.hh | 64 +- .../draw/engines/eevee_next/eevee_world.cc | 5 +- .../shaders/eevee_ambient_occlusion_lib.glsl | 45 +- .../eevee_ambient_occlusion_pass_comp.glsl | 81 +- .../shaders/eevee_attributes_lib.glsl | 14 +- .../eevee_next/shaders/eevee_bxdf_lib.glsl | 1 - .../shaders/eevee_bxdf_sampling_lib.glsl | 14 +- .../eevee_next/shaders/eevee_camera_lib.glsl | 6 +- .../shaders/eevee_colorspace_lib.glsl | 13 - .../eevee_debug_irradiance_grid_vert.glsl | 4 +- .../shaders/eevee_debug_surfels_frag.glsl | 2 +- .../shaders/eevee_debug_surfels_vert.glsl | 16 +- .../shaders/eevee_deferred_capture_frag.glsl | 38 +- .../shaders/eevee_deferred_combine_frag.glsl | 2 +- .../shaders/eevee_deferred_light_frag.glsl | 101 +- .../shaders/eevee_deferred_planar_frag.glsl | 49 - .../eevee_depth_of_field_accumulator_lib.glsl | 28 +- .../eevee_depth_of_field_bokeh_lut_comp.glsl | 4 +- .../eevee_depth_of_field_downsample_comp.glsl | 3 +- .../shaders/eevee_depth_of_field_lib.glsl | 13 +- .../eevee_depth_of_field_reduce_comp.glsl | 13 +- .../eevee_depth_of_field_resolve_comp.glsl | 6 +- .../eevee_depth_of_field_scatter_frag.glsl | 7 +- .../eevee_depth_of_field_scatter_vert.glsl | 3 +- .../eevee_depth_of_field_setup_comp.glsl | 9 +- .../eevee_depth_of_field_stabilize_comp.glsl | 15 +- .../eevee_display_probe_grid_frag.glsl | 6 +- .../eevee_display_probe_grid_vert.glsl | 6 +- .../eevee_next/shaders/eevee_film_comp.glsl | 1 + .../eevee_film_cryptomatte_post_comp.glsl | 2 +- .../eevee_next/shaders/eevee_film_frag.glsl | 3 +- .../eevee_next/shaders/eevee_film_lib.glsl | 41 +- .../shaders/eevee_geom_curves_vert.glsl | 27 +- .../shaders/eevee_geom_gpencil_vert.glsl | 11 +- .../shaders/eevee_geom_mesh_vert.glsl | 12 +- .../shaders/eevee_geom_point_cloud_vert.glsl | 14 +- .../shaders/eevee_geom_volume_vert.glsl | 24 - .../shaders/eevee_geom_world_vert.glsl | 4 +- .../shaders/eevee_hiz_update_comp.glsl | 18 +- .../eevee_light_culling_debug_frag.glsl | 15 +- .../eevee_light_culling_select_comp.glsl | 10 +- .../eevee_light_culling_sort_comp.glsl | 2 +- .../eevee_light_culling_tile_comp.glsl | 10 +- .../eevee_light_culling_zbin_comp.glsl | 5 +- .../shaders/eevee_light_eval_lib.glsl | 255 +-- .../shaders/eevee_light_iter_lib.glsl | 72 +- .../eevee_next/shaders/eevee_light_lib.glsl | 295 +-- .../shaders/eevee_lightprobe_eval_lib.glsl | 176 +- ...vee_lightprobe_irradiance_bounds_comp.glsl | 3 +- .../eevee_lightprobe_irradiance_ray_comp.glsl | 14 +- .../shaders/eevee_lightprobe_lib.glsl | 38 - .../eevee_next/shaders/eevee_ltc_lib.glsl | 12 +- .../eevee_motion_blur_dilate_comp.glsl | 2 +- .../eevee_motion_blur_flatten_comp.glsl | 2 +- .../eevee_motion_blur_gather_comp.glsl | 7 +- .../shaders/eevee_nodetree_lib.glsl | 26 +- .../shaders/eevee_occupancy_convert_frag.glsl | 94 - .../shaders/eevee_occupancy_lib.glsl | 175 -- .../shaders/eevee_occupancy_test.glsl | 110 -- .../shaders/eevee_octahedron_lib.glsl | 23 +- .../eevee_ray_denoise_bilateral_comp.glsl | 9 +- .../eevee_ray_denoise_spatial_comp.glsl | 66 +- .../eevee_ray_denoise_temporal_comp.glsl | 12 +- .../shaders/eevee_ray_generate_comp.glsl | 3 +- .../shaders/eevee_ray_generate_lib.glsl | 5 +- .../shaders/eevee_ray_tile_compact_comp.glsl | 46 +- .../eevee_ray_trace_fallback_comp.glsl | 22 +- .../shaders/eevee_ray_trace_planar_comp.glsl | 101 - .../shaders/eevee_ray_trace_screen_comp.glsl | 35 +- .../shaders/eevee_ray_trace_screen_lib.glsl | 79 +- .../shaders/eevee_ray_types_lib.glsl | 21 +- .../eevee_reflection_probe_eval_lib.glsl | 89 +- .../shaders/eevee_reflection_probe_lib.glsl | 39 +- .../eevee_reflection_probe_remap_comp.glsl | 41 +- .../eevee_reflection_probe_select_comp.glsl | 29 - ...flection_probe_update_irradiance_comp.glsl | 7 +- .../shaders/eevee_sampling_lib.glsl | 8 +- .../shaders/eevee_shadow_debug_frag.glsl | 6 +- .../eevee_next/shaders/eevee_shadow_lib.glsl | 49 +- .../shaders/eevee_shadow_page_clear_comp.glsl | 7 +- .../eevee_shadow_page_defrag_comp.glsl | 6 - .../shaders/eevee_shadow_page_mask_comp.glsl | 9 - .../shaders/eevee_shadow_page_tile_frag.glsl | 71 - .../shaders/eevee_shadow_page_tile_vert.glsl | 41 - .../shaders/eevee_shadow_tag_update_comp.glsl | 3 +- .../shaders/eevee_shadow_tag_usage_comp.glsl | 4 +- .../shaders/eevee_shadow_tag_usage_frag.glsl | 26 +- .../shaders/eevee_shadow_tag_usage_lib.glsl | 7 +- .../shaders/eevee_shadow_tag_usage_vert.glsl | 33 +- .../eevee_shadow_tag_usage_volume_comp.glsl | 8 +- .../eevee_shadow_tilemap_bounds_comp.glsl | 1 + .../eevee_shadow_tilemap_finalize_comp.glsl | 12 +- .../eevee_shadow_tilemap_init_comp.glsl | 1 + .../shaders/eevee_shadow_tilemap_lib.glsl | 3 +- .../shaders/eevee_shadow_tracing_lib.glsl | 92 +- .../eevee_spherical_harmonics_lib.glsl | 21 - .../eevee_subsurface_convolve_comp.glsl | 16 +- .../shaders/eevee_subsurface_lib.glsl | 32 - .../shaders/eevee_subsurface_setup_comp.glsl | 8 +- .../shaders/eevee_surf_capture_frag.glsl | 10 +- .../shaders/eevee_surf_deferred_frag.glsl | 11 +- .../shaders/eevee_surf_depth_frag.glsl | 26 +- .../shaders/eevee_surf_forward_frag.glsl | 114 +- .../eevee_next/shaders/eevee_surf_lib.glsl | 58 +- .../shaders/eevee_surf_occupancy_frag.glsl | 75 - .../shaders/eevee_surf_shadow_frag.glsl | 23 +- .../shaders/eevee_surf_world_frag.glsl | 18 +- .../shaders/eevee_surfel_light_comp.glsl | 84 +- .../shaders/eevee_surfel_list_build_comp.glsl | 2 +- .../shaders/eevee_surfel_list_lib.glsl | 9 +- .../shaders/eevee_surfel_ray_comp.glsl | 13 +- .../shaders/eevee_thickness_lib.glsl | 62 - .../shaders/eevee_velocity_lib.glsl | 7 +- .../eevee_volume_integration_comp.glsl | 5 +- .../eevee_next/shaders/eevee_volume_lib.glsl | 71 +- .../shaders/eevee_volume_material_comp.glsl | 22 +- .../shaders/eevee_volume_resolve_frag.glsl | 2 +- .../shaders/eevee_volume_scatter_comp.glsl | 41 +- .../shaders/infos/eevee_deferred_info.hh | 32 +- .../shaders/infos/eevee_hiz_info.hh | 8 +- .../infos/eevee_irradiance_cache_info.hh | 13 +- .../shaders/infos/eevee_material_info.hh | 162 +- .../infos/eevee_reflection_probe_info.hh | 28 +- .../shaders/infos/eevee_shadow_info.hh | 66 +- .../shaders/infos/eevee_subsurface_info.hh | 8 +- .../shaders/infos/eevee_tracing_info.hh | 28 +- .../shaders/infos/eevee_volume_info.hh | 17 - .../engines/gpencil/gpencil_cache_utils.cc | 6 +- .../draw/engines/gpencil/gpencil_engine.cc | 2 +- .../draw/engines/gpencil/gpencil_layer.hh | 2 +- .../draw/engines/gpencil/gpencil_object.hh | 70 +- .../draw/engines/gpencil/gpencil_shader.cc | 2 +- .../grease_pencil_depth_merge_vert.glsl | 9 - .../gpencil/shaders/grease_pencil_frag.glsl | 142 -- .../gpencil/shaders/grease_pencil_vert.glsl | 2 +- .../gpencil/shaders/infos/gpencil_info.hh | 12 +- .../shaders/image_engine_color_vert.glsl | 1 - .../shaders/image_engine_depth_vert.glsl | 1 - .../draw/engines/overlay/overlay_extra.cc | 16 +- .../engines/overlay/overlay_grease_pencil.cc | 26 +- .../draw/engines/overlay/overlay_outline.cc | 6 +- .../draw/engines/overlay/overlay_private.hh | 1 - .../engines/select/select_debug_engine.cc | 4 +- .../{select_defines.hh => select_defines.h} | 0 .../draw/engines/select/select_draw_utils.cc | 18 +- .../draw/engines/select/select_engine.cc | 157 +- .../{select_engine.hh => select_engine.h} | 9 + .../draw/engines/select/select_instance.cc | 2 +- .../draw/engines/select/select_instance.hh | 2 +- .../{select_private.hh => select_private.h} | 42 +- .../select/shaders/infos/select_id_info.hh | 2 +- .../engines/workbench/workbench_shadow.cc | 4 +- source/blender/draw/intern/DRW_gpu_wrapper.hh | 34 - source/blender/draw/intern/draw_cache.h | 1 - .../draw_cache_extract_mesh_render_data.cc | 96 +- .../intern/draw_cache_impl_grease_pencil.cc | 147 +- .../intern/draw_cache_impl_subdivision.cc | 7 +- source/blender/draw/intern/draw_command.cc | 42 +- source/blender/draw/intern/draw_command.hh | 12 - source/blender/draw/intern/draw_curves.cc | 2 +- source/blender/draw/intern/draw_defines.h | 7 - source/blender/draw/intern/draw_hair.cc | 2 +- source/blender/draw/intern/draw_manager_c.cc | 31 +- .../blender/draw/intern/draw_manager_data.cc | 13 +- .../draw/intern/draw_manager_shader.cc | 13 +- source/blender/draw/intern/draw_pass.hh | 33 - source/blender/draw/intern/draw_pbvh.cc | 223 +-- source/blender/draw/intern/draw_pointcloud.cc | 2 +- source/blender/draw/intern/draw_resource.hh | 4 +- source/blender/draw/intern/draw_sculpt.cc | 3 +- .../blender/draw/intern/draw_select_buffer.cc | 64 +- source/blender/draw/intern/draw_shader.cc | 2 +- .../blender/draw/intern/draw_shader_shared.h | 7 + source/blender/draw/intern/draw_view.cc | 9 - source/blender/draw/intern/draw_view.hh | 12 - source/blender/draw/intern/draw_view_c.cc | 124 +- .../intern/mesh_extractors/extract_mesh.hh | 3 +- .../extract_mesh_vbo_pos_nor.cc | 10 +- .../shaders/common_grease_pencil_lib.glsl | 16 +- .../intern/shaders/common_intersect_lib.glsl | 2 +- .../draw/intern/shaders/common_math_lib.glsl | 8 +- .../draw/intern/shaders/common_shape_lib.glsl | 2 + .../intern/shaders/draw_intersect_lib.glsl | 520 ------ .../intern/shaders/draw_math_geom_lib.glsl | 208 --- .../draw/intern/shaders/draw_model_lib.glsl | 120 -- .../draw/intern/shaders/draw_view_info.hh | 14 +- .../draw/intern/shaders/draw_view_lib.glsl | 188 -- .../shaders/draw_view_reconstruction_lib.glsl | 82 - source/blender/draw/tests/draw_pass_test.cc | 15 - source/blender/draw/tests/eevee_test.cc | 17 +- .../animation/anim_channels_defines.cc | 50 +- source/blender/editors/animation/drivers.cc | 2 +- .../editors/animation/keyframes_general.cc | 4 +- .../blender/editors/animation/keyframing.cc | 1621 ++++++++++++++++- .../blender/editors/animation/keyingsets.cc | 334 ++-- .../blender/editors/armature/armature_add.cc | 2 +- .../editors/armature/armature_naming.cc | 2 +- .../editors/armature/armature_select.cc | 11 +- .../editors/armature/armature_skinning.cc | 2 +- .../editors/armature/armature_utils.cc | 2 +- source/blender/editors/armature/pose_edit.cc | 5 +- source/blender/editors/armature/pose_lib_2.cc | 10 +- .../blender/editors/armature/pose_select.cc | 3 +- .../editors/armature/pose_transform.cc | 18 +- source/blender/editors/armature/pose_utils.cc | 11 +- .../editors/asset/intern/asset_catalog.cc | 2 +- source/blender/editors/curve/editcurve.cc | 7 +- .../blender/editors/curve/editcurve_paint.cc | 2 +- source/blender/editors/curve/editcurve_pen.cc | 7 +- .../blender/editors/curve/editcurve_select.cc | 9 +- source/blender/editors/curve/editfont.cc | 3 +- source/blender/editors/curves/CMakeLists.txt | 1 - .../curves/intern/curves_attribute_set.cc | 229 --- .../editors/curves/intern/curves_ops.cc | 1 - .../editors/curves/intern/curves_selection.cc | 3 +- .../editors/geometry/geometry_attributes.cc | 133 -- .../editors/geometry/node_group_operator.cc | 2 +- .../gizmo_library/gizmo_types/snap3d_gizmo.cc | 6 +- .../editors/gpencil_legacy/gpencil_convert.cc | 81 +- .../editors/gpencil_legacy/gpencil_data.cc | 2 +- .../editors/gpencil_legacy/gpencil_edit.cc | 5 +- .../editors/gpencil_legacy/gpencil_fill.cc | 20 +- .../editors/gpencil_legacy/gpencil_paint.cc | 12 +- .../gpencil_legacy/gpencil_primitive.cc | 7 +- .../gpencil_legacy/gpencil_sculpt_paint.cc | 18 +- .../gpencil_legacy/gpencil_trace_ops.cc | 19 +- .../editors/grease_pencil/CMakeLists.txt | 5 - .../grease_pencil/intern/grease_pencil_add.cc | 361 ++-- .../intern/grease_pencil_edit.cc | 335 +--- .../intern/grease_pencil_frames.cc | 3 +- .../intern/grease_pencil_geom.cc | 160 -- .../intern/grease_pencil_layers.cc | 13 +- .../intern/grease_pencil_select.cc | 31 +- .../intern/grease_pencil_utils.cc | 79 - source/blender/editors/include/ED_clip.hh | 9 +- source/blender/editors/include/ED_curves.hh | 9 - source/blender/editors/include/ED_geometry.hh | 27 - .../editors/include/ED_grease_pencil.hh | 19 +- source/blender/editors/include/ED_image.hh | 5 +- .../blender/editors/include/ED_keyframing.hh | 145 +- source/blender/editors/include/ED_mesh.hh | 2 +- source/blender/editors/include/ED_screen.hh | 1 + source/blender/editors/include/ED_view3d.hh | 2 +- .../blender/editors/include/ED_viewer_path.hh | 10 - source/blender/editors/include/UI_icons.hh | 6 +- .../blender/editors/include/UI_interface_c.hh | 17 +- .../blender/editors/include/UI_tree_view.hh | 7 +- .../blender/editors/interface/CMakeLists.txt | 7 +- .../interface/eyedroppers/eyedropper_color.cc | 68 +- source/blender/editors/interface/interface.cc | 37 +- .../editors/interface/interface_anim.cc | 5 +- .../editors/interface/interface_draw.cc | 2 +- .../editors/interface/interface_handlers.cc | 68 +- .../editors/interface/interface_icons.cc | 5 +- .../editors/interface/interface_intern.hh | 25 +- .../editors/interface/interface_layout.cc | 16 +- .../editors/interface/interface_panel.cc | 59 +- .../interface_region_color_picker.cc | 2 +- .../interface/interface_region_menu_popup.cc | 4 - .../interface/interface_region_search.cc | 6 +- .../interface/interface_region_tooltip.cc | 28 +- .../editors/interface/interface_style.cc | 5 - ...rface_template_grease_pencil_layer_tree.cc | 35 +- .../interface_template_light_linking.cc | 8 +- .../interface_template_node_tree_interface.cc | 21 +- .../interface_template_search_menu.cc | 2 +- .../editors/interface/interface_templates.cc | 2 +- .../editors/interface/interface_widgets.cc | 60 +- source/blender/editors/interface/view2d.cc | 3 +- .../blender/editors/interface/view2d_draw.cc | 2 +- .../blender/editors/interface/view2d_ops.cc | 4 +- .../editors/interface/views/tree_view.cc | 18 +- source/blender/editors/io/io_usd.cc | 13 +- .../editors/lattice/editlattice_select.cc | 6 +- source/blender/editors/mask/CMakeLists.txt | 1 - source/blender/editors/mask/mask_ops.cc | 11 +- source/blender/editors/mesh/editface.cc | 3 +- .../editors/mesh/editmesh_attribute.cc | 161 +- source/blender/editors/mesh/editmesh_bevel.cc | 10 +- .../blender/editors/mesh/editmesh_extrude.cc | 3 +- source/blender/editors/mesh/editmesh_inset.cc | 2 +- source/blender/editors/mesh/editmesh_knife.cc | 3 +- .../editors/mesh/editmesh_knife_project.cc | 3 +- .../blender/editors/mesh/editmesh_loopcut.cc | 4 +- source/blender/editors/mesh/editmesh_path.cc | 3 +- .../editors/mesh/editmesh_polybuild.cc | 31 +- source/blender/editors/mesh/editmesh_rip.cc | 40 +- .../blender/editors/mesh/editmesh_select.cc | 33 +- source/blender/editors/mesh/editmesh_tools.cc | 26 +- source/blender/editors/mesh/mesh_data.cc | 36 + source/blender/editors/mesh/meshtools.cc | 8 +- source/blender/editors/metaball/mball_edit.cc | 6 +- source/blender/editors/object/object_add.cc | 46 +- source/blender/editors/object/object_bake.cc | 8 +- .../blender/editors/object/object_bake_api.cc | 9 +- .../editors/object/object_bake_simulation.cc | 40 +- .../editors/object/object_data_transfer.cc | 5 + source/blender/editors/object/object_edit.cc | 41 +- .../editors/object/object_gpencil_modifier.cc | 2 +- source/blender/editors/object/object_intern.h | 1 - .../blender/editors/object/object_modifier.cc | 12 +- source/blender/editors/object/object_ops.cc | 1 - .../blender/editors/object/object_remesh.cc | 21 +- .../blender/editors/object/object_select.cc | 2 +- .../editors/object/object_transform.cc | 13 +- .../blender/editors/object/object_vgroup.cc | 1 + .../editors/physics/dynamicpaint_ops.cc | 12 +- .../blender/editors/physics/particle_edit.cc | 2 +- .../blender/editors/physics/physics_fluid.cc | 31 +- .../editors/physics/physics_pointcache.cc | 12 +- .../blender/editors/render/render_internal.cc | 12 +- .../blender/editors/render/render_opengl.cc | 2 - .../blender/editors/render/render_preview.cc | 42 +- .../blender/editors/render/render_shading.cc | 32 +- .../blender/editors/render/render_update.cc | 2 +- source/blender/editors/screen/area.cc | 17 +- source/blender/editors/screen/screen_edit.cc | 4 +- source/blender/editors/screen/screen_ops.cc | 9 + .../editors/sculpt_paint/CMakeLists.txt | 1 - .../editors/sculpt_paint/curves_sculpt_add.cc | 12 +- .../sculpt_paint/curves_sculpt_density.cc | 13 +- .../sculpt_paint/curves_sculpt_puff.cc | 14 +- .../sculpt_paint/curves_sculpt_slide.cc | 14 +- .../sculpt_paint/grease_pencil_draw_ops.cc | 38 +- .../sculpt_paint/grease_pencil_erase.cc | 22 +- .../sculpt_paint/grease_pencil_paint.cc | 625 +------ .../editors/sculpt_paint/paint_cursor.cc | 183 +- .../editors/sculpt_paint/paint_curve.cc | 3 - .../editors/sculpt_paint/paint_hide.cc | 49 +- .../editors/sculpt_paint/paint_image.cc | 8 +- .../sculpt_paint/paint_image_ops_paint.cc | 2 +- .../editors/sculpt_paint/paint_image_proj.cc | 8 +- .../editors/sculpt_paint/paint_mask.cc | 8 +- .../blender/editors/sculpt_paint/paint_ops.cc | 5 +- .../editors/sculpt_paint/paint_stroke.cc | 2 +- .../editors/sculpt_paint/paint_utils.cc | 5 +- .../editors/sculpt_paint/paint_vertex.cc | 2 +- .../sculpt_paint/paint_vertex_weight_ops.cc | 6 +- .../sculpt_paint/paint_vertex_weight_utils.cc | 2 +- .../editors/sculpt_paint/paint_weight.cc | 2 +- source/blender/editors/sculpt_paint/sculpt.cc | 50 +- .../editors/sculpt_paint/sculpt_boundary.cc | 12 +- .../sculpt_paint/sculpt_brush_types.cc | 67 +- .../editors/sculpt_paint/sculpt_cloth.cc | 6 +- .../editors/sculpt_paint/sculpt_detail.cc | 13 +- .../editors/sculpt_paint/sculpt_dyntopo.cc | 4 + .../editors/sculpt_paint/sculpt_expand.cc | 4 +- .../editors/sculpt_paint/sculpt_face_set.cc | 6 +- .../sculpt_paint/sculpt_filter_color.cc | 2 +- .../sculpt_paint/sculpt_filter_mask.cc | 4 +- .../sculpt_paint/sculpt_filter_mesh.cc | 7 +- .../editors/sculpt_paint/sculpt_intern.hh | 37 +- .../sculpt_paint/sculpt_multiplane_scrape.cc | 4 +- .../editors/sculpt_paint/sculpt_ops.cc | 14 +- .../sculpt_paint/sculpt_paint_color.cc | 6 +- .../editors/sculpt_paint/sculpt_pose.cc | 2 +- .../editors/sculpt_paint/sculpt_smooth.cc | 31 +- .../editors/sculpt_paint/sculpt_transform.cc | 12 +- .../editors/sculpt_paint/sculpt_undo.cc | 215 ++- .../editors/space_action/CMakeLists.txt | 1 - .../editors/space_action/action_edit.cc | 30 +- .../editors/space_clip/clip_buttons.cc | 2 +- .../blender/editors/space_clip/clip_editor.cc | 27 +- source/blender/editors/space_clip/clip_ops.cc | 14 +- .../editors/space_clip/tracking_ops.cc | 2 +- .../editors/space_clip/tracking_ops_solve.cc | 13 +- .../editors/space_clip/tracking_ops_track.cc | 19 +- .../space_file/asset_catalog_tree_view.cc | 28 +- source/blender/editors/space_file/filelist.cc | 14 +- source/blender/editors/space_file/fsmenu.cc | 14 +- .../blender/editors/space_file/space_file.cc | 2 +- .../editors/space_graph/CMakeLists.txt | 1 - .../blender/editors/space_graph/graph_edit.cc | 25 +- .../editors/space_image/image_buttons.cc | 2 +- .../blender/editors/space_image/image_ops.cc | 11 +- source/blender/editors/space_node/drawnode.cc | 34 +- .../blender/editors/space_node/node_draw.cc | 66 +- .../blender/editors/space_node/node_edit.cc | 34 +- .../blender/editors/space_node/node_group.cc | 2 +- .../blender/editors/space_node/node_intern.hh | 10 +- .../editors/space_node/node_relationships.cc | 43 +- .../blender/editors/space_node/node_select.cc | 21 +- .../editors/space_node/node_shader_preview.cc | 17 +- .../editors/space_node/node_templates.cc | 3 +- .../blender/editors/space_node/space_node.cc | 13 +- .../editors/space_outliner/outliner_draw.cc | 16 +- .../space_sequencer/sequencer_drag_drop.cc | 27 +- .../space_sequencer/sequencer_preview.cc | 12 +- .../space_sequencer/sequencer_proxy.cc | 5 +- .../space_sequencer/sequencer_thumbnails.cc | 13 +- .../sequencer_timeline_draw.cc | 2 +- .../space_spreadsheet/space_spreadsheet.cc | 11 +- .../spreadsheet_data_source_geometry.cc | 72 +- .../spreadsheet_data_source_geometry.hh | 7 - .../spreadsheet_dataset_draw.cc | 93 +- .../blender/editors/space_text/text_format.cc | 2 +- .../editors/space_view3d/CMakeLists.txt | 1 - .../editors/space_view3d/space_view3d.cc | 126 +- .../editors/space_view3d/view3d_context.cc | 142 -- .../editors/space_view3d/view3d_draw.cc | 6 +- .../view3d_gizmo_preselect_type.cc | 6 +- .../editors/space_view3d/view3d_intern.h | 5 - .../editors/space_view3d/view3d_navigate.cc | 7 +- .../editors/space_view3d/view3d_project.cc | 7 +- .../editors/space_view3d/view3d_select.cc | 130 +- .../editors/space_view3d/view3d_snap.cc | 58 +- .../editors/space_view3d/view3d_utils.cc | 16 +- .../blender/editors/transform/CMakeLists.txt | 1 - source/blender/editors/transform/transform.cc | 65 +- source/blender/editors/transform/transform.hh | 2 +- .../editors/transform/transform_convert.cc | 140 +- .../editors/transform/transform_convert.hh | 23 +- .../transform/transform_convert_armature.cc | 67 +- .../transform/transform_convert_curves.cc | 186 +- .../transform_convert_gpencil_legacy.cc | 7 +- .../transform_convert_grease_pencil.cc | 156 -- .../transform/transform_convert_mask.cc | 5 +- .../transform/transform_convert_object.cc | 138 +- .../transform_convert_sequencer_image.cc | 21 +- .../editors/transform/transform_generics.cc | 5 - .../editors/transform/transform_gizmo_3d.cc | 37 +- .../editors/transform/transform_mode.cc | 47 +- .../transform_mode_edge_rotate_normal.cc | 3 +- .../transform/transform_mode_edge_slide.cc | 2 +- .../transform/transform_mode_gpopacity.cc | 14 +- .../transform/transform_orientations.cc | 2 +- .../editors/transform/transform_snap.cc | 2 +- source/blender/editors/util/ed_viewer_path.cc | 34 - .../editors/uvedit/uvedit_unwrap_ops.cc | 25 +- .../blender_interface/BlenderFileLoader.cpp | 11 +- .../blender/freestyle/intern/geometry/Grid.h | 2 +- .../blender/freestyle/intern/stroke/Curve.cpp | 2 +- .../freestyle/intern/system/PointerSequence.h | 22 +- .../freestyle/intern/view_map/ViewMap.h | 6 +- .../functions/FN_lazy_function_execute.hh | 28 - .../functions/FN_lazy_function_graph.hh | 13 - .../functions/intern/lazy_function_execute.cc | 72 - .../intern/lazy_function_graph_executor.cc | 44 +- source/blender/geometry/CMakeLists.txt | 2 - .../blender/geometry/GEO_join_geometries.hh | 15 - .../geometry/GEO_mesh_copy_selection.hh | 6 +- source/blender/geometry/GEO_randomize.hh | 6 +- .../blender/geometry/GEO_resample_curves.hh | 3 - source/blender/geometry/GEO_uv_pack.hh | 8 +- .../blender/geometry/intern/fillet_curves.cc | 17 +- .../geometry/intern/join_geometries.cc | 210 --- .../geometry/intern/mesh_copy_selection.cc | 21 +- .../geometry/intern/mesh_split_edges.cc | 25 +- .../intern/point_merge_by_distance.cc | 3 +- source/blender/geometry/intern/randomize.cc | 39 +- .../geometry/intern/realize_instances.cc | 77 +- .../geometry/intern/resample_curves.cc | 51 +- .../blender/geometry/intern/set_curve_type.cc | 93 +- .../geometry/intern/subdivide_curves.cc | 17 +- source/blender/geometry/intern/trim_curves.cc | 35 +- source/blender/geometry/intern/uv_pack.cc | 106 +- .../geometry/intern/uv_parametrizer.cc | 2 +- .../intern/MOD_gpencil_legacy_shrinkwrap.cc | 2 +- .../intern/lineart/lineart_cpu.cc | 4 + .../intern/lineart/lineart_ops.cc | 16 +- source/blender/gpu/CMakeLists.txt | 32 +- source/blender/gpu/GPU_capabilities.h | 3 - source/blender/gpu/GPU_common_types.h | 54 +- source/blender/gpu/GPU_framebuffer.h | 32 - source/blender/gpu/GPU_platform.h | 14 - source/blender/gpu/GPU_shader_shared_utils.h | 1 - source/blender/gpu/GPU_storage_buffer.h | 16 +- source/blender/gpu/GPU_texture.h | 17 +- source/blender/gpu/dummy/dummy_backend.hh | 3 +- source/blender/gpu/dummy/dummy_framebuffer.hh | 3 - .../blender/gpu/intern/gpu_batch_presets.cc | 5 - source/blender/gpu/intern/gpu_capabilities.cc | 5 - .../gpu/intern/gpu_capabilities_private.hh | 1 - source/blender/gpu/intern/gpu_context.cc | 2 +- source/blender/gpu/intern/gpu_framebuffer.cc | 8 - .../gpu/intern/gpu_framebuffer_private.hh | 3 - source/blender/gpu/intern/gpu_platform.cc | 12 +- .../gpu/intern/gpu_platform_private.hh | 4 +- source/blender/gpu/intern/gpu_shader.cc | 2 +- .../gpu/intern/gpu_shader_builder_stubs.cc | 9 + .../gpu/intern/gpu_shader_create_info.cc | 35 +- source/blender/gpu/intern/gpu_shader_log.cc | 2 +- .../blender/gpu/intern/gpu_storage_buffer.cc | 5 - .../gpu/intern/gpu_storage_buffer_private.hh | 1 - source/blender/gpu/intern/gpu_texture.cc | 16 - .../metal/kernels/compute_texture_update.msl | 20 +- .../gpu/metal/kernels/depth_2d_update_info.hh | 4 +- source/blender/gpu/metal/mtl_backend.mm | 13 +- .../blender/gpu/metal/mtl_command_buffer.mm | 6 +- source/blender/gpu/metal/mtl_common.hh | 2 +- source/blender/gpu/metal/mtl_context.hh | 6 +- source/blender/gpu/metal/mtl_context.mm | 10 +- source/blender/gpu/metal/mtl_framebuffer.hh | 3 - .../blender/gpu/metal/mtl_storage_buffer.hh | 5 - .../blender/gpu/metal/mtl_storage_buffer.mm | 110 +- source/blender/gpu/metal/mtl_texture.hh | 17 +- source/blender/gpu/metal/mtl_texture.mm | 190 +- source/blender/gpu/metal/mtl_texture_util.mm | 10 +- source/blender/gpu/opengl/gl_backend.cc | 70 +- source/blender/gpu/opengl/gl_batch.cc | 15 + source/blender/gpu/opengl/gl_compute.cc | 6 - source/blender/gpu/opengl/gl_context.cc | 1 + source/blender/gpu/opengl/gl_context.hh | 3 +- source/blender/gpu/opengl/gl_debug.cc | 6 +- source/blender/gpu/opengl/gl_framebuffer.cc | 107 -- source/blender/gpu/opengl/gl_framebuffer.hh | 7 +- source/blender/gpu/opengl/gl_immediate.cc | 7 +- source/blender/gpu/opengl/gl_shader.cc | 208 +-- source/blender/gpu/opengl/gl_shader.hh | 2 +- source/blender/gpu/opengl/gl_shader_log.cc | 5 +- .../blender/gpu/opengl/gl_storage_buffer.cc | 8 +- .../blender/gpu/opengl/gl_storage_buffer.hh | 1 - source/blender/gpu/opengl/gl_texture.cc | 34 +- source/blender/gpu/opengl/gl_texture.hh | 10 +- .../common/gpu_shader_common_math.glsl | 11 - .../gpu_shader_debug_gradients_lib.glsl | 28 - .../common/gpu_shader_math_base_lib.glsl | 25 - .../common/gpu_shader_math_matrix_lib.glsl | 24 - .../common/gpu_shader_math_vector_lib.glsl | 125 -- .../common/gpu_shader_utildefines_lib.glsl | 5 +- .../gpu/shaders/gpu_shader_codegen_lib.glsl | 6 +- .../gpu/shaders/infos/gpu_shader_test_info.hh | 6 - .../gpu_shader_material_attribute.glsl | 4 +- .../material/gpu_shader_material_camera.glsl | 5 +- .../gpu_shader_material_displacement.glsl | 9 +- .../gpu_shader_material_eevee_specular.glsl | 2 +- .../material/gpu_shader_material_fresnel.glsl | 2 +- .../material/gpu_shader_material_glass.glsl | 2 +- .../material/gpu_shader_material_glossy.glsl | 2 +- .../gpu_shader_material_layer_weight.glsl | 2 +- .../gpu_shader_material_principled.glsl | 15 +- .../material/gpu_shader_material_tangent.glsl | 4 +- .../material/gpu_shader_material_tex_sky.glsl | 3 +- ...u_shader_material_texture_coordinates.glsl | 12 +- .../gpu_shader_material_transform_utils.glsl | 36 +- ...u_shader_material_vector_displacement.glsl | 21 +- .../gpu/shaders/metal/mtl_shader_defines.msl | 190 +- .../blender/gpu/tests/buffer_texture_test.cc | 2 +- source/blender/gpu/tests/framebuffer_test.cc | 32 +- source/blender/gpu/tests/shader_test.cc | 1 - source/blender/gpu/tests/texture_test.cc | 52 +- source/blender/gpu/vulkan/vk_backend.cc | 36 +- source/blender/gpu/vulkan/vk_batch.cc | 25 +- source/blender/gpu/vulkan/vk_buffer.cc | 4 +- .../blender/gpu/vulkan/vk_command_buffer.cc | 398 +++- .../blender/gpu/vulkan/vk_command_buffer.hh | 161 +- .../blender/gpu/vulkan/vk_command_buffers.cc | 614 ------- .../blender/gpu/vulkan/vk_command_buffers.hh | 200 -- source/blender/gpu/vulkan/vk_context.cc | 38 +- source/blender/gpu/vulkan/vk_context.hh | 8 +- .../blender/gpu/vulkan/vk_data_conversion.cc | 22 + .../blender/gpu/vulkan/vk_data_conversion.hh | 21 + source/blender/gpu/vulkan/vk_debug.cc | 11 - .../blender/gpu/vulkan/vk_descriptor_set.cc | 1 + source/blender/gpu/vulkan/vk_device.cc | 23 - source/blender/gpu/vulkan/vk_device.hh | 26 - source/blender/gpu/vulkan/vk_framebuffer.cc | 234 +-- source/blender/gpu/vulkan/vk_framebuffer.hh | 18 - source/blender/gpu/vulkan/vk_image_view.cc | 25 +- source/blender/gpu/vulkan/vk_image_view.hh | 7 - source/blender/gpu/vulkan/vk_immediate.cc | 7 +- source/blender/gpu/vulkan/vk_index_buffer.cc | 5 +- source/blender/gpu/vulkan/vk_pipeline.cc | 6 +- .../blender/gpu/vulkan/vk_pipeline_state.cc | 9 +- source/blender/gpu/vulkan/vk_pixel_buffer.hh | 5 - .../blender/gpu/vulkan/vk_push_constants.cc | 7 +- .../blender/gpu/vulkan/vk_resource_tracker.cc | 4 +- .../blender/gpu/vulkan/vk_resource_tracker.hh | 4 +- source/blender/gpu/vulkan/vk_shader.cc | 177 +- source/blender/gpu/vulkan/vk_shader.hh | 27 +- source/blender/gpu/vulkan/vk_state_manager.cc | 3 +- .../blender/gpu/vulkan/vk_storage_buffer.cc | 14 +- .../blender/gpu/vulkan/vk_storage_buffer.hh | 1 - source/blender/gpu/vulkan/vk_texture.cc | 168 +- source/blender/gpu/vulkan/vk_texture.hh | 6 +- .../gpu/vulkan/vk_vertex_attribute_object.cc | 8 +- source/blender/gpu/vulkan/vk_vertex_buffer.cc | 3 +- source/blender/imbuf/intern/indexer.cc | 2 +- .../imbuf/intern/oiio/openimageio_support.cc | 2 +- .../imbuf/intern/openexr/openexr_api.cpp | 12 +- source/blender/imbuf/intern/readimage.cc | 6 +- source/blender/imbuf/intern/thumbs.cc | 4 +- source/blender/imbuf/intern/webp.cc | 6 +- .../io/alembic/exporter/abc_archive.cc | 2 +- .../io/alembic/exporter/abc_export_capi.cc | 28 +- .../alembic/exporter/abc_writer_abstract.cc | 2 +- .../io/alembic/exporter/abc_writer_mesh.cc | 89 +- .../io/alembic/intern/abc_reader_archive.cc | 2 +- .../io/alembic/intern/abc_reader_mesh.cc | 2 + .../blender/io/alembic/intern/alembic_capi.cc | 17 +- .../blender/io/collada/AnimationImporter.cpp | 2 +- .../blender/io/collada/GeometryExporter.cpp | 15 +- source/blender/io/collada/MeshImporter.cpp | 1 + .../gpencil/intern/gpencil_io_export_pdf.cc | 2 +- .../gpencil/intern/gpencil_io_export_svg.cc | 2 +- .../io/stl/importer/stl_import_mesh.cc | 1 + source/blender/io/usd/hydra/mesh.cc | 8 +- .../io/usd/hydra/usd_scene_delegate.cc | 14 - .../blender/io/usd/intern/usd_asset_utils.cc | 158 +- .../blender/io/usd/intern/usd_asset_utils.h | 12 +- .../blender/io/usd/intern/usd_capi_export.cc | 98 +- .../blender/io/usd/intern/usd_capi_import.cc | 84 +- source/blender/io/usd/intern/usd_hook.cc | 35 +- source/blender/io/usd/intern/usd_hook.h | 6 +- .../io/usd/intern/usd_reader_material.cc | 2 +- .../io/usd/intern/usd_reader_material.h | 8 - .../blender/io/usd/intern/usd_reader_mesh.cc | 120 +- .../blender/io/usd/intern/usd_reader_prim.h | 14 +- .../blender/io/usd/intern/usd_reader_shape.cc | 19 +- .../io/usd/intern/usd_reader_skeleton.cc | 2 +- .../blender/io/usd/intern/usd_reader_stage.cc | 18 +- .../blender/io/usd/intern/usd_reader_stage.h | 10 +- .../blender/io/usd/intern/usd_skel_convert.cc | 193 +- .../blender/io/usd/intern/usd_skel_convert.h | 14 +- .../io/usd/intern/usd_writer_abstract.cc | 9 +- .../io/usd/intern/usd_writer_abstract.h | 9 - .../io/usd/intern/usd_writer_curves.cc | 62 +- .../io/usd/intern/usd_writer_material.cc | 47 +- .../blender/io/usd/intern/usd_writer_mesh.cc | 72 +- .../io/usd/intern/usd_writer_volume.cc | 15 +- .../blender/io/usd/tests/usd_curves_test.cc | 2 +- .../blender/io/usd/tests/usd_export_test.cc | 4 +- .../io/usd/tests/usd_usdz_export_test.cc | 2 +- source/blender/io/usd/usd.h | 15 +- .../wavefront_obj/exporter/obj_export_mesh.cc | 21 +- .../wavefront_obj/exporter/obj_export_mesh.hh | 2 + .../io/wavefront_obj/exporter/obj_exporter.cc | 3 + .../importer/importer_mesh_utils.cc | 4 +- .../wavefront_obj/importer/obj_import_mesh.cc | 1 + .../wavefront_obj/tests/obj_importer_tests.cc | 4 +- source/blender/makesdna/DNA_curve_types.h | 2 +- .../makesdna/DNA_grease_pencil_types.h | 57 +- source/blender/makesdna/DNA_ipo_types.h | 2 +- .../makesdna/DNA_lightprobe_defaults.h | 1 + .../blender/makesdna/DNA_lightprobe_types.h | 23 +- source/blender/makesdna/DNA_mask_types.h | 5 +- source/blender/makesdna/DNA_material_types.h | 26 +- source/blender/makesdna/DNA_mesh_defaults.h | 1 + source/blender/makesdna/DNA_mesh_types.h | 31 +- source/blender/makesdna/DNA_movieclip_types.h | 5 - .../makesdna/DNA_node_tree_interface_types.h | 18 +- source/blender/makesdna/DNA_node_types.h | 26 +- source/blender/makesdna/DNA_object_types.h | 6 +- source/blender/makesdna/DNA_scene_defaults.h | 1 - source/blender/makesdna/DNA_scene_types.h | 8 +- source/blender/makesdna/DNA_screen_types.h | 26 +- source/blender/makesdna/DNA_space_types.h | 3 - source/blender/makesdna/DNA_texture_types.h | 5 +- source/blender/makesdna/DNA_userdef_types.h | 7 +- .../makesdna/DNA_windowmanager_types.h | 22 +- source/blender/makesdna/DNA_world_types.h | 15 +- .../blender/makesdna/intern/dna_rename_defs.h | 1 - source/blender/makesrna/RNA_enum_items.hh | 2 - source/blender/makesrna/RNA_types.hh | 2 +- source/blender/makesrna/intern/makesrna.cc | 18 +- source/blender/makesrna/intern/rna_ID.cc | 2 +- .../makesrna/intern/rna_animation_api.cc | 25 +- .../blender/makesrna/intern/rna_armature.cc | 2 +- .../blender/makesrna/intern/rna_attribute.cc | 20 - source/blender/makesrna/intern/rna_define.cc | 81 +- .../makesrna/intern/rna_gpencil_legacy.cc | 2 +- .../intern/rna_gpencil_legacy_modifier.cc | 2 +- .../makesrna/intern/rna_grease_pencil.cc | 3 - source/blender/makesrna/intern/rna_key.cc | 2 +- source/blender/makesrna/intern/rna_layer.cc | 2 +- .../blender/makesrna/intern/rna_lightprobe.cc | 42 +- .../blender/makesrna/intern/rna_linestyle.cc | 2 +- .../blender/makesrna/intern/rna_material.cc | 78 +- source/blender/makesrna/intern/rna_mesh.cc | 136 +- .../blender/makesrna/intern/rna_mesh_api.cc | 44 +- .../makesrna/intern/rna_node_socket.cc | 3 - .../intern/rna_node_tree_interface.cc | 101 - .../blender/makesrna/intern/rna_nodetree.cc | 678 ++++--- source/blender/makesrna/intern/rna_object.cc | 16 +- .../blender/makesrna/intern/rna_object_api.cc | 2 +- .../blender/makesrna/intern/rna_particle.cc | 2 +- source/blender/makesrna/intern/rna_path.cc | 8 +- source/blender/makesrna/intern/rna_pose.cc | 2 +- source/blender/makesrna/intern/rna_scene.cc | 15 +- .../blender/makesrna/intern/rna_sequencer.cc | 4 +- source/blender/makesrna/intern/rna_space.cc | 10 +- source/blender/makesrna/intern/rna_ui_api.cc | 6 +- source/blender/makesrna/intern/rna_userdef.cc | 16 +- source/blender/makesrna/intern/rna_wm.cc | 2 +- .../blender/makesrna/intern/rna_wm_gizmo.cc | 2 +- source/blender/modifiers/intern/MOD_bevel.cc | 8 + .../modifiers/intern/MOD_datatransfer.cc | 8 +- .../blender/modifiers/intern/MOD_displace.cc | 23 +- .../blender/modifiers/intern/MOD_explode.cc | 2 +- .../blender/modifiers/intern/MOD_multires.cc | 14 +- source/blender/modifiers/intern/MOD_nodes.cc | 290 ++- .../modifiers/intern/MOD_normal_edit.cc | 18 + .../modifiers/intern/MOD_shrinkwrap.cc | 2 +- .../modifiers/intern/MOD_simpledeform.cc | 6 +- .../blender/modifiers/intern/MOD_subsurf.cc | 18 +- .../modifiers/intern/MOD_triangulate.cc | 12 +- .../blender/modifiers/intern/MOD_ui_common.cc | 2 +- .../blender/modifiers/intern/MOD_uvproject.cc | 2 +- source/blender/modifiers/intern/MOD_uvwarp.cc | 6 +- source/blender/modifiers/intern/MOD_warp.cc | 7 +- source/blender/modifiers/intern/MOD_wave.cc | 2 +- .../modifiers/intern/MOD_weighted_normal.cc | 29 +- .../modifiers/intern/MOD_weightvgedit.cc | 2 +- .../modifiers/intern/MOD_weightvgproximity.cc | 3 +- source/blender/nodes/CMakeLists.txt | 6 +- source/blender/nodes/NOD_common.h | 4 +- .../{NOD_composite.hh => NOD_composite.h} | 8 + source/blender/nodes/NOD_geometry.hh | 51 +- source/blender/nodes/NOD_geometry_exec.hh | 10 - .../nodes/NOD_geometry_nodes_lazy_function.hh | 2 +- .../blender/nodes/NOD_geometry_nodes_log.hh | 4 - source/blender/nodes/NOD_node_declaration.hh | 665 +++++-- source/blender/nodes/NOD_socket.hh | 1 - .../blender/nodes/NOD_socket_declarations.hh | 2 +- source/blender/nodes/NOD_socket_items.hh | 314 ---- source/blender/nodes/NOD_static_types.h | 12 +- source/blender/nodes/NOD_zone_socket_items.hh | 144 -- .../nodes/composite/node_composite_tree.cc | 4 +- .../nodes/composite/node_composite_util.hh | 2 +- .../nodes/node_composite_bokehblur.cc | 2 +- .../composite/nodes/node_composite_common.cc | 4 +- .../nodes/node_composite_composite.cc | 6 +- .../nodes/node_composite_double_edge_mask.cc | 8 +- .../nodes/node_composite_hue_sat_val.cc | 2 +- .../composite/nodes/node_composite_image.cc | 21 +- .../composite/nodes/node_composite_inpaint.cc | 6 +- .../nodes/node_composite_keyingscreen.cc | 83 +- .../nodes/node_composite_kuwahara.cc | 93 +- .../nodes/node_composite_movieclip.cc | 10 +- .../nodes/node_composite_output_file.cc | 2 +- .../composite/nodes/node_composite_rotate.cc | 9 +- .../composite/nodes/node_composite_scale.cc | 14 +- .../nodes/node_composite_split_viewer.cc | 19 +- .../nodes/node_composite_stabilize2d.cc | 16 +- .../nodes/node_composite_switchview.cc | 19 +- .../nodes/node_composite_transform.cc | 15 +- .../composite/nodes/node_composite_viewer.cc | 39 +- .../function/nodes/node_fn_random_value.cc | 2 +- .../function/nodes/node_fn_replace_string.cc | 2 +- source/blender/nodes/geometry/CMakeLists.txt | 2 - .../nodes/geometry/node_geometry_tree.cc | 14 - .../nodes/geometry/node_geometry_util.cc | 41 +- .../nodes/geometry/node_geometry_util.hh | 15 +- .../nodes/node_geo_accumulate_field.cc | 11 +- .../nodes/node_geo_attribute_capture.cc | 22 +- .../nodes/node_geo_attribute_domain_size.cc | 17 - .../nodes/node_geo_attribute_statistic.cc | 5 +- .../geometry/nodes/node_geo_blur_attribute.cc | 15 +- .../nodes/geometry/nodes/node_geo_boolean.cc | 5 +- .../nodes/node_geo_collection_info.cc | 1 + .../nodes/geometry/nodes/node_geo_common.cc | 31 +- .../geometry/nodes/node_geo_convex_hull.cc | 52 - .../geometry/nodes/node_geo_curve_fill.cc | 71 +- .../geometry/nodes/node_geo_curve_fillet.cc | 128 +- .../geometry/nodes/node_geo_curve_length.cc | 49 +- .../node_geo_curve_primitive_quadrilateral.cc | 2 +- .../geometry/nodes/node_geo_curve_resample.cc | 77 +- .../geometry/nodes/node_geo_curve_reverse.cc | 59 +- .../geometry/nodes/node_geo_curve_sample.cc | 16 +- .../nodes/node_geo_curve_subdivide.cc | 95 +- .../geometry/nodes/node_geo_curve_to_mesh.cc | 96 +- .../nodes/node_geo_curve_to_points.cc | 179 +- ...node_geo_curve_topology_points_of_curve.cc | 3 +- .../geometry/nodes/node_geo_curve_trim.cc | 97 +- .../node_geo_deform_curves_on_surface.cc | 11 +- .../nodes/node_geo_delete_geometry.cc | 127 +- .../node_geo_distribute_points_on_faces.cc | 39 +- .../nodes/node_geo_duplicate_elements.cc | 10 +- .../nodes/node_geo_evaluate_at_index.cc | 8 +- .../nodes/node_geo_evaluate_on_domain.cc | 42 +- .../geometry/nodes/node_geo_extrude_mesh.cc | 1 + .../node_geo_input_mesh_face_is_planar.cc | 8 +- .../nodes/node_geo_input_named_attribute.cc | 4 +- .../node_geo_input_named_layer_selection.cc | 52 - .../nodes/node_geo_input_signed_distance.cc | 2 +- .../nodes/node_geo_instance_on_points.cc | 55 +- .../nodes/node_geo_interpolate_curves.cc | 2 +- .../geometry/nodes/node_geo_join_geometry.cc | 184 +- .../nodes/node_geo_material_replace.cc | 26 +- .../nodes/node_geo_material_selection.cc | 86 +- .../node_geo_mesh_face_group_boundaries.cc | 2 +- .../nodes/node_geo_mesh_primitive_circle.cc | 6 +- .../node_geo_mesh_primitive_ico_sphere.cc | 2 - .../nodes/node_geo_mesh_primitive_line.cc | 2 +- .../nodes/node_geo_mesh_to_sdf_volume.cc | 6 +- .../geometry/nodes/node_geo_mesh_to_volume.cc | 2 +- .../node_geo_mesh_topology_corners_of_edge.cc | 2 +- .../node_geo_mesh_topology_corners_of_face.cc | 3 +- ...ode_geo_mesh_topology_corners_of_vertex.cc | 2 +- .../node_geo_mesh_topology_edges_of_vertex.cc | 2 +- .../nodes/node_geo_offset_sdf_volume.cc | 2 +- .../nodes/geometry/nodes/node_geo_points.cc | 10 +- .../nodes/node_geo_points_to_curves.cc | 2 +- .../nodes/node_geo_points_to_sdf_volume.cc | 4 +- .../nodes/node_geo_points_to_vertices.cc | 2 + .../nodes/node_geo_points_to_volume.cc | 2 +- .../nodes/geometry/nodes/node_geo_raycast.cc | 18 +- .../nodes/node_geo_realize_instances.cc | 2 +- .../nodes/node_geo_remove_attribute.cc | 2 +- .../geometry/nodes/node_geo_repeat_input.cc | 74 +- .../geometry/nodes/node_geo_repeat_output.cc | 262 ++- .../geometry/nodes/node_geo_sample_index.cc | 16 +- .../nodes/node_geo_sample_nearest_surface.cc | 16 +- .../nodes/node_geo_sample_uv_surface.cc | 16 +- .../geometry/nodes/node_geo_sample_volume.cc | 6 +- .../nodes/node_geo_sdf_volume_sphere.cc | 6 +- .../nodes/node_geo_separate_components.cc | 6 - .../nodes/node_geo_set_curve_normal.cc | 35 +- .../nodes/node_geo_set_curve_radius.cc | 34 +- .../geometry/nodes/node_geo_set_curve_tilt.cc | 38 +- .../geometry/nodes/node_geo_set_material.cc | 85 +- .../nodes/node_geo_set_material_index.cc | 59 +- .../nodes/node_geo_set_point_radius.cc | 7 +- .../geometry/nodes/node_geo_set_position.cc | 55 +- .../nodes/node_geo_set_shade_smooth.cc | 2 +- .../nodes/node_geo_set_spline_cyclic.cc | 38 +- .../nodes/node_geo_set_spline_resolution.cc | 37 +- .../nodes/node_geo_simulation_input.cc | 82 +- .../nodes/node_geo_simulation_output.cc | 416 ++++- .../nodes/node_geo_split_to_instances.cc | 382 ---- .../nodes/node_geo_store_named_attribute.cc | 26 +- .../nodes/node_geo_subdivision_surface.cc | 10 +- .../nodes/node_geo_transform_geometry.cc | 29 - .../nodes/geometry/nodes/node_geo_viewer.cc | 4 +- .../geometry/nodes/node_geo_volume_cube.cc | 24 +- .../geometry/nodes/node_geo_volume_to_mesh.cc | 4 +- .../nodes/intern/geometry_nodes_execute.cc | 22 +- .../intern/geometry_nodes_lazy_function.cc | 118 +- .../nodes/intern/geometry_nodes_log.cc | 31 +- source/blender/nodes/intern/node_common.cc | 85 +- .../blender/nodes/intern/node_declaration.cc | 543 +----- source/blender/nodes/intern/node_socket.cc | 18 +- .../nodes/intern/node_zone_socket_items.cc | 59 - .../nodes/intern/socket_search_link.cc | 12 +- source/blender/nodes/shader/CMakeLists.txt | 4 +- .../nodes/node_shader_bsdf_principled.cc | 46 +- .../nodes/shader/nodes/node_shader_common.cc | 4 +- .../nodes/shader/nodes/node_shader_curves.cc | 20 +- .../nodes/shader/nodes/node_shader_mix.cc | 8 +- .../shader/nodes/node_shader_normal_map.cc | 2 +- .../nodes/node_shader_output_material.cc | 2 +- .../nodes/shader/nodes/node_shader_tangent.cc | 15 +- .../nodes/shader/nodes/node_shader_tex_sky.cc | 2 +- .../nodes/node_shader_tex_white_noise.cc | 64 - .../nodes/shader/nodes/node_shader_uvmap.cc | 2 +- .../texture/nodes/node_texture_common.cc | 2 +- source/blender/python/bmesh/bmesh_py_types.cc | 2 +- source/blender/python/generic/CMakeLists.txt | 13 +- source/blender/python/gpu/CMakeLists.txt | 11 +- source/blender/python/gpu/gpu_py_shader.cc | 2 +- source/blender/python/intern/CMakeLists.txt | 2 +- source/blender/python/intern/bpy.cc | 2 +- .../python/intern/bpy_app_translations.cc | 2 +- .../blender/python/intern/bpy_capi_utils.cc | 2 +- source/blender/python/intern/bpy_capi_utils.h | 5 +- .../python/intern/bpy_interface_run.cc | 6 +- .../blender/python/intern/bpy_library_load.cc | 2 - .../python/intern/bpy_library_write.cc | 5 +- source/blender/python/intern/bpy_operator.cc | 1 - source/blender/python/intern/bpy_rna.cc | 10 +- source/blender/python/intern/bpy_rna_anim.cc | 43 +- source/blender/render/RE_engine.h | 1 + source/blender/render/intern/bake.cc | 15 +- source/blender/render/intern/compositor.cc | 120 +- source/blender/render/intern/engine.cc | 5 + source/blender/render/intern/multires_bake.cc | 3 +- source/blender/render/intern/pipeline.cc | 4 +- source/blender/render/intern/render_result.cc | 2 +- source/blender/render/intern/texture_image.cc | 6 +- .../blender/render/intern/texture_margin.cc | 2 +- .../render/intern/texture_pointdensity.cc | 5 +- .../render/intern/texture_procedural.cc | 4 +- source/blender/sequencer/SEQ_proxy.h | 6 +- source/blender/sequencer/intern/modifier.cc | 2 +- source/blender/sequencer/intern/prefetch.cc | 5 +- source/blender/sequencer/intern/proxy.cc | 19 +- source/blender/sequencer/intern/proxy_job.cc | 6 +- .../simulation/intern/SIM_mass_spring.cc | 4 +- source/blender/windowmanager/CMakeLists.txt | 7 +- source/blender/windowmanager/WM_api.hh | 27 +- source/blender/windowmanager/WM_types.hh | 86 +- .../gizmo/intern/wm_gizmo_group.cc | 2 +- .../gizmo/intern/wm_gizmo_target_props.cc | 5 +- source/blender/windowmanager/intern/wm.cc | 8 +- .../windowmanager/intern/wm_dragdrop.cc | 12 +- .../blender/windowmanager/intern/wm_draw.cc | 7 +- .../windowmanager/intern/wm_event_system.cc | 157 +- .../blender/windowmanager/intern/wm_files.cc | 2 +- .../windowmanager/intern/wm_files_link.cc | 14 +- .../windowmanager/intern/wm_init_exit.cc | 27 +- .../blender/windowmanager/intern/wm_jobs.cc | 68 +- .../blender/windowmanager/intern/wm_keymap.cc | 2 +- .../windowmanager/intern/wm_operators.cc | 22 +- .../windowmanager/intern/wm_playanim.cc | 905 +++++---- .../blender/windowmanager/intern/wm_window.cc | 714 ++++---- .../blender/windowmanager/wm_event_system.h | 6 +- source/blender/windowmanager/wm_window.hh | 2 - source/creator/CMakeLists.txt | 7 - source/creator/creator.cc | 5 +- source/creator/creator_args.cc | 29 +- tests/CMakeLists.txt | 4 +- tests/python/CMakeLists.txt | 12 - tests/python/bl_usd_export_test.py | 16 +- tests/python/bl_usd_import_test.py | 8 +- tests/python/eevee_next_render_tests.py | 147 -- tests/python/eevee_render_tests.py | 4 +- tools/check_source/check_spelling_config.py | 4 - 1348 files changed, 18793 insertions(+), 33630 deletions(-) create mode 100644 intern/ghost/intern/GHOST_WaylandCursorSettings.hh rename intern/utfconv/{utf_winfunc.cc => utf_winfunc.c} (98%) rename intern/utfconv/{utf_winfunc.hh => utf_winfunc.h} (100%) rename intern/utfconv/{utfconv.cc => utfconv.c} (99%) rename intern/utfconv/{utfconv.hh => utfconv.h} (97%) delete mode 100644 source/blender/animrig/ANIM_fcurve.hh delete mode 100644 source/blender/animrig/ANIM_keyframing.hh delete mode 100644 source/blender/animrig/ANIM_visualkey.hh delete mode 100644 source/blender/animrig/intern/fcurve.cc delete mode 100644 source/blender/animrig/intern/keyframing.cc delete mode 100644 source/blender/animrig/intern/keyframing_auto.cc delete mode 100644 source/blender/animrig/intern/visualkey.cc rename source/blender/blenlib/{BLI_string_utils.hh => BLI_string_utils.h} (96%) rename source/blender/blenlib/intern/{BLI_filelist.cc => BLI_filelist.c} (89%) rename source/blender/blenlib/intern/{dynlib.cc => dynlib.c} (87%) rename source/blender/blenlib/intern/{fileops_c.cc => fileops.c} (93%) rename source/blender/blenlib/intern/{path_util.cc => path_util.c} (98%) rename source/blender/blenlib/intern/{storage.cc => storage.c} (90%) rename source/blender/blenlib/intern/{string_utf8.cc => string_utf8.c} (96%) rename source/blender/blenlib/intern/{string_utils.cc => string_utils.c} (85%) rename source/blender/blenlib/intern/{winstuff.cc => winstuff.c} (96%) rename source/blender/blenlib/intern/{winstuff_dir.cc => winstuff_dir.c} (97%) delete mode 100644 source/blender/blenlib/tests/BLI_string_utils_test.cc rename source/blender/compositor/{COM_compositor.hh => COM_compositor.h} (98%) delete mode 100644 source/blender/compositor/realtime_compositor/algorithms/COM_algorithm_realize_on_domain.hh delete mode 100644 source/blender/compositor/realtime_compositor/algorithms/COM_algorithm_transform.hh delete mode 100644 source/blender/compositor/realtime_compositor/algorithms/intern/realize_on_domain.cc delete mode 100644 source/blender/compositor/realtime_compositor/algorithms/intern/transform.cc delete mode 100644 source/blender/compositor/realtime_compositor/cached_resources/COM_keying_screen.hh delete mode 100644 source/blender/compositor/realtime_compositor/cached_resources/intern/keying_screen.cc delete mode 100644 source/blender/compositor/realtime_compositor/shaders/compositor_keying_screen.glsl rename source/blender/compositor/realtime_compositor/shaders/{compositor_read_input.glsl => compositor_read_pass.glsl} (61%) delete mode 100644 source/blender/compositor/realtime_compositor/shaders/infos/compositor_keying_screen_info.hh delete mode 100644 source/blender/compositor/realtime_compositor/shaders/infos/compositor_read_input_info.hh create mode 100644 source/blender/compositor/realtime_compositor/shaders/infos/compositor_read_pass_info.hh rename source/blender/draw/{DRW_select_buffer.hh => DRW_select_buffer.h} (51%) delete mode 100644 source/blender/draw/engines/eevee_next/eevee_planar_probes.cc delete mode 100644 source/blender/draw/engines/eevee_next/eevee_planar_probes.hh delete mode 100644 source/blender/draw/engines/eevee_next/shaders/eevee_deferred_planar_frag.glsl delete mode 100644 source/blender/draw/engines/eevee_next/shaders/eevee_geom_volume_vert.glsl delete mode 100644 source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_convert_frag.glsl delete mode 100644 source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_lib.glsl delete mode 100644 source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_test.glsl delete mode 100644 source/blender/draw/engines/eevee_next/shaders/eevee_ray_trace_planar_comp.glsl delete mode 100644 source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_select_comp.glsl delete mode 100644 source/blender/draw/engines/eevee_next/shaders/eevee_shadow_page_tile_frag.glsl delete mode 100644 source/blender/draw/engines/eevee_next/shaders/eevee_shadow_page_tile_vert.glsl delete mode 100644 source/blender/draw/engines/eevee_next/shaders/eevee_subsurface_lib.glsl delete mode 100644 source/blender/draw/engines/eevee_next/shaders/eevee_surf_occupancy_frag.glsl delete mode 100644 source/blender/draw/engines/eevee_next/shaders/eevee_thickness_lib.glsl delete mode 100644 source/blender/draw/engines/gpencil/shaders/grease_pencil_depth_merge_vert.glsl delete mode 100644 source/blender/draw/engines/gpencil/shaders/grease_pencil_frag.glsl rename source/blender/draw/engines/select/{select_defines.hh => select_defines.h} (100%) rename source/blender/draw/engines/select/{select_engine.hh => select_engine.h} (90%) rename source/blender/draw/engines/select/{select_private.hh => select_private.h} (63%) delete mode 100644 source/blender/draw/intern/shaders/draw_intersect_lib.glsl delete mode 100644 source/blender/draw/intern/shaders/draw_math_geom_lib.glsl delete mode 100644 source/blender/draw/intern/shaders/draw_model_lib.glsl delete mode 100644 source/blender/draw/intern/shaders/draw_view_lib.glsl delete mode 100644 source/blender/draw/intern/shaders/draw_view_reconstruction_lib.glsl delete mode 100644 source/blender/editors/curves/intern/curves_attribute_set.cc delete mode 100644 source/blender/editors/grease_pencil/intern/grease_pencil_geom.cc delete mode 100644 source/blender/editors/grease_pencil/intern/grease_pencil_utils.cc delete mode 100644 source/blender/editors/space_view3d/view3d_context.cc delete mode 100644 source/blender/editors/transform/transform_convert_grease_pencil.cc delete mode 100644 source/blender/geometry/GEO_join_geometries.hh delete mode 100644 source/blender/geometry/intern/join_geometries.cc delete mode 100644 source/blender/gpu/shaders/common/gpu_shader_debug_gradients_lib.glsl delete mode 100644 source/blender/gpu/vulkan/vk_command_buffers.cc delete mode 100644 source/blender/gpu/vulkan/vk_command_buffers.hh rename source/blender/nodes/{NOD_composite.hh => NOD_composite.h} (98%) delete mode 100644 source/blender/nodes/NOD_socket_items.hh delete mode 100644 source/blender/nodes/NOD_zone_socket_items.hh delete mode 100644 source/blender/nodes/geometry/nodes/node_geo_input_named_layer_selection.cc delete mode 100644 source/blender/nodes/geometry/nodes/node_geo_split_to_instances.cc delete mode 100644 source/blender/nodes/intern/node_zone_socket_items.cc delete mode 100644 tests/python/eevee_next_render_tests.py diff --git a/CMakeLists.txt b/CMakeLists.txt index eb9c4ee9d59..0c3f026bc52 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -335,6 +335,13 @@ if(UNIX AND NOT (APPLE OR HAIKU)) option(WITH_GHOST_WAYLAND_LIBDECOR "Optionally build with LibDecor window decorations" ON) mark_as_advanced(WITH_GHOST_WAYLAND_LIBDECOR) + option(WITH_GHOST_WAYLAND_DBUS "\ +Optionally build with DBUS support (used for Cursor themes). \ +May hang on startup systems where DBUS is not used." + OFF + ) + mark_as_advanced(WITH_GHOST_WAYLAND_DBUS) + option(WITH_GHOST_WAYLAND_DYNLOAD "Enable runtime dynamic WAYLAND libraries loading" ON) mark_as_advanced(WITH_GHOST_WAYLAND_DYNLOAD) @@ -511,10 +518,8 @@ Boost uses ICU library (required for linking with static Boost built with libicu endif() # Misc -if(WIN32 OR APPLE OR ((UNIX AND (NOT HAIKU)) AND WITH_GHOST_WAYLAND)) +if(WIN32 OR APPLE) option(WITH_INPUT_IME "Enable Input Method Editor (IME) for complex Asian character input" ON) -else() - set(WITH_INPUT_IME OFF) endif() option(WITH_INPUT_NDOF "Enable NDOF input devices (SpaceNavigator and friends)" ON) if(UNIX AND NOT APPLE) @@ -647,7 +652,7 @@ if(NOT APPLE) gfx900 gfx90c gfx902 gfx1010 gfx1011 gfx1012 gfx1030 gfx1031 gfx1032 gfx1034 gfx1035 gfx1036 - gfx1100 gfx1101 gfx1102 gfx1103 + gfx1100 gfx1101 gfx1102 CACHE STRING "AMD HIP architectures to build binaries for" ) mark_as_advanced(WITH_CYCLES_DEVICE_HIP) @@ -679,10 +684,8 @@ This option is only for debugging purposes." ) # https://www.intel.com/content/www/us/en/develop/documentation/oneapi-dpcpp-cpp-compiler-dev-guide-and-reference/top/compilation/ahead-of-time-compilation.html - # The target architectures levels can be retrieved from `ocloc` output when running - # `ocloc compile -device {device_id} test.c` for given GPUs PCI device IDs. - # 12.55.8 is for Arc Alchemist GPUs. 12.70.0 for Meteor Lake iGPUs. - set(CYCLES_ONEAPI_INTEL_BINARIES_ARCH 12.55.8 12.70.0 CACHE STRING "\ + # acm-g10 is the target for the first Intel Arc Alchemist GPUs. + set(CYCLES_ONEAPI_SPIR64_GEN_DEVICES "acm-g10" CACHE STRING "\ oneAPI Intel GPU architectures to build binaries for" ) set(CYCLES_ONEAPI_SYCL_TARGETS spir64 spir64_gen CACHE STRING "\ @@ -690,7 +693,7 @@ oneAPI targets to build AOT binaries for" ) mark_as_advanced(WITH_CYCLES_ONEAPI_HOST_TASK_EXECUTION) - mark_as_advanced(CYCLES_ONEAPI_INTEL_BINARIES_ARCH) + mark_as_advanced(CYCLES_ONEAPI_SPIR64_GEN_DEVICES) mark_as_advanced(CYCLES_ONEAPI_SYCL_TARGETS) endif() @@ -790,20 +793,15 @@ else() endif() # Vulkan -if(NOT APPLE) - option(WITH_VULKAN_BACKEND "Enable Vulkan as graphics backend (experimental)" ON) - option(WITH_VULKAN_GUARDEDALLOC "\ - Use guardedalloc for host allocations done inside Vulkan (development option)" - OFF - ) - mark_as_advanced( - WITH_VULKAN_BACKEND - WITH_VULKAN_GUARDEDALLOC - ) - if(NOT WITH_EXPERIMENTAL_FEATURES) - set(WITH_VULKAN_BACKEND OFF) - endif() -endif() +option(WITH_VULKAN_BACKEND "Enable Vulkan as graphics backend (only for development)" OFF) +option(WITH_VULKAN_GUARDEDALLOC "\ +Use guardedalloc for host allocations done inside Vulkan (development option)" + OFF +) +mark_as_advanced( + WITH_VULKAN_BACKEND + WITH_VULKAN_GUARDEDALLOC +) # Metal if(APPLE) @@ -846,12 +844,6 @@ Build and link against address sanitizer (only for Debug & RelWithDebInfo target OFF ) mark_as_advanced(WITH_COMPILER_ASAN) -option(WITH_COMPILER_ASAN_EXTERN "\ -Build `extern` dependencies with address sanitizer when WITH_COMPILER_ASAN is on. \ -Can cause linking issues due to too large binary size." - OFF -) -mark_as_advanced(WITH_EXTERN_ASAN) if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_C_COMPILER_ID MATCHES "Clang") if(WITH_COMPILER_ASAN) @@ -953,10 +945,7 @@ Include the files needed for debugging python scripts with visual studio 2017+." option(WITH_WINDOWS_SCCACHE "Use sccache to speed up builds (Ninja builder only)" OFF) mark_as_advanced(WITH_WINDOWS_SCCACHE) - option(WITH_WINDOWS_RELEASE_PDB "\ -Generate a pdb file for client side stacktraces for release builds" - ON - ) + option(WITH_WINDOWS_RELEASE_PDB "Generate a pdb file for client side stacktraces for release builds" ON) mark_as_advanced(WITH_WINDOWS_RELEASE_PDB) option(WITH_WINDOWS_RELEASE_STRIPPED_PDB "Use a stripped PDB file for release builds" ON) @@ -1140,19 +1129,17 @@ if(WITH_INSTALL_PORTABLE) set(CMAKE_SKIP_BUILD_RPATH TRUE) endif() -if(UNIX AND NOT (APPLE OR HAIKU)) - set_and_warn_incompatible(WITH_HEADLESS WITH_GHOST_WAYLAND OFF) - set_and_warn_incompatible(WITH_HEADLESS WITH_GHOST_X11 OFF) +if(WITH_GHOST_SDL OR WITH_HEADLESS) + message(STATUS "Disabling Ghost Wayland, X11, Input IME, and OpenXR") + set(WITH_GHOST_WAYLAND OFF) + set(WITH_GHOST_X11 OFF) + set(WITH_X11_XINPUT OFF) + set(WITH_X11_XF86VMODE OFF) + set(WITH_X11_XFIXES OFF) + set(WITH_GHOST_XDND OFF) + set(WITH_INPUT_IME OFF) + set(WITH_XR_OPENXR OFF) endif() -set_and_warn_incompatible(WITH_HEADLESS WITH_GHOST_SDL OFF) - -if(WITH_INPUT_IME) - set_and_warn_incompatible(WITH_HEADLESS WITH_INPUT_IME OFF) - set_and_warn_incompatible(WITH_GHOST_SDL WITH_INPUT_IME OFF) -endif() - -set_and_warn_incompatible(WITH_HEADLESS WITH_XR_OPENXR OFF) -set_and_warn_incompatible(WITH_GHOST_SDL WITH_XR_OPENXR OFF) if(WITH_BUILDINFO) find_package(Git) @@ -1374,27 +1361,23 @@ if(WITH_CPU_SIMD) endif() endif() endif() -endif() -# Print instructions used on first run. -if(FIRST_RUN) - if(WITH_CPU_SIMD) - if(SUPPORT_NEON_BUILD) - if(SSE2NEON_FOUND) - message(STATUS "Neon SIMD instructions enabled") - else() - message(STATUS "Neon SIMD instructions detected but unused, requires sse2neon") - endif() - elseif(SUPPORT_SSE2_BUILD) - message(STATUS "SSE2 SIMD instructions enabled") - elseif(SUPPORT_SSE_BUILD) - message(STATUS "SSE SIMD instructions enabled") + # Print instructions used + if(SUPPORT_NEON_BUILD) + if(SSE2NEON_FOUND) + message(STATUS "Neon SIMD instructions enabled") else() - message(STATUS "No SIMD instructions detected") + message(STATUS "Neon SIMD instructions detected but unused, requires sse2neon") endif() + elseif(SUPPORT_SSE2_BUILD) + message(STATUS "SSE2 SIMD instructions enabled") + elseif(SUPPORT_SSE_BUILD) + message(STATUS "SSE SIMD instructions enabled") else() - message(STATUS "SIMD instructions disabled") + message(STATUS "No SIMD instructions detected") endif() +else() + message(STATUS "SIMD instructions disabled") endif() # set the endian define @@ -1533,8 +1516,13 @@ endif() # ----------------------------------------------------------------------------- # Configure Python -# Not currently supported due to different required Python link flags. -set_and_warn_incompatible(WITH_PYTHON_MODULE WITH_GTESTS OFF) +if(WITH_PYTHON_MODULE) + # Not currently supported due to different required Python link flags. + if(WITH_GTESTS) + message(STATUS "GTests not compatible with Python module, disabling WITH_GTESTS") + set(WITH_GTESTS OFF) + endif() +endif() # ----------------------------------------------------------------------------- @@ -1903,7 +1891,6 @@ elseif(CMAKE_C_COMPILER_ID MATCHES "Clang") C_WARN_NO_STRICT_PROTOTYPES -Wno-strict-prototypes C_WARN_NO_BITWISE_INSTEAD_OF_LOGICAL -Wno-bitwise-instead-of-logical C_WARN_NO_IMPLICIT_CONST_INT_FLOAT_CONVERSION -Wno-implicit-const-int-float-conversion - C_WARN_NO_SINGLE_BIT_BITFIELD_CONSTANT_CONVERSION -Wno-single-bit-bitfield-constant-conversion ) add_check_cxx_compiler_flags( @@ -2286,6 +2273,7 @@ if(FIRST_RUN) if(WITH_GHOST_WAYLAND) info_cfg_option(WITH_GHOST_WAYLAND_DYNLOAD) info_cfg_option(WITH_GHOST_WAYLAND_LIBDECOR) + info_cfg_option(WITH_GHOST_WAYLAND_DBUS) endif() endif() diff --git a/build_files/build_environment/cmake/download.cmake b/build_files/build_environment/cmake/download.cmake index 58058b60a19..4b36684df91 100644 --- a/build_files/build_environment/cmake/download.cmake +++ b/build_files/build_environment/cmake/download.cmake @@ -3,7 +3,7 @@ # SPDX-License-Identifier: GPL-2.0-or-later ## Update and uncomment this in the release branch -# set(BLENDER_VERSION 3.1) +set(BLENDER_VERSION 4.0) function(download_source dep) set(TARGET_FILE ${${dep}_FILE}) diff --git a/build_files/cmake/Modules/FindWebP.cmake b/build_files/cmake/Modules/FindWebP.cmake index 25a91e2a106..390986d4881 100644 --- a/build_files/cmake/Modules/FindWebP.cmake +++ b/build_files/cmake/Modules/FindWebP.cmake @@ -57,7 +57,7 @@ foreach(COMPONENT ${_webp_FIND_COMPONENTS}) PATH_SUFFIXES lib64 lib lib/static ) - if(WEBP_${UPPERCOMPONENT}_LIBRARY) + if (WEBP_${UPPERCOMPONENT}_LIBRARY) list(APPEND _webp_LIBRARIES "${WEBP_${UPPERCOMPONENT}_LIBRARY}") endif() endforeach() diff --git a/build_files/cmake/macros.cmake b/build_files/cmake/macros.cmake index 4b5b164540c..c942d2d86ac 100644 --- a/build_files/cmake/macros.cmake +++ b/build_files/cmake/macros.cmake @@ -1131,7 +1131,7 @@ function(data_to_c set(optional_args "") foreach(f ${ARGN}) - if(f STREQUAL "STRIP_LEADING_C_COMMENTS") + if (f STREQUAL "STRIP_LEADING_C_COMMENTS") set(optional_args "--options=strip_leading_c_comments") else() message(FATAL_ERROR "Unknown optional argument ${f} to \"data_to_c\"") @@ -1168,7 +1168,7 @@ function(data_to_c_simple set(optional_args "") foreach(f ${ARGN}) - if(f STREQUAL "STRIP_LEADING_C_COMMENTS") + if (f STREQUAL "STRIP_LEADING_C_COMMENTS") set(optional_args "--options=strip_leading_c_comments") else() message(FATAL_ERROR "Unknown optional argument ${f} to \"data_to_c_simple\"") @@ -1526,24 +1526,6 @@ macro(set_and_warn_dependency endif() endmacro() -macro(set_and_warn_incompatible - _dependency _setting _val) - # when $_dependency is enabled, forces $_setting = $_val - # Both should be defined, warn if they're not. - if(NOT DEFINED ${_dependency}) - message(STATUS "${_dependency} not defined!") - elseif(NOT DEFINED ${_setting}) - message(STATUS "${_setting} not defined!") - elseif(${${_dependency}} AND ${${_setting}}) - if(WITH_STRICT_BUILD_OPTIONS) - message(SEND_ERROR "${_dependency} enabled but incompatible with ${_setting}") - else() - message(STATUS "${_dependency} is enabled but incompatible, setting ${_setting}=${_val}") - endif() - set(${_setting} ${_val}) - endif() -endmacro() - macro(set_and_warn_library_found _library_name _library_found _setting) if(((NOT ${_library_found}) OR (NOT ${${_library_found}})) AND ${${_setting}}) diff --git a/build_files/cmake/platform/platform_apple.cmake b/build_files/cmake/platform/platform_apple.cmake index 8e124b6c163..fb9386b8af6 100644 --- a/build_files/cmake/platform/platform_apple.cmake +++ b/build_files/cmake/platform/platform_apple.cmake @@ -54,9 +54,7 @@ if(NOT DEFINED LIBDIR) set(LIBDIR ${CMAKE_SOURCE_DIR}/../lib/darwin_${CMAKE_OSX_ARCHITECTURES}) endif() else() - if(FIRST_RUN) - message(STATUS "Using pre-compiled LIBDIR: ${LIBDIR}") - endif() + message(STATUS "Using pre-compiled LIBDIR: ${LIBDIR}") endif() if(NOT EXISTS "${LIBDIR}/") message(FATAL_ERROR "Mac OSX requires pre-compiled libs at: '${LIBDIR}'") @@ -104,6 +102,12 @@ if(WITH_MATERIALX) endif() add_bundled_libraries(materialx/lib) +if(WITH_VULKAN_BACKEND) + find_package(MoltenVK REQUIRED) + find_package(ShaderC REQUIRED) + find_package(Vulkan REQUIRED) +endif() + if(WITH_OPENSUBDIV) find_package(OpenSubdiv) endif() diff --git a/build_files/cmake/platform/platform_unix.cmake b/build_files/cmake/platform/platform_unix.cmake index c436c97e485..fbf7d47aa42 100644 --- a/build_files/cmake/platform/platform_unix.cmake +++ b/build_files/cmake/platform/platform_unix.cmake @@ -46,13 +46,12 @@ else() endif() endif() + # Support restoring this value once pre-compiled libraries have been handled. set(WITH_STATIC_LIBS_INIT ${WITH_STATIC_LIBS}) if(DEFINED LIBDIR) - if(FIRST_RUN) - message(STATUS "Using pre-compiled LIBDIR: ${LIBDIR}") - endif() + message(STATUS "Using pre-compiled LIBDIR: ${LIBDIR}") file(GLOB LIB_SUBDIRS ${LIBDIR}/*) @@ -119,24 +118,17 @@ if(WITH_VULKAN_BACKEND) find_package_wrapper(Vulkan REQUIRED) find_package_wrapper(ShaderC REQUIRED) endif() -add_bundled_libraries(vulkan/lib) function(check_freetype_for_brotli) - if((DEFINED HAVE_BROTLI AND HAVE_BROTLI) AND - (DEFINED HAVE_BROTLI_INC AND ("${HAVE_BROTLI_INC}" STREQUAL "${FREETYPE_INCLUDE_DIRS}"))) - # Pass, the includes didn't change, use the cached value. - else() + include(CheckSymbolExists) + set(CMAKE_REQUIRED_INCLUDES ${FREETYPE_INCLUDE_DIRS}) + check_symbol_exists(FT_CONFIG_OPTION_USE_BROTLI "freetype/config/ftconfig.h" HAVE_BROTLI) + unset(CMAKE_REQUIRED_INCLUDES) + if(NOT HAVE_BROTLI) unset(HAVE_BROTLI CACHE) - include(CheckSymbolExists) - set(CMAKE_REQUIRED_INCLUDES ${FREETYPE_INCLUDE_DIRS}) - check_symbol_exists(FT_CONFIG_OPTION_USE_BROTLI "freetype/config/ftconfig.h" HAVE_BROTLI) - unset(CMAKE_REQUIRED_INCLUDES) - if(NOT HAVE_BROTLI) - unset(HAVE_BROTLI CACHE) - message(FATAL_ERROR "Freetype needs to be compiled with brotli support!") - endif() - set(HAVE_BROTLI_INC "${FREETYPE_INCLUDE_DIRS}" CACHE INTERNAL "") + message(FATAL_ERROR "Freetype needs to be compiled with brotli support!") endif() + unset(HAVE_BROTLI CACHE) endfunction() if(NOT WITH_SYSTEM_FREETYPE) @@ -551,9 +543,7 @@ if(WITH_CYCLES AND WITH_CYCLES_PATH_GUIDING) if(openpgl_FOUND) get_target_property(OPENPGL_LIBRARIES openpgl::openpgl LOCATION) get_target_property(OPENPGL_INCLUDE_DIR openpgl::openpgl INTERFACE_INCLUDE_DIRECTORIES) - if(FIRST_RUN) - message(STATUS "Found OpenPGL: ${OPENPGL_LIBRARIES}") - endif() + message(STATUS "Found OpenPGL: ${OPENPGL_LIBRARIES}") else() set(WITH_CYCLES_PATH_GUIDING OFF) message(STATUS "OpenPGL not found, disabling WITH_CYCLES_PATH_GUIDING") @@ -720,6 +710,10 @@ if(WITH_GHOST_WAYLAND) set_and_warn_library_found("xkbcommon" xkbcommon_FOUND WITH_GHOST_WAYLAND) if(WITH_GHOST_WAYLAND) + if(WITH_GHOST_WAYLAND_DBUS) + pkg_check_modules(dbus REQUIRED dbus-1) + endif() + if(WITH_GHOST_WAYLAND_LIBDECOR) if(_use_system_wayland) pkg_check_modules(libdecor libdecor-0>=0.1) @@ -730,6 +724,10 @@ if(WITH_GHOST_WAYLAND) set_and_warn_library_found("libdecor" libdecor_FOUND WITH_GHOST_WAYLAND_LIBDECOR) endif() + if(WITH_GHOST_WAYLAND_DBUS) + add_definitions(-DWITH_GHOST_WAYLAND_DBUS) + endif() + if(WITH_GHOST_WAYLAND_LIBDECOR) add_definitions(-DWITH_GHOST_WAYLAND_LIBDECOR) endif() diff --git a/build_files/cmake/platform/platform_win32.cmake b/build_files/cmake/platform/platform_win32.cmake index 14ad89adee6..6efb768cb8f 100644 --- a/build_files/cmake/platform/platform_win32.cmake +++ b/build_files/cmake/platform/platform_win32.cmake @@ -278,9 +278,7 @@ if(NOT DEFINED LIBDIR) set(LIBDIR ${CMAKE_SOURCE_DIR}/../lib/${LIBDIR_BASE}_vc15) endif() else() - if(FIRST_RUN) - message(STATUS "Using pre-compiled LIBDIR: ${LIBDIR}") - endif() + message(STATUS "Using pre-compiled LIBDIR: ${LIBDIR}") endif() if(NOT EXISTS "${LIBDIR}/") message(FATAL_ERROR "\n\nWindows requires pre-compiled libs at: '${LIBDIR}'. Please run `make update` in the blender source folder to obtain them.") diff --git a/build_files/config/pipeline_config.yaml b/build_files/config/pipeline_config.yaml index 193a493318c..379f6477d61 100644 --- a/build_files/config/pipeline_config.yaml +++ b/build_files/config/pipeline_config.yaml @@ -19,7 +19,7 @@ buildbot: optix: version: '7.3.0' ocloc: - version: '101.4723' + version: '101.4369' cmake: default: version: any diff --git a/doc/doxygen/Doxyfile b/doc/doxygen/Doxyfile index 80eaa0ec556..f03012d7c5d 100644 --- a/doc/doxygen/Doxyfile +++ b/doc/doxygen/Doxyfile @@ -38,7 +38,7 @@ PROJECT_NAME = Blender # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = V4.1 +PROJECT_NUMBER = V4.0 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index 6040c5f938b..381f6d8ee6a 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -20,14 +20,6 @@ elseif(CMAKE_C_COMPILER_ID MATCHES "Clang") ) endif() -if(WITH_COMPILER_ASAN AND NOT WITH_COMPILER_ASAN_EXTERN) - # Disable ASAN for extern dependencies, as it can lead to linking issues due to too large binaries. - string(APPEND CMAKE_CXX_FLAGS_RELWITHDEBINFO " -fno-sanitize=all") - string(APPEND CMAKE_C_FLAGS_RELWITHDEBINFO " -fno-sanitize=all") - string(APPEND CMAKE_CXX_FLAGS_DEBUG " -fno-sanitize=all") - string(APPEND CMAKE_C_FLAGS_DEBUG " -fno-sanitize=all") -endif() - add_subdirectory(rangetree) add_subdirectory(nanosvg) diff --git a/extern/vulkan_memory_allocator/CMakeLists.txt b/extern/vulkan_memory_allocator/CMakeLists.txt index cc3220695e0..b343a99173b 100644 --- a/extern/vulkan_memory_allocator/CMakeLists.txt +++ b/extern/vulkan_memory_allocator/CMakeLists.txt @@ -8,23 +8,15 @@ set(INC set(INC_SYS ${VULKAN_INCLUDE_DIRS} + ${MOLTENVK_INCLUDE_DIRS} ) -if(APPLE) - list(APPEND INC_SYS - ${MOLTENVK_INCLUDE_DIRS} - ) -endif() - set(SRC vk_mem_alloc_impl.cc vk_mem_alloc.h ) -set(LIB -) - blender_add_lib(extern_vulkan_memory_allocator "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_C_COMPILER_ID MATCHES "Clang") diff --git a/extern/wcwidth/wcwidth.h b/extern/wcwidth/wcwidth.h index 320a6ea130d..08e398f85eb 100644 --- a/extern/wcwidth/wcwidth.h +++ b/extern/wcwidth/wcwidth.h @@ -30,17 +30,9 @@ typedef unsigned int char32_t; # endif #endif -#ifdef __cplusplus -extern "C" { -#endif - int mk_wcwidth(char32_t ucs); int mk_wcswidth(const char32_t *pwcs, size_t n); int mk_wcwidth_cjk(char32_t ucs); int mk_wcswidth_cjk(const char32_t *pwcs, size_t n); -#ifdef __cplusplus -} -#endif - #endif diff --git a/intern/cycles/blender/addon/properties.py b/intern/cycles/blender/addon/properties.py index 7ddbb90b35a..61c1a17ff6b 100644 --- a/intern/cycles/blender/addon/properties.py +++ b/intern/cycles/blender/addon/properties.py @@ -1019,11 +1019,6 @@ class CyclesMaterialSettings(bpy.types.PropertyGroup): "disabling will render faster but not give accurate shadows", default=True, ) - use_bump_map_correction: BoolProperty( - name="Bump Map Correction", - description="Apply corrections to solve shadow terminator artifacts caused by bump mapping", - default=True, - ) homogeneous_volume: BoolProperty( name="Homogeneous Volume", description="When using volume rendering, assume volume has the same density everywhere " @@ -1648,7 +1643,7 @@ class CyclesPreferences(bpy.types.AddonPreferences): elif device_type == 'ONEAPI': import sys if sys.platform.startswith("win"): - driver_version = "XX.X.101.4824" + driver_version = "XX.X.101.4644" col.label(text="Requires Intel GPU with Xe-HPG architecture", icon='BLANK1') col.label(text=iface_("and Windows driver version %s or newer") % driver_version, icon='BLANK1', translate=False) diff --git a/intern/cycles/blender/addon/ui.py b/intern/cycles/blender/addon/ui.py index 749d32cc52a..e7726a53d72 100644 --- a/intern/cycles/blender/addon/ui.py +++ b/intern/cycles/blender/addon/ui.py @@ -1967,7 +1967,6 @@ class CYCLES_MATERIAL_PT_settings_surface(CyclesButtonsPanel, Panel): col.prop(cmat, "displacement_method", text="Displacement") col.prop(cmat, "emission_sampling") col.prop(cmat, "use_transparent_shadow") - col.prop(cmat, "use_bump_map_correction") def draw(self, context): self.draw_shared(self, context.material) diff --git a/intern/cycles/blender/mesh.cpp b/intern/cycles/blender/mesh.cpp index cdfeadb806a..00586b6cf00 100644 --- a/intern/cycles/blender/mesh.cpp +++ b/intern/cycles/blender/mesh.cpp @@ -816,10 +816,9 @@ static void create_mesh(Scene *scene, const blender::OffsetIndices faces = b_mesh.faces(); const blender::Span corner_verts = b_mesh.corner_verts(); const blender::bke::AttributeAccessor b_attributes = b_mesh.attributes(); - const blender::bke::MeshNormalDomain normals_domain = b_mesh.normals_domain(); int numfaces = (!subdivision) ? b_mesh.looptris().size() : faces.size(); - bool use_loop_normals = normals_domain == blender::bke::MeshNormalDomain::Corner && + bool use_loop_normals = (b_mesh.flag & ME_AUTOSMOOTH) && (mesh->get_subdivision_type() != Mesh::SUBDIVISION_CATMULL_CLARK); /* If no faces, create empty mesh. */ @@ -833,7 +832,9 @@ static void create_mesh(Scene *scene, ATTR_DOMAIN_FACE); blender::Span corner_normals; if (use_loop_normals) { - corner_normals = b_mesh.corner_normals(); + corner_normals = { + static_cast(CustomData_get_layer(&b_mesh.loop_data, CD_NORMAL)), + corner_verts.size()}; } int numngons = 0; @@ -938,8 +939,7 @@ static void create_mesh(Scene *scene, } } else { - /* If only face normals are needed, all faces are sharp. */ - std::fill(smooth, smooth + numtris, normals_domain != blender::bke::MeshNormalDomain::Face); + std::fill(smooth, smooth + numtris, true); } if (use_loop_normals && !corner_normals.is_empty()) { diff --git a/intern/cycles/blender/shader.cpp b/intern/cycles/blender/shader.cpp index 7e131ea9749..7f8cce86776 100644 --- a/intern/cycles/blender/shader.cpp +++ b/intern/cycles/blender/shader.cpp @@ -1551,7 +1551,6 @@ void BlenderSync::sync_materials(BL::Depsgraph &b_depsgraph, bool update_all) PointerRNA cmat = RNA_pointer_get(&b_mat.ptr, "cycles"); shader->set_emission_sampling_method(get_emission_sampling(cmat)); shader->set_use_transparent_shadow(get_boolean(cmat, "use_transparent_shadow")); - shader->set_use_bump_map_correction(get_boolean(cmat, "use_bump_map_correction")); shader->set_heterogeneous_volume(!get_boolean(cmat, "homogeneous_volume")); shader->set_volume_sampling_method(get_volume_sampling(cmat)); shader->set_volume_interpolation_method(get_volume_interpolation(cmat)); diff --git a/intern/cycles/blender/util.h b/intern/cycles/blender/util.h index 7ced0d48136..7583e87f4e3 100644 --- a/intern/cycles/blender/util.h +++ b/intern/cycles/blender/util.h @@ -94,9 +94,8 @@ static inline BL::Mesh object_to_mesh(BL::BlendData & /*data*/, /* Make a copy to split faces if we use auto-smooth, 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.normals_domain() == BL::Mesh::normals_domain_CORNER && - subdivision_type == Mesh::SUBDIVISION_NONE)) - { + 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); } @@ -120,7 +119,8 @@ static inline BL::Mesh object_to_mesh(BL::BlendData & /*data*/, #endif if ((bool)mesh && subdivision_type == Mesh::SUBDIVISION_NONE) { - if (mesh.normals_domain() == BL::Mesh::normals_domain_CORNER) { + if (mesh.use_auto_smooth()) { + mesh.calc_normals_split(); mesh.split_faces(); } diff --git a/intern/cycles/cmake/macros.cmake b/intern/cycles/cmake/macros.cmake index 1dba462d273..0e1e3cc67ea 100644 --- a/intern/cycles/cmake/macros.cmake +++ b/intern/cycles/cmake/macros.cmake @@ -128,7 +128,7 @@ macro(cycles_external_libraries_append libraries) if(WITH_PATH_GUIDING) list(APPEND ${libraries} ${OPENPGL_LIBRARIES}) endif() - if(WITH_WEBP) + if (WITH_WEBP) list(APPEND ${libraries} ${WEBP_LIBRARIES}) endif() if(UNIX AND NOT APPLE) diff --git a/intern/cycles/device/oneapi/device_impl.cpp b/intern/cycles/device/oneapi/device_impl.cpp index 4d9d79ec930..4928b1124fc 100644 --- a/intern/cycles/device/oneapi/device_impl.cpp +++ b/intern/cycles/device/oneapi/device_impl.cpp @@ -853,11 +853,11 @@ void OneapiDevice::get_adjusted_global_and_local_sizes(SyclQueue *queue, /* Compute-runtime (ie. NEO) version is what gets returned by sycl/L0 on Windows * since Windows driver 101.3268. */ -static const int lowest_supported_driver_version_win = 1014824; +static const int lowest_supported_driver_version_win = 1014644; # ifdef _WIN32 -/* For Windows driver 101.4824, compute-runtime version is 26957. +/* For Windows driver 101.4644, compute-runtime version is 26771. * This information is returned by `ocloc query OCL_DRIVER_VERSION`.*/ -static const int lowest_supported_driver_version_neo = 26957; +static const int lowest_supported_driver_version_neo = 26771; # else static const int lowest_supported_driver_version_neo = 25812; # endif diff --git a/intern/cycles/integrator/tile.cpp b/intern/cycles/integrator/tile.cpp index b930ce59aac..02a0de56411 100644 --- a/intern/cycles/integrator/tile.cpp +++ b/intern/cycles/integrator/tile.cpp @@ -7,8 +7,6 @@ #include "util/log.h" #include "util/math.h" -#include - CCL_NAMESPACE_BEGIN std::ostream &operator<<(std::ostream &os, const TileSize &tile_size) diff --git a/intern/cycles/kernel/CMakeLists.txt b/intern/cycles/kernel/CMakeLists.txt index 8fa7343dfb0..604ccb81359 100644 --- a/intern/cycles/kernel/CMakeLists.txt +++ b/intern/cycles/kernel/CMakeLists.txt @@ -43,7 +43,7 @@ set(SRC_KERNEL_DEVICE_OPTIX if(WITH_CYCLES_OSL) math(EXPR OSL_LIBRARY_VERSION_CODE "${OSL_LIBRARY_VERSION_MAJOR} * 10000 + ${OSL_LIBRARY_VERSION_MINOR} * 100 + ${OSL_LIBRARY_VERSION_PATCH}") - if(OSL_LIBRARY_VERSION_CODE GREATER_EQUAL 11300) + if (OSL_LIBRARY_VERSION_CODE GREATER_EQUAL 11300) set(SRC_KERNEL_DEVICE_OPTIX ${SRC_KERNEL_DEVICE_OPTIX} osl/services_optix.cu @@ -884,32 +884,7 @@ if(WITH_CYCLES_DEVICE_ONEAPI) endif() # Enable zebin, a graphics binary format with improved compatibility. string(PREPEND CYCLES_ONEAPI_SYCL_OPTIONS_spir64_gen "--format zebin ") - - # Add the list of Intel devices to build binaries for. - foreach(device ${CYCLES_ONEAPI_INTEL_BINARIES_ARCH}) - # Run ocloc ids to test if the device is supported. - if(WIN32) - execute_process(COMMAND ${OCLOC_INSTALL_DIR}/ocloc.exe ids ${device} - RESULT_VARIABLE oclocids_ret - OUTPUT_QUIET - ERROR_QUIET) - else() - execute_process(COMMAND ${CMAKE_COMMAND} - -E env "LD_LIBRARY_PATH=${OCLOC_INSTALL_DIR}/lib:${IGC_INSTALL_DIR}/lib" - ${OCLOC_INSTALL_DIR}/bin/ocloc ids ${device} - RESULT_VARIABLE oclocids_ret - OUTPUT_QUIET - ERROR_QUIET) - endif() - if(NOT oclocids_ret EQUAL 0) - list(REMOVE_ITEM CYCLES_ONEAPI_INTEL_BINARIES_ARCH ${device}) - message(STATUS "binaries for ${device} not supported by Intel Graphics Compiler/ocloc, skipped.") - endif() - endforeach() - list(JOIN CYCLES_ONEAPI_INTEL_BINARIES_ARCH "," gen_devices_string) - if(NOT "${gen_devices_string}" STREQUAL "") - string(PREPEND CYCLES_ONEAPI_SYCL_OPTIONS_spir64_gen "-device ${gen_devices_string} ") - endif() + string(PREPEND CYCLES_ONEAPI_SYCL_OPTIONS_spir64_gen "-device ${CYCLES_ONEAPI_SPIR64_GEN_DEVICES} ") # Host execution won't use GPU binaries, no need to compile them. if(WITH_CYCLES_ONEAPI_BINARIES AND NOT WITH_CYCLES_ONEAPI_HOST_TASK_EXECUTION) diff --git a/intern/cycles/kernel/closure/bsdf.h b/intern/cycles/kernel/closure/bsdf.h index e6f25dc7df3..6f45b7d2053 100644 --- a/intern/cycles/kernel/closure/bsdf.h +++ b/intern/cycles/kernel/closure/bsdf.h @@ -235,7 +235,7 @@ ccl_device_inline int bsdf_sample(KernelGlobals kg, *eval *= shift_cos_in(cosNO, frequency_multiplier); } if (label & LABEL_DIFFUSE) { - if ((sd->flag & SD_USE_BUMP_MAP_CORRECTION) && !isequal(sc->N, sd->N)) { + if (!isequal(sc->N, sd->N)) { *eval *= bump_shadowing_term(sd->N, sc->N, *wo); } } @@ -529,7 +529,7 @@ ccl_device_inline } if (CLOSURE_IS_BSDF_DIFFUSE(sc->type)) { - if ((sd->flag & SD_USE_BUMP_MAP_CORRECTION) && !isequal(sc->N, sd->N)) { + if (!isequal(sc->N, sd->N)) { eval *= bump_shadowing_term(sd->N, sc->N, wo); } } diff --git a/intern/cycles/kernel/closure/bsdf_util.h b/intern/cycles/kernel/closure/bsdf_util.h index bd9dace413d..e1cec858599 100644 --- a/intern/cycles/kernel/closure/bsdf_util.h +++ b/intern/cycles/kernel/closure/bsdf_util.h @@ -191,9 +191,6 @@ ccl_device float3 ensure_valid_specular_reflection(float3 Ng, float3 I, float3 N * normal and the shading normal is the same. */ ccl_device float3 maybe_ensure_valid_specular_reflection(ccl_private ShaderData *sd, float3 N) { - if ((sd->flag & SD_USE_BUMP_MAP_CORRECTION) == 0) { - return N; - } if ((sd->type & PRIMITIVE_CURVE) || isequal(sd->Ng, N)) { return N; } diff --git a/intern/cycles/kernel/device/oneapi/compat.h b/intern/cycles/kernel/device/oneapi/compat.h index 7c7f4f7e29f..60bce580386 100644 --- a/intern/cycles/kernel/device/oneapi/compat.h +++ b/intern/cycles/kernel/device/oneapi/compat.h @@ -223,7 +223,9 @@ ccl_device_forceinline int __float_as_int(float x) #define fmodf(x, y) sycl::fmod((x), (y)) #define lgammaf(x) sycl::lgamma((x)) -#define cosf(x) sycl::native::cos(((float)(x))) +/* sycl::native::cos precision is not sufficient when using Nishita Sky node + * with a small sun size. */ +#define cosf(x) sycl::cos(((float)(x))) #define sinf(x) sycl::native::sin(((float)(x))) #define powf(x, y) sycl::native::powr(((float)(x)), ((float)(y))) #define tanf(x) sycl::native::tan(((float)(x))) diff --git a/intern/cycles/kernel/integrator/shade_surface.h b/intern/cycles/kernel/integrator/shade_surface.h index e34eb347f13..24ce4692126 100644 --- a/intern/cycles/kernel/integrator/shade_surface.h +++ b/intern/cycles/kernel/integrator/shade_surface.h @@ -492,7 +492,7 @@ ccl_device_forceinline int integrate_surface_bsdf_bssrdf_bounce( /* Update path state */ if (!(label & LABEL_TRANSPARENT)) { INTEGRATOR_STATE_WRITE(state, path, mis_ray_pdf) = mis_pdf; - INTEGRATOR_STATE_WRITE(state, path, mis_origin_n) = sc->N; + INTEGRATOR_STATE_WRITE(state, path, mis_origin_n) = sd->N; INTEGRATOR_STATE_WRITE(state, path, min_ray_pdf) = fminf( unguided_bsdf_pdf, INTEGRATOR_STATE(state, path, min_ray_pdf)); } diff --git a/intern/cycles/kernel/light/area.h b/intern/cycles/kernel/light/area.h index 197aefb4216..7d870fe15fc 100644 --- a/intern/cycles/kernel/light/area.h +++ b/intern/cycles/kernel/light/area.h @@ -45,7 +45,7 @@ ccl_device_inline float area_light_rect_sample(float3 P, float4 diff = make_float4(x0, y1, x1, y0) - make_float4(x1, y0, x0, y1); float4 nz = make_float4(y0, x1, y1, x0) * diff; nz = nz / sqrt(z0 * z0 * diff * diff + nz * nz); - /* The original paper uses `acos()` to compute the internal angles here, and then computes the + /* The original paper uses acos() to compute the internal angles here, and then computes the * solid angle as their sum minus 2*pi. However, for very small rectangles, this results in * excessive cancellation error since the sum will be almost 2*pi as well. * This can be avoided by using that `asin(x) = pi/2 - acos(x)`. */ @@ -62,8 +62,8 @@ ccl_device_inline float area_light_rect_sample(float3 P, float b0sq = b0 * b0; /* Compute cu. * In the original paper, an additional constant k is involved here. However, just like above, - * it causes cancellation issues. The same `asin()` terms from above can be used instead, and - * the extra +pi that would remain in the expression for au can be removed by flipping the sign + * it causes cancellation issues. The same asin() terms from above can be used instead, and the + * extra +pi that would remain in the expression for au can be removed by flipping the sign * of cos(au) and sin(au), which also cancels if we flip the sign of b1 in the fu term. */ float au = rand.x * S + g2 + g3; float fu = (cosf(au) * b0 + b1) / sinf(au); diff --git a/intern/cycles/kernel/types.h b/intern/cycles/kernel/types.h index 50713f31357..046f9eff12f 100644 --- a/intern/cycles/kernel/types.h +++ b/intern/cycles/kernel/types.h @@ -836,8 +836,6 @@ enum ShaderDataFlag { /* Shader flags. */ - /* Apply a correction term to smooth illumination on grazing angles when using bump mapping.. */ - SD_USE_BUMP_MAP_CORRECTION = (1 << 15), /* Use front side for direct light sampling. */ SD_MIS_FRONT = (1 << 16), /* Has transparent shadow. */ diff --git a/intern/cycles/scene/shader.cpp b/intern/cycles/scene/shader.cpp index 72db06e2604..e72d4eb2876 100644 --- a/intern/cycles/scene/shader.cpp +++ b/intern/cycles/scene/shader.cpp @@ -54,7 +54,6 @@ NODE_DEFINE(Shader) EMISSION_SAMPLING_AUTO); SOCKET_BOOLEAN(use_transparent_shadow, "Use Transparent Shadow", true); - SOCKET_BOOLEAN(use_bump_map_correction, "Bump Map Correction", true); SOCKET_BOOLEAN(heterogeneous_volume, "Heterogeneous Volume", true); static NodeEnum volume_sampling_method_enum; @@ -591,9 +590,6 @@ void ShaderManager::device_update_common(Device * /*device*/, if (shader->get_displacement_method() != DISPLACE_BUMP) { flag |= SD_HAS_DISPLACEMENT; } - if (shader->get_use_bump_map_correction()) { - flag |= SD_USE_BUMP_MAP_CORRECTION; - } /* constant emission check */ if (shader->emission_is_constant) { diff --git a/intern/cycles/scene/shader.h b/intern/cycles/scene/shader.h index 7fc99d1badd..4466c2e4e69 100644 --- a/intern/cycles/scene/shader.h +++ b/intern/cycles/scene/shader.h @@ -77,7 +77,6 @@ class Shader : public Node { /* sampling */ NODE_SOCKET_API(EmissionSampling, emission_sampling_method) NODE_SOCKET_API(bool, use_transparent_shadow) - NODE_SOCKET_API(bool, use_bump_map_correction) NODE_SOCKET_API(bool, heterogeneous_volume) NODE_SOCKET_API(VolumeSampling, volume_sampling_method) NODE_SOCKET_API(int, volume_interpolation_method) diff --git a/intern/ghost/CMakeLists.txt b/intern/ghost/CMakeLists.txt index 617fe7017fb..99e509f493e 100644 --- a/intern/ghost/CMakeLists.txt +++ b/intern/ghost/CMakeLists.txt @@ -9,6 +9,7 @@ set(INC ) set(INC_SYS + ${Epoxy_INCLUDE_DIRS} ) set(SRC @@ -71,36 +72,16 @@ set(SRC ) set(LIB + ${Epoxy_LIBRARIES} PRIVATE bf::blenlib PRIVATE bf::dna ) -if(WITH_INPUT_IME) - add_definitions(-DWITH_INPUT_IME) -endif() - if(WITH_OPENGL_BACKEND) - list(APPEND INC_SYS - ${Epoxy_INCLUDE_DIRS} - ) - list(APPEND LIB - ${Epoxy_LIBRARIES} - ) add_definitions(-DWITH_OPENGL_BACKEND) endif() if(WITH_VULKAN_BACKEND) - - if(APPLE) - list(APPEND INC_SYS - PUBLIC ${MOLTENVK_INCLUDE_DIRS} - ) - - list(APPEND LIB - ${MOLTENVK_LIBRARIES} - ) - endif() - list(APPEND SRC intern/GHOST_ContextVK.cc @@ -109,10 +90,12 @@ if(WITH_VULKAN_BACKEND) list(APPEND INC_SYS PUBLIC ${VULKAN_INCLUDE_DIRS} + PUBLIC ${MOLTENVK_INCLUDE_DIRS} ) list(APPEND LIB ${VULKAN_LIBRARIES} + ${MOLTENVK_LIBRARIES} ) add_definitions(-DWITH_VULKAN_BACKEND) @@ -181,6 +164,9 @@ elseif(WITH_GHOST_SDL) endif() elseif(APPLE AND NOT WITH_GHOST_X11) + if(WITH_INPUT_IME) + add_definitions(-DWITH_INPUT_IME) + endif() list(APPEND SRC intern/GHOST_DisplayManagerCocoa.mm intern/GHOST_SystemCocoa.mm @@ -230,13 +216,11 @@ elseif(WITH_GHOST_X11 OR WITH_GHOST_WAYLAND) intern/GHOST_WindowX11.hh ) - if(WITH_OPENGL_BACKEND) - list(APPEND SRC - intern/GHOST_ContextGLX.cc + list(APPEND SRC + intern/GHOST_ContextGLX.cc - intern/GHOST_ContextGLX.hh - ) - endif() + intern/GHOST_ContextGLX.hh + ) if(WITH_GHOST_XDND) add_definitions(-DWITH_XDND) @@ -323,6 +307,15 @@ elseif(WITH_GHOST_X11 OR WITH_GHOST_WAYLAND) ) endif() + if(WITH_GHOST_WAYLAND_DBUS) + list(APPEND INC_SYS + ${dbus_INCLUDE_DIRS} + ) + list(APPEND LIB + ${dbus_LINK_LIBRARIES} + ) + endif() + if(WITH_GHOST_WAYLAND_LIBDECOR) list(APPEND INC_SYS ${libdecor_INCLUDE_DIRS} @@ -352,6 +345,7 @@ elseif(WITH_GHOST_X11 OR WITH_GHOST_WAYLAND) intern/GHOST_WindowWayland.cc intern/GHOST_SystemWayland.hh + intern/GHOST_WaylandCursorSettings.hh intern/GHOST_WaylandUtils.hh intern/GHOST_WindowWayland.hh ) @@ -440,11 +434,6 @@ elseif(WITH_GHOST_X11 OR WITH_GHOST_WAYLAND) generate_protocol_bindings( "${WAYLAND_PROTOCOLS_DIR}/unstable/primary-selection/primary-selection-unstable-v1.xml" ) - if(WITH_INPUT_IME) - generate_protocol_bindings( - "${WAYLAND_PROTOCOLS_DIR}/unstable/text-input/text-input-unstable-v3.xml" - ) - endif() unset(INC_DST) @@ -504,6 +493,8 @@ elseif(WIN32) ) if(WITH_INPUT_IME) + add_definitions(-DWITH_INPUT_IME) + list(APPEND SRC intern/GHOST_ImeWin32.cc @@ -521,13 +512,11 @@ elseif(WIN32) endif() if(UNIX AND NOT APPLE) - if(WITH_OPENGL_BACKEND) - list(APPEND SRC - intern/GHOST_ContextEGL.cc + list(APPEND SRC + intern/GHOST_ContextEGL.cc - intern/GHOST_ContextEGL.hh - ) - endif() + intern/GHOST_ContextEGL.hh + ) endif() if(APPLE) diff --git a/intern/ghost/GHOST_IEvent.hh b/intern/ghost/GHOST_IEvent.hh index d14f1c0d469..3391079f4a0 100644 --- a/intern/ghost/GHOST_IEvent.hh +++ b/intern/ghost/GHOST_IEvent.hh @@ -35,26 +35,26 @@ class GHOST_IEvent { * Returns the event type. * \return The event type. */ - virtual GHOST_TEventType getType() const = 0; + virtual GHOST_TEventType getType() = 0; /** * Returns the time this event was generated. * \return The event generation time. */ - virtual uint64_t getTime() const = 0; + virtual uint64_t getTime() = 0; /** * Returns the window this event was generated on, * or nullptr if it is a 'system' event. * \return The generating window. */ - virtual GHOST_IWindow *getWindow() const = 0; + virtual GHOST_IWindow *getWindow() = 0; /** * Returns the event data. * \return The event data. */ - virtual GHOST_TEventDataPtr getData() const = 0; + virtual GHOST_TEventDataPtr getData() = 0; #ifdef WITH_CXX_GUARDEDALLOC MEM_CXX_CLASS_ALLOC_FUNCS("GHOST:GHOST_IEvent") diff --git a/intern/ghost/GHOST_IEventConsumer.hh b/intern/ghost/GHOST_IEventConsumer.hh index 546fd592518..90d71decbbf 100644 --- a/intern/ghost/GHOST_IEventConsumer.hh +++ b/intern/ghost/GHOST_IEventConsumer.hh @@ -32,7 +32,7 @@ class GHOST_IEventConsumer { * \param event: The event that can be handled or ignored. * \return Indication as to whether the event was handled. */ - virtual bool processEvent(const GHOST_IEvent *event) = 0; + virtual bool processEvent(GHOST_IEvent *event) = 0; #ifdef WITH_CXX_GUARDEDALLOC MEM_CXX_CLASS_ALLOC_FUNCS("GHOST:GHOST_IEventConsumer") diff --git a/intern/ghost/GHOST_Types.h b/intern/ghost/GHOST_Types.h index 79494825bef..a2f4738a3be 100644 --- a/intern/ghost/GHOST_Types.h +++ b/intern/ghost/GHOST_Types.h @@ -119,10 +119,6 @@ typedef enum { * Support for sampling a color outside of the Blender windows. */ GHOST_kCapabilityDesktopSample = (1 << 5), - /** - * Supports IME text input methods (when `WITH_INPUT_IME` is defined). - */ - GHOST_kCapabilityInputIME = (1 << 6), } GHOST_TCapabilityFlag; /** @@ -132,7 +128,7 @@ typedef enum { #define GHOST_CAPABILITY_FLAG_ALL \ (GHOST_kCapabilityCursorWarp | GHOST_kCapabilityWindowPosition | \ GHOST_kCapabilityPrimaryClipboard | GHOST_kCapabilityGPUReadFrontBuffer | \ - GHOST_kCapabilityClipboardImages | GHOST_kCapabilityDesktopSample | GHOST_kCapabilityInputIME) + GHOST_kCapabilityClipboardImages | GHOST_kCapabilityDesktopSample) /* Xtilt and Ytilt represent how much the pen is tilted away from * vertically upright in either the X or Y direction, with X and Y the @@ -308,6 +304,8 @@ typedef enum { GHOST_kEventOpenMainFile, /* Needed for Cocoa to open double-clicked .blend file at startup. */ GHOST_kEventNativeResolutionChange, /* Needed for Cocoa when window moves to other display. */ + GHOST_kEventTimer, + GHOST_kEventImeCompositionStart, GHOST_kEventImeComposition, GHOST_kEventImeCompositionEnd, @@ -539,7 +537,7 @@ typedef enum { GHOST_kAxisY = (1 << 1), } GHOST_TAxisFlag; -typedef const void *GHOST_TEventDataPtr; +typedef void *GHOST_TEventDataPtr; typedef struct { /** The x-coordinate of the cursor position. */ @@ -593,8 +591,6 @@ typedef enum { GHOST_kDragnDropTypeBitmap /* Bitmap image data. */ } GHOST_TDragnDropTypes; -typedef void *GHOST_TDragnDropDataPtr; - typedef struct { /** The x-coordinate of the cursor position. */ int32_t x; @@ -603,13 +599,10 @@ typedef struct { /** The dropped item type */ GHOST_TDragnDropTypes dataType; /** The "dropped content" */ - GHOST_TDragnDropDataPtr data; + GHOST_TEventDataPtr data; } GHOST_TEventDragnDropData; -/** - * \warning this is a duplicate of #wmImeData. - * All members must remain aligned and the struct size match! - */ +/** similar to wmImeData */ typedef struct { /** size_t */ GHOST_TUserDataPtr result_len, composite_len; diff --git a/intern/ghost/intern/GHOST_CallbackEventConsumer.cc b/intern/ghost/intern/GHOST_CallbackEventConsumer.cc index 25cf79823ed..7933c128cbe 100644 --- a/intern/ghost/intern/GHOST_CallbackEventConsumer.cc +++ b/intern/ghost/intern/GHOST_CallbackEventConsumer.cc @@ -21,7 +21,7 @@ GHOST_CallbackEventConsumer::GHOST_CallbackEventConsumer(GHOST_EventCallbackProc m_userData = userData; } -bool GHOST_CallbackEventConsumer::processEvent(const GHOST_IEvent *event) +bool GHOST_CallbackEventConsumer::processEvent(GHOST_IEvent *event) { return m_eventCallback((GHOST_EventHandle)event, m_userData); } diff --git a/intern/ghost/intern/GHOST_CallbackEventConsumer.hh b/intern/ghost/intern/GHOST_CallbackEventConsumer.hh index 4966e56e0d5..2495aecf67f 100644 --- a/intern/ghost/intern/GHOST_CallbackEventConsumer.hh +++ b/intern/ghost/intern/GHOST_CallbackEventConsumer.hh @@ -36,7 +36,7 @@ class GHOST_CallbackEventConsumer : public GHOST_IEventConsumer { * \param event: The event that can be handled or ignored. * \return Indication as to whether the event was handled. */ - bool processEvent(const GHOST_IEvent *event); + bool processEvent(GHOST_IEvent *event); protected: /** The call-back routine invoked. */ diff --git a/intern/ghost/intern/GHOST_Context.cc b/intern/ghost/intern/GHOST_Context.cc index 0fd7e35f3cb..5c96d4a8054 100644 --- a/intern/ghost/intern/GHOST_Context.cc +++ b/intern/ghost/intern/GHOST_Context.cc @@ -22,9 +22,7 @@ # endif #endif -#ifdef WITH_OPENGL_BACKEND -# include -#endif +#include #include #include @@ -129,11 +127,9 @@ bool win32_chk(bool result, const char *file, int line, const char *text) #endif // _WIN32 -#ifdef WITH_OPENGL_BACKEND void GHOST_Context::initClearGL() { glClearColor(0.294, 0.294, 0.294, 0.000); glClear(GL_COLOR_BUFFER_BIT); glClearColor(0.000, 0.000, 0.000, 0.000); } -#endif diff --git a/intern/ghost/intern/GHOST_Context.hh b/intern/ghost/intern/GHOST_Context.hh index acd22d426a8..b35a1d169ab 100644 --- a/intern/ghost/intern/GHOST_Context.hh +++ b/intern/ghost/intern/GHOST_Context.hh @@ -187,9 +187,7 @@ class GHOST_Context : public GHOST_IContext { /** Caller specified, not for internal use. */ void *m_user_data = nullptr; -#ifdef WITH_OPENGL_BACKEND static void initClearGL(); -#endif #ifdef WITH_CXX_GUARDEDALLOC MEM_CXX_CLASS_ALLOC_FUNCS("GHOST:GHOST_Context") diff --git a/intern/ghost/intern/GHOST_ContextCGL.hh b/intern/ghost/intern/GHOST_ContextCGL.hh index 9ff23bff6dc..abec8717bcb 100644 --- a/intern/ghost/intern/GHOST_ContextCGL.hh +++ b/intern/ghost/intern/GHOST_ContextCGL.hh @@ -162,6 +162,8 @@ class GHOST_ContextCGL : public GHOST_Context { int mtl_SwapInterval; const bool m_debug; + /** The first created OpenGL context (for sharing display lists) */ + static NSOpenGLContext *s_sharedOpenGLContext; static int s_sharedCount; /* Single device queue for multiple contexts. */ diff --git a/intern/ghost/intern/GHOST_ContextCGL.mm b/intern/ghost/intern/GHOST_ContextCGL.mm index 33c1823ef41..a496e30912b 100644 --- a/intern/ghost/intern/GHOST_ContextCGL.mm +++ b/intern/ghost/intern/GHOST_ContextCGL.mm @@ -20,6 +20,8 @@ #include #include +#include + #include #include @@ -41,6 +43,7 @@ static void ghost_fatal_error_dialog(const char *msg) exit(1); } +NSOpenGLContext *GHOST_ContextCGL::s_sharedOpenGLContext = nil; MTLCommandQueue *GHOST_ContextCGL::s_sharedMetalCommandQueue = nil; int GHOST_ContextCGL::s_sharedCount = 0; diff --git a/intern/ghost/intern/GHOST_ContextD3D.cc b/intern/ghost/intern/GHOST_ContextD3D.cc index 220b9320f01..36bf7f81a57 100644 --- a/intern/ghost/intern/GHOST_ContextD3D.cc +++ b/intern/ghost/intern/GHOST_ContextD3D.cc @@ -256,7 +256,7 @@ class GHOST_SharedOpenGLResource { return GHOST_kFailure; } - /* Build the render-buffer. */ + /* Build the renderbuffer. */ glGenRenderbuffers(1, &m_gl_render_target); glBindRenderbuffer(GL_RENDERBUFFER, m_gl_render_target); @@ -273,7 +273,7 @@ class GHOST_SharedOpenGLResource { reregisterSharedObject(TARGET_TEX2D); } - /* Build the frame-buffer. */ + /* Build the framebuffer */ glGenFramebuffers(1, &m_shared.fbo); glBindFramebuffer(GL_FRAMEBUFFER, m_shared.fbo); if (m_use_gl_texture2d) { diff --git a/intern/ghost/intern/GHOST_ContextEGL.hh b/intern/ghost/intern/GHOST_ContextEGL.hh index f43d37074dc..bb47a2922c4 100644 --- a/intern/ghost/intern/GHOST_ContextEGL.hh +++ b/intern/ghost/intern/GHOST_ContextEGL.hh @@ -8,10 +8,6 @@ #pragma once -#ifndef WITH_OPENGL_BACKEND -# error "ContextEGL requires WITH_OPENGL_BACKEND" -#endif - #include "GHOST_Context.hh" #include "GHOST_System.hh" diff --git a/intern/ghost/intern/GHOST_ContextVK.cc b/intern/ghost/intern/GHOST_ContextVK.cc index 96da8b12d88..ec404493adc 100644 --- a/intern/ghost/intern/GHOST_ContextVK.cc +++ b/intern/ghost/intern/GHOST_ContextVK.cc @@ -179,7 +179,7 @@ class GHOST_DeviceVK { } } - bool has_extensions(const vector &required_extensions) + bool extensions_support(const vector &required_extensions) { uint32_t ext_count; vkEnumerateDeviceExtensionProperties(physical_device, nullptr, &ext_count, nullptr); @@ -231,20 +231,22 @@ class GHOST_DeviceVK { device_features.drawIndirectFirstInstance = VK_TRUE; device_features.fragmentStoresAndAtomics = VK_TRUE; + VkPhysicalDeviceVulkan12Features device_12_features = {}; + device_12_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES; + device_12_features.shaderOutputLayer = VK_TRUE; + device_12_features.shaderOutputViewportIndex = VK_TRUE; + + VkPhysicalDeviceMaintenance4FeaturesKHR maintenance_4 = {}; + maintenance_4.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_4_FEATURES_KHR; + maintenance_4.pNext = &device_12_features; + maintenance_4.maintenance4 = VK_TRUE; + /* Enable shader draw parameters on logical device when supported on physical device. */ VkPhysicalDeviceShaderDrawParametersFeatures shader_draw_parameters = {}; shader_draw_parameters.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_DRAW_PARAMETERS_FEATURES; shader_draw_parameters.shaderDrawParameters = features_11.shaderDrawParameters; - - VkPhysicalDeviceMaintenance4FeaturesKHR maintenance_4 = {}; - maintenance_4.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_4_FEATURES_KHR; - maintenance_4.maintenance4 = VK_TRUE; - /* Mainenance4 is core in Vulkan 1.3 so we need to query for availability. */ - if (has_extensions({VK_KHR_MAINTENANCE_4_EXTENSION_NAME})) { - maintenance_4.pNext = shader_draw_parameters.pNext; - shader_draw_parameters.pNext = &maintenance_4; - } + shader_draw_parameters.pNext = &maintenance_4; VkDeviceCreateInfo device_create_info = {}; device_create_info.pNext = &shader_draw_parameters; @@ -318,7 +320,7 @@ static GHOST_TSuccess ensure_vulkan_device(VkInstance vk_instance, for (const auto &physical_device : physical_devices) { GHOST_DeviceVK device_vk(vk_instance, physical_device); - if (!device_vk.has_extensions(required_extensions)) { + if (!device_vk.extensions_support(required_extensions)) { continue; } @@ -393,7 +395,6 @@ GHOST_ContextVK::GHOST_ContextVK(bool stereoVisual, /* Wayland */ wl_surface *wayland_surface, wl_display *wayland_display, - const GHOST_ContextVK_WindowInfo *wayland_window_info, #endif int contextMajorVersion, int contextMinorVersion, @@ -411,7 +412,6 @@ GHOST_ContextVK::GHOST_ContextVK(bool stereoVisual, /* Wayland */ m_wayland_surface(wayland_surface), m_wayland_display(wayland_display), - m_wayland_window_info(wayland_window_info), #endif m_context_major_version(contextMajorVersion), m_context_minor_version(contextMinorVersion), @@ -471,25 +471,6 @@ GHOST_TSuccess GHOST_ContextVK::swapBuffers() return GHOST_kFailure; } -#ifdef WITH_GHOST_WAYLAND - /* Wayland doesn't provide a WSI with windowing capabilities, therefore cannot detect whether the - * swap-chain needs to be recreated. But as a side effect we can recreate the swap chain before - * presenting. */ - if (m_wayland_window_info) { - const bool recreate_swapchain = - ((m_wayland_window_info->size[0] != - std::max(m_render_extent.width, m_render_extent_min.width)) || - (m_wayland_window_info->size[1] != - std::max(m_render_extent.height, m_render_extent_min.height))); - - if (recreate_swapchain) { - /* Swap-chain is out of date. Recreate swap-chain. */ - destroySwapchain(); - createSwapchain(); - } - } -#endif - assert(vulkan_device.has_value() && vulkan_device->device != VK_NULL_HANDLE); VkDevice device = vulkan_device->device; vkAcquireNextImageKHR(device, m_swapchain, UINT64_MAX, VK_NULL_HANDLE, m_fence, &m_currentImage); @@ -809,29 +790,11 @@ GHOST_TSuccess GHOST_ContextVK::createSwapchain() vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical_device, m_surface, &capabilities); m_render_extent = capabilities.currentExtent; - m_render_extent_min = capabilities.minImageExtent; if (m_render_extent.width == UINT32_MAX) { /* Window Manager is going to set the surface size based on the given size. * Choose something between minImageExtent and maxImageExtent. */ - int width = 0; - int height = 0; - -#ifdef WITH_GHOST_WAYLAND - /* Wayland doesn't provide a windowing API via WSI. */ - if (m_wayland_window_info) { - width = m_wayland_window_info->size[0]; - height = m_wayland_window_info->size[1]; - } -#endif - - if (width == 0 || height == 0) { - width = 1280; - height = 720; - } - - m_render_extent.width = width; - m_render_extent.height = height; - + m_render_extent.width = 1280; + m_render_extent.height = 720; if (capabilities.minImageExtent.width > m_render_extent.width) { m_render_extent.width = capabilities.minImageExtent.width; } @@ -973,6 +936,8 @@ GHOST_TSuccess GHOST_ContextVK::initializeDrawingContext() if (m_debug) { enableLayer(layers_available, layers_enabled, VkLayer::KHRONOS_validation, m_debug); requireExtension(extensions_available, extensions_enabled, VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + requireExtension( + extensions_available, extensions_enabled, VK_KHR_SHADER_NON_SEMANTIC_INFO_EXTENSION_NAME); } if (use_window_surface) { @@ -985,6 +950,8 @@ GHOST_TSuccess GHOST_ContextVK::initializeDrawingContext() } extensions_device.push_back("VK_KHR_dedicated_allocation"); extensions_device.push_back("VK_KHR_get_memory_requirements2"); + /* Allow relaxed interface matching between shader stages. */ + extensions_device.push_back("VK_KHR_maintenance4"); /* Enable MoltenVK required instance extensions. */ #ifdef VK_MVK_MOLTENVK_EXTENSION_NAME requireExtension( @@ -1075,7 +1042,7 @@ GHOST_TSuccess GHOST_ContextVK::initializeDrawingContext() /* According to the Vulkan specs, when `VK_KHR_portability_subset` is available it should be * enabled. See * https://vulkan.lunarg.com/doc/view/1.2.198.1/mac/1.2-extensions/vkspec.html#VUID-VkDeviceCreateInfo-pProperties-04451*/ - if (vulkan_device->has_extensions({VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME})) { + if (vulkan_device->extensions_support({VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME})) { extensions_device.push_back(VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME); } #endif diff --git a/intern/ghost/intern/GHOST_ContextVK.hh b/intern/ghost/intern/GHOST_ContextVK.hh index e9b542b5c0d..e0d5a4a3ef8 100644 --- a/intern/ghost/intern/GHOST_ContextVK.hh +++ b/intern/ghost/intern/GHOST_ContextVK.hh @@ -8,10 +8,6 @@ #pragma once -#ifndef WITH_VULKAN_BACKEND -# error "ContextVK requires WITH_VULKAN_BACKEND" -#endif - #include "GHOST_Context.hh" #ifdef _WIN32 @@ -46,10 +42,6 @@ typedef enum { #endif } GHOST_TVulkanPlatformType; -struct GHOST_ContextVK_WindowInfo { - int size[2]; -}; - class GHOST_ContextVK : public GHOST_Context { public: /** @@ -69,7 +61,6 @@ class GHOST_ContextVK : public GHOST_Context { /* Wayland */ wl_surface *wayland_surface, wl_display *wayland_display, - const GHOST_ContextVK_WindowInfo *wayland_window_info, #endif int contextMajorVersion, int contextMinorVersion, @@ -160,7 +151,6 @@ class GHOST_ContextVK : public GHOST_Context { /* Wayland */ wl_surface *m_wayland_surface; wl_display *m_wayland_display; - const GHOST_ContextVK_WindowInfo *m_wayland_window_info; #endif const int m_context_major_version; @@ -179,7 +169,6 @@ class GHOST_ContextVK : public GHOST_Context { std::vector m_swapchain_images; VkExtent2D m_render_extent; - VkExtent2D m_render_extent_min; VkSurfaceFormatKHR m_surface_format; VkFence m_fence; diff --git a/intern/ghost/intern/GHOST_DropTargetWin32.cc b/intern/ghost/intern/GHOST_DropTargetWin32.cc index 1c56c3b79b8..e44be902779 100644 --- a/intern/ghost/intern/GHOST_DropTargetWin32.cc +++ b/intern/ghost/intern/GHOST_DropTargetWin32.cc @@ -10,8 +10,8 @@ #include "GHOST_Debug.hh" #include -#include "utf_winfunc.hh" -#include "utfconv.hh" +#include "utf_winfunc.h" +#include "utfconv.h" #ifdef WITH_GHOST_DEBUG /* utility */ diff --git a/intern/ghost/intern/GHOST_Event.hh b/intern/ghost/intern/GHOST_Event.hh index 3ba88180bd9..81d37181ea4 100644 --- a/intern/ghost/intern/GHOST_Event.hh +++ b/intern/ghost/intern/GHOST_Event.hh @@ -31,7 +31,7 @@ class GHOST_Event : public GHOST_IEvent { * Returns the event type. * \return The event type. */ - GHOST_TEventType getType() const + GHOST_TEventType getType() { return m_type; } @@ -40,7 +40,7 @@ class GHOST_Event : public GHOST_IEvent { * Returns the time this event was generated. * \return The event generation time. */ - uint64_t getTime() const + uint64_t getTime() { return m_time; } @@ -50,7 +50,7 @@ class GHOST_Event : public GHOST_IEvent { * or nullptr if it is a 'system' event. * \return The generating window. */ - GHOST_IWindow *getWindow() const + GHOST_IWindow *getWindow() { return m_window; } @@ -59,7 +59,7 @@ class GHOST_Event : public GHOST_IEvent { * Returns the event data. * \return The event data. */ - GHOST_TEventDataPtr getData() const + GHOST_TEventDataPtr getData() { return m_data; } diff --git a/intern/ghost/intern/GHOST_EventDragnDrop.hh b/intern/ghost/intern/GHOST_EventDragnDrop.hh index 8a56f75a333..bb9c77c925f 100644 --- a/intern/ghost/intern/GHOST_EventDragnDrop.hh +++ b/intern/ghost/intern/GHOST_EventDragnDrop.hh @@ -61,7 +61,7 @@ class GHOST_EventDragnDrop : public GHOST_Event { GHOST_IWindow *window, int x, int y, - GHOST_TDragnDropDataPtr data) + GHOST_TEventDataPtr data) : GHOST_Event(time, type, window) { m_dragnDropEventData.x = x; diff --git a/intern/ghost/intern/GHOST_EventManager.cc b/intern/ghost/intern/GHOST_EventManager.cc index 9e5bcb2a3b2..a7eac30c419 100644 --- a/intern/ghost/intern/GHOST_EventManager.cc +++ b/intern/ghost/intern/GHOST_EventManager.cc @@ -45,7 +45,7 @@ uint32_t GHOST_EventManager::getNumEvents(GHOST_TEventType type) return numEvents; } -GHOST_TSuccess GHOST_EventManager::pushEvent(const GHOST_IEvent *event) +GHOST_TSuccess GHOST_EventManager::pushEvent(GHOST_IEvent *event) { GHOST_TSuccess success; GHOST_ASSERT(event, "invalid event"); @@ -59,7 +59,7 @@ GHOST_TSuccess GHOST_EventManager::pushEvent(const GHOST_IEvent *event) return success; } -void GHOST_EventManager::dispatchEvent(const GHOST_IEvent *event) +void GHOST_EventManager::dispatchEvent(GHOST_IEvent *event) { TConsumerVector::iterator iter; @@ -70,7 +70,7 @@ void GHOST_EventManager::dispatchEvent(const GHOST_IEvent *event) void GHOST_EventManager::dispatchEvent() { - const GHOST_IEvent *event = m_events.back(); + GHOST_IEvent *event = m_events.back(); m_events.pop_back(); m_handled_events.push_back(event); @@ -130,7 +130,7 @@ void GHOST_EventManager::removeWindowEvents(const GHOST_IWindow *window) TEventStack::iterator iter; iter = m_events.begin(); while (iter != m_events.end()) { - const GHOST_IEvent *event = *iter; + GHOST_IEvent *event = *iter; if (event->getWindow() == window) { GHOST_PRINT("GHOST_EventManager::removeWindowEvents(): removing event\n"); /* @@ -152,7 +152,7 @@ void GHOST_EventManager::removeTypeEvents(GHOST_TEventType type, const GHOST_IWi TEventStack::iterator iter; iter = m_events.begin(); while (iter != m_events.end()) { - const GHOST_IEvent *event = *iter; + GHOST_IEvent *event = *iter; if ((event->getType() == type) && (!window || (event->getWindow() == window))) { GHOST_PRINT("GHOST_EventManager::removeTypeEvents(): removing event\n"); /* diff --git a/intern/ghost/intern/GHOST_EventManager.hh b/intern/ghost/intern/GHOST_EventManager.hh index 8f52dca3717..12c6dfa37d0 100644 --- a/intern/ghost/intern/GHOST_EventManager.hh +++ b/intern/ghost/intern/GHOST_EventManager.hh @@ -53,12 +53,12 @@ class GHOST_EventManager { * Do not delete the event! * \param event: The event to push on the stack. */ - GHOST_TSuccess pushEvent(const GHOST_IEvent *event); + GHOST_TSuccess pushEvent(GHOST_IEvent *event); /** * Dispatches the given event directly, bypassing the event stack. */ - void dispatchEvent(const GHOST_IEvent *event); + void dispatchEvent(GHOST_IEvent *event); /** * Dispatches the event at the back of the stack. @@ -108,11 +108,11 @@ class GHOST_EventManager { void disposeEvents(); /** A stack with events. */ - typedef std::deque TEventStack; + typedef std::deque TEventStack; /** The event stack. */ - std::deque m_events; - std::deque m_handled_events; + std::deque m_events; + std::deque m_handled_events; /** A vector with event consumers. */ typedef std::vector TConsumerVector; diff --git a/intern/ghost/intern/GHOST_EventPrinter.cc b/intern/ghost/intern/GHOST_EventPrinter.cc index 53413930c71..27d9f52c6ad 100644 --- a/intern/ghost/intern/GHOST_EventPrinter.cc +++ b/intern/ghost/intern/GHOST_EventPrinter.cc @@ -17,8 +17,6 @@ #include -/* For now only used with NDOF. */ -#ifdef WITH_INPUT_NDOF static const char *getButtonActionString(const GHOST_TButtonAction action) { switch (action) { @@ -29,9 +27,8 @@ static const char *getButtonActionString(const GHOST_TButtonAction action) } return "Unknown"; } -#endif /* WITH_INPUT_NDOF */ -bool GHOST_EventPrinter::processEvent(const GHOST_IEvent *event) +bool GHOST_EventPrinter::processEvent(GHOST_IEvent *event) { bool handled = false; @@ -40,9 +37,6 @@ bool GHOST_EventPrinter::processEvent(const GHOST_IEvent *event) if (event->getType() == GHOST_kEventWindowUpdate) { return false; } - - GHOST_TEventDataPtr data = event->getData(); - std::cout << "GHOST_EventPrinter::processEvent, time: " << int32_t(event->getTime()) << ", type: "; @@ -62,26 +56,30 @@ bool GHOST_EventPrinter::processEvent(const GHOST_IEvent *event) break; } case GHOST_kEventCursorMove: { - const GHOST_TEventCursorData *cursorData = static_cast(data); + GHOST_TEventCursorData *cursorData = + (GHOST_TEventCursorData *)((GHOST_IEvent *)event)->getData(); std::cout << "GHOST_kEventCursorMove, (x,y): (" << cursorData->x << "," << cursorData->y << ")"; handled = true; break; } case GHOST_kEventButtonDown: { - const GHOST_TEventButtonData *buttonData = static_cast(data); + GHOST_TEventButtonData *buttonData = + (GHOST_TEventButtonData *)((GHOST_IEvent *)event)->getData(); std::cout << "GHOST_kEventButtonDown, button: " << buttonData->button; handled = true; break; } case GHOST_kEventButtonUp: { - const GHOST_TEventButtonData *buttonData = static_cast(data); + GHOST_TEventButtonData *buttonData = + (GHOST_TEventButtonData *)((GHOST_IEvent *)event)->getData(); std::cout << "GHOST_kEventCursorButtonUp, button: " << buttonData->button; handled = true; break; } case GHOST_kEventWheel: { - const GHOST_TEventWheelData *wheelData = static_cast(data); + GHOST_TEventWheelData *wheelData = + (GHOST_TEventWheelData *)((GHOST_IEvent *)event)->getData(); std::cout << "GHOST_kEventWheel, z: " << wheelData->z; handled = true; break; @@ -92,7 +90,7 @@ bool GHOST_EventPrinter::processEvent(const GHOST_IEvent *event) #ifdef WITH_INPUT_NDOF case GHOST_kEventNDOFMotion: { const GHOST_TEventNDOFMotionData *ndof_motion = - static_cast(data); + (GHOST_TEventNDOFMotionData *)((GHOST_IEvent *)event)->getData(); std::cout << "GHOST_kEventNDOFMotion: "; std::cout << std::fixed << std::setprecision(2) << /* Translation. */ @@ -105,7 +103,7 @@ bool GHOST_EventPrinter::processEvent(const GHOST_IEvent *event) } case GHOST_kEventNDOFButton: { const GHOST_TEventNDOFButtonData *ndof_button = - static_cast(data); + (GHOST_TEventNDOFButtonData *)((GHOST_IEvent *)event)->getData(); std::cout << "GHOST_kEventNDOFButton: " << getButtonActionString(ndof_button->action) << " button=" << ndof_button->button; handled = true; @@ -114,13 +112,13 @@ bool GHOST_EventPrinter::processEvent(const GHOST_IEvent *event) #endif /* WITH_INPUT_NDOF */ case GHOST_kEventKeyDown: { - const GHOST_TEventKeyData *keyData = static_cast(data); + GHOST_TEventKeyData *keyData = (GHOST_TEventKeyData *)((GHOST_IEvent *)event)->getData(); std::cout << "GHOST_kEventKeyDown, key: " << getKeyString(keyData->key); handled = true; break; } case GHOST_kEventKeyUp: { - const GHOST_TEventKeyData *keyData = static_cast(data); + GHOST_TEventKeyData *keyData = (GHOST_TEventKeyData *)((GHOST_IEvent *)event)->getData(); std::cout << "GHOST_kEventKeyUp, key: " << getKeyString(keyData->key); handled = true; break; @@ -137,8 +135,8 @@ bool GHOST_EventPrinter::processEvent(const GHOST_IEvent *event) CASE_TYPE(GHOST_kEventWindowDPIHintChanged); case GHOST_kEventDraggingEntered: { - const GHOST_TEventDragnDropData *dragnDropData = - static_cast(data); + GHOST_TEventDragnDropData *dragnDropData = + (GHOST_TEventDragnDropData *)((GHOST_IEvent *)event)->getData(); std::cout << "GHOST_kEventDraggingEntered, dragged object type : " << dragnDropData->dataType; std::cout << " mouse at x=" << dragnDropData->x << " y=" << dragnDropData->y; @@ -146,8 +144,8 @@ bool GHOST_EventPrinter::processEvent(const GHOST_IEvent *event) break; } case GHOST_kEventDraggingUpdated: { - const GHOST_TEventDragnDropData *dragnDropData = - static_cast(data); + GHOST_TEventDragnDropData *dragnDropData = + (GHOST_TEventDragnDropData *)((GHOST_IEvent *)event)->getData(); std::cout << "GHOST_kEventDraggingUpdated, dragged object type : " << dragnDropData->dataType; std::cout << " mouse at x=" << dragnDropData->x << " y=" << dragnDropData->y; @@ -155,15 +153,15 @@ bool GHOST_EventPrinter::processEvent(const GHOST_IEvent *event) break; } case GHOST_kEventDraggingExited: { - const GHOST_TEventDragnDropData *dragnDropData = - static_cast(data); + GHOST_TEventDragnDropData *dragnDropData = + (GHOST_TEventDragnDropData *)((GHOST_IEvent *)event)->getData(); std::cout << "GHOST_kEventDraggingExited, dragged object type : " << dragnDropData->dataType; handled = true; break; } case GHOST_kEventDraggingDropDone: { - const GHOST_TEventDragnDropData *dragnDropData = - static_cast(data); + GHOST_TEventDragnDropData *dragnDropData = + (GHOST_TEventDragnDropData *)((GHOST_IEvent *)event)->getData(); std::cout << "GHOST_kEventDraggingDropDone,"; std::cout << " mouse at x=" << dragnDropData->x << " y=" << dragnDropData->y; switch (dragnDropData->dataType) { @@ -191,8 +189,10 @@ bool GHOST_EventPrinter::processEvent(const GHOST_IEvent *event) break; } case GHOST_kEventOpenMainFile: { - if (data) { - std::cout << "GHOST_kEventOpenMainFile for path: " << static_cast(data); + GHOST_TEventDataPtr eventData = ((GHOST_IEvent *)event)->getData(); + + if (eventData) { + std::cout << "GHOST_kEventOpenMainFile for path : " << (char *)eventData; } else { std::cout << "GHOST_kEventOpenMainFile with no path specified!!"; @@ -203,6 +203,8 @@ bool GHOST_EventPrinter::processEvent(const GHOST_IEvent *event) CASE_TYPE(GHOST_kEventNativeResolutionChange); + CASE_TYPE(GHOST_kEventTimer); + CASE_TYPE(GHOST_kEventImeCompositionStart); CASE_TYPE(GHOST_kEventImeComposition); CASE_TYPE(GHOST_kEventImeCompositionEnd); diff --git a/intern/ghost/intern/GHOST_EventPrinter.hh b/intern/ghost/intern/GHOST_EventPrinter.hh index ef6fcbad073..b2708748043 100644 --- a/intern/ghost/intern/GHOST_EventPrinter.hh +++ b/intern/ghost/intern/GHOST_EventPrinter.hh @@ -22,7 +22,7 @@ class GHOST_EventPrinter : public GHOST_IEventConsumer { * \param event: The event that can be handled or not. * \return Indication as to whether the event was handled. */ - bool processEvent(const GHOST_IEvent *event); + bool processEvent(GHOST_IEvent *event); protected: /** diff --git a/intern/ghost/intern/GHOST_EventString.hh b/intern/ghost/intern/GHOST_EventString.hh index 0ca22dc8ff9..80a498bb209 100644 --- a/intern/ghost/intern/GHOST_EventString.hh +++ b/intern/ghost/intern/GHOST_EventString.hh @@ -35,7 +35,7 @@ class GHOST_EventString : public GHOST_Event { ~GHOST_EventString() { if (m_data) { - free((void *)m_data); + free(m_data); } } }; diff --git a/intern/ghost/intern/GHOST_ImeWin32.cc b/intern/ghost/intern/GHOST_ImeWin32.cc index 438ebd0c099..9b11c73ebe6 100644 --- a/intern/ghost/intern/GHOST_ImeWin32.cc +++ b/intern/ghost/intern/GHOST_ImeWin32.cc @@ -11,7 +11,7 @@ # include "GHOST_ImeWin32.hh" # include "GHOST_C-api.h" # include "GHOST_WindowWin32.hh" -# include "utfconv.hh" +# include "utfconv.h" /* ISO_639-1 2-Letter Abbreviations. */ # define IMELANG_ENGLISH "en" diff --git a/intern/ghost/intern/GHOST_System.cc b/intern/ghost/intern/GHOST_System.cc index a20e5c626bb..65d1323938b 100644 --- a/intern/ghost/intern/GHOST_System.cc +++ b/intern/ghost/intern/GHOST_System.cc @@ -267,7 +267,7 @@ GHOST_TSuccess GHOST_System::removeEventConsumer(GHOST_IEventConsumer *consumer) return success; } -GHOST_TSuccess GHOST_System::pushEvent(const GHOST_IEvent *event) +GHOST_TSuccess GHOST_System::pushEvent(GHOST_IEvent *event) { GHOST_TSuccess success; if (m_eventManager) { diff --git a/intern/ghost/intern/GHOST_System.hh b/intern/ghost/intern/GHOST_System.hh index 26b5263cf71..3bfaa370028 100644 --- a/intern/ghost/intern/GHOST_System.hh +++ b/intern/ghost/intern/GHOST_System.hh @@ -285,7 +285,7 @@ class GHOST_System : public GHOST_ISystem { * Do not delete the event! * \param event: The event to push on the stack. */ - GHOST_TSuccess pushEvent(const GHOST_IEvent *event); + GHOST_TSuccess pushEvent(GHOST_IEvent *event); /** * \return The timer manager. diff --git a/intern/ghost/intern/GHOST_SystemCocoa.mm b/intern/ghost/intern/GHOST_SystemCocoa.mm index e4a97d0bdda..650c3bd0698 100644 --- a/intern/ghost/intern/GHOST_SystemCocoa.mm +++ b/intern/ghost/intern/GHOST_SystemCocoa.mm @@ -338,14 +338,14 @@ static GHOST_TKey convertKey(int rawCode, unichar recvChar) #define FIRSTFILEBUFLG 512 static bool g_hasFirstFile = false; -static char g_firstFileBuf[FIRSTFILEBUFLG]; +static char g_firstFileBuf[512]; -/* TODO: Need to investigate this. - * Function called too early in creator.c to have g_hasFirstFile == true */ +// TODO: Need to investigate this. +// Function called too early in creator.c to have g_hasFirstFile == true extern "C" int GHOST_HACK_getFirstFile(char buf[FIRSTFILEBUFLG]) { if (g_hasFirstFile) { - memcpy(buf, g_firstFileBuf, FIRSTFILEBUFLG); + strncpy(buf, g_firstFileBuf, FIRSTFILEBUFLG - 1); buf[FIRSTFILEBUFLG - 1] = '\0'; return 1; } @@ -1219,7 +1219,7 @@ GHOST_TSuccess GHOST_SystemCocoa::handleDraggingEvent(GHOST_TEventType eventType NSArray *droppedArray; size_t pastedTextSize; NSString *droppedStr; - GHOST_TDragnDropDataPtr eventData; + GHOST_TEventDataPtr eventData; int i; if (!data) { @@ -1254,14 +1254,15 @@ GHOST_TSuccess GHOST_SystemCocoa::handleDraggingEvent(GHOST_TEventType eventType break; } - memcpy( - temp_buff, [droppedStr cStringUsingEncoding:NSUTF8StringEncoding], pastedTextSize); + strncpy((char *)temp_buff, + [droppedStr cStringUsingEncoding:NSUTF8StringEncoding], + pastedTextSize); temp_buff[pastedTextSize] = '\0'; strArray->strings[i] = temp_buff; } - eventData = (GHOST_TDragnDropDataPtr)strArray; + eventData = (GHOST_TEventDataPtr)strArray; break; case GHOST_kDragnDropTypeString: @@ -1274,11 +1275,13 @@ GHOST_TSuccess GHOST_SystemCocoa::handleDraggingEvent(GHOST_TEventType eventType return GHOST_kFailure; } - memcpy( - temp_buff, [droppedStr cStringUsingEncoding:NSUTF8StringEncoding], pastedTextSize); + strncpy((char *)temp_buff, + [droppedStr cStringUsingEncoding:NSUTF8StringEncoding], + pastedTextSize); + temp_buff[pastedTextSize] = '\0'; - eventData = (GHOST_TDragnDropDataPtr)temp_buff; + eventData = (GHOST_TEventDataPtr)temp_buff; break; case GHOST_kDragnDropTypeBitmap: { @@ -1410,7 +1413,7 @@ GHOST_TSuccess GHOST_SystemCocoa::handleDraggingEvent(GHOST_TEventType eventType [droppedImg release]; } - eventData = (GHOST_TDragnDropDataPtr)ibuf; + eventData = (GHOST_TEventDataPtr)ibuf; break; } @@ -1481,7 +1484,7 @@ bool GHOST_SystemCocoa::handleOpenDocumentRequest(void *filepathStr) return GHOST_kFailure; } - memcpy(temp_buff, [filepath cStringUsingEncoding:NSUTF8StringEncoding], filenameTextSize); + strncpy(temp_buff, [filepath cStringUsingEncoding:NSUTF8StringEncoding], filenameTextSize); temp_buff[filenameTextSize] = '\0'; pushEvent(new GHOST_EventString( @@ -2036,7 +2039,8 @@ char *GHOST_SystemCocoa::getClipboard(bool /*selection*/) const return nullptr; } - memcpy(temp_buff, [textPasted cStringUsingEncoding:NSUTF8StringEncoding], pastedTextSize); + strncpy(temp_buff, [textPasted cStringUsingEncoding:NSUTF8StringEncoding], pastedTextSize); + temp_buff[pastedTextSize] = '\0'; if (temp_buff) { diff --git a/intern/ghost/intern/GHOST_SystemHeadless.hh b/intern/ghost/intern/GHOST_SystemHeadless.hh index 406c608b0e2..59406ca44f2 100644 --- a/intern/ghost/intern/GHOST_SystemHeadless.hh +++ b/intern/ghost/intern/GHOST_SystemHeadless.hh @@ -14,7 +14,7 @@ #include "GHOST_System.hh" #include "GHOST_WindowNULL.hh" -#if defined(WITH_OPENGL_BACKEND) && defined(__linux__) +#ifdef __linux__ # include "GHOST_ContextEGL.hh" #endif #include "GHOST_ContextNone.hh" @@ -51,7 +51,7 @@ class GHOST_SystemHeadless : public GHOST_System { ~(GHOST_kCapabilityWindowPosition | GHOST_kCapabilityCursorWarp | GHOST_kCapabilityPrimaryClipboard | GHOST_kCapabilityDesktopSample | - GHOST_kCapabilityClipboardImages | GHOST_kCapabilityInputIME)); + GHOST_kCapabilityClipboardImages)); } char *getClipboard(bool /*selection*/) const override { @@ -84,7 +84,7 @@ class GHOST_SystemHeadless : public GHOST_System { } GHOST_IContext *createOffscreenContext(GHOST_GPUSettings /*gpuSettings*/) override { -#if defined(WITH_OPENGL_BACKEND) && defined(__linux__) +#ifdef __linux__ GHOST_Context *context; for (int minor = 6; minor >= 3; --minor) { context = new GHOST_ContextEGL((GHOST_System *)this, diff --git a/intern/ghost/intern/GHOST_SystemPathsWin32.cc b/intern/ghost/intern/GHOST_SystemPathsWin32.cc index cb183a9e8a7..58fea5ebc19 100644 --- a/intern/ghost/intern/GHOST_SystemPathsWin32.cc +++ b/intern/ghost/intern/GHOST_SystemPathsWin32.cc @@ -12,7 +12,7 @@ #ifndef _WIN32_IE # define _WIN32_IE 0x0501 #endif -#include "utfconv.hh" +#include "utfconv.h" #include GHOST_SystemPathsWin32::GHOST_SystemPathsWin32() {} diff --git a/intern/ghost/intern/GHOST_SystemSDL.cc b/intern/ghost/intern/GHOST_SystemSDL.cc index 9afcf744b03..16613751005 100644 --- a/intern/ghost/intern/GHOST_SystemSDL.cc +++ b/intern/ghost/intern/GHOST_SystemSDL.cc @@ -781,9 +781,7 @@ GHOST_TCapabilityFlag GHOST_SystemSDL::getCapabilities() const /* This SDL back-end has not yet implemented color sampling the desktop. */ GHOST_kCapabilityDesktopSample | /* This SDL back-end has not yet implemented image copy/paste. */ - GHOST_kCapabilityClipboardImages | - /* No support yet for IME input methods. */ - GHOST_kCapabilityInputIME)); + GHOST_kCapabilityClipboardImages)); } char *GHOST_SystemSDL::getClipboard(bool /*selection*/) const diff --git a/intern/ghost/intern/GHOST_SystemWayland.cc b/intern/ghost/intern/GHOST_SystemWayland.cc index 819862d240f..57460d86942 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.cc +++ b/intern/ghost/intern/GHOST_SystemWayland.cc @@ -37,12 +37,10 @@ # include /* For `ghost_wl_dynload_libraries`. */ #endif -#ifdef WITH_OPENGL_BACKEND -# ifdef WITH_GHOST_WAYLAND_DYNLOAD -# include -# endif -# include -#endif /* WITH_OPENGL_BACKEND */ +#ifdef WITH_GHOST_WAYLAND_DYNLOAD +# include +#endif +#include #include #include @@ -56,6 +54,8 @@ #endif #include +#include "GHOST_WaylandCursorSettings.hh" + #include #include @@ -69,9 +69,6 @@ #include #include #include -#ifdef WITH_INPUT_IME -# include -#endif /* Decorations `xdg_decor`. */ #include @@ -396,7 +393,7 @@ struct GWL_Cursor { /** The size of `custom_data` in bytes. */ size_t custom_data_size = 0; /** - * The name of the theme (set by an environment variable). + * The name of the theme (loaded by DBUS, depends on #WITH_GHOST_WAYLAND_DBUS). * When disabled, leave as an empty string and the default theme will be used. */ std::string theme_name; @@ -724,51 +721,6 @@ static void gwl_primary_selection_discard_source(GWL_PrimarySelection *primary) primary->data_source = nullptr; } -#ifdef WITH_INPUT_IME -struct GWL_SeatIME { - struct wl_surface *surface_window = nullptr; - GHOST_TEventImeData event_ime_data = { - /*result_len*/ nullptr, - /*composite_len*/ nullptr, - /*result*/ nullptr, - /*composite*/ nullptr, - /*cursor_position*/ -1, - /*target_start*/ -1, - /*target_end*/ -1, - }; - /** When true, the client has indicated that IME input should be activated on text entry. */ - bool is_enabled = false; - /** - * When true, some pre-edit text has been entered - * (an IME popup may be showing however this isn't known). - */ - bool has_preedit = false; - - /** Storage for #GHOST_TEventImeData::result (the result of the `commit_string` callback). */ - std::string result; - /** Storage for #GHOST_TEventImeData::composite (the result of the `preedit_string` callback). */ - std::string composite; - - /** #zwp_text_input_v3_listener::commit_string was called with a null text argument. */ - bool result_is_null = false; - /** #zwp_text_input_v3_listener::preedit_string was called with a null text argument. */ - bool composite_is_null = false; - - /** #zwp_text_input_v3_listener::preedit_string was called. */ - bool has_preedit_string_callback = false; - /** #zwp_text_input_v3_listener::commit_string was called. */ - bool has_commit_string_callback = false; - - /** Bounds (use for comparison). */ - struct { - int x = -1; - int y = -1; - int w = -1; - int h = -1; - } rect; -}; -#endif /* WITH_INPUT_IME */ - struct GWL_Seat { /** Wayland core types. */ @@ -804,10 +756,6 @@ struct GWL_Seat { /** All currently active tablet tools (needed for changing the cursor). */ std::unordered_set tablet_tools; - -#ifdef WITH_INPUT_IME - struct zwp_text_input_v3 *text_input = nullptr; -#endif } wp; /** XKB native types. */ @@ -839,10 +787,6 @@ struct GWL_Seat { } xkb; -#ifdef WITH_INPUT_IME - GWL_SeatIME ime; -#endif - GHOST_SystemWayland *system = nullptr; std::string name; @@ -977,60 +921,6 @@ static void gwl_seat_key_repeat_timer_remove(GWL_Seat *seat) seat->key_repeat.timer = nullptr; } -#ifdef WITH_INPUT_IME - -static void gwl_seat_ime_full_reset(GWL_Seat *seat) -{ - const GWL_SeatIME ime_default{}; - /* Preserve the following members since they represent the state of the connection to Wayland. - * or which callbacks have run (which shouldn't be reset). */ - wl_surface *surface_window = seat->ime.surface_window; - const bool is_enabled = seat->ime.is_enabled; - const bool has_preedit_string_callback = seat->ime.has_preedit_string_callback; - const bool has_commit_string_callback = seat->ime.has_commit_string_callback; - - seat->ime = ime_default; - - seat->ime.surface_window = surface_window; - seat->ime.is_enabled = is_enabled; - seat->ime.has_preedit_string_callback = has_preedit_string_callback; - seat->ime.has_commit_string_callback = has_commit_string_callback; -} - -static void gwl_seat_ime_result_reset(GWL_Seat *seat) -{ - seat->ime.result.clear(); - seat->ime.result_is_null = false; - - GHOST_TEventImeData &event_ime_data = seat->ime.event_ime_data; - event_ime_data.result_len = nullptr; - event_ime_data.result = nullptr; -} - -static void gwl_seat_ime_preedit_reset(GWL_Seat *seat) -{ - seat->ime.composite.clear(); - seat->ime.composite_is_null = false; - - GHOST_TEventImeData &event_ime_data = seat->ime.event_ime_data; - event_ime_data.composite_len = nullptr; - event_ime_data.composite = nullptr; - - event_ime_data.cursor_position = -1; - event_ime_data.target_start = -1; - event_ime_data.target_end = -1; -} - -static void gwl_seat_ime_rect_reset(GWL_Seat *seat) -{ - seat->ime.rect.x = -1; - seat->ime.rect.y = -1; - seat->ime.rect.w = -1; - seat->ime.rect.h = -1; -} - -#endif /* WITH_INPUT_IME */ - /** \} */ /* -------------------------------------------------------------------- */ @@ -1062,9 +952,6 @@ struct GWL_Display { wp_viewporter *viewporter = nullptr; zwp_pointer_constraints_v1 *pointer_constraints = nullptr; zwp_pointer_gestures_v1 *pointer_gestures = nullptr; -#ifdef WITH_INPUT_IME - struct zwp_text_input_manager_v3 *text_input_manager = nullptr; -#endif } wp; /** Wayland XDG types. */ @@ -1125,7 +1012,7 @@ struct GWL_Display { * Events added from the event reading thread. * Added into the main event queue when on #GHOST_SystemWayland::processEvents. */ - std::vector events_pending; + std::vector events_pending; /** Guard against multiple threads accessing `events_pending` at once. */ std::mutex events_pending_mutex; @@ -1201,7 +1088,7 @@ static void gwl_display_destroy(GWL_Display *display) display->ghost_timer_manager = nullptr; } /* Pending events may be left unhandled. */ - for (const GHOST_IEvent *event : display->events_pending) { + for (GHOST_IEvent *event : display->events_pending) { delete event; } @@ -4540,228 +4427,6 @@ static const zwp_primary_selection_source_v1_listener primary_selection_source_l /** \} */ -/* -------------------------------------------------------------------- */ -/** \name Listener (Text Input), #zwp_text_input_manager_v3 - * \{ */ - -#ifdef WITH_INPUT_IME - -class GHOST_EventIME : public GHOST_Event { - public: - /** - * Constructor. - * \param msec: The time this event was generated. - * \param type: The type of key event. - * \param key: The key code of the key. - */ - GHOST_EventIME(uint64_t msec, GHOST_TEventType type, GHOST_IWindow *window, void *customdata) - : GHOST_Event(msec, type, window) - { - this->m_data = customdata; - } -}; - -static CLG_LogRef LOG_WL_TEXT_INPUT = {"ghost.wl.handle.text_input"}; -# define LOG (&LOG_WL_TEXT_INPUT) - -static void text_input_handle_enter(void *data, - zwp_text_input_v3 * /*zwp_text_input_v3*/, - struct wl_surface *surface) -{ - if (!ghost_wl_surface_own(surface)) { - return; - } - CLOG_INFO(LOG, 2, "enter"); - GWL_Seat *seat = static_cast(data); - seat->ime.surface_window = surface; -} - -static void text_input_handle_leave(void *data, - zwp_text_input_v3 * /*zwp_text_input_v3*/, - struct wl_surface *surface) -{ - /* Can be null when closing a window. */ - if (!ghost_wl_surface_own_with_null_check(surface)) { - return; - } - CLOG_INFO(LOG, 2, "leave"); - GWL_Seat *seat = static_cast(data); - if (seat->ime.surface_window == surface) { - seat->ime.surface_window = nullptr; - } -} - -static void text_input_handle_preedit_string(void *data, - zwp_text_input_v3 * /*zwp_text_input_v3*/, - const char *text, - int32_t cursor_begin, - int32_t cursor_end) -{ - CLOG_INFO(LOG, - 2, - "preedit_string (text=\"%s\", cursor_begin=%d, cursor_end=%d)", - text ? text : "", - cursor_begin, - cursor_end); - - GWL_Seat *seat = static_cast(data); - if (UNLIKELY(seat->ime.surface_window == nullptr)) { - return; - } - - if (seat->ime.has_preedit == false) { - /* Starting IME input. */ - gwl_seat_ime_full_reset(seat); - } - - seat->ime.composite_is_null = (text == nullptr); - if (!seat->ime.composite_is_null) { - seat->ime.composite = text; - seat->ime.event_ime_data.composite = (void *)seat->ime.composite.c_str(); - seat->ime.event_ime_data.composite_len = (void *)seat->ime.composite.size(); - - seat->ime.event_ime_data.cursor_position = cursor_begin; - seat->ime.event_ime_data.target_start = cursor_begin; - seat->ime.event_ime_data.target_end = cursor_end; - } - - seat->ime.has_preedit_string_callback = true; -} - -static void text_input_handle_commit_string(void *data, - zwp_text_input_v3 * /*zwp_text_input_v3*/, - const char *text) -{ - CLOG_INFO(LOG, 2, "commit_string (text=\"%s\")", text ? text : ""); - - GWL_Seat *seat = static_cast(data); - if (UNLIKELY(seat->ime.surface_window == nullptr)) { - return; - } - - seat->ime.result_is_null = (text == nullptr); - if (seat->ime.result_is_null) { - seat->ime.result = ""; - } - else { - seat->ime.result = text; - } - - seat->ime.result_is_null = (text == nullptr); - seat->ime.event_ime_data.result = (void *)seat->ime.result.c_str(); - seat->ime.event_ime_data.result_len = (void *)seat->ime.result.size(); - seat->ime.event_ime_data.cursor_position = seat->ime.result.size(); - - seat->ime.has_commit_string_callback = true; -} - -static void text_input_handle_delete_surrounding_text(void * /*data*/, - zwp_text_input_v3 * /*zwp_text_input_v3*/, - uint32_t before_length, - uint32_t after_length) -{ - CLOG_INFO(LOG, - 2, - "delete_surrounding_text (before_length=%u, after_length=%u)", - before_length, - after_length); - - /* NOTE: Currently unused, do we care about this event? - * SDL ignores this event. */ -} - -static void text_input_handle_done(void *data, - zwp_text_input_v3 * /*zwp_text_input_v3*/, - uint32_t /*serial*/) -{ - CLOG_INFO(LOG, 2, "done"); - - GWL_Seat *seat = static_cast(data); - GHOST_SystemWayland *system = seat->system; - - GHOST_WindowWayland *win = ghost_wl_surface_user_data(seat->ime.surface_window); - if (seat->ime.has_commit_string_callback) { - if (seat->ime.has_preedit) { - const bool is_end = seat->ime.composite_is_null; - if (is_end) { - seat->ime.has_preedit = false; - /* `commit_string` (end). */ - system->pushEvent_maybe_pending(new GHOST_EventIME(system->getMilliSeconds(), - GHOST_kEventImeComposition, - win, - &seat->ime.event_ime_data)); - system->pushEvent_maybe_pending(new GHOST_EventIME(system->getMilliSeconds(), - GHOST_kEventImeCompositionEnd, - win, - &seat->ime.event_ime_data)); - } - else { - /* `commit_string` (continues). */ - system->pushEvent_maybe_pending(new GHOST_EventIME(system->getMilliSeconds(), - GHOST_kEventImeComposition, - win, - &seat->ime.event_ime_data)); - } - } - else { - /* `commit_string` ran with no active IME popup, start & end to insert text. */ - system->pushEvent_maybe_pending(new GHOST_EventIME(system->getMilliSeconds(), - GHOST_kEventImeCompositionStart, - win, - &seat->ime.event_ime_data)); - system->pushEvent_maybe_pending(new GHOST_EventIME( - system->getMilliSeconds(), GHOST_kEventImeComposition, win, &seat->ime.event_ime_data)); - system->pushEvent_maybe_pending(new GHOST_EventIME(system->getMilliSeconds(), - GHOST_kEventImeCompositionEnd, - win, - &seat->ime.event_ime_data)); - } - - if (seat->ime.has_preedit == false) { - gwl_seat_ime_preedit_reset(seat); - } - } - else if (seat->ime.has_preedit_string_callback) { - const bool is_end = seat->ime.composite_is_null; - if (is_end) { - /* `preedit_string` (end). */ - seat->ime.has_preedit = false; - system->pushEvent_maybe_pending(new GHOST_EventIME(seat->system->getMilliSeconds(), - GHOST_kEventImeCompositionEnd, - win, - &seat->ime.event_ime_data)); - } - else { - const bool is_start = seat->ime.has_preedit == false; - /* `preedit_string` (start or continue). */ - seat->ime.has_preedit = true; - system->pushEvent_maybe_pending(new GHOST_EventIME( - seat->system->getMilliSeconds(), - is_start ? GHOST_kEventImeCompositionStart : GHOST_kEventImeComposition, - win, - &seat->ime.event_ime_data)); - } - } - - seat->ime.has_preedit_string_callback = false; - seat->ime.has_commit_string_callback = false; -} - -static struct zwp_text_input_v3_listener text_input_listener = { - /*enter*/ text_input_handle_enter, - /*leave*/ text_input_handle_leave, - /*preedit_string*/ text_input_handle_preedit_string, - /*commit_string*/ text_input_handle_commit_string, - /*delete_surrounding_text*/ text_input_handle_delete_surrounding_text, - /*done*/ text_input_handle_done, -}; - -# undef LOG - -#endif /* WITH_INPUT_IME. */ - -/** \} */ - /* -------------------------------------------------------------------- */ /** \name Listener (Seat), #wl_seat_listener * \{ */ @@ -4778,7 +4443,7 @@ static void gwl_seat_capability_pointer_enable(GWL_Seat *seat) seat->cursor.wl.surface_cursor = wl_compositor_create_surface(seat->system->wl_compositor_get()); seat->cursor.visible = true; seat->cursor.wl.buffer = nullptr; - { + if (!get_cursor_settings(seat->cursor.theme_name, seat->cursor.theme_size)) { /* Use environment variables, falling back to defaults. * These environment variables are used by enough WAYLAND applications * that it makes sense to check them (see `Xcursor` man page). */ @@ -5459,20 +5124,6 @@ static void gwl_registry_wl_seat_update(GWL_Display *display, else { seat->wp.primary_selection_device = nullptr; } - -#ifdef WITH_INPUT_IME - if (display->wp.text_input_manager) { - if (seat->wp.text_input == nullptr) { - seat->wp.text_input = zwp_text_input_manager_v3_get_text_input( - display->wp.text_input_manager, seat->wl.seat); - zwp_text_input_v3_set_user_data(seat->wp.text_input, seat); - zwp_text_input_v3_add_listener(seat->wp.text_input, &text_input_listener, seat); - } - } - else { - seat->wp.text_input = nullptr; - } -#endif /* WITH_INPUT_IME */ } static void gwl_registry_wl_seat_remove(GWL_Display *display, void *user_data, const bool on_exit) { @@ -5758,28 +5409,6 @@ static void gwl_registry_wp_primary_selection_device_manager_remove(GWL_Display *value_p = nullptr; } -#ifdef WITH_INPUT_IME - -/* #GWL_Display.wp_text_input_manager */ - -static void gwl_registry_wp_text_input_manager_add(GWL_Display *display, - const GWL_RegisteryAdd_Params *params) -{ - display->wp.text_input_manager = static_cast(wl_registry_bind( - display->wl.registry, params->name, &zwp_text_input_manager_v3_interface, 1)); - gwl_registry_entry_add(display, params, nullptr); -} -static void gwl_registry_wp_text_input_manager_remove(GWL_Display *display, - void * /*user_data*/, - const bool /*on_exit*/) -{ - struct zwp_text_input_manager_v3 **value_p = &display->wp.text_input_manager; - zwp_text_input_manager_v3_destroy(*value_p); - *value_p = nullptr; -} - -#endif /* WITH_INPUT_IME */ - /** * Map interfaces to initialization functions. * @@ -5847,14 +5476,6 @@ static const GWL_RegistryHandler gwl_registry_handlers[] = { /*update_fn*/ nullptr, /*remove_fn*/ gwl_registry_wp_relative_pointer_manager_remove, }, -#ifdef WITH_INPUT_IME - { - /*interface_p*/ &zwp_text_input_manager_v3_interface.name, - /*add_fn*/ gwl_registry_wp_text_input_manager_add, - /*update_fn*/ nullptr, - /*remove_fn*/ gwl_registry_wp_text_input_manager_remove, - }, -#endif /* Higher level interfaces. */ { /*interface_p*/ &zwp_pointer_constraints_v1_interface.name, @@ -6242,7 +5863,7 @@ bool GHOST_SystemWayland::processEvents(bool waitForEvent) { std::lock_guard lock{display_->events_pending_mutex}; - for (const GHOST_IEvent *event : display_->events_pending) { + for (GHOST_IEvent *event : display_->events_pending) { /* Perform actions that aren't handled in a thread. */ switch (event->getType()) { @@ -6853,7 +6474,9 @@ GHOST_IContext *GHOST_SystemWayland::createOffscreenContext(GHOST_GPUSettings gp std::lock_guard lock_server_guard{*server_mutex}; #endif +#ifdef WITH_VULKAN_BACKEND const bool debug_context = (gpuSettings.flags & GHOST_gpuDebugContext) != 0; +#endif switch (gpuSettings.context_type) { @@ -6868,7 +6491,6 @@ GHOST_IContext *GHOST_SystemWayland::createOffscreenContext(GHOST_GPUSettings gp nullptr, wl_surface, display_->wl.display, - nullptr, 1, 2, debug_context); @@ -6894,18 +6516,16 @@ GHOST_IContext *GHOST_SystemWayland::createOffscreenContext(GHOST_GPUSettings gp for (int minor = 6; minor >= 3; --minor) { /* Caller must lock `system->server_mutex`. */ - GHOST_Context *context = new GHOST_ContextEGL( - this, - false, - EGLNativeWindowType(egl_window), - EGLNativeDisplayType(display_->wl.display), - EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT, - 4, - minor, - GHOST_OPENGL_EGL_CONTEXT_FLAGS | - (debug_context ? EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR : 0), - GHOST_OPENGL_EGL_RESET_NOTIFICATION_STRATEGY, - EGL_OPENGL_API); + GHOST_Context *context = new GHOST_ContextEGL(this, + false, + EGLNativeWindowType(egl_window), + EGLNativeDisplayType(display_->wl.display), + EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT, + 4, + minor, + GHOST_OPENGL_EGL_CONTEXT_FLAGS, + GHOST_OPENGL_EGL_RESET_NOTIFICATION_STRATEGY, + EGL_OPENGL_API); if (context->initializeDrawingContext()) { wl_surface_set_user_data(wl_surface, egl_window); @@ -6937,39 +6557,18 @@ GHOST_TSuccess GHOST_SystemWayland::disposeContext(GHOST_IContext *context) #ifdef USE_EVENT_BACKGROUND_THREAD std::lock_guard lock_server_guard{*server_mutex}; #endif - - GHOST_TDrawingContextType type = GHOST_kDrawingContextTypeNone; -#ifdef WITH_OPENGL_BACKEND - if (dynamic_cast(context)) { - type = GHOST_kDrawingContextTypeOpenGL; - } -#endif /* WITH_OPENGL_BACKEND */ -#ifdef WITH_VULKAN_BACKEND - if (dynamic_cast(context)) { - type = GHOST_kDrawingContextTypeVulkan; - } -#endif /* WITH_VULKAN_BACKEND */ - wl_surface *wl_surface = static_cast( (static_cast(context))->getUserData()); - /* Delete the context before the window so the context is able to release * native resources (such as the #EGLSurface) before WAYLAND frees them. */ delete context; -#ifdef WITH_OPENGL_BACKEND - if (type == GHOST_kDrawingContextTypeOpenGL) { - wl_egl_window *egl_window = static_cast(wl_surface_get_user_data(wl_surface)); - if (egl_window != nullptr) { - wl_egl_window_destroy(egl_window); - } + wl_egl_window *egl_window = static_cast(wl_surface_get_user_data(wl_surface)); + if (egl_window != nullptr) { + wl_egl_window_destroy(egl_window); } -#endif /* WITH_OPENGL_BACKEND */ - wl_surface_destroy(wl_surface); - (void)type; /* Maybe unused. */ - return GHOST_kSuccess; } @@ -6997,8 +6596,7 @@ GHOST_IWindow *GHOST_SystemWayland::createWindow(const char *title, gpuSettings.context_type, is_dialog, ((gpuSettings.flags & GHOST_gpuStereoVisual) != 0), - exclusive, - (gpuSettings.flags & GHOST_gpuDebugContext) != 0); + exclusive); if (window) { if (window->getValid()) { @@ -7621,104 +7219,6 @@ GHOST_TimerManager *GHOST_SystemWayland::ghost_timer_manager() /** \} */ -/* -------------------------------------------------------------------- */ -/** \name Public WAYLAND Text Input (IME) Functions - * - * Functionality only used for the WAYLAND implementation. - * \{ */ - -#ifdef WITH_INPUT_IME - -void GHOST_SystemWayland::ime_begin( - GHOST_WindowWayland *win, int32_t x, int32_t y, int32_t w, int32_t h, bool completed) const -{ - GWL_Seat *seat = gwl_display_seat_active_get(display_); - if (UNLIKELY(!seat)) { - return; - } - if (seat->wp.text_input == nullptr) { - return; - } - - /* Prevent a feedback loop because the commits from this function cause - * #zwp_text_input_v3_listener::preedit_string to run again which sends an event, - * refreshing the position, running this function again. */ - gwl_seat_ime_result_reset(seat); - - /* Don't re-enable if we're already enabled. */ - if (seat->ime.is_enabled && completed) { - return; - } - - bool force_rect_update = false; - if (seat->ime.is_enabled == false) { - seat->ime.has_preedit = false; - seat->ime.is_enabled = true; - - /* NOTE(@flibit): For some reason this has to be done twice, - * it appears to be a bug in mutter? Maybe? */ - zwp_text_input_v3_enable(seat->wp.text_input); - zwp_text_input_v3_commit(seat->wp.text_input); - zwp_text_input_v3_enable(seat->wp.text_input); - zwp_text_input_v3_commit(seat->wp.text_input); - - /* Now that it's enabled, set the input properties. */ - zwp_text_input_v3_set_content_type(seat->wp.text_input, - ZWP_TEXT_INPUT_V3_CONTENT_HINT_NONE, - ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NORMAL); - - gwl_seat_ime_rect_reset(seat); - force_rect_update = true; - } - - if ((force_rect_update == false) && /* Was just created, always update. */ - (seat->ime.rect.x == x) && /* X. */ - (seat->ime.rect.y == y) && /* Y. */ - (seat->ime.rect.w == w) && /* W. */ - (seat->ime.rect.h == h)) /* H. */ - { - /* Only re-update the rectangle as needed. */ - } - else { - const int rect_x = wl_fixed_to_int(win->wl_fixed_from_window(wl_fixed_from_int(x))); - const int rect_y = wl_fixed_to_int(win->wl_fixed_from_window(wl_fixed_from_int(y))); - const int rect_w = wl_fixed_to_int(win->wl_fixed_from_window(wl_fixed_from_int(w))) + 1; - const int rect_h = wl_fixed_to_int(win->wl_fixed_from_window(wl_fixed_from_int(h))) + 1; - - zwp_text_input_v3_set_cursor_rectangle(seat->wp.text_input, rect_x, rect_y, rect_w, rect_h); - - zwp_text_input_v3_commit(seat->wp.text_input); - - seat->ime.rect.x = x; - seat->ime.rect.y = y; - seat->ime.rect.w = w; - seat->ime.rect.h = h; - } -} - -void GHOST_SystemWayland::ime_end(GHOST_WindowWayland * /*window*/) const -{ - GWL_Seat *seat = gwl_display_seat_active_get(display_); - if (UNLIKELY(!seat)) { - return; - } - - seat->ime.is_enabled = false; - - gwl_seat_ime_rect_reset(seat); - - if (seat->wp.text_input == nullptr) { - return; - } - - zwp_text_input_v3_disable(seat->wp.text_input); - zwp_text_input_v3_commit(seat->wp.text_input); -} - -#endif /* WITH_INPUT_IME */ - -/** \} */ - /* -------------------------------------------------------------------- */ /** \name Public WAYLAND Query Access * \{ */ @@ -7791,9 +7291,6 @@ bool GHOST_SystemWayland::window_surface_unref(const wl_surface *wl_surface) SURFACE_CLEAR_PTR(seat->tablet.wl.surface_window); SURFACE_CLEAR_PTR(seat->keyboard.wl.surface_window); SURFACE_CLEAR_PTR(seat->wl.surface_window_focus_dnd); -#ifdef WITH_INPUT_IME - SURFACE_CLEAR_PTR(seat->ime.surface_window); -#endif } #undef SURFACE_CLEAR_PTR @@ -7881,16 +7378,16 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod /* Ignore, if the required protocols are not supported. */ if (UNLIKELY(!display_->wp.relative_pointer_manager || !display_->wp.pointer_constraints)) { - return false; + return GHOST_kFailure; } GWL_Seat *seat = gwl_display_seat_active_get(display_); if (UNLIKELY(!seat)) { - return false; + return GHOST_kFailure; } /* No change, success. */ if (mode == mode_current) { - return true; + return GHOST_kSuccess; } #ifdef USE_GNOME_CONFINE_HACK @@ -8057,7 +7554,7 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod seat->use_pointer_software_confine = use_software_confine; #endif - return true; + return GHOST_kSuccess; } #ifdef WITH_GHOST_WAYLAND_LIBDECOR @@ -8081,11 +7578,7 @@ bool ghost_wl_dynload_libraries_init() if (wayland_dynload_client_init(verbose) && /* `libwayland-client`. */ wayland_dynload_cursor_init(verbose) && /* `libwayland-cursor`. */ -# ifdef WITH_OPENGL_BACKEND - wayland_dynload_egl_init(verbose) /* `libwayland-egl`. */ -# else - true -# endif + wayland_dynload_egl_init(verbose) /* `libwayland-egl`. */ ) { # ifdef WITH_GHOST_WAYLAND_LIBDECOR @@ -8096,9 +7589,7 @@ bool ghost_wl_dynload_libraries_init() wayland_dynload_client_exit(); wayland_dynload_cursor_exit(); -# ifdef WITH_OPENGL_BACKEND wayland_dynload_egl_exit(); -# endif return false; } @@ -8107,9 +7598,7 @@ void ghost_wl_dynload_libraries_exit() { wayland_dynload_client_exit(); wayland_dynload_cursor_exit(); -# ifdef WITH_OPENGL_BACKEND wayland_dynload_egl_exit(); -# endif # ifdef WITH_GHOST_WAYLAND_LIBDECOR wayland_dynload_libdecor_exit(); # endif diff --git a/intern/ghost/intern/GHOST_SystemWayland.hh b/intern/ghost/intern/GHOST_SystemWayland.hh index de0674320cc..4f69920537b 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.hh +++ b/intern/ghost/intern/GHOST_SystemWayland.hh @@ -236,10 +236,6 @@ class GHOST_SystemWayland : public GHOST_System { struct wl_shm *wl_shm_get() const; - void ime_begin( - GHOST_WindowWayland *win, int32_t x, int32_t y, int32_t w, int32_t h, bool completed) const; - void ime_end(GHOST_WindowWayland *win) const; - static const char *xdg_app_id_get(); /* WAYLAND utility functions. */ diff --git a/intern/ghost/intern/GHOST_SystemWin32.cc b/intern/ghost/intern/GHOST_SystemWin32.cc index 0380c4c511b..5fdeb83fc6e 100644 --- a/intern/ghost/intern/GHOST_SystemWin32.cc +++ b/intern/ghost/intern/GHOST_SystemWin32.cc @@ -23,8 +23,8 @@ #include #include -#include "utf_winfunc.hh" -#include "utfconv.hh" +#include "utf_winfunc.h" +#include "utfconv.h" #include "IMB_imbuf.h" #include "IMB_imbuf_types.h" diff --git a/intern/ghost/intern/GHOST_SystemX11.cc b/intern/ghost/intern/GHOST_SystemX11.cc index e9e2332c139..91ea997fd8c 100644 --- a/intern/ghost/intern/GHOST_SystemX11.cc +++ b/intern/ghost/intern/GHOST_SystemX11.cc @@ -358,16 +358,8 @@ GHOST_IContext *GHOST_SystemX11::createOffscreenContext(GHOST_GPUSettings gpuSet switch (gpuSettings.context_type) { #ifdef WITH_VULKAN_BACKEND case GHOST_kDrawingContextTypeVulkan: { - GHOST_Context *context = new GHOST_ContextVK(false, - GHOST_kVulkanPlatformX11, - 0, - m_display, - nullptr, - nullptr, - nullptr, - 1, - 2, - debug_context); + GHOST_Context *context = new GHOST_ContextVK( + false, GHOST_kVulkanPlatformX11, 0, m_display, nullptr, nullptr, 1, 2, debug_context); if (context->initializeDrawingContext()) { return context; } @@ -1742,9 +1734,7 @@ GHOST_TCapabilityFlag GHOST_SystemX11::getCapabilities() const /* No support yet for desktop sampling. */ GHOST_kCapabilityDesktopSample | /* No support yet for image copy/paste. */ - GHOST_kCapabilityClipboardImages | - /* No support yet for IME input methods. */ - GHOST_kCapabilityInputIME)); + GHOST_kCapabilityClipboardImages)); } void GHOST_SystemX11::addDirtyWindow(GHOST_WindowX11 *bad_wind) diff --git a/intern/ghost/intern/GHOST_WaylandCursorSettings.hh b/intern/ghost/intern/GHOST_WaylandCursorSettings.hh new file mode 100644 index 00000000000..634588f8650 --- /dev/null +++ b/intern/ghost/intern/GHOST_WaylandCursorSettings.hh @@ -0,0 +1,131 @@ +/* SPDX-FileCopyrightText: 2021-2023 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup GHOST + */ + +#pragma once +#include + +#ifdef WITH_GHOST_WAYLAND_DBUS +# include +#endif + +#ifdef WITH_GHOST_WAYLAND_DBUS +static DBusMessage *get_setting_sync(DBusConnection *const connection, + const char *key, + const char *value) +{ + DBusError error; + dbus_bool_t success; + DBusMessage *message; + DBusMessage *reply; + + dbus_error_init(&error); + + message = dbus_message_new_method_call("org.freedesktop.portal.Desktop", + "/org/freedesktop/portal/desktop", + "org.freedesktop.portal.Settings", + "Read"); + + success = dbus_message_append_args( + message, DBUS_TYPE_STRING, &key, DBUS_TYPE_STRING, &value, DBUS_TYPE_INVALID); + + if (!success) { + return nullptr; + } + + reply = dbus_connection_send_with_reply_and_block( + connection, message, DBUS_TIMEOUT_USE_DEFAULT, &error); + + dbus_message_unref(message); + + if (dbus_error_is_set(&error)) { + return nullptr; + } + + return reply; +} + +static bool parse_type(DBusMessage *const reply, const int type, void *value) +{ + DBusMessageIter iter[3]; + + dbus_message_iter_init(reply, &iter[0]); + if (dbus_message_iter_get_arg_type(&iter[0]) != DBUS_TYPE_VARIANT) { + return false; + } + + dbus_message_iter_recurse(&iter[0], &iter[1]); + if (dbus_message_iter_get_arg_type(&iter[1]) != DBUS_TYPE_VARIANT) { + return false; + } + + dbus_message_iter_recurse(&iter[1], &iter[2]); + if (dbus_message_iter_get_arg_type(&iter[2]) != type) { + return false; + } + + dbus_message_iter_get_basic(&iter[2], value); + + return true; +} +#endif /* WITH_GHOST_WAYLAND_DBUS */ + +static bool get_cursor_settings(std::string &theme, int &size) +{ +#ifdef WITH_GHOST_WAYLAND_DBUS + static const char name[] = "org.gnome.desktop.interface"; + static const char key_theme[] = "cursor-theme"; + static const char key_size[] = "cursor-size"; + + DBusError error; + DBusConnection *connection; + DBusMessage *reply; + const char *value_theme = nullptr; + + dbus_error_init(&error); + + connection = dbus_bus_get(DBUS_BUS_SESSION, &error); + + if (dbus_error_is_set(&error)) { + return false; + } + + reply = get_setting_sync(connection, name, key_theme); + if (!reply) { + return false; + } + + if (!parse_type(reply, DBUS_TYPE_STRING, &value_theme)) { + dbus_message_unref(reply); + return false; + } + + theme = std::string(value_theme); + + dbus_message_unref(reply); + + reply = get_setting_sync(connection, name, key_size); + if (!reply) { + return false; + } + + if (!parse_type(reply, DBUS_TYPE_INT32, &size)) { + dbus_message_unref(reply); + return false; + } + + dbus_message_unref(reply); + + return true; +#else + /* NOTE: eventually we could have alternative ways to access the theme, + * this uses the "default" theme which is functional (instead of a user-defined theme). */ + (void)theme; + (void)size; + return false; +#endif /* !WITH_GHOST_WAYLAND_DBUS */ +} diff --git a/intern/ghost/intern/GHOST_WindowWayland.cc b/intern/ghost/intern/GHOST_WindowWayland.cc index 9212be7bc4a..8ccb8b73fa9 100644 --- a/intern/ghost/intern/GHOST_WindowWayland.cc +++ b/intern/ghost/intern/GHOST_WindowWayland.cc @@ -24,12 +24,10 @@ #include -#ifdef WITH_OPENGL_BACKEND -# ifdef WITH_GHOST_WAYLAND_DYNLOAD -# include -# endif -# include +#ifdef WITH_GHOST_WAYLAND_DYNLOAD +# include #endif +#include #include /* For `std::find`. */ @@ -258,6 +256,8 @@ struct GWL_Window { /** Wayland core types. */ struct { wl_surface *surface = nullptr; + + wl_egl_window *egl_window = nullptr; } wl; /** Wayland native types. */ @@ -277,19 +277,8 @@ struct GWL_Window { xdg_activation_token_v1 *activation_token = nullptr; } xdg; - struct { -#ifdef WITH_OPENGL_BACKEND - wl_egl_window *egl_window = nullptr; -#endif -#ifdef WITH_VULKAN_BACKEND - GHOST_ContextVK_WindowInfo *vulkan_window_info = nullptr; -#endif - } backend; - GHOST_WindowWayland *ghost_window = nullptr; GHOST_SystemWayland *ghost_system = nullptr; - GHOST_TDrawingContextType ghost_context_type = GHOST_kDrawingContextTypeNone; - /** * Outputs on which the window is currently shown on. * @@ -334,21 +323,6 @@ struct GWL_Window { #endif /* USE_EVENT_BACKGROUND_THREAD */ }; -static void gwl_window_resize_for_backend(GWL_Window *win, const int32_t size[2]) -{ -#ifdef WITH_OPENGL_BACKEND - if (win->ghost_context_type == GHOST_kDrawingContextTypeOpenGL) { - wl_egl_window_resize(win->backend.egl_window, UNPACK2(size), 0, 0); - } -#endif -#ifdef WITH_VULKAN_BACKEND - if (win->ghost_context_type == GHOST_kDrawingContextTypeVulkan) { - win->backend.vulkan_window_info->size[0] = size[0]; - win->backend.vulkan_window_info->size[1] = size[1]; - } -#endif -} - static void gwl_window_title_set(GWL_Window *win, const char *title) { #ifdef WITH_GHOST_WAYLAND_LIBDECOR @@ -709,7 +683,7 @@ static void gwl_window_frame_pending_fractional_scale_set(GWL_Window *win, static void gwl_window_frame_pending_size_set(GWL_Window *win, bool *r_surface_needs_commit, - bool *r_surface_needs_resize_for_backend, + bool *r_surface_needs_egl_resize, bool *r_surface_needs_buffer_scale) { if (win->frame_pending.size[0] == 0 || win->frame_pending.size[1] == 0) { @@ -729,11 +703,11 @@ static void gwl_window_frame_pending_size_set(GWL_Window *win, gwl_window_viewport_size_update(win); } - if (r_surface_needs_resize_for_backend) { - *r_surface_needs_resize_for_backend = true; + if (r_surface_needs_egl_resize) { + *r_surface_needs_egl_resize = true; } else { - gwl_window_resize_for_backend(win, win->frame.size); + wl_egl_window_resize(win->wl.egl_window, UNPACK2(win->frame.size), 0, 0); } win->ghost_window->notify_size(); @@ -802,17 +776,15 @@ static void gwl_window_frame_update_from_pending_no_lock(GWL_Window *win) const bool dpi_changed = win->frame_pending.fractional_scale != win->frame.fractional_scale; bool surface_needs_commit = false; - bool surface_needs_resize_for_backend = false; + bool surface_needs_egl_resize = false; bool surface_needs_buffer_scale = false; if (win->frame_pending.size[0] != 0 && win->frame_pending.size[1] != 0) { if ((win->frame.size[0] != win->frame_pending.size[0]) || (win->frame.size[1] != win->frame_pending.size[1])) { - gwl_window_frame_pending_size_set(win, - &surface_needs_commit, - &surface_needs_resize_for_backend, - &surface_needs_buffer_scale); + gwl_window_frame_pending_size_set( + win, &surface_needs_commit, &surface_needs_egl_resize, &surface_needs_buffer_scale); } } @@ -827,8 +799,8 @@ static void gwl_window_frame_update_from_pending_no_lock(GWL_Window *win) } } - if (surface_needs_resize_for_backend) { - gwl_window_resize_for_backend(win, win->frame.size); + if (surface_needs_egl_resize) { + wl_egl_window_resize(win->wl.egl_window, UNPACK2(win->frame.size), 0, 0); } if (surface_needs_buffer_scale) { @@ -1462,12 +1434,10 @@ GHOST_WindowWayland::GHOST_WindowWayland(GHOST_SystemWayland *system, const GHOST_TDrawingContextType type, const bool is_dialog, const bool stereoVisual, - const bool exclusive, - const bool is_debug) + const bool exclusive) : GHOST_Window(width, height, state, stereoVisual, exclusive), system_(system), - window_(new GWL_Window), - is_debug_context_(is_debug) + window_(new GWL_Window) { #ifdef USE_EVENT_BACKGROUND_THREAD std::lock_guard lock_server_guard{*system->server_mutex}; @@ -1475,7 +1445,6 @@ GHOST_WindowWayland::GHOST_WindowWayland(GHOST_SystemWayland *system, window_->ghost_window = this; window_->ghost_system = system; - window_->ghost_context_type = type; /* NOTE(@ideasman42): The scale set here to avoid flickering on startup. * When all monitors use the same scale (which is quite common) there aren't any problems. @@ -1516,19 +1485,8 @@ GHOST_WindowWayland::GHOST_WindowWayland(GHOST_SystemWayland *system, wl_surface_add_listener(window_->wl.surface, &wl_surface_listener, window_); -#ifdef WITH_OPENGL_BACKEND - if (type == GHOST_kDrawingContextTypeOpenGL) { - window_->backend.egl_window = wl_egl_window_create( - window_->wl.surface, int(window_->frame.size[0]), int(window_->frame.size[1])); - } -#endif -#ifdef WITH_VULKAN_BACKEND - if (type == GHOST_kDrawingContextTypeVulkan) { - window_->backend.vulkan_window_info = new GHOST_ContextVK_WindowInfo; - window_->backend.vulkan_window_info->size[0] = window_->frame.size[0]; - window_->backend.vulkan_window_info->size[1] = window_->frame.size[1]; - } -#endif + window_->wl.egl_window = wl_egl_window_create( + window_->wl.surface, int(window_->frame.size[0]), int(window_->frame.size[1])); wp_fractional_scale_manager_v1 *fractional_scale_manager = system->wp_fractional_scale_manager_get(); @@ -1661,9 +1619,9 @@ GHOST_WindowWayland::GHOST_WindowWayland(GHOST_SystemWayland *system, gwl_window_state_set(window_, state); } - /* Drawing context. */ + /* EGL context. */ if (setDrawingContextType(type) == GHOST_kFailure) { - GHOST_PRINT("Failed to create drawing context" << std::endl); + GHOST_PRINT("Failed to create EGL context" << std::endl); } /* Set swap interval to 0 to prevent blocking. */ @@ -1832,16 +1790,7 @@ GHOST_WindowWayland::~GHOST_WindowWayland() releaseNativeHandles(); -#ifdef WITH_OPENGL_BACKEND - if (window_->ghost_context_type == GHOST_kDrawingContextTypeOpenGL) { - wl_egl_window_destroy(window_->backend.egl_window); - } -#endif -#ifdef WITH_VULKAN_BACKEND - if (window_->ghost_context_type == GHOST_kDrawingContextTypeVulkan) { - delete window_->backend.vulkan_window_info; - } -#endif + wl_egl_window_destroy(window_->wl.egl_window); if (window_->xdg.activation_token) { xdg_activation_token_v1_destroy(window_->xdg.activation_token); @@ -2006,16 +1955,15 @@ GHOST_Context *GHOST_WindowWayland::newDrawingContext(GHOST_TDrawingContextType #ifdef WITH_VULKAN_BACKEND case GHOST_kDrawingContextTypeVulkan: { - GHOST_ContextVK *context = new GHOST_ContextVK(m_wantStereoVisual, - GHOST_kVulkanPlatformWayland, - 0, - nullptr, - window_->wl.surface, - system_->wl_display_get(), - window_->backend.vulkan_window_info, - 1, - 2, - is_debug_context_); + GHOST_Context *context = new GHOST_ContextVK(m_wantStereoVisual, + GHOST_kVulkanPlatformWayland, + 0, + nullptr, + window_->wl.surface, + system_->wl_display_get(), + 1, + 2, + true); if (context->initializeDrawingContext()) { return context; } @@ -2030,13 +1978,12 @@ GHOST_Context *GHOST_WindowWayland::newDrawingContext(GHOST_TDrawingContextType GHOST_Context *context = new GHOST_ContextEGL( system_, m_wantStereoVisual, - EGLNativeWindowType(window_->backend.egl_window), + EGLNativeWindowType(window_->wl.egl_window), EGLNativeDisplayType(system_->wl_display_get()), EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT, 4, minor, - GHOST_OPENGL_EGL_CONTEXT_FLAGS | - (is_debug_context_ ? EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR : 0), + GHOST_OPENGL_EGL_CONTEXT_FLAGS, GHOST_OPENGL_EGL_RESET_NOTIFICATION_STRATEGY, EGL_OPENGL_API); @@ -2055,20 +2002,6 @@ GHOST_Context *GHOST_WindowWayland::newDrawingContext(GHOST_TDrawingContextType } } -#ifdef WITH_INPUT_IME - -void GHOST_WindowWayland::beginIME(int32_t x, int32_t y, int32_t w, int32_t h, bool completed) -{ - system_->ime_begin(this, x, y, w, h, completed); -} - -void GHOST_WindowWayland::endIME() -{ - system_->ime_end(this); -} - -#endif - /** \} */ /* -------------------------------------------------------------------- */ diff --git a/intern/ghost/intern/GHOST_WindowWayland.hh b/intern/ghost/intern/GHOST_WindowWayland.hh index db1f941c86e..502e879a8e2 100644 --- a/intern/ghost/intern/GHOST_WindowWayland.hh +++ b/intern/ghost/intern/GHOST_WindowWayland.hh @@ -74,8 +74,7 @@ class GHOST_WindowWayland : public GHOST_Window { GHOST_TDrawingContextType type, const bool is_dialog, const bool stereoVisual, - const bool exclusive, - const bool is_debug); + const bool exclusive); ~GHOST_WindowWayland() override; @@ -140,11 +139,6 @@ class GHOST_WindowWayland : public GHOST_Window { void setOpaque() const; #endif -#ifdef WITH_INPUT_IME - void beginIME(int32_t x, int32_t y, int32_t w, int32_t h, bool completed) override; - void endIME() override; -#endif /* WITH_INPUT_IME */ - /* WAYLAND direct-data access. */ int scale_get() const; @@ -195,7 +189,6 @@ class GHOST_WindowWayland : public GHOST_Window { private: GHOST_SystemWayland *system_; struct GWL_Window *window_; - bool is_debug_context_; /** * \param type: The type of rendering context create. diff --git a/intern/ghost/intern/GHOST_WindowWin32.cc b/intern/ghost/intern/GHOST_WindowWin32.cc index d1af0ae01af..f3bf84a3dd2 100644 --- a/intern/ghost/intern/GHOST_WindowWin32.cc +++ b/intern/ghost/intern/GHOST_WindowWin32.cc @@ -12,8 +12,8 @@ #include "GHOST_DropTargetWin32.hh" #include "GHOST_SystemWin32.hh" #include "GHOST_WindowManager.hh" -#include "utf_winfunc.hh" -#include "utfconv.hh" +#include "utf_winfunc.h" +#include "utfconv.h" #ifdef WITH_OPENGL_BACKEND # include "GHOST_ContextWGL.hh" diff --git a/intern/ghost/intern/GHOST_WindowX11.cc b/intern/ghost/intern/GHOST_WindowX11.cc index 0c643ca4e3d..8e8f17c541c 100644 --- a/intern/ghost/intern/GHOST_WindowX11.cc +++ b/intern/ghost/intern/GHOST_WindowX11.cc @@ -1185,7 +1185,6 @@ GHOST_Context *GHOST_WindowX11::newDrawingContext(GHOST_TDrawingContextType type m_display, nullptr, nullptr, - nullptr, 1, 2, m_is_debug_context); diff --git a/intern/ghost/test/gears/GHOST_Test.cpp b/intern/ghost/test/gears/GHOST_Test.cpp index 5c2409a12f0..f338d6bfa8b 100644 --- a/intern/ghost/test/gears/GHOST_Test.cpp +++ b/intern/ghost/test/gears/GHOST_Test.cpp @@ -443,7 +443,7 @@ Application::~Application() } } -bool Application::processEvent(const GHOST_IEvent *event) +bool Application::processEvent(GHOST_IEvent *event) { GHOST_IWindow *window = event->getWindow(); bool handled = true; diff --git a/intern/utfconv/CMakeLists.txt b/intern/utfconv/CMakeLists.txt index 0f567f13a7c..057fc4d43ee 100644 --- a/intern/utfconv/CMakeLists.txt +++ b/intern/utfconv/CMakeLists.txt @@ -11,9 +11,9 @@ set(INC_SYS ) set(SRC - utfconv.cc + utfconv.c - utfconv.hh + utfconv.h ) set(LIB @@ -24,8 +24,8 @@ set(LIB # ... because one day we might want to use it on other platforms. if(WIN32) list(APPEND SRC - utf_winfunc.cc - utf_winfunc.hh + utf_winfunc.c + utf_winfunc.h ) endif() diff --git a/intern/utfconv/utf_winfunc.cc b/intern/utfconv/utf_winfunc.c similarity index 98% rename from intern/utfconv/utf_winfunc.cc rename to intern/utfconv/utf_winfunc.c index 673be18b139..e76b43c9813 100644 --- a/intern/utfconv/utf_winfunc.cc +++ b/intern/utfconv/utf_winfunc.c @@ -10,8 +10,8 @@ # define _WIN32_IE 0x0501 #endif -#include "utf_winfunc.hh" -#include "utfconv.hh" +#include "utf_winfunc.h" +#include "utfconv.h" #include #include #include diff --git a/intern/utfconv/utf_winfunc.hh b/intern/utfconv/utf_winfunc.h similarity index 100% rename from intern/utfconv/utf_winfunc.hh rename to intern/utfconv/utf_winfunc.h diff --git a/intern/utfconv/utfconv.cc b/intern/utfconv/utfconv.c similarity index 99% rename from intern/utfconv/utfconv.cc rename to intern/utfconv/utfconv.c index 46afdf4de8a..ddc47153dfe 100644 --- a/intern/utfconv/utfconv.cc +++ b/intern/utfconv/utfconv.c @@ -6,7 +6,7 @@ * \ingroup intern_utf_conv */ -#include "utfconv.hh" +#include "utfconv.h" size_t count_utf_8_from_16(const wchar_t *string16) { diff --git a/intern/utfconv/utfconv.hh b/intern/utfconv/utfconv.h similarity index 97% rename from intern/utfconv/utfconv.hh rename to intern/utfconv/utfconv.h index 075ccadff65..441814f8dd7 100644 --- a/intern/utfconv/utfconv.hh +++ b/intern/utfconv/utfconv.h @@ -13,6 +13,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + /** * Counts how many bytes is required for future utf-8 string using utf-16 * \param string16: pointer to working utf-16 string @@ -86,4 +90,8 @@ wchar_t *alloc_utf16_from_8(const char *in8, size_t add); } \ (void)0 +#ifdef __cplusplus +} +#endif + #endif /* __UTFCONV_H__ */ diff --git a/intern/wayland_dynload/CMakeLists.txt b/intern/wayland_dynload/CMakeLists.txt index 233ec230720..5db155dc9cb 100644 --- a/intern/wayland_dynload/CMakeLists.txt +++ b/intern/wayland_dynload/CMakeLists.txt @@ -11,32 +11,23 @@ set(INC set(INC_SYS ${wayland-client_INCLUDE_DIRS} + ${wayland-egl_INCLUDE_DIRS} ${wayland-cursor_INCLUDE_DIRS} ) set(SRC intern/wayland_dynload_client.c intern/wayland_dynload_cursor.c + intern/wayland_dynload_egl.c intern/wayland_dynload_utils.c extern/wayland_dynload_API.h extern/wayland_dynload_client.h extern/wayland_dynload_cursor.h + extern/wayland_dynload_egl.h intern/wayland_dynload_utils.h ) -if(WITH_OPENGL_BACKEND) - list(APPEND INC_SYS - ${wayland-egl_INCLUDE_DIRS} - ) - list(APPEND SRC - intern/wayland_dynload_egl.c - - extern/wayland_dynload_egl.h - ) - add_definitions(-DWITH_OPENGL_BACKEND) -endif() - if(WITH_GHOST_WAYLAND_LIBDECOR) list(APPEND INC_SYS ${libdecor_INCLUDE_DIRS} diff --git a/intern/wayland_dynload/extern/wayland_dynload_API.h b/intern/wayland_dynload/extern/wayland_dynload_API.h index da8b3c4fc60..a5f15e4b8a1 100644 --- a/intern/wayland_dynload/extern/wayland_dynload_API.h +++ b/intern/wayland_dynload/extern/wayland_dynload_API.h @@ -20,10 +20,8 @@ void wayland_dynload_client_exit(void); bool wayland_dynload_cursor_init(bool verbose); void wayland_dynload_cursor_exit(void); -#ifdef WITH_OPENGL_BACKEND bool wayland_dynload_egl_init(bool verbose); void wayland_dynload_egl_exit(void); -#endif #ifdef WITH_GHOST_WAYLAND_LIBDECOR bool wayland_dynload_libdecor_init(bool verbose); diff --git a/release/datafiles/colormanagement/config.ocio b/release/datafiles/colormanagement/config.ocio index 2565bf03dfd..d12f87c61c7 100644 --- a/release/datafiles/colormanagement/config.ocio +++ b/release/datafiles/colormanagement/config.ocio @@ -642,7 +642,7 @@ looks: contrast: {rgb: [0.7, 0.7, 0.7], master: 1} saturation: 1.15 pivot: {contrast: -0.2} - + - ! name: False Color - Punchy process_space: AgX Log @@ -744,4 +744,4 @@ looks: style: log contrast: {rgb: [0.7, 0.7, 0.7], master: 1} saturation: 1.15 - pivot: {contrast: -0.2} + pivot: {contrast: -0.2} \ No newline at end of file diff --git a/release/datafiles/splash.png b/release/datafiles/splash.png index 4475ee064ac0f39c371b79e36bf7249c8c4068c8..35467cb6029a73cc594688d024dfe111f32a46df 100644 GIT binary patch literal 1153018 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

)Z0;_WG}V2f{-; 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

^)OdedX(f2C&f|f?tq2aN%BK={`N0l;GIV{ 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�m??PcZ+6qjaxG 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

s|G!L3Xv=TMxJ!^)-cHT~@0+6( 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 (iqU@CDXh~;z6VeT;u8S>KNe2a` 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

eE(X@Hws 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

tW4HS~7a4EtLKo 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

  • &TV%Y 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 N002ovPDHLkV1lpYnP30_ literal 730581 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

    weQC!Xaij6SRGmSv!wy1wdo5)%SAy3I8&Ndi@ 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

    *{Bt~j&D|tBl^1SE6r~4_HL%CoG7l`f|Lxs~l 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

    3tKx` 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�@7>S3l?cFaOe$~fy!@$5r8!Q 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≶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 Dz`b-@ diff --git a/scripts/modules/bl_i18n_utils/bl_extract_messages.py b/scripts/modules/bl_i18n_utils/bl_extract_messages.py index ea9664b3da9..1c3b26651b5 100644 --- a/scripts/modules/bl_i18n_utils/bl_extract_messages.py +++ b/scripts/modules/bl_i18n_utils/bl_extract_messages.py @@ -588,7 +588,6 @@ def dump_py_messages_from_files(msgs, reports, files, settings): ), "message": (), "heading": (), - "placeholder": ((("text_ctxt",), _ctxt_to_ctxt),), } context_kw_set = {} @@ -622,8 +621,7 @@ def dump_py_messages_from_files(msgs, reports, files, settings): for arg_pos, (arg_kw, arg) in enumerate(func.parameters.items()): if (not arg.is_output) and (arg.type == 'STRING'): for msgid, msgctxts in context_kw_set.items(): - # The msgid can be missing if it is used in only some UILayout functions but not all - if arg_kw in msgctxts and msgid in func_translate_args[func_id]: + if arg_kw in msgctxts: func_translate_args[func_id][msgid][1][arg_kw] = arg_pos # The report() func of operators. for func_id, func in bpy.types.Operator.bl_rna.functions.items(): diff --git a/scripts/modules/bpy_extras/anim_utils.py b/scripts/modules/bpy_extras/anim_utils.py index f0db486f07e..d0aab8edd3d 100644 --- a/scripts/modules/bpy_extras/anim_utils.py +++ b/scripts/modules/bpy_extras/anim_utils.py @@ -12,7 +12,6 @@ __all__ = ( import bpy from bpy.types import Action -from dataclasses import dataclass from typing import ( List, @@ -32,47 +31,11 @@ FCurveKey = Tuple[ ListKeyframes = List[float] -@dataclass -class BakeOptions: - only_selected: bool - """Only bake selected bones.""" - - do_pose: bool - """Bake pose channels""" - - do_object: bool - """Bake objects.""" - - do_visual_keying: bool - """Use the final transformations for baking ('visual keying').""" - - do_constraint_clear: bool - """Remove constraints after baking.""" - - do_parents_clear: bool - """Unparent after baking objects.""" - - do_clean: bool - """Remove redundant keyframes after baking.""" - - do_location: bool - """Bake location channels""" - - do_rotation: bool - """Bake rotation channels""" - - do_scale: bool - """Bake scale channels""" - - do_bbone: bool - """Bake b-bone channels""" - - def bake_action( obj, *, action, frames, - bake_options: BakeOptions + **kwargs ): """ :arg obj: Object to bake. @@ -86,13 +49,13 @@ def bake_action( :return: an action or None :rtype: :class:`bpy.types.Action` """ - if not (bake_options.do_pose or bake_options.do_object): + if not (kwargs.get("do_pose") or kwargs.get("do_object")): return None action, = bake_action_objects( [(obj, action)], frames=frames, - bake_options=bake_options + **kwargs, ) return action @@ -101,7 +64,7 @@ def bake_action_objects( object_action_pairs, *, frames, - bake_options: BakeOptions + **kwargs ): """ A version of :func:`bake_action_objects_iter` that takes frames and returns the output. @@ -112,7 +75,7 @@ def bake_action_objects( :return: A sequence of Action or None types (aligned with `object_action_pairs`) :rtype: sequence of :class:`bpy.types.Action` """ - iter = bake_action_objects_iter(object_action_pairs, bake_options=bake_options) + iter = bake_action_objects_iter(object_action_pairs, **kwargs) iter.send(None) for frame in frames: iter.send(frame) @@ -121,7 +84,7 @@ def bake_action_objects( def bake_action_objects_iter( object_action_pairs, - bake_options: BakeOptions + **kwargs ): """ An coroutine that bakes actions for multiple objects. @@ -133,7 +96,7 @@ def bake_action_objects_iter( scene = bpy.context.scene frame_back = scene.frame_current iter_all = tuple( - bake_action_iter(obj, action=action, bake_options=bake_options) + bake_action_iter(obj, action=action, **kwargs) for (obj, action) in object_action_pairs ) for iter in iter_all: @@ -155,7 +118,13 @@ def bake_action_iter( obj, *, action, - bake_options: BakeOptions + only_selected=False, + do_pose=True, + do_object=True, + do_visual_keying=True, + do_constraint_clear=False, + do_parents_clear=False, + do_clean=False ): """ An coroutine that bakes action for a single object. @@ -165,8 +134,20 @@ def bake_action_iter( :arg action: An action to bake the data into, or None for a new action to be created. :type action: :class:`bpy.types.Action` or None - :arg bake_options: Boolean options of what to include into the action bake. - :type bake_options: :class: `anim_utils.BakeOptions` + :arg only_selected: Only bake selected bones. + :type only_selected: bool + :arg do_pose: Bake pose channels. + :type do_pose: bool + :arg do_object: Bake objects. + :type do_object: bool + :arg do_visual_keying: Use the final transformations for baking ('visual keying') + :type do_visual_keying: bool + :arg do_constraint_clear: Remove constraints after baking. + :type do_constraint_clear: bool + :arg do_parents_clear: Unparent after baking objects. + :type do_parents_clear: bool + :arg do_clean: Remove redundant keyframes after baking. + :type do_clean: bool :return: an action or None :rtype: :class:`bpy.types.Action` @@ -199,7 +180,7 @@ def bake_action_iter( matrix = {} bbones = {} for name, pbone in obj.pose.bones.items(): - if bake_options.do_visual_keying: + if do_visual_keying: # Get the final transform of the bone in its own local space... matrix[name] = obj.convert_space(pose_bone=pbone, matrix=pbone.matrix, from_space='POSE', to_space='LOCAL') @@ -211,8 +192,8 @@ def bake_action_iter( bbones[name] = {bb_prop: getattr(pbone, bb_prop) for bb_prop in BBONE_PROPS} return matrix, bbones - if bake_options.do_parents_clear: - if bake_options.do_visual_keying: + if do_parents_clear: + if do_visual_keying: def obj_frame_info(obj): return obj.matrix_world.copy() else: @@ -224,7 +205,7 @@ def bake_action_iter( else: return matrix.copy() else: - if bake_options.do_visual_keying: + if do_visual_keying: def obj_frame_info(obj): parent = obj.parent matrix = obj.matrix_world @@ -240,9 +221,9 @@ def bake_action_iter( # Setup the Context if obj.pose is None: - bake_options.do_pose = False + do_pose = False - if not (bake_options.do_pose or bake_options.do_object): + if not (do_pose or do_object): raise Exception("Pose and object baking is disabled, no action needed") pose_info = [] @@ -259,14 +240,14 @@ def bake_action_iter( if frame is None: break - if bake_options.do_pose: + if do_pose: pose_info.append((frame, *pose_frame_info(obj))) - if bake_options.do_object: + if do_object: obj_info.append((frame, obj_frame_info(obj))) # ------------------------------------------------------------------------- # Clean (store initial data) - if bake_options.do_clean and action is not None: + if do_clean and action is not None: clean_orig_data = {fcu: {p.co[1] for p in fcu.keyframe_points} for fcu in action.fcurves} else: clean_orig_data = {} @@ -297,12 +278,12 @@ def bake_action_iter( # pose lookup_fcurves = {(fcurve.data_path, fcurve.array_index): fcurve for fcurve in action.fcurves} - if bake_options.do_pose: + if do_pose: for name, pbone in obj.pose.bones.items(): - if bake_options.only_selected and not pbone.bone.select: + if only_selected and not pbone.bone.select: continue - if bake_options.do_constraint_clear: + if do_constraint_clear: while pbone.constraints: pbone.constraints.remove(pbone.constraints[0]) @@ -319,17 +300,13 @@ def bake_action_iter( paths_bbprops = [(base_fcurve_path + bbprop) for bbprop in BBONE_PROPS] keyframes = KeyframesCo() + keyframes.add_paths(path_location, 3) + keyframes.add_paths(path_quaternion, 4) + keyframes.add_paths(path_axis_angle, 4) + keyframes.add_paths(path_euler, 3) + keyframes.add_paths(path_scale, 3) - if bake_options.do_location: - keyframes.add_paths(path_location, 3) - if bake_options.do_rotation: - keyframes.add_paths(path_quaternion, 4) - keyframes.add_paths(path_axis_angle, 4) - keyframes.add_paths(path_euler, 3) - if bake_options.do_scale: - keyframes.add_paths(path_scale, 3) - - if bake_options.do_bbone and pbone.bone.bbone_segments > 1: + if pbone.bone.bbone_segments > 1: for prop_name, path in zip(BBONE_PROPS, paths_bbprops): keyframes.add_paths(path, BBONE_PROPS_LENGTHS[prop_name]) @@ -338,35 +315,32 @@ def bake_action_iter( for (f, matrix, bbones) in pose_info: pbone.matrix_basis = matrix[name].copy() - if bake_options.do_location: - keyframes.extend_co_values(path_location, 3, f, pbone.location) + keyframes.extend_co_values(path_location, 3, f, pbone.location) - if bake_options.do_rotation: - if rotation_mode == 'QUATERNION': - if quat_prev is not None: - quat = pbone.rotation_quaternion.copy() - quat.make_compatible(quat_prev) - pbone.rotation_quaternion = quat - quat_prev = quat - del quat - else: - quat_prev = pbone.rotation_quaternion.copy() - keyframes.extend_co_values(path_quaternion, 4, f, pbone.rotation_quaternion) - elif rotation_mode == 'AXIS_ANGLE': - keyframes.extend_co_values(path_axis_angle, 4, f, pbone.rotation_axis_angle) - else: # euler, XYZ, ZXY etc - if euler_prev is not None: - euler = pbone.matrix_basis.to_euler(pbone.rotation_mode, euler_prev) - pbone.rotation_euler = euler - del euler - euler_prev = pbone.rotation_euler.copy() - keyframes.extend_co_values(path_euler, 3, f, pbone.rotation_euler) + if rotation_mode == 'QUATERNION': + if quat_prev is not None: + quat = pbone.rotation_quaternion.copy() + quat.make_compatible(quat_prev) + pbone.rotation_quaternion = quat + quat_prev = quat + del quat + else: + quat_prev = pbone.rotation_quaternion.copy() + keyframes.extend_co_values(path_quaternion, 4, f, pbone.rotation_quaternion) + elif rotation_mode == 'AXIS_ANGLE': + keyframes.extend_co_values(path_axis_angle, 4, f, pbone.rotation_axis_angle) + else: # euler, XYZ, ZXY etc + if euler_prev is not None: + euler = pbone.matrix_basis.to_euler(pbone.rotation_mode, euler_prev) + pbone.rotation_euler = euler + del euler + euler_prev = pbone.rotation_euler.copy() + keyframes.extend_co_values(path_euler, 3, f, pbone.rotation_euler) - if bake_options.do_scale: - keyframes.extend_co_values(path_scale, 3, f, pbone.scale) + keyframes.extend_co_values(path_scale, 3, f, pbone.scale) # Bendy Bones - if bake_options.do_bbone and pbone.bone.bbone_segments > 1: + if pbone.bone.bbone_segments > 1: bbone_shape = bbones[name] for prop_index, prop_name in enumerate(BBONE_PROPS): prop_len = BBONE_PROPS_LENGTHS[prop_name] @@ -385,8 +359,8 @@ def bake_action_iter( keyframes.insert_keyframes_into_existing_action(lookup_fcurves, total_new_keys, action, name) # object. TODO. multiple objects - if bake_options.do_object: - if bake_options.do_constraint_clear: + if do_object: + if do_constraint_clear: while obj.constraints: obj.constraints.remove(obj.constraints[0]) @@ -401,14 +375,11 @@ def bake_action_iter( path_scale = "scale" keyframes = KeyframesCo() - if bake_options.do_location: - keyframes.add_paths(path_location, 3) - if bake_options.do_rotation: - keyframes.add_paths(path_quaternion, 4) - keyframes.add_paths(path_axis_angle, 4) - keyframes.add_paths(path_euler, 3) - if bake_options.do_scale: - keyframes.add_paths(path_scale, 3) + keyframes.add_paths(path_location, 3) + keyframes.add_paths(path_quaternion, 4) + keyframes.add_paths(path_axis_angle, 4) + keyframes.add_paths(path_euler, 3) + keyframes.add_paths(path_scale, 3) rotation_mode = obj.rotation_mode total_new_keys = len(obj_info) @@ -416,44 +387,41 @@ def bake_action_iter( name = "Action Bake" # XXX: placeholder obj.matrix_basis = matrix - if bake_options.do_location: - keyframes.extend_co_values(path_location, 3, f, obj.location) + keyframes.extend_co_values(path_location, 3, f, obj.location) - if bake_options.do_rotation: - if rotation_mode == 'QUATERNION': - if quat_prev is not None: - quat = obj.rotation_quaternion.copy() - quat.make_compatible(quat_prev) - obj.rotation_quaternion = quat - quat_prev = quat - del quat - else: - quat_prev = obj.rotation_quaternion.copy() - keyframes.extend_co_values(path_quaternion, 4, f, obj.rotation_quaternion) + if rotation_mode == 'QUATERNION': + if quat_prev is not None: + quat = obj.rotation_quaternion.copy() + quat.make_compatible(quat_prev) + obj.rotation_quaternion = quat + quat_prev = quat + del quat + else: + quat_prev = obj.rotation_quaternion.copy() + keyframes.extend_co_values(path_quaternion, 4, f, obj.rotation_quaternion) - elif rotation_mode == 'AXIS_ANGLE': - keyframes.extend_co_values(path_axis_angle, 4, f, obj.rotation_axis_angle) - else: # euler, XYZ, ZXY etc - if euler_prev is not None: - obj.rotation_euler = matrix.to_euler(obj.rotation_mode, euler_prev) - euler_prev = obj.rotation_euler.copy() - keyframes.extend_co_values(path_euler, 3, f, obj.rotation_euler) + elif rotation_mode == 'AXIS_ANGLE': + keyframes.extend_co_values(path_axis_angle, 4, f, obj.rotation_axis_angle) + else: # euler, XYZ, ZXY etc + if euler_prev is not None: + obj.rotation_euler = matrix.to_euler(obj.rotation_mode, euler_prev) + euler_prev = obj.rotation_euler.copy() + keyframes.extend_co_values(path_euler, 3, f, obj.rotation_euler) - if bake_options.do_scale: - keyframes.extend_co_values(path_scale, 3, f, obj.scale) + keyframes.extend_co_values(path_scale, 3, f, obj.scale) if is_new_action: keyframes.insert_keyframes_into_new_action(total_new_keys, action, name) else: keyframes.insert_keyframes_into_existing_action(lookup_fcurves, total_new_keys, action, name) - if bake_options.do_parents_clear: + if do_parents_clear: obj.parent = None # ------------------------------------------------------------------------- # Clean - if bake_options.do_clean: + if do_clean: for fcu in action.fcurves: fcu_orig_data = clean_orig_data.get(fcu, set()) diff --git a/scripts/modules/rna_xml.py b/scripts/modules/rna_xml.py index 76c11f36be9..18641d1530d 100644 --- a/scripts/modules/rna_xml.py +++ b/scripts/modules/rna_xml.py @@ -139,8 +139,8 @@ def rna2xml( # check if the list contains native types subvalue_rna = value.path_resolve(prop, False) if type(subvalue_rna).__name__ == "bpy_prop_array": - # Check if this is a 0-1 color (RGB, RGBA) - # in that case write as a hexadecimal. + # check if this is a 0-1 color (rgb, rgba) + # in that case write as a hexadecimal prop_rna = value.bl_rna.properties[prop] if (prop_rna.subtype == 'COLOR_GAMMA' and prop_rna.hard_min == 0.0 and diff --git a/scripts/presets/keyconfig/keymap_data/blender_default.py b/scripts/presets/keyconfig/keymap_data/blender_default.py index 48ae9739fb2..d1419ed584d 100644 --- a/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -4569,11 +4569,6 @@ def km_grease_pencil_paint(_params): ) items.extend([ - *_template_paint_radial_control("gpencil_paint"), - ("brush.scale_size", {"type": 'LEFT_BRACKET', "value": 'PRESS', "repeat": True}, - {"properties": [("scalar", 0.9)]}), - ("brush.scale_size", {"type": 'RIGHT_BRACKET', "value": 'PRESS', "repeat": True}, - {"properties": [("scalar", 1.0 / 0.9)]}), ("grease_pencil.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS'}, None), ("grease_pencil.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True}, {"properties": [("mode", 'INVERT')]}), @@ -4608,27 +4603,6 @@ def km_grease_pencil_edit(params): # Delete all active frames ("grease_pencil.delete_frame", {"type": 'DEL', "value": 'PRESS', "shift": True}, {"properties": [("type", "ALL_FRAMES")]}), - # Keyframe Menu - op_menu("VIEW3D_MT_edit_greasepencil_animation", {"type": 'I', "value": 'PRESS'}), - - # Transform Actions. - *_template_items_transform_actions(params, use_bend=True, use_mirror=True, use_tosphere=True, use_shear=True), - ("transform.transform", {"type": 'S', "value": 'PRESS', "alt": True}, - {"properties": [("mode", 'CURVE_SHRINKFATTEN')]}), - ("transform.transform", {"type": 'F', "value": 'PRESS', "shift": True}, - {"properties": [("mode", 'GPENCIL_OPACITY')]}), - - # Proportional editing. - *_template_items_proportional_editing( - params, connected=True, toggle_data_path='tool_settings.use_proportional_edit'), - - # Cyclical set - ("grease_pencil.cyclical_set", {"type": 'F', "value": 'PRESS'}, {"properties": [("type", "CLOSE")]}), - ("grease_pencil.cyclical_set", {"type": 'C', "value": 'PRESS', - "alt": True}, {"properties": [("type", "TOGGLE")]}), - - # Context menu - *_template_items_context_menu("VIEW3D_MT_greasepencil_edit_context_menu", params.context_menu_event), ]) return keymap @@ -7281,18 +7255,6 @@ def km_3d_view_tool_shear(params): ) -def km_3d_view_tool_bend(params): - return ( - "3D View Tool: Bend", - {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, - {"items": [ - # No need for `tool_modifier` since this takes all input. - ("transform.bend", params.tool_maybe_tweak_event, - {"properties": [("release_confirm", True)]}), - ]}, - ) - - def km_3d_view_tool_measure(params): return ( "3D View Tool: Measure", @@ -8594,7 +8556,6 @@ def generate_keymaps(params=None): km_3d_view_tool_rotate(params), km_3d_view_tool_scale(params), km_3d_view_tool_shear(params), - km_3d_view_tool_bend(params), km_3d_view_tool_measure(params), km_3d_view_tool_interactive_add(params), km_3d_view_tool_pose_breakdowner(params), diff --git a/scripts/startup/bl_operators/anim.py b/scripts/startup/bl_operators/anim.py index 542ee9696fd..c8c2ce41216 100644 --- a/scripts/startup/bl_operators/anim.py +++ b/scripts/startup/bl_operators/anim.py @@ -252,43 +252,19 @@ class NLA_OT_bake(Operator): ), default={'POSE'}, ) - channel_types: EnumProperty( - name="Channels", - description="Which channels to bake", - options={'ENUM_FLAG'}, - items=( - ('LOCATION', "Location", "Bake location channels"), - ('ROTATION', "Rotation", "Bake rotation channels"), - ('SCALE', "Scale", "Bake scale channels"), - ('BBONE', "B-Bone", "Bake b-bone channels"), - ), - default={'LOCATION', 'ROTATION', 'SCALE', 'BBONE'}, - ) def execute(self, context): from bpy_extras import anim_utils + do_pose = 'POSE' in self.bake_types + do_object = 'OBJECT' in self.bake_types - bake_options = anim_utils.BakeOptions( - only_selected=self.only_selected, - do_pose='POSE' in self.bake_types, - do_object='OBJECT' in self.bake_types, - do_visual_keying=self.visual_keying, - do_constraint_clear=self.clear_constraints, - do_parents_clear=self.clear_parents, - do_clean=self.clean_curves, - do_location='LOCATION' in self.channel_types, - do_rotation='ROTATION' in self.channel_types, - do_scale='SCALE' in self.channel_types, - do_bbone='BBONE' in self.channel_types, - ) - - if bake_options.do_pose and self.only_selected: + if do_pose and self.only_selected: pose_bones = context.selected_pose_bones or [] armatures = {pose_bone.id_data for pose_bone in pose_bones} objects = list(armatures) else: objects = context.selected_editable_objects - if bake_options.do_pose and not bake_options.do_object: + if do_pose and not do_object: objects = [obj for obj in objects if obj.pose is not None] object_action_pairs = ( @@ -300,7 +276,13 @@ class NLA_OT_bake(Operator): actions = anim_utils.bake_action_objects( object_action_pairs, frames=range(self.frame_start, self.frame_end + 1, self.step), - bake_options=bake_options + only_selected=self.only_selected, + do_pose=do_pose, + do_object=do_object, + do_visual_keying=self.visual_keying, + do_constraint_clear=self.clear_constraints, + do_parents_clear=self.clear_parents, + do_clean=self.clean_curves, ) if not any(actions): diff --git a/scripts/startup/bl_operators/geometry_nodes.py b/scripts/startup/bl_operators/geometry_nodes.py index b43c27c968a..ec3e8c3e900 100644 --- a/scripts/startup/bl_operators/geometry_nodes.py +++ b/scripts/startup/bl_operators/geometry_nodes.py @@ -68,7 +68,7 @@ def geometry_modifier_poll(context): ob = context.object # Test object support for geometry node modifier - if not ob or ob.type not in {'MESH', 'POINTCLOUD', 'VOLUME', 'CURVE', 'FONT', 'CURVES', 'GREASEPENCIL'}: + if not ob or ob.type not in {'MESH', 'POINTCLOUD', 'VOLUME', 'CURVE', 'FONT', 'CURVES'}: return False return True @@ -311,7 +311,10 @@ class NewGeometryNodeGroupTool(Operator): return {'FINISHED'} -class ZoneOperator: +class SimulationZoneOperator: + input_node_type = 'GeometryNodeSimulationInput' + output_node_type = 'GeometryNodeSimulationOutput' + @classmethod def get_output_node(cls, context): node = context.active_node @@ -334,50 +337,57 @@ class ZoneOperator: return True -class ZoneItemAddOperator: - items_name = None - active_index_name = None +class SimulationZoneItemAddOperator(SimulationZoneOperator, Operator): + """Add a state item to the simulation zone""" + bl_idname = "node.simulation_zone_item_add" + bl_label = "Add State Item" + bl_options = {'REGISTER', 'UNDO'} + default_socket_type = 'GEOMETRY' def execute(self, context): node = self.get_output_node(context) - items = getattr(node, self.items_name) + state_items = node.state_items + # Remember index to move the item. - old_active_index = getattr(node, self.active_index_name) - if 0 <= old_active_index < len(items): - old_active_item = items[old_active_index] - dst_index = old_active_index + 1 - dst_type = old_active_item.socket_type - dst_name = old_active_item.name + if node.active_item: + dst_index = node.active_index + 1 + dst_type = node.active_item.socket_type + dst_name = node.active_item.name else: - dst_index = len(items) + dst_index = len(state_items) dst_type = self.default_socket_type # Empty name so it is based on the type. dst_name = "" - items.new(dst_type, dst_name) - items.move(len(items) - 1, dst_index) - setattr(node, self.active_index_name, dst_index) + state_items.new(dst_type, dst_name) + state_items.move(len(state_items) - 1, dst_index) + node.active_index = dst_index + return {'FINISHED'} -class ZoneItemRemoveOperator: - items_name = None - active_index_name = None +class SimulationZoneItemRemoveOperator(SimulationZoneOperator, Operator): + """Remove a state item from the simulation zone""" + bl_idname = "node.simulation_zone_item_remove" + bl_label = "Remove State Item" + bl_options = {'REGISTER', 'UNDO'} def execute(self, context): node = self.get_output_node(context) - items = getattr(node, self.items_name) - old_active_index = getattr(node, self.active_index_name) + state_items = node.state_items - if 0 <= old_active_index < len(items): - items.remove(items[old_active_index]) + if node.active_item: + state_items.remove(node.active_item) + node.active_index = min(node.active_index, len(state_items) - 1) return {'FINISHED'} -class ZoneMoveItemOperator: - items_name = None - active_index_name = None +class SimulationZoneItemMoveOperator(SimulationZoneOperator, Operator): + """Move a simulation state item up or down in the list""" + bl_idname = "node.simulation_zone_item_move" + bl_label = "Move State Item" + bl_options = {'REGISTER', 'UNDO'} direction: EnumProperty( name="Direction", @@ -387,76 +397,116 @@ class ZoneMoveItemOperator: def execute(self, context): node = self.get_output_node(context) - items = getattr(node, self.items_name) - old_active_index = getattr(node, self.active_index_name) + state_items = node.state_items - if self.direction == 'UP' and old_active_index > 0: - items.move(old_active_index, old_active_index - 1) - setattr(node, self.active_index_name, old_active_index - 1) - elif self.direction == 'DOWN' and old_active_index < len(items) - 1: - items.move(old_active_index, old_active_index + 1) - setattr(node, self.active_index_name, old_active_index + 1) + if self.direction == 'UP' and node.active_index > 0: + state_items.move(node.active_index, node.active_index - 1) + node.active_index = node.active_index - 1 + elif self.direction == 'DOWN' and node.active_index < len(state_items) - 1: + state_items.move(node.active_index, node.active_index + 1) + node.active_index = node.active_index + 1 return {'FINISHED'} -class SimulationZoneOperator(ZoneOperator): - input_node_type = 'GeometryNodeSimulationInput' - output_node_type = 'GeometryNodeSimulationOutput' - - items_name = "state_items" - active_index_name = "active_index" - - -class SimulationZoneItemAddOperator(SimulationZoneOperator, ZoneItemAddOperator, Operator): - """Add a state item to the simulation zone""" - bl_idname = "node.simulation_zone_item_add" - bl_label = "Add State Item" - bl_options = {'REGISTER', 'UNDO'} - - -class SimulationZoneItemRemoveOperator(SimulationZoneOperator, ZoneItemRemoveOperator, Operator): - """Remove a state item from the simulation zone""" - bl_idname = "node.simulation_zone_item_remove" - bl_label = "Remove State Item" - bl_options = {'REGISTER', 'UNDO'} - - -class SimulationZoneItemMoveOperator(SimulationZoneOperator, ZoneMoveItemOperator, Operator): - """Move a simulation state item up or down in the list""" - bl_idname = "node.simulation_zone_item_move" - bl_label = "Move State Item" - bl_options = {'REGISTER', 'UNDO'} - - -class RepeatZoneOperator(ZoneOperator): +class RepeatZoneOperator: input_node_type = 'GeometryNodeRepeatInput' output_node_type = 'GeometryNodeRepeatOutput' - items_name = "repeat_items" - active_index_name = "active_index" + @classmethod + def get_output_node(cls, context): + node = context.active_node + if node.bl_idname == cls.input_node_type: + return node.paired_output + if node.bl_idname == cls.output_node_type: + return node + return None + + @classmethod + def poll(cls, context): + space = context.space_data + # Needs active node editor and a tree. + if not space or space.type != 'NODE_EDITOR' or not space.edit_tree or space.edit_tree.library: + return False + node = context.active_node + if node is None or node.bl_idname not in [cls.input_node_type, cls.output_node_type]: + return False + if cls.get_output_node(context) is None: + return False + return True -class RepeatZoneItemAddOperator(RepeatZoneOperator, ZoneItemAddOperator, Operator): +class RepeatZoneItemAddOperator(RepeatZoneOperator, Operator): """Add a repeat item to the repeat zone""" bl_idname = "node.repeat_zone_item_add" bl_label = "Add Repeat Item" bl_options = {'REGISTER', 'UNDO'} + default_socket_type = 'GEOMETRY' -class RepeatZoneItemRemoveOperator(RepeatZoneOperator, ZoneItemRemoveOperator, Operator): + def execute(self, context): + node = self.get_output_node(context) + repeat_items = node.repeat_items + + # Remember index to move the item. + if node.active_item: + dst_index = node.active_index + 1 + dst_type = node.active_item.socket_type + dst_name = node.active_item.name + else: + dst_index = len(repeat_items) + dst_type = self.default_socket_type + # Empty name so it is based on the type. + dst_name = "" + repeat_items.new(dst_type, dst_name) + repeat_items.move(len(repeat_items) - 1, dst_index) + node.active_index = dst_index + + return {'FINISHED'} + + +class RepeatZoneItemRemoveOperator(RepeatZoneOperator, Operator): """Remove a repeat item from the repeat zone""" bl_idname = "node.repeat_zone_item_remove" bl_label = "Remove Repeat Item" bl_options = {'REGISTER', 'UNDO'} + def execute(self, context): + node = self.get_output_node(context) + repeat_items = node.repeat_items -class RepeatZoneItemMoveOperator(RepeatZoneOperator, ZoneMoveItemOperator, Operator): + if node.active_item: + repeat_items.remove(node.active_item) + node.active_index = min(node.active_index, len(repeat_items) - 1) + + return {'FINISHED'} + + +class RepeatZoneItemMoveOperator(RepeatZoneOperator, Operator): """Move a repeat item up or down in the list""" bl_idname = "node.repeat_zone_item_move" bl_label = "Move Repeat Item" bl_options = {'REGISTER', 'UNDO'} + direction: EnumProperty( + name="Direction", + items=[('UP', "Up", ""), ('DOWN', "Down", "")], + default='UP', + ) + + def execute(self, context): + node = self.get_output_node(context) + repeat_items = node.repeat_items + + if self.direction == 'UP' and node.active_index > 0: + repeat_items.move(node.active_index, node.active_index - 1) + node.active_index = node.active_index - 1 + elif self.direction == 'DOWN' and node.active_index < len(repeat_items) - 1: + repeat_items.move(node.active_index, node.active_index + 1) + node.active_index = node.active_index + 1 + + return {'FINISHED'} + classes = ( NewGeometryNodesModifier, diff --git a/scripts/startup/bl_operators/wm.py b/scripts/startup/bl_operators/wm.py index 6e402e71685..634959c09d1 100644 --- a/scripts/startup/bl_operators/wm.py +++ b/scripts/startup/bl_operators/wm.py @@ -1409,14 +1409,8 @@ rna_custom_property_subtype_vector_items = ( rna_custom_property_subtype_none_item, ('COLOR', "Linear Color", "Color in the linear space"), ('COLOR_GAMMA', "Gamma-Corrected Color", "Color in the gamma corrected space"), - ('TRANSLATION', "Translation", ""), - ('DIRECTION', "Direction", ""), - ('VELOCITY', "Velocity", ""), - ('ACCELERATION', "Acceleration", ""), ('EULER', "Euler Angles", "Euler rotation angles in radians"), ('QUATERNION', "Quaternion Rotation", "Quaternion rotation (affects NLA blending)"), - ('AXISANGLE', "Axis-Angle", "Angle and axis to rotate around"), - ('XYZ', "XYZ", ""), ) rna_id_type_items = tuple((item.identifier, item.name, item.description, item.icon, item.value) @@ -2689,9 +2683,6 @@ class WM_OT_batch_rename(Operator): ('NODE', "Nodes", ""), ('SEQUENCE_STRIP', "Sequence Strips", ""), ('ACTION_CLIP', "Action Clips", ""), - None, - ('SCENE', "Scenes", ""), - ('BRUSH', "Brushes", ""), ), description="Type of data to rename", ) @@ -2888,26 +2879,6 @@ class WM_OT_batch_rename(Operator): "name", iface_("Action(s)"), ) - elif data_type == 'SCENE': - data = ( - ( - # Outliner. - cls._selected_ids_from_outliner_by_type(context, bpy.types.Scene) - if ((space_type == 'OUTLINER') and only_selected) else [id for id in bpy.data.scenes if id.library is None] - ), - "name", - iface_("Scene(s)"), - ) - elif data_type == 'BRUSH': - data = ( - ( - # Outliner. - cls._selected_ids_from_outliner_by_type(context, bpy.types.Brush) - if ((space_type == 'OUTLINER') and only_selected) else [id for id in bpy.data.brushes if id.library is None] - ), - "name", - iface_("Brush(es)"), - ) elif data_type in object_data_type_attrs_map.keys(): attr, descr, ty = object_data_type_attrs_map[data_type] data = ( diff --git a/scripts/startup/bl_ui/node_add_menu_geometry.py b/scripts/startup/bl_ui/node_add_menu_geometry.py index 10a4f4ce169..aacd43d5875 100644 --- a/scripts/startup/bl_ui/node_add_menu_geometry.py +++ b/scripts/startup/bl_ui/node_add_menu_geometry.py @@ -217,7 +217,6 @@ class NODE_MT_geometry_node_GEO_GEOMETRY_OPERATIONS(Menu): layout.separator() node_add_menu.add_node_type(layout, "GeometryNodeSeparateComponents") node_add_menu.add_node_type(layout, "GeometryNodeSeparateGeometry") - node_add_menu.add_node_type(layout, "GeometryNodeSplitToInstances") node_add_menu.draw_assets_for_catalog(layout, "Geometry/Operations") @@ -286,8 +285,6 @@ class NODE_MT_geometry_node_GEO_INPUT_SCENE(Menu): node_add_menu.add_node_type(layout, "GeometryNodeCollectionInfo") node_add_menu.add_node_type(layout, "GeometryNodeImageInfo") node_add_menu.add_node_type(layout, "GeometryNodeIsViewport") - if context.preferences.experimental.use_grease_pencil_version3: - node_add_menu.add_node_type(layout, "GeometryNodeInputNamedLayerSelection") node_add_menu.add_node_type(layout, "GeometryNodeObjectInfo") node_add_menu.add_node_type(layout, "GeometryNodeInputSceneTime") node_add_menu.add_node_type(layout, "GeometryNodeSelfObject") diff --git a/scripts/startup/bl_ui/properties_data_gpencil.py b/scripts/startup/bl_ui/properties_data_gpencil.py index c7c16b9cd5e..4313bb4bde8 100644 --- a/scripts/startup/bl_ui/properties_data_gpencil.py +++ b/scripts/startup/bl_ui/properties_data_gpencil.py @@ -170,7 +170,7 @@ class DATA_PT_gpencil_layers(DataButtonsPanel, Panel): col.prop(gpl, "opacity", text="Opacity", slider=True) col = layout.row(align=True) - col.prop(gpl, "use_lights", text="Lights") + col.prop(gpl, "use_lights") class DATA_PT_gpencil_layer_masks(LayerDataButtonsPanel, GreasePencilLayerMasksPanel, Panel): diff --git a/scripts/startup/bl_ui/properties_data_lightprobe.py b/scripts/startup/bl_ui/properties_data_lightprobe.py index ab85b93b268..e1fc1546dee 100644 --- a/scripts/startup/bl_ui/properties_data_lightprobe.py +++ b/scripts/startup/bl_ui/properties_data_lightprobe.py @@ -46,7 +46,7 @@ class DATA_PT_lightprobe(DataButtonsPanel, Panel): # layout.prop(probe, "type") - if probe.type == 'VOLUME': + if probe.type == 'GRID': col = layout.column() col.prop(probe, "influence_distance", text="Distance") col.prop(probe, "falloff") @@ -57,7 +57,7 @@ class DATA_PT_lightprobe(DataButtonsPanel, Panel): sub.prop(probe, "grid_resolution_y", text="Y") sub.prop(probe, "grid_resolution_z", text="Z") - elif probe.type == 'PLANE': + elif probe.type == 'PLANAR': col = layout.column() col.prop(probe, "influence_distance", text="Distance") col.prop(probe, "falloff") @@ -74,12 +74,12 @@ class DATA_PT_lightprobe(DataButtonsPanel, Panel): col.prop(probe, "intensity") sub = col.column(align=True) - if probe.type != 'PLANE': + if probe.type != 'PLANAR': sub.prop(probe, "clip_start", text="Clipping Start") else: sub.prop(probe, "clip_start", text="Clipping Offset") - if probe.type != 'PLANE': + if probe.type != 'PLANAR': sub.prop(probe, "clip_end", text="End") @@ -93,7 +93,7 @@ class DATA_PT_lightprobe_eevee_next(DataButtonsPanel, Panel): probe = context.lightprobe - if probe.type == 'VOLUME': + if probe.type == 'GRID': col = layout.column() sub = col.column(align=True) @@ -144,27 +144,15 @@ class DATA_PT_lightprobe_eevee_next(DataButtonsPanel, Panel): col.prop(probe, "grid_capture_indirect") col.prop(probe, "grid_capture_emission") - elif probe.type == 'SPHERE': + elif probe.type == 'CUBEMAP': col = layout.column() - col.prop(probe, "influence_type") - - if probe.influence_type == 'ELIPSOID': - influence_text = "Radius" - else: - influence_text = "Size" - - col.prop(probe, "influence_distance", text=influence_text) - col.prop(probe, "falloff") - + col.prop(probe, "resolution") sub = layout.column(align=True) sub.prop(probe, "clip_start", text="Clipping Start") sub.prop(probe, "clip_end", text="End") - elif probe.type == 'PLANE': - col = layout.column() - row = col.row() - col.prop(probe, "clip_start", text="Clipping Offset") - col.prop(probe, "influence_distance", text="Distance") + elif probe.type == 'PLANAR': + # Currently unsupported pass else: # Currently unsupported @@ -184,7 +172,7 @@ class DATA_PT_lightprobe_visibility(DataButtonsPanel, Panel): col = layout.column() - if probe.type == 'VOLUME': + if probe.type == 'GRID': col.prop(probe, "visibility_buffer_bias", text="Bias") col.prop(probe, "visibility_bleed_bias", text="Bleed Bias") col.prop(probe, "visibility_blur", text="Blur") @@ -197,12 +185,12 @@ class DATA_PT_lightprobe_visibility(DataButtonsPanel, Panel): class DATA_PT_lightprobe_parallax(DataButtonsPanel, Panel): bl_label = "Custom Parallax" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_EEVEE', 'BLENDER_RENDER', 'BLENDER_EEVEE_NEXT'} + COMPAT_ENGINES = {'BLENDER_EEVEE', 'BLENDER_RENDER'} @classmethod def poll(cls, context): engine = context.engine - return context.lightprobe and context.lightprobe.type == 'SPHERE' and (engine in cls.COMPAT_ENGINES) + return context.lightprobe and context.lightprobe.type == 'CUBEMAP' and (engine in cls.COMPAT_ENGINES) def draw_header(self, context): probe = context.lightprobe @@ -239,16 +227,16 @@ class DATA_PT_lightprobe_display(DataButtonsPanel, Panel): col = layout.column() - if probe.type == 'PLANE': + if probe.type == 'PLANAR': col.prop(ob, "empty_display_size", text="Arrow Size") col.prop(probe, "show_influence") col.prop(probe, "show_data") - if probe.type in {'VOLUME', 'SPHERE'}: + if probe.type in {'GRID', 'CUBEMAP'}: col.prop(probe, "show_influence") col.prop(probe, "show_clip") - if probe.type == 'SPHERE': + if probe.type == 'CUBEMAP': sub = col.column() sub.active = probe.use_custom_parallax sub.prop(probe, "show_parallax") diff --git a/scripts/startup/bl_ui/properties_data_mesh.py b/scripts/startup/bl_ui/properties_data_mesh.py index f510c37f9bb..3eacf3ced3d 100644 --- a/scripts/startup/bl_ui/properties_data_mesh.py +++ b/scripts/startup/bl_ui/properties_data_mesh.py @@ -183,6 +183,33 @@ class DATA_PT_context_mesh(MeshButtonsPanel, Panel): layout.template_ID(space, "pin_id") +class DATA_PT_normals(MeshButtonsPanel, Panel): + bl_label = "Normals" + bl_options = {'DEFAULT_CLOSED'} + COMPAT_ENGINES = { + 'BLENDER_RENDER', + 'BLENDER_EEVEE', + 'BLENDER_EEVEE_NEXT', + 'BLENDER_WORKBENCH', + } + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + + mesh = context.mesh + + col = layout.column(align=False, heading="Auto Smooth") + col.use_property_decorate = False + row = col.row(align=True) + sub = row.row(align=True) + sub.prop(mesh, "use_auto_smooth", text="") + sub = sub.row(align=True) + sub.active = mesh.use_auto_smooth and not mesh.has_custom_normals + sub.prop(mesh, "auto_smooth_angle", text="") + row.prop_decorator(mesh, "auto_smooth_angle") + + class DATA_PT_texture_space(MeshButtonsPanel, Panel): bl_label = "Texture Space" bl_options = {'DEFAULT_CLOSED'} @@ -701,6 +728,7 @@ classes = ( DATA_PT_uv_texture, DATA_PT_vertex_colors, DATA_PT_mesh_attributes, + DATA_PT_normals, DATA_PT_texture_space, DATA_PT_remesh, DATA_PT_customdata, diff --git a/scripts/startup/bl_ui/properties_data_modifier.py b/scripts/startup/bl_ui/properties_data_modifier.py index 8d00bc53746..bbbdbb735f8 100644 --- a/scripts/startup/bl_ui/properties_data_modifier.py +++ b/scripts/startup/bl_ui/properties_data_modifier.py @@ -57,8 +57,7 @@ class OBJECT_MT_modifier_add(ModifierAddMenu, Menu): def draw(self, context): layout = self.layout ob_type = context.object.type - geometry_nodes_supported = ob_type in {'MESH', 'CURVE', 'CURVES', - 'FONT', 'VOLUME', 'POINTCLOUD', 'GREASEPENCIL'} + geometry_nodes_supported = ob_type in {'MESH', 'CURVE', 'CURVES', 'FONT', 'VOLUME', 'POINTCLOUD'} if layout.operator_context == 'EXEC_REGION_WIN': layout.operator_context = 'INVOKE_REGION_WIN' diff --git a/scripts/startup/bl_ui/properties_grease_pencil_common.py b/scripts/startup/bl_ui/properties_grease_pencil_common.py index e638c505c50..8c64e6675ed 100644 --- a/scripts/startup/bl_ui/properties_grease_pencil_common.py +++ b/scripts/startup/bl_ui/properties_grease_pencil_common.py @@ -554,10 +554,7 @@ class GreasePencilMaterialsPanel: icon_link = 'MESH_DATA' if slot.link == 'DATA' else 'OBJECT_DATA' row.prop(slot, "link", icon=icon_link, icon_only=True) - if is_grease_pencil_version3 and ob.mode == 'EDIT': - row = layout.row(align=True) - row.operator("grease_pencil.stroke_change_color", text="Assign") - elif not is_grease_pencil_version3 and ob.data.use_stroke_edit_mode: + if not is_grease_pencil_version3 and ob.data.use_stroke_edit_mode: row = layout.row(align=True) row.operator("gpencil.stroke_change_color", text="Assign") row.operator("gpencil.material_select", text="Select").deselect = False diff --git a/scripts/startup/bl_ui/properties_material.py b/scripts/startup/bl_ui/properties_material.py index fb6957b2e37..0e041605e92 100644 --- a/scripts/startup/bl_ui/properties_material.py +++ b/scripts/startup/bl_ui/properties_material.py @@ -183,7 +183,7 @@ class EEVEE_MATERIAL_PT_volume(MaterialButtonsPanel, Panel): bl_translation_context = i18n_contexts.id_id bl_context = "material" bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT'} + COMPAT_ENGINES = {'BLENDER_EEVEE'} @classmethod def poll(cls, context): @@ -201,29 +201,6 @@ class EEVEE_MATERIAL_PT_volume(MaterialButtonsPanel, Panel): panel_node_draw(layout, mat.node_tree, 'OUTPUT_MATERIAL', "Volume") -class EEVEE_MATERIAL_PT_displacement(MaterialButtonsPanel, Panel): - bl_label = "Displacement" - bl_translation_context = i18n_contexts.id_id - bl_context = "material" - bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT'} - - @classmethod - def poll(cls, context): - engine = context.engine - mat = context.material - return mat and mat.use_nodes and (engine in cls.COMPAT_ENGINES) and not mat.grease_pencil - - def draw(self, context): - layout = self.layout - - layout.use_property_split = True - - mat = context.material - - panel_node_draw(layout, mat.node_tree, 'OUTPUT_MATERIAL', "Displacement") - - def draw_material_settings(self, context): layout = self.layout layout.use_property_split = True @@ -279,56 +256,21 @@ class EEVEE_NEXT_MATERIAL_PT_settings(MaterialButtonsPanel, Panel): mat = context.material + layout.prop(mat, "use_backface_culling") + layout.prop(mat, "blend_method") + layout.prop(mat, "shadow_method") + + row = layout.row() + row.active = ((mat.blend_method == 'CLIP') or (mat.shadow_method == 'CLIP')) + row.prop(mat, "alpha_threshold") + + if mat.blend_method not in {'OPAQUE', 'CLIP', 'HASHED'}: + layout.prop(mat, "show_transparent_back") + + layout.prop(mat, "use_screen_refraction") layout.prop(mat, "pass_index") -class EEVEE_NEXT_MATERIAL_PT_settings_surface(MaterialButtonsPanel, Panel): - bl_label = "Surface" - bl_context = "material" - bl_parent_id = "EEVEE_NEXT_MATERIAL_PT_settings" - COMPAT_ENGINES = {'BLENDER_EEVEE_NEXT'} - - def draw(self, context): - layout = self.layout - layout.use_property_split = True - layout.use_property_decorate = False - - mat = context.material - - col = layout.column(heading="Backface Culling") - col.prop(mat, "use_backface_culling", text="Camera") - col.prop(mat, "use_backface_culling_shadow", text="Shadow") - - # TODO(fclem): Displacement option - # TODO(fclem): Transparent shadow option - - col = layout.column() - col.prop(mat, "surface_render_method", text="Render Method") - if mat.surface_render_method == 'BLENDED': - col.prop(mat, "show_transparent_back", text="Transparency Overlap") - elif mat.surface_render_method == 'DITHERED': - col.prop(mat, "use_screen_refraction", text="Raytraced Refraction") - - col = layout.column(heading="Light Probe Volume") - col.prop(mat, "lightprobe_volume_single_sided", text="Single Sided") - - -class EEVEE_NEXT_MATERIAL_PT_settings_volume(MaterialButtonsPanel, Panel): - bl_label = "Volume" - bl_context = "material" - bl_parent_id = "EEVEE_NEXT_MATERIAL_PT_settings" - COMPAT_ENGINES = {'BLENDER_EEVEE_NEXT'} - - def draw(self, context): - layout = self.layout - layout.use_property_split = True - layout.use_property_decorate = False - - mat = context.material - - layout.prop(mat, "volume_intersection_method", text="Intersection") - - class MATERIAL_PT_viewport(MaterialButtonsPanel, Panel): bl_label = "Viewport Display" bl_context = "material" @@ -396,11 +338,8 @@ classes = ( EEVEE_MATERIAL_PT_context_material, EEVEE_MATERIAL_PT_surface, EEVEE_MATERIAL_PT_volume, - EEVEE_MATERIAL_PT_displacement, EEVEE_MATERIAL_PT_settings, EEVEE_NEXT_MATERIAL_PT_settings, - EEVEE_NEXT_MATERIAL_PT_settings_surface, - EEVEE_NEXT_MATERIAL_PT_settings_volume, MATERIAL_PT_lineart, MATERIAL_PT_viewport, EEVEE_MATERIAL_PT_viewport_settings, diff --git a/scripts/startup/bl_ui/properties_object.py b/scripts/startup/bl_ui/properties_object.py index 900543c5585..0fdbf920194 100644 --- a/scripts/startup/bl_ui/properties_object.py +++ b/scripts/startup/bl_ui/properties_object.py @@ -395,7 +395,6 @@ class OBJECT_PT_visibility(ObjectButtonsPanel, Panel): col = layout.column(heading="Light Probes") col.prop(ob, "hide_probe_volume", text="Volume", toggle=False, invert_checkbox=True) col.prop(ob, "hide_probe_cubemap", text="Cubemap", toggle=False, invert_checkbox=True) - col.prop(ob, "hide_probe_planar", text="Planar", toggle=False, invert_checkbox=True) if ob.type == 'GPENCIL': col = layout.column(heading="Grease Pencil") diff --git a/scripts/startup/bl_ui/properties_paint_common.py b/scripts/startup/bl_ui/properties_paint_common.py index ff7d2e9247f..616824d2416 100644 --- a/scripts/startup/bl_ui/properties_paint_common.py +++ b/scripts/startup/bl_ui/properties_paint_common.py @@ -82,8 +82,6 @@ class UnifiedPaintPanel: return tool_settings.gpencil_vertex_paint elif mode == 'SCULPT_CURVES': return tool_settings.curves_sculpt - elif mode == 'PAINT_GREASE_PENCIL': - return tool_settings.gpencil_paint return None @staticmethod @@ -868,11 +866,6 @@ def brush_shared_settings(layout, context, brush, popover=False): strength = True direction = brush.curves_sculpt_tool in {'GROW_SHRINK', 'SELECTION_PAINT'} - # Grease Pencil # - if mode == 'PAINT_GREASE_PENCIL': - size = True - strength = True - ### Draw settings. ### ups = context.scene.tool_settings.unified_paint_settings diff --git a/scripts/startup/bl_ui/properties_render.py b/scripts/startup/bl_ui/properties_render.py index 18c72c9fc46..74ef1792188 100644 --- a/scripts/startup/bl_ui/properties_render.py +++ b/scripts/startup/bl_ui/properties_render.py @@ -177,8 +177,8 @@ class RENDER_PT_eevee_ambient_occlusion(RenderButtonsPanel, Panel): col.prop(props, "use_gtao_bounce") -class RENDER_PT_eevee_next_horizon_scan(RenderButtonsPanel, Panel): - bl_label = "Horizon Scan" +class RENDER_PT_eevee_next_ambient_occlusion(RenderButtonsPanel, Panel): + bl_label = "Ambient Occlusion" bl_options = {'DEFAULT_CLOSED'} COMPAT_ENGINES = {'BLENDER_EEVEE_NEXT'} @@ -193,7 +193,8 @@ class RENDER_PT_eevee_next_horizon_scan(RenderButtonsPanel, Panel): props = scene.eevee col = layout.column() - col.prop(props, "gtao_quality", text="Precision") + col.prop(props, "gtao_distance") + col.prop(props, "gtao_quality") class RENDER_PT_eevee_motion_blur(RenderButtonsPanel, Panel): @@ -255,7 +256,7 @@ class RENDER_PT_eevee_next_motion_blur(RenderButtonsPanel, Panel): col.prop(props, "motion_blur_steps", text="Steps") -class RENDER_PT_eevee_next_motion_blur_curve(RenderButtonsPanel, Panel): +class RENDER_PT_motion_blur_curve(RenderButtonsPanel, Panel): bl_label = "Shutter Curve" bl_parent_id = "RENDER_PT_eevee_next_motion_blur" bl_options = {'DEFAULT_CLOSED'} @@ -437,8 +438,8 @@ class RENDER_PT_eevee_volumetric_shadows(RenderButtonsPanel, Panel): layout.prop(props, "volumetric_shadow_samples", text="Samples") -class RENDER_PT_eevee_next_volumes(RenderButtonsPanel, Panel): - bl_label = "Volumes" +class RENDER_PT_eevee_next_volumetric(RenderButtonsPanel, Panel): + bl_label = "Volumetrics" bl_options = {'DEFAULT_CLOSED'} COMPAT_ENGINES = {'BLENDER_EEVEE_NEXT'} @@ -461,12 +462,11 @@ class RENDER_PT_eevee_next_volumes(RenderButtonsPanel, Panel): col.prop(props, "volumetric_tile_size") col.prop(props, "volumetric_samples") col.prop(props, "volumetric_sample_distribution", text="Distribution") - col.prop(props, "volumetric_ray_depth", text="Max Depth") -class RENDER_PT_eevee_next_volumes_lighting(RenderButtonsPanel, Panel): - bl_label = "Volumes Lighting" - bl_parent_id = "RENDER_PT_eevee_next_volumes" +class RENDER_PT_eevee_next_volumetric_lighting(RenderButtonsPanel, Panel): + bl_label = "Volumetric Lighting" + bl_parent_id = "RENDER_PT_eevee_next_volumetric" COMPAT_ENGINES = {'BLENDER_EEVEE_NEXT'} def draw_header(self, context): @@ -485,9 +485,9 @@ class RENDER_PT_eevee_next_volumes_lighting(RenderButtonsPanel, Panel): layout.prop(props, "volumetric_light_clamp", text="Light Clamping") -class RENDER_PT_eevee_next_volumes_shadows(RenderButtonsPanel, Panel): - bl_label = "Volumes Shadows" - bl_parent_id = "RENDER_PT_eevee_next_volumes" +class RENDER_PT_eevee_next_volumetric_shadows(RenderButtonsPanel, Panel): + bl_label = "Volumetric Shadows" + bl_parent_id = "RENDER_PT_eevee_next_volumetric" COMPAT_ENGINES = {'BLENDER_EEVEE_NEXT'} def draw_header(self, context): @@ -639,11 +639,11 @@ class EeveeRaytracingDenoisePanel(RenderButtonsPanel, Panel): col.prop(props, "denoise_spatial") col = layout.column() - col.active = props.use_denoise and props.denoise_spatial + col.active = props.denoise_spatial col.prop(props, "denoise_temporal") col = layout.column() - col.active = props.use_denoise and props.denoise_spatial and props.denoise_temporal + col.active = props.denoise_spatial and props.denoise_temporal col.prop(props, "denoise_bilateral") @@ -655,7 +655,7 @@ class RENDER_PT_eevee_next_raytracing_reflection(EeveeRaytracingOptionsPanel): def draw_header(self, context): layout = self.layout if context.scene.eevee.ray_split_settings == 'UNIFIED': - layout.label(text="Reflection / Refraction / Diffuse") + layout.label(text="Reflection & Refraction & Diffuse") else: layout.label(text="Reflection") @@ -762,26 +762,6 @@ class RENDER_PT_eevee_shadows(RenderButtonsPanel, Panel): col.prop(props, "light_threshold") -class RENDER_PT_eevee_next_lights(RenderButtonsPanel, Panel): - bl_label = "Lights" - bl_options = {'DEFAULT_CLOSED'} - COMPAT_ENGINES = {'BLENDER_EEVEE_NEXT'} - - @classmethod - def poll(cls, context): - return (context.engine in cls.COMPAT_ENGINES) - - def draw(self, context): - layout = self.layout - layout.use_property_split = True - - scene = context.scene - props = scene.eevee - - col = layout.column() - col.prop(props, "light_threshold") - - class RENDER_PT_eevee_next_shadows(RenderButtonsPanel, Panel): bl_label = "Shadows" bl_options = {'DEFAULT_CLOSED'} @@ -805,13 +785,10 @@ class RENDER_PT_eevee_next_shadows(RenderButtonsPanel, Panel): col = layout.column() col.prop(props, "shadow_pool_size", text="Pool Size") - - col = layout.column(heading="Tracing", align=True) - col.prop(props, "shadow_ray_count", text="Rays") - col.prop(props, "shadow_step_count", text="Steps") - - col = layout.column() - col.prop(props, "shadow_normal_bias", text="Normal Bias") + col.prop(props, "shadow_ray_count") + col.prop(props, "shadow_step_count") + col.prop(props, "shadow_normal_bias") + col.prop(props, "light_threshold") class RENDER_PT_eevee_sampling(RenderButtonsPanel, Panel): @@ -842,43 +819,6 @@ class RENDER_PT_eevee_next_sampling(RenderButtonsPanel, Panel): bl_label = "Sampling" COMPAT_ENGINES = {'BLENDER_EEVEE_NEXT'} - @classmethod - def poll(cls, context): - return (context.engine in cls.COMPAT_ENGINES) - - def draw(self, context): - pass - - -class RENDER_PT_eevee_next_sampling_viewport(RenderButtonsPanel, Panel): - bl_label = "Viewport" - bl_parent_id = "RENDER_PT_eevee_next_sampling" - COMPAT_ENGINES = {'BLENDER_EEVEE_NEXT'} - - @classmethod - def poll(cls, context): - return (context.engine in cls.COMPAT_ENGINES) - - def draw(self, context): - layout = self.layout - layout.use_property_split = True - layout.use_property_decorate = False # No animation. - - scene = context.scene - props = scene.eevee - - col = layout.column() - col.prop(props, "taa_samples", text="Samples") - col.prop(props, "use_taa_reprojection", text="Temporal Reprojection") - - # Add SSS sample count here. - - -class RENDER_PT_eevee_next_sampling_render(RenderButtonsPanel, Panel): - bl_label = "Render" - bl_parent_id = "RENDER_PT_eevee_next_sampling" - COMPAT_ENGINES = {'BLENDER_EEVEE_NEXT'} - @classmethod def poll(cls, context): return (context.engine in cls.COMPAT_ENGINES) @@ -892,9 +832,11 @@ class RENDER_PT_eevee_next_sampling_render(RenderButtonsPanel, Panel): props = scene.eevee col = layout.column(align=True) - col.prop(props, "taa_render_samples", text="Samples") + col.prop(props, "taa_render_samples", text="Render") + col.prop(props, "taa_samples", text="Viewport") - # Add SSS sample count here. + col = layout.column() + col.prop(props, "use_taa_reprojection") class RENDER_PT_eevee_indirect_lighting(RenderButtonsPanel, Panel): @@ -916,7 +858,7 @@ class RENDER_PT_eevee_indirect_lighting(RenderButtonsPanel, Panel): col = layout.column() col.operator("scene.light_cache_bake", text="Bake Indirect Lighting", icon='RENDER_STILL') - col.operator("scene.light_cache_bake", text="Bake Cubemap Only", icon='LIGHTPROBE_SPHERE').subset = 'CUBEMAPS' + col.operator("scene.light_cache_bake", text="Bake Cubemap Only", icon='LIGHTPROBE_CUBEMAP').subset = 'CUBEMAPS' col.operator("scene.light_cache_free", text="Delete Lighting Cache") cache_info = scene.eevee.gi_cache_info @@ -933,24 +875,11 @@ class RENDER_PT_eevee_indirect_lighting(RenderButtonsPanel, Panel): col.prop(props, "gi_filter_quality") -class RENDER_PT_eevee_next_light_probes(RenderButtonsPanel, Panel): - bl_label = "Light Probes" +class RENDER_PT_eevee_next_indirect_lighting(RenderButtonsPanel, Panel): + bl_label = "Indirect Lighting" bl_options = {'DEFAULT_CLOSED'} COMPAT_ENGINES = {'BLENDER_EEVEE_NEXT'} - @classmethod - def poll(cls, context): - return (context.engine in cls.COMPAT_ENGINES) - - def draw(self, context): - pass - - -class RENDER_PT_eevee_next_light_probes_sphere(RenderButtonsPanel, Panel): - bl_label = "Sphere" - bl_parent_id = "RENDER_PT_eevee_next_light_probes" - COMPAT_ENGINES = {'BLENDER_EEVEE_NEXT'} - @classmethod def poll(cls, context): return (context.engine in cls.COMPAT_ENGINES) @@ -964,33 +893,11 @@ class RENDER_PT_eevee_next_light_probes_sphere(RenderButtonsPanel, Panel): props = scene.eevee col = layout.column() - col.prop(props, "gi_cubemap_resolution", text="Resolution") + col.operator("object.lightprobe_cache_bake", text="Bake Light Caches", icon='RENDER_STILL').subset = 'ALL' + col.operator("object.lightprobe_cache_free", text="Delete Light Caches").subset = 'ALL' - -class RENDER_PT_eevee_next_light_probes_volume(RenderButtonsPanel, Panel): - bl_label = "Volume" - bl_parent_id = "RENDER_PT_eevee_next_light_probes" - COMPAT_ENGINES = {'BLENDER_EEVEE_NEXT'} - - @classmethod - def poll(cls, context): - return (context.engine in cls.COMPAT_ENGINES) - - def draw(self, context): - layout = self.layout - layout.use_property_split = True - layout.use_property_decorate = False # No animation. - - scene = context.scene - props = scene.eevee - - col = layout.column() col.prop(props, "gi_irradiance_pool_size", text="Pool Size") - row = col.row(align=True) - row.operator("object.lightprobe_cache_bake", text="Bake Volumes").subset = 'ALL' - row.operator("object.lightprobe_cache_free", text="", icon='TRASH').subset = 'ALL' - class RENDER_PT_eevee_indirect_lighting_display(RenderButtonsPanel, Panel): bl_label = "Display" @@ -1018,6 +925,28 @@ class RENDER_PT_eevee_indirect_lighting_display(RenderButtonsPanel, Panel): row.prop(props, "gi_show_irradiance", text="", toggle=True) +class RENDER_PT_eevee_next_indirect_lighting_display(RenderButtonsPanel, Panel): + bl_label = "Display" + bl_parent_id = "RENDER_PT_eevee_next_indirect_lighting" + COMPAT_ENGINES = {'BLENDER_EEVEE_NEXT'} + + @classmethod + def poll(cls, context): + return (context.engine in cls.COMPAT_ENGINES) + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False # No animation. + + scene = context.scene + props = scene.eevee + + row = layout.row(align=True) + row.prop(props, "gi_irradiance_display_size", text="Irradiance Size") + row.prop(props, "gi_show_irradiance", text="", toggle=True) + + class RENDER_PT_eevee_film(RenderButtonsPanel, Panel): bl_label = "Film" bl_options = {'DEFAULT_CLOSED'} @@ -1337,15 +1266,13 @@ classes = ( RENDER_PT_context, RENDER_PT_eevee_sampling, RENDER_PT_eevee_next_sampling, - RENDER_PT_eevee_next_sampling_viewport, - RENDER_PT_eevee_next_sampling_render, RENDER_PT_eevee_ambient_occlusion, + RENDER_PT_eevee_next_ambient_occlusion, RENDER_PT_eevee_bloom, RENDER_PT_eevee_depth_of_field, RENDER_PT_eevee_next_depth_of_field, RENDER_PT_eevee_subsurface_scattering, RENDER_PT_eevee_screen_space_reflections, - RENDER_PT_eevee_next_horizon_scan, RENDER_PT_eevee_next_raytracing, RENDER_PT_eevee_next_raytracing_reflection, RENDER_PT_eevee_next_raytracing_refraction, @@ -1357,25 +1284,23 @@ classes = ( RENDER_PT_eevee_next_denoise_refraction, RENDER_PT_eevee_next_denoise_diffuse, RENDER_PT_eevee_motion_blur, + RENDER_PT_eevee_next_motion_blur, + RENDER_PT_motion_blur_curve, RENDER_PT_eevee_volumetric, RENDER_PT_eevee_volumetric_lighting, RENDER_PT_eevee_volumetric_shadows, - RENDER_PT_eevee_next_volumes, - RENDER_PT_eevee_next_volumes_lighting, - RENDER_PT_eevee_next_volumes_shadows, + RENDER_PT_eevee_next_volumetric, + RENDER_PT_eevee_next_volumetric_lighting, + RENDER_PT_eevee_next_volumetric_shadows, RENDER_PT_eevee_performance, RENDER_PT_eevee_hair, RENDER_PT_eevee_shadows, - RENDER_PT_eevee_next_lights, RENDER_PT_eevee_next_shadows, RENDER_PT_eevee_indirect_lighting, RENDER_PT_eevee_indirect_lighting_display, - RENDER_PT_eevee_next_light_probes, - RENDER_PT_eevee_next_light_probes_sphere, - RENDER_PT_eevee_next_light_probes_volume, + RENDER_PT_eevee_next_indirect_lighting, + RENDER_PT_eevee_next_indirect_lighting_display, RENDER_PT_eevee_film, - RENDER_PT_eevee_next_motion_blur, - RENDER_PT_eevee_next_motion_blur_curve, RENDER_PT_eevee_next_film, diff --git a/scripts/startup/bl_ui/properties_view_layer.py b/scripts/startup/bl_ui/properties_view_layer.py index bcba3b876f7..c19f1b4ffa6 100644 --- a/scripts/startup/bl_ui/properties_view_layer.py +++ b/scripts/startup/bl_ui/properties_view_layer.py @@ -125,7 +125,7 @@ class VIEWLAYER_PT_workbench_layer_passes_data(ViewLayerButtonsPanel, Panel): class VIEWLAYER_PT_eevee_layer_passes_light(ViewLayerButtonsPanel, Panel): bl_label = "Light" bl_parent_id = "VIEWLAYER_PT_layer_passes" - COMPAT_ENGINES = {'BLENDER_EEVEE'} + COMPAT_ENGINES = {'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT'} def draw(self, context): layout = self.layout @@ -155,44 +155,6 @@ class VIEWLAYER_PT_eevee_layer_passes_light(ViewLayerButtonsPanel, Panel): text="Ambient Occlusion") -class VIEWLAYER_PT_eevee_layer_passes_light(ViewLayerButtonsPanel, Panel): - bl_label = "Light" - bl_parent_id = "VIEWLAYER_PT_layer_passes" - COMPAT_ENGINES = {'BLENDER_EEVEE_NEXT'} - - def draw(self, context): - layout = self.layout - - layout.use_property_split = True - layout.use_property_decorate = False - - view_layer = context.view_layer - view_layer_eevee = view_layer.eevee - - col = layout.column(heading="Diffuse", align=True) - col.prop(view_layer, "use_pass_diffuse_direct", text="Light") - col.prop(view_layer, "use_pass_diffuse_color", text="Color") - - col = layout.column(heading="Specular", align=True) - col.prop(view_layer, "use_pass_glossy_direct", text="Light") - col.prop(view_layer, "use_pass_glossy_color", text="Color") - - col = layout.column(heading="Volume", heading_ctxt=i18n_contexts.id_id, align=True) - col.prop(view_layer_eevee, "use_pass_volume_direct", text="Light") - - col = layout.column(heading="Other", align=True) - col.prop(view_layer, "use_pass_emit", text="Emission") - col.prop(view_layer, "use_pass_environment") - col.prop(view_layer, "use_pass_shadow") - col.prop(view_layer, "use_pass_ambient_occlusion", - text="Ambient Occlusion") - - col = layout.column() - col.active = view_layer.use_pass_ambient_occlusion - # TODO Move to view layer. - col.prop(context.scene.eevee, "gtao_distance", text="Occlusion Distance") - - class VIEWLAYER_PT_eevee_layer_passes_effects(ViewLayerButtonsPanel, Panel): bl_label = "Effects" bl_parent_id = "VIEWLAYER_PT_layer_passes" diff --git a/scripts/startup/bl_ui/properties_world.py b/scripts/startup/bl_ui/properties_world.py index f1d069160a4..39f1d53d779 100644 --- a/scripts/startup/bl_ui/properties_world.py +++ b/scripts/startup/bl_ui/properties_world.py @@ -149,7 +149,7 @@ class EEVEE_WORLD_PT_volume(WorldButtonsPanel, Panel): class EEVEE_WORLD_PT_probe(WorldButtonsPanel, Panel): - bl_label = "Light Probe" + bl_label = "Probe" bl_translation_context = i18n_contexts.id_id bl_options = {'DEFAULT_CLOSED'} COMPAT_ENGINES = {'BLENDER_EEVEE_NEXT'} diff --git a/scripts/startup/bl_ui/space_dopesheet.py b/scripts/startup/bl_ui/space_dopesheet.py index ef102f9dd47..c2192b204a9 100644 --- a/scripts/startup/bl_ui/space_dopesheet.py +++ b/scripts/startup/bl_ui/space_dopesheet.py @@ -841,7 +841,7 @@ class DOPESHEET_PT_gpencil_mode(LayersDopeSheetPanel, Panel): row.prop(gpl, "opacity", text="Opacity", slider=True) row = layout.row(align=True) - row.prop(gpl, "use_lights", text="Lights") + row.prop(gpl, "use_lights") class DOPESHEET_PT_gpencil_layer_masks(LayersDopeSheetPanel, GreasePencilLayerMasksPanel, Panel): diff --git a/scripts/startup/bl_ui/space_node.py b/scripts/startup/bl_ui/space_node.py index 0c48c81cf80..3f5b1a2cb12 100644 --- a/scripts/startup/bl_ui/space_node.py +++ b/scripts/startup/bl_ui/space_node.py @@ -991,19 +991,16 @@ class NODE_PT_node_tree_properties(Panel): col.prop(group, "is_tool") -def draw_socket_item_in_list(uilist, layout, item, icon): - if uilist.layout_type in {'DEFAULT', 'COMPACT'}: - row = layout.row(align=True) - row.template_node_socket(color=item.color) - row.prop(item, "name", text="", emboss=False, icon_value=icon) - elif uilist.layout_type == 'GRID': - layout.alignment = 'CENTER' - layout.template_node_socket(color=item.color) - - class NODE_UL_simulation_zone_items(bpy.types.UIList): def draw_item(self, context, layout, _data, item, icon, _active_data, _active_propname, _index): - draw_socket_item_in_list(self, layout, item, icon) + if self.layout_type in {'DEFAULT', 'COMPACT'}: + row = layout.row(align=True) + + row.template_node_socket(color=item.color) + row.prop(item, "name", text="", emboss=False, icon_value=icon) + elif self.layout_type == 'GRID': + layout.alignment = 'CENTER' + layout.template_node_socket(color=item.color) class NODE_PT_simulation_zone_items(Panel): @@ -1075,7 +1072,13 @@ class NODE_PT_simulation_zone_items(Panel): class NODE_UL_repeat_zone_items(bpy.types.UIList): def draw_item(self, _context, layout, _data, item, icon, _active_data, _active_propname, _index): - draw_socket_item_in_list(self, layout, item, icon) + if self.layout_type in {'DEFAULT', 'COMPACT'}: + row = layout.row(align=True) + row.template_node_socket(color=item.color) + row.prop(item, "name", text="", emboss=False, icon_value=icon) + elif self.layout_type == 'GRID': + layout.alignment = 'CENTER' + layout.template_node_socket(color=item.color) class NODE_PT_repeat_zone_items(Panel): diff --git a/scripts/startup/bl_ui/space_toolsystem_toolbar.py b/scripts/startup/bl_ui/space_toolsystem_toolbar.py index 1cbede60f72..3039742e8bc 100644 --- a/scripts/startup/bl_ui/space_toolsystem_toolbar.py +++ b/scripts/startup/bl_ui/space_toolsystem_toolbar.py @@ -366,16 +366,6 @@ class _defs_transform: draw_settings=draw_settings, ) - @ToolDef.from_fn - def bend(): - return dict( - idname="builtin.bend", - label="Bend", - icon="ops.gpencil.edit_bend", - widget=None, - keymap="3D View Tool: Bend", - ) - @ToolDef.from_fn def transform(): def draw_settings(context, layout, tool): @@ -1754,11 +1744,21 @@ class _defs_paint_grease_pencil: @ToolDef.from_fn def erase(): + def draw_settings(context, layout, _tool): + paint = context.tool_settings.gpencil_paint + brush = paint.brush + if not brush: + return + layout.prop(brush.gpencil_settings, "eraser_mode", expand=True) + if brush.gpencil_settings.eraser_mode == 'HARD': + layout.prop(brush.gpencil_settings, "use_keep_caps_eraser") + layout.prop(brush.gpencil_settings, "use_active_layer_only") return dict( idname="builtin_brush.Erase", label="Erase", icon="brush.gpencil_draw.erase", data_block='ERASE', + draw_settings=draw_settings, ) @@ -3022,18 +3022,6 @@ class VIEW3D_PT_tools_active(ToolSelectPanelHelper, Panel): ], 'EDIT_GREASE_PENCIL': [ *_tools_select, - _defs_view3d_generic.cursor, - None, - *_tools_transform, - None, - _defs_edit_curve.curve_radius, - _defs_transform.bend, - ( - _defs_transform.shear, - _defs_edit_mesh.tosphere, - ), - None, - *_tools_annotate, ], 'PARTICLE': [ *_tools_select, diff --git a/scripts/startup/bl_ui/space_topbar.py b/scripts/startup/bl_ui/space_topbar.py index bd5c928cc12..eaf99658e09 100644 --- a/scripts/startup/bl_ui/space_topbar.py +++ b/scripts/startup/bl_ui/space_topbar.py @@ -159,7 +159,7 @@ class TOPBAR_PT_gpencil_layers(Panel): icon='MOD_MASK' if gpl.use_mask_layer else 'LAYER_ACTIVE') srow = col.row(align=True) - srow.prop(gpl, "use_lights", text="Lights") + srow.prop(gpl, "use_lights") col = row.column() diff --git a/scripts/startup/bl_ui/space_userpref.py b/scripts/startup/bl_ui/space_userpref.py index bb58c689f06..c8d68bf9779 100644 --- a/scripts/startup/bl_ui/space_userpref.py +++ b/scripts/startup/bl_ui/space_userpref.py @@ -1076,7 +1076,6 @@ class USERPREF_PT_theme_text_style(ThemePanel, CenterAlignMixIn, Panel): col = flow.column() col.prop(font_style, "points") - col.prop(font_style, "character_weight", text="Weight") col = flow.column(align=True) col.prop(font_style, "shadow_offset_x", text="Shadow Offset X") @@ -1580,6 +1579,51 @@ class USERPREF_UL_asset_libraries(bpy.types.UIList): layout.prop(asset_library, "name", text="", emboss=False) +class USERPREF_PT_file_paths_extension_repos(FilePathsPanel, Panel): + bl_label = "Extension Repositories" + + @classmethod + def poll(cls, context): + return context.preferences.experimental.use_extension_repos + + def draw(self, context): + layout = self.layout + layout.use_property_split = False + layout.use_property_decorate = False + + paths = context.preferences.filepaths + active_library_index = paths.active_extension_repo + + row = layout.row() + + row.template_list( + "USERPREF_UL_extension_repos", "user_extension_repos", + paths, "extension_repos", + paths, "active_extension_repo" + ) + + col = row.column(align=True) + col.operator("preferences.extension_repo_add", text="", icon='ADD') + props = col.operator("preferences.extension_repo_remove", text="", icon='REMOVE') + props.index = active_library_index + + try: + active_repo = None if active_library_index < 0 else paths.extension_repos[active_library_index] + except IndexError: + active_repo = None + + if active_repo is None: + return + + layout.separator() + + layout.prop(active_repo, "directory") + layout.prop(active_repo, "remote_path") + row = layout.row() + row.prop(active_repo, "use_cache") + row.prop(active_repo, "module") + + class USERPREF_UL_extension_repos(bpy.types.UIList): def draw_item(self, _context, layout, _data, item, icon, _active_data, _active_propname, _index): repo = item @@ -2012,51 +2056,6 @@ class USERPREF_PT_extensions(ExtensionsPanel, Panel): ).url = "https://projects.blender.org/ideasman42/bl_ext" -class USERPREF_PT_extensions_repos(ExtensionsPanel, Panel): - bl_label = "Extension Repositories" - - @classmethod - def poll(cls, context): - return context.preferences.experimental.use_extension_repos - - def draw(self, context): - layout = self.layout - layout.use_property_split = False - layout.use_property_decorate = False - - paths = context.preferences.filepaths - active_library_index = paths.active_extension_repo - - row = layout.row() - - row.template_list( - "USERPREF_UL_extension_repos", "user_extension_repos", - paths, "extension_repos", - paths, "active_extension_repo" - ) - - col = row.column(align=True) - col.operator("preferences.extension_repo_add", text="", icon='ADD') - props = col.operator("preferences.extension_repo_remove", text="", icon='REMOVE') - props.index = active_library_index - - try: - active_repo = None if active_library_index < 0 else paths.extension_repos[active_library_index] - except IndexError: - active_repo = None - - if active_repo is None: - return - - layout.separator() - - layout.prop(active_repo, "directory") - layout.prop(active_repo, "remote_path") - row = layout.row() - row.prop(active_repo, "use_cache") - row.prop(active_repo, "module") - - # ----------------------------------------------------------------------------- # Add-On Panels @@ -2557,6 +2556,7 @@ class USERPREF_PT_experimental_prototypes(ExperimentalPanel, Panel): ({"property": "use_new_point_cloud_type"}, ("blender/blender/issues/75717", "#75717")), ({"property": "use_sculpt_texture_paint"}, ("blender/blender/issues/96225", "#96225")), ({"property": "use_experimental_compositors"}, ("blender/blender/issues/88150", "#88150")), + ({"property": "enable_eevee_next"}, ("blender/blender/issues/93220", "#93220")), ({"property": "use_grease_pencil_version3"}, ("blender/blender/projects/6", "Grease Pencil 3.0")), ({"property": "enable_overlay_next"}, ("blender/blender/issues/102179", "#102179")), ({"property": "use_extension_repos"}, ("/blender/blender/issues/106254", "#106254")), @@ -2670,6 +2670,7 @@ classes = ( USERPREF_PT_file_paths_render, USERPREF_PT_file_paths_asset_libraries, USERPREF_PT_file_paths_script_directories, + USERPREF_PT_file_paths_extension_repos, USERPREF_PT_file_paths_applications, USERPREF_PT_text_editor, USERPREF_PT_text_editor_presets, @@ -2694,10 +2695,8 @@ classes = ( USERPREF_PT_keymap, - USERPREF_PT_addons, - USERPREF_PT_extensions, - USERPREF_PT_extensions_repos, + USERPREF_PT_addons, USERPREF_PT_studiolight_lights, USERPREF_PT_studiolight_light_editor, diff --git a/scripts/startup/bl_ui/space_view3d.py b/scripts/startup/bl_ui/space_view3d.py index c1c210afdb5..1e58217f023 100644 --- a/scripts/startup/bl_ui/space_view3d.py +++ b/scripts/startup/bl_ui/space_view3d.py @@ -582,62 +582,6 @@ class _draw_tool_settings_context_mode: return True - @staticmethod - def PAINT_GREASE_PENCIL(context, layout, tool): - if (tool is None) or (not tool.has_datablock): - return False - - tool_settings = context.tool_settings - paint = tool_settings.gpencil_paint - - brush = paint.brush - if brush is None: - return False - - row = layout.row(align=True) - row.template_ID_preview(paint, "brush", rows=3, cols=8, hide_buttons=True) - - grease_pencil_tool = brush.gpencil_tool - - if grease_pencil_tool == 'DRAW': - from bl_ui.properties_paint_common import ( - brush_basic__draw_color_selector, - ) - brush_basic__draw_color_selector(context, layout, brush, brush.gpencil_settings, None) - - UnifiedPaintPanel.prop_unified( - layout, - context, - brush, - "size", - unified_name="use_unified_size", - pressure_name="use_pressure_size", - text="Radius", - slider=True, - header=True, - ) - - UnifiedPaintPanel.prop_unified( - layout, - context, - brush, - "strength", - pressure_name="use_pressure_strength", - unified_name="use_unified_strength", - slider=True, - header=True, - ) - - if grease_pencil_tool == 'DRAW': - layout.prop(brush.gpencil_settings, "active_smooth_factor") - elif grease_pencil_tool == 'ERASE': - layout.prop(brush.gpencil_settings, "eraser_mode", expand=True) - if brush.gpencil_settings.eraser_mode == "HARD": - layout.prop(brush.gpencil_settings, "use_keep_caps_eraser") - layout.prop(brush.gpencil_settings, "use_active_layer_only") - - return True - class VIEW3D_HT_header(Header): bl_space_type = 'VIEW_3D' @@ -681,7 +625,7 @@ class VIEW3D_HT_header(Header): else: if (object_mode not in { 'SCULPT', 'SCULPT_CURVES', 'VERTEX_PAINT', 'WEIGHT_PAINT', 'TEXTURE_PAINT', - 'PAINT_GPENCIL', 'SCULPT_GPENCIL', 'WEIGHT_GPENCIL', 'VERTEX_GPENCIL', 'PAINT_GREASE_PENCIL', + 'PAINT_GPENCIL', 'SCULPT_GPENCIL', 'WEIGHT_GPENCIL', 'VERTEX_GPENCIL', }) or has_pose_mode: show_snap = True else: @@ -1153,7 +1097,6 @@ class VIEW3D_MT_editor_menus(Menu): layout.template_node_operator_asset_root_items() elif mode_string == 'EDIT_GREASE_PENCIL': layout.menu("VIEW3D_MT_edit_greasepencil_stroke") - layout.menu("VIEW3D_MT_edit_greasepencil_point") elif obj: if mode_string not in {'PAINT_TEXTURE', 'SCULPT_CURVES'}: @@ -1238,10 +1181,10 @@ class VIEW3D_MT_transform(VIEW3D_MT_transform_base, Menu): if context.mode == 'EDIT_MESH': layout.operator("transform.shrink_fatten", text="Shrink/Fatten").alt_navigation = alt_navigation layout.operator("transform.skin_resize") - elif context.mode in ['EDIT_CURVE', 'EDIT_GREASE_PENCIL']: + elif context.mode == 'EDIT_CURVE': layout.operator("transform.transform", text="Radius").mode = 'CURVE_SHRINKFATTEN' - if context.mode != 'EDIT_CURVES' and context.mode != 'EDIT_GREASE_PENCIL': + if context.mode != 'EDIT_CURVES': layout.separator() props = layout.operator("transform.translate", text="Move Texture Space") props.texture_space = True @@ -2686,7 +2629,7 @@ class VIEW3D_MT_object(Menu): bl_context = "objectmode" bl_label = "Object" - def draw(self, context): + def draw(self, _context): layout = self.layout layout.menu("VIEW3D_MT_transform_object") @@ -2724,8 +2667,7 @@ class VIEW3D_MT_object(Menu): layout.separator() layout.operator("object.shade_smooth") - if context.object and context.object.type == 'MESH': - layout.operator("object.shade_smooth_by_angle") + layout.operator("object.shade_smooth", text="Shade Auto Smooth").use_auto_smooth = True layout.operator("object.shade_flat") layout.separator() @@ -2969,9 +2911,8 @@ class VIEW3D_MT_object_context_menu(Menu): if obj is not None: if obj.type in {'MESH', 'CURVE', 'SURFACE'}: layout.operator("object.shade_smooth") - if obj.type == 'MESH': - layout.operator("object.shade_smooth_by_angle") - layout.operator("object.shade_flat") + layout.operator("object.shade_smooth", text="Shade Auto Smooth").use_auto_smooth = True + layout.operator("object.shade_flat", text="Shade Flat") layout.separator() @@ -3521,7 +3462,7 @@ class VIEW3D_MT_paint_weight(Menu): class VIEW3D_MT_sculpt(Menu): bl_label = "Sculpt" - def draw(self, context): + def draw(self, _context): layout = self.layout layout.operator("transform.translate") @@ -3614,11 +3555,6 @@ class VIEW3D_MT_sculpt(Menu): # Rebuild BVH layout.operator("sculpt.optimize") - layout.operator( - "sculpt.dynamic_topology_toggle", - icon='CHECKBOX_HLT' if context.sculpt_object.use_dynamic_topology_sculpting else 'CHECKBOX_DEHLT', - ) - layout.separator() layout.operator("object.transfer_mode", text="Transfer Sculpt Mode") @@ -5023,8 +4959,6 @@ class VIEW3D_MT_edit_greasepencil_delete(Menu): layout.operator_enum("grease_pencil.dissolve", "type") - layout.separator() - layout.operator( "grease_pencil.delete_frame", text="Delete Active Keyframe (Active Layer)", @@ -5767,15 +5701,6 @@ class VIEW3D_MT_gpencil_animation(Menu): layout.operator("gpencil.active_frames_delete_all", text="Delete Active Keyframes (All Layers)") -class VIEW3D_MT_edit_greasepencil_animation(Menu): - bl_label = "Animation" - - def draw(self, context): - layout = self.layout - layout.operator("grease_pencil.insert_blank_frame", text="Insert Blank Keyframe (Active Layer)") - layout.operator("grease_pencil.insert_blank_frame", text="Insert Blank Keyframe (All Layer)").all_layers = True - - class VIEW3D_MT_edit_gpencil_transform(Menu): bl_label = "Transform" @@ -5813,11 +5738,6 @@ class VIEW3D_MT_edit_greasepencil(Menu): def draw(self, _context): layout = self.layout - layout.menu("VIEW3D_MT_transform") - layout.menu("VIEW3D_MT_mirror") - - layout.separator() - layout.menu("VIEW3D_MT_edit_greasepencil_delete") @@ -5826,19 +5746,8 @@ class VIEW3D_MT_edit_greasepencil_stroke(Menu): def draw(self, _context): layout = self.layout - layout.operator("grease_pencil.stroke_simplify", text="Simplify") - - layout.separator() - - layout.operator("grease_pencil.cyclical_set", text="Toggle Cyclic").type = 'TOGGLE' - - -class VIEW3D_MT_edit_greasepencil_point(Menu): - bl_label = "Point" - - def draw(self, _context): - layout = self.layout - layout.operator("grease_pencil.stroke_smooth", text="Smooth Points") + layout.operator("grease_pencil.stroke_smooth") + layout.operator("grease_pencil.stroke_simplify") class VIEW3D_MT_edit_curves(Menu): @@ -5849,7 +5758,6 @@ class VIEW3D_MT_edit_curves(Menu): layout.menu("VIEW3D_MT_transform") layout.separator() - layout.operator("curves.attribute_set") layout.operator("curves.delete") layout.template_node_operator_asset_menu_items(catalog_path=self.bl_label) @@ -6443,13 +6351,8 @@ class VIEW3D_PT_shading_lighting(Panel): @classmethod def poll(cls, context): shading = VIEW3D_PT_shading.get_shading(context) - if shading.type in {'SOLID', 'MATERIAL'}: - return True - if shading.type == 'RENDERED': - engine = context.scene.render.engine - if engine in {'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT'}: - return True - return False + engine = context.scene.render.engine + return shading.type in {'SOLID', 'MATERIAL'} or engine == 'BLENDER_EEVEE' and shading.type == 'RENDERED' def draw(self, context): layout = self.layout @@ -8050,64 +7953,6 @@ class VIEW3D_MT_gpencil_edit_context_menu(Menu): col.operator("gpencil.reproject", text="Reproject") -class VIEW3D_MT_greasepencil_edit_context_menu(Menu): - bl_label = "" - - def draw(self, context): - layout = self.layout - tool_settings = context.tool_settings - - is_point_mode = tool_settings.gpencil_selectmode_edit == 'POINT' - is_stroke_mode = tool_settings.gpencil_selectmode_edit == 'STROKE' - - layout.operator_context = 'INVOKE_REGION_WIN' - - row = layout.row() - - if is_point_mode: - col = row.column(align=True) - col.label(text="Point", icon='GP_SELECT_POINTS') - - # Main Strokes Operators - col.operator("grease_pencil.stroke_simplify", text="Simplify") - - col.separator() - - # Deform Operators - col.operator("transform.tosphere", text="To Sphere") - col.operator("transform.shear", text="Shear") - col.operator("transform.bend", text="Bend") - col.operator("transform.push_pull", text="Push/Pull") - col.operator("transform.transform", text="Radius").mode = 'GPENCIL_SHRINKFATTEN' - col.operator("grease_pencil.stroke_smooth", text="Smooth Points") - - col.separator() - - col.menu("VIEW3D_MT_mirror", text="Mirror Points") - - # Removal Operators - col.separator() - - col.operator_enum("grease_pencil.dissolve", "type") - - if is_stroke_mode: - col = row.column(align=True) - col.label(text="Stroke", icon='GP_SELECT_STROKES') - - # Main Strokes Operators - col.operator("grease_pencil.stroke_simplify", text="Simplify") - - col.separator() - - # Deform Operators - col.operator("grease_pencil.stroke_smooth", text="Smooth Points") - col.operator("transform.transform", text="Radius").mode = 'CURVE_SHRINKFATTEN' - - col.separator() - - col.menu("VIEW3D_MT_mirror") - - def draw_gpencil_layer_active(context, layout): gpl = context.active_gpencil_layer if gpl: @@ -8530,7 +8375,7 @@ class TOPBAR_PT_gpencil_materials(GreasePencilMaterialsPanel, Panel): @classmethod def poll(cls, context): ob = context.object - return ob and (ob.type == 'GPENCIL' or ob.type == 'GREASEPENCIL') + return ob and ob.type == 'GPENCIL' class TOPBAR_PT_gpencil_vertexcolor(GreasePencilVertexcolorPanel, Panel): @@ -8798,12 +8643,9 @@ classes = ( VIEW3D_MT_gpencil_simplify, VIEW3D_MT_gpencil_autoweights, VIEW3D_MT_gpencil_edit_context_menu, - VIEW3D_MT_greasepencil_edit_context_menu, VIEW3D_MT_edit_greasepencil, VIEW3D_MT_edit_greasepencil_delete, VIEW3D_MT_edit_greasepencil_stroke, - VIEW3D_MT_edit_greasepencil_point, - VIEW3D_MT_edit_greasepencil_animation, VIEW3D_MT_edit_curve, VIEW3D_MT_edit_curve_ctrlpoints, VIEW3D_MT_edit_curve_segments, diff --git a/source/blender/animrig/ANIM_fcurve.hh b/source/blender/animrig/ANIM_fcurve.hh deleted file mode 100644 index 0de834f8981..00000000000 --- a/source/blender/animrig/ANIM_fcurve.hh +++ /dev/null @@ -1,18 +0,0 @@ -/* SPDX-FileCopyrightText: 2023 Blender Foundation - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -/** \file - * \ingroup animrig - * - * \brief Functions to modify FCurves. - */ - -struct AnimData; -struct FCurve; - -namespace blender::animrig { - -bool delete_keyframe_fcurve(AnimData *adt, FCurve *fcu, float cfra); - -} // namespace blender::animrig diff --git a/source/blender/animrig/ANIM_keyframing.hh b/source/blender/animrig/ANIM_keyframing.hh deleted file mode 100644 index ea184054ddb..00000000000 --- a/source/blender/animrig/ANIM_keyframing.hh +++ /dev/null @@ -1,150 +0,0 @@ -/* SPDX-FileCopyrightText: 2023 Blender Foundation - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -/** \file - * \ingroup animrig - * - * \brief Functions to insert, delete or modify keyframes. - */ - -#pragma once - -#include "DNA_anim_types.h" -#include "ED_transform.hh" -#include "RNA_types.hh" - -struct ID; -struct ListBase; -struct Main; -struct Scene; -struct ViewLayer; - -struct AnimationEvalContext; -struct NlaKeyframingContext; - -namespace blender::animrig { - -/* -------------------------------------------------------------------- */ -/** \name Key-Framing Management - * \{ */ - -/** - * \brief Main Insert Key-framing API call. - * - * Use this to create any necessary animation data, and then insert a keyframe - * using the current value being keyframed, in the relevant place. - * - * \param flag: Used for special settings that alter the behavior of the keyframe insertion. - * These include the 'visual' key-framing modes, quick refresh, and extra keyframe filtering. - * - * \param array_index: The index to key or -1 keys all array indices. - * \return The number of key-frames inserted. - */ -int insert_keyframe(Main *bmain, - ReportList *reports, - ID *id, - bAction *act, - const char group[], - const char rna_path[], - int array_index, - const AnimationEvalContext *anim_eval_context, - eBezTriple_KeyframeType keytype, - eInsertKeyFlags flag); - -/** - * \brief Secondary Insert Key-framing API call. - * - * Use this when validation of necessary animation data is not necessary, - * since an RNA-pointer to the necessary data being keyframed, - * and a pointer to the F-Curve to use have both been provided. - * - * This function can't keyframe quaternion channels on some NLA strip types. - * - * \param keytype: The "keyframe type" (eBezTriple_KeyframeType), as shown in the Dope Sheet. - * - * \param flag: Used for special settings that alter the behavior of the keyframe insertion. - * These include the 'visual' key-framing modes, quick refresh, - * and extra keyframe filtering. - * \return Success. - */ -bool insert_keyframe_direct(ReportList *reports, - PointerRNA ptr, - PropertyRNA *prop, - FCurve *fcu, - const AnimationEvalContext *anim_eval_context, - eBezTriple_KeyframeType keytype, - NlaKeyframingContext *nla, - eInsertKeyFlags flag); - -/** - * \brief Main Delete Key-Framing API call. - * - * Use this to delete keyframe on current frame for relevant channel. - * Will perform checks just in case. - * \return The number of key-frames deleted. - */ -int delete_keyframe(Main *bmain, - ReportList *reports, - ID *id, - bAction *act, - const char rna_path[], - int array_index, - float cfra); - -int clear_keyframe(Main *bmain, - ReportList *reports, - ID *id, - bAction *act, - const char rna_path[], - int array_index, - eInsertKeyFlags /*flag*/); - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Auto keyframing - * Notes: - * - All the defines for this (User-Pref settings and Per-Scene settings) - * are defined in DNA_userdef_types.h - * - Scene settings take precedence over those for user-preferences, with old files - * inheriting user-preferences settings for the scene settings - * - "On/Off + Mode" are stored per Scene, but "settings" are currently stored as user-preferences. - * \{ */ - -/** Check if auto-key-framing is enabled (per scene takes precedence). */ -bool is_autokey_on(const Scene *scene); - -/** Check the mode for auto-keyframing (per scene takes precedence). */ -bool is_autokey_mode(const Scene *scene, eAutokey_Mode mode); - -/** Check if a flag is set for auto-key-framing (per scene takes precedence). */ -bool is_autokey_flag(const Scene *scene, eAutokey_Flag flag); - -/** - * Auto-keyframing feature - checks for whether anything should be done for the current frame. - */ -bool autokeyframe_cfra_can_key(const Scene *scene, ID *id); - -void autokeyframe_object( - bContext *C, Scene *scene, ViewLayer *view_layer, Object *ob, eTfmMode tmode); -bool autokeyframe_object(bContext *C, Scene *scene, Object *ob, KeyingSet *ks); -bool autokeyframe_pchan(bContext *C, Scene *scene, Object *ob, bPoseChannel *pchan, KeyingSet *ks); - -/** - * Use for auto-key-framing. - * \param only_if_property_keyed: if true, auto-key-framing only creates keyframes on already keyed - * properties. This is by design when using buttons. For other callers such as gizmos or sequencer - * preview transform, creating new animation/keyframes also on non-keyed properties is desired. - */ -bool autokeyframe_property(bContext *C, - Scene *scene, - PointerRNA *ptr, - PropertyRNA *prop, - int rnaindex, - float cfra, - bool only_if_property_keyed); - -/** \} */ - -} // namespace blender::animrig diff --git a/source/blender/animrig/ANIM_visualkey.hh b/source/blender/animrig/ANIM_visualkey.hh deleted file mode 100644 index 0617aa91454..00000000000 --- a/source/blender/animrig/ANIM_visualkey.hh +++ /dev/null @@ -1,21 +0,0 @@ -/* SPDX-FileCopyrightText: 2023 Blender Foundation - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -/** \file - * \ingroup animrig - * - * \brief Functions to work with the visual keying system. - */ - -#include "BLI_vector.hh" - -struct PointerRNA; -struct PropertyRNA; - -namespace blender::animrig { - -bool visualkey_can_use(PointerRNA *ptr, PropertyRNA *prop); -Vector visualkey_get_values(PointerRNA *ptr, PropertyRNA *prop); - -} // namespace blender::animrig diff --git a/source/blender/animrig/CMakeLists.txt b/source/blender/animrig/CMakeLists.txt index 025aea66e4a..637a2d35b45 100644 --- a/source/blender/animrig/CMakeLists.txt +++ b/source/blender/animrig/CMakeLists.txt @@ -8,12 +8,7 @@ set(INC ../blenkernel ../blentranslation - ../depsgraph ../editors/include - ../makesrna - ../windowmanager - # RNA_prototypes.h - ${CMAKE_BINARY_DIR}/source/blender/makesrna ) set(INC_SYS @@ -22,18 +17,10 @@ set(INC_SYS set(SRC intern/bone_collections.cc intern/bonecolor.cc - intern/fcurve.cc - intern/keyframing.cc - intern/keyframing_auto.cc - intern/visualkey.cc - ANIM_armature_iter.hh ANIM_bone_collections.h ANIM_bone_collections.hh ANIM_bonecolor.hh - ANIM_fcurve.hh - ANIM_keyframing.hh - ANIM_visualkey.hh ) set(LIB diff --git a/source/blender/animrig/intern/bone_collections.cc b/source/blender/animrig/intern/bone_collections.cc index 7f8c04fa753..215a7c789b6 100644 --- a/source/blender/animrig/intern/bone_collections.cc +++ b/source/blender/animrig/intern/bone_collections.cc @@ -12,7 +12,7 @@ #include "BLI_math_color.h" #include "BLI_string.h" #include "BLI_string_utf8.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_utildefines.h" #include "BLT_translation.h" diff --git a/source/blender/animrig/intern/fcurve.cc b/source/blender/animrig/intern/fcurve.cc deleted file mode 100644 index 434565ce785..00000000000 --- a/source/blender/animrig/intern/fcurve.cc +++ /dev/null @@ -1,42 +0,0 @@ -/* SPDX-FileCopyrightText: 2023 Blender Foundation - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -/** \file - * \ingroup animrig - */ - -#include "ANIM_fcurve.hh" -#include "BKE_fcurve.h" -#include "DNA_anim_types.h" -#include "ED_anim_api.hh" - -namespace blender::animrig { - -/** - * \note caller needs to run #BKE_nla_tweakedit_remap to get NLA relative frame. - * caller should also check #BKE_fcurve_is_protected before keying. - */ -bool delete_keyframe_fcurve(AnimData *adt, FCurve *fcu, float cfra) -{ - bool found; - int i; - - /* try to find index of beztriple to get rid of */ - i = BKE_fcurve_bezt_binarysearch_index(fcu->bezt, cfra, fcu->totvert, &found); - if (found) { - /* delete the key at the index (will sanity check + do recalc afterwards) */ - BKE_fcurve_delete_key(fcu, i); - BKE_fcurve_handles_recalc(fcu); - - /* Only delete curve too if it won't be doing anything anymore */ - if (BKE_fcurve_is_empty(fcu)) { - ANIM_fcurve_delete_from_animdata(nullptr, adt, fcu); - } - - /* return success */ - return true; - } - return false; -} -} // namespace blender::animrig diff --git a/source/blender/animrig/intern/keyframing.cc b/source/blender/animrig/intern/keyframing.cc deleted file mode 100644 index 2795b4dbb5c..00000000000 --- a/source/blender/animrig/intern/keyframing.cc +++ /dev/null @@ -1,1006 +0,0 @@ -/* SPDX-FileCopyrightText: 2023 Blender Foundation - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -/** \file - * \ingroup animrig - */ - -#include -#include - -#include "ANIM_fcurve.hh" -#include "ANIM_keyframing.hh" -#include "ANIM_visualkey.hh" - -#include "BKE_action.h" -#include "BKE_anim_data.h" -#include "BKE_animsys.h" -#include "BKE_fcurve.h" -#include "BKE_fcurve_driver.h" -#include "BKE_idtype.h" -#include "BKE_lib_id.h" -#include "BKE_nla.h" -#include "BKE_report.h" - -#include "BLI_dynstr.h" -#include "BLI_utildefines.h" -#include "BLT_translation.h" - -#include "DEG_depsgraph.hh" -#include "DEG_depsgraph_query.hh" -#include "DNA_anim_types.h" -#include "ED_anim_api.hh" -#include "ED_keyframing.hh" -#include "MEM_guardedalloc.h" -#include "RNA_access.hh" -#include "RNA_define.hh" -#include "RNA_path.hh" -#include "RNA_types.hh" - -#include "WM_api.hh" -#include "WM_types.hh" - -namespace blender::animrig { - -/** Used to make curves newly added to a cyclic Action cycle with the correct period. */ -static void make_new_fcurve_cyclic(const bAction *act, FCurve *fcu) -{ - /* The curve must contain one (newly-added) keyframe. */ - if (fcu->totvert != 1 || !fcu->bezt) { - return; - } - - const float period = act->frame_end - act->frame_start; - - if (period < 0.1f) { - return; - } - - /* Move the keyframe into the range. */ - const float frame_offset = fcu->bezt[0].vec[1][0] - act->frame_start; - const float fix = floorf(frame_offset / period) * period; - - fcu->bezt[0].vec[0][0] -= fix; - fcu->bezt[0].vec[1][0] -= fix; - fcu->bezt[0].vec[2][0] -= fix; - - /* Duplicate and offset the keyframe. */ - fcu->bezt = static_cast(MEM_reallocN(fcu->bezt, sizeof(BezTriple) * 2)); - fcu->totvert = 2; - - fcu->bezt[1] = fcu->bezt[0]; - fcu->bezt[1].vec[0][0] += period; - fcu->bezt[1].vec[1][0] += period; - fcu->bezt[1].vec[2][0] += period; - - /* Add the cycles modifier. */ - if (!fcu->modifiers.first) { - add_fmodifier(&fcu->modifiers, FMODIFIER_TYPE_CYCLES, fcu); - } -} - -/* Check indices that were intended to be remapped and report any failed remaps. */ -static void get_keyframe_values_create_reports(ReportList *reports, - PointerRNA ptr, - PropertyRNA *prop, - const int index, - const int count, - const bool force_all, - const BLI_bitmap *successful_remaps) -{ - - DynStr *ds_failed_indices = BLI_dynstr_new(); - - int total_failed = 0; - for (int i = 0; i < count; i++) { - const bool cur_index_evaluated = ELEM(index, i, -1) || force_all; - if (!cur_index_evaluated) { - /* `values[i]` was never intended to be remapped. */ - continue; - } - - if (BLI_BITMAP_TEST_BOOL(successful_remaps, i)) { - /* `values[i]` successfully remapped. */ - continue; - } - - total_failed++; - /* Report that `values[i]` were intended to be remapped but failed remapping process. */ - BLI_dynstr_appendf(ds_failed_indices, "%d, ", i); - } - - if (total_failed == 0) { - BLI_dynstr_free(ds_failed_indices); - return; - } - - char *str_failed_indices = BLI_dynstr_get_cstring(ds_failed_indices); - BLI_dynstr_free(ds_failed_indices); - - BKE_reportf(reports, - RPT_WARNING, - "Could not insert %i keyframe(s) due to zero NLA influence, base value, or value " - "remapping failed: %s.%s for indices [%s]", - total_failed, - ptr.owner_id->name, - RNA_property_ui_name(prop), - str_failed_indices); - - MEM_freeN(str_failed_indices); -} - -/** - * Retrieve current property values to keyframe, - * possibly applying NLA correction when necessary. - * - * \param r_successful_remaps: Enables bits for indices which are both intended to be remapped and - * were successfully remapped. Bitmap allocated so it must be freed afterward. - */ -static Vector get_keyframe_values(ReportList *reports, - PointerRNA ptr, - PropertyRNA *prop, - int index, - NlaKeyframingContext *nla_context, - eInsertKeyFlags flag, - const AnimationEvalContext *anim_eval_context, - bool *r_force_all, - BLI_bitmap **r_successful_remaps) -{ - Vector values; - - if ((flag & INSERTKEY_MATRIX) && visualkey_can_use(&ptr, prop)) { - /* visual-keying is only available for object and pchan datablocks, as - * it works by keyframing using a value extracted from the final matrix - * instead of using the kt system to extract a value. - */ - values = visualkey_get_values(&ptr, prop); - } - else { - /* read value from system */ - values = ANIM_setting_get_rna_values(&ptr, prop); - } - - *r_successful_remaps = BLI_BITMAP_NEW(values.size(), __func__); - - /* adjust the value for NLA factors */ - BKE_animsys_nla_remap_keyframe_values(nla_context, - &ptr, - prop, - values.as_mutable_span(), - index, - anim_eval_context, - r_force_all, - *r_successful_remaps); - get_keyframe_values_create_reports(reports, - ptr, - prop, - index, - values.size(), - r_force_all ? *r_force_all : false, - *r_successful_remaps); - - return values; -} - -/** - * Move the point where a key is about to be inserted to be inside the main cycle range. - * Returns the type of the cycle if it is enabled and valid. - */ -static eFCU_Cycle_Type remap_cyclic_keyframe_location(FCurve *fcu, float *px, float *py) -{ - if (fcu->totvert < 2 || !fcu->bezt) { - return FCU_CYCLE_NONE; - } - - eFCU_Cycle_Type type = BKE_fcurve_get_cycle_type(fcu); - - if (type == FCU_CYCLE_NONE) { - return FCU_CYCLE_NONE; - } - - BezTriple *first = &fcu->bezt[0], *last = &fcu->bezt[fcu->totvert - 1]; - const float start = first->vec[1][0], end = last->vec[1][0]; - - if (start >= end) { - return FCU_CYCLE_NONE; - } - - if (*px < start || *px > end) { - float period = end - start; - float step = floorf((*px - start) / period); - *px -= step * period; - - if (type == FCU_CYCLE_OFFSET) { - /* Nasty check to handle the case when the modes are different better. */ - FMod_Cycles *data = static_cast(((FModifier *)fcu->modifiers.first)->data); - short mode = (step >= 0) ? data->after_mode : data->before_mode; - - if (mode == FCM_EXTRAPOLATE_CYCLIC_OFFSET) { - *py -= step * (last->vec[1][1] - first->vec[1][1]); - } - } - } - - return type; -} - -/* return codes for new_key_needed */ -enum { - KEYNEEDED_DONTADD = 0, - KEYNEEDED_JUSTADD, - KEYNEEDED_DELPREV, - KEYNEEDED_DELNEXT, -} /*eKeyNeededStatus*/; - -/** - * This helper function determines whether a new keyframe is needed. - * - * Cases where keyframes should not be added: - * 1. Keyframe to be added between two keyframes with similar values. - * 2. Keyframe to be added on frame where two keyframes are already situated. - * 3. Keyframe lies at point that intersects the linear line between two keyframes. - */ -static short new_key_needed(FCurve *fcu, float cFrame, float nValue) -{ - /* safety checking */ - if (fcu == nullptr) { - return KEYNEEDED_JUSTADD; - } - int totCount = fcu->totvert; - if (totCount == 0) { - return KEYNEEDED_JUSTADD; - } - - /* loop through checking if any are the same */ - BezTriple *bezt = fcu->bezt; - BezTriple *prev = nullptr; - for (int i = 0; i < totCount; i++) { - float prevPosi = 0.0f, prevVal = 0.0f; - float beztPosi = 0.0f, beztVal = 0.0f; - - /* get current time+value */ - beztPosi = bezt->vec[1][0]; - beztVal = bezt->vec[1][1]; - - if (prev) { - /* there is a keyframe before the one currently being examined */ - - /* get previous time+value */ - prevPosi = prev->vec[1][0]; - prevVal = prev->vec[1][1]; - - /* keyframe to be added at point where there are already two similar points? */ - if (IS_EQF(prevPosi, cFrame) && IS_EQF(beztPosi, cFrame) && IS_EQF(beztPosi, prevPosi)) { - return KEYNEEDED_DONTADD; - } - - /* keyframe between prev+current points ? */ - if ((prevPosi <= cFrame) && (cFrame <= beztPosi)) { - /* is the value of keyframe to be added the same as keyframes on either side ? */ - if (IS_EQF(prevVal, nValue) && IS_EQF(beztVal, nValue) && IS_EQF(prevVal, beztVal)) { - return KEYNEEDED_DONTADD; - } - - float realVal; - - /* get real value of curve at that point */ - realVal = evaluate_fcurve(fcu, cFrame); - - /* compare whether it's the same as proposed */ - if (IS_EQF(realVal, nValue)) { - return KEYNEEDED_DONTADD; - } - return KEYNEEDED_JUSTADD; - } - - /* new keyframe before prev beztriple? */ - if (cFrame < prevPosi) { - /* A new keyframe will be added. However, whether the previous beztriple - * stays around or not depends on whether the values of previous/current - * beztriples and new keyframe are the same. - */ - if (IS_EQF(prevVal, nValue) && IS_EQF(beztVal, nValue) && IS_EQF(prevVal, beztVal)) { - return KEYNEEDED_DELNEXT; - } - - return KEYNEEDED_JUSTADD; - } - } - else { - /* just add a keyframe if there's only one keyframe - * and the new one occurs before the existing one does. - */ - if ((cFrame < beztPosi) && (totCount == 1)) { - return KEYNEEDED_JUSTADD; - } - } - - /* continue. frame to do not yet passed (or other conditions not met) */ - if (i < (totCount - 1)) { - prev = bezt; - bezt++; - } - else { - break; - } - } - - /* Frame in which to add a new-keyframe occurs after all other keys - * -> If there are at least two existing keyframes, then if the values of the - * last two keyframes and the new-keyframe match, the last existing keyframe - * gets deleted as it is no longer required. - * -> Otherwise, a keyframe is just added. 1.0 is added so that fake-2nd-to-last - * keyframe is not equal to last keyframe. - */ - bezt = (fcu->bezt + (fcu->totvert - 1)); - const float valA = bezt->vec[1][1]; - float valB; - if (prev) { - valB = prev->vec[1][1]; - } - else { - valB = bezt->vec[1][1] + 1.0f; - } - - if (IS_EQF(valA, nValue) && IS_EQF(valA, valB)) { - return KEYNEEDED_DELPREV; - } - - return KEYNEEDED_JUSTADD; -} - -static AnimationEvalContext nla_time_remap(const AnimationEvalContext *anim_eval_context, - PointerRNA *id_ptr, - AnimData *adt, - bAction *act, - ListBase *nla_cache, - NlaKeyframingContext **r_nla_context) -{ - if (adt && adt->action == act) { - /* Get NLA context for value remapping. */ - *r_nla_context = BKE_animsys_get_nla_keyframing_context( - nla_cache, id_ptr, adt, anim_eval_context); - - /* Apply NLA-mapping to frame. */ - const float remapped_frame = BKE_nla_tweakedit_remap( - adt, anim_eval_context->eval_time, NLATIME_CONVERT_UNMAP); - return BKE_animsys_eval_context_construct_at(anim_eval_context, remapped_frame); - } - - *r_nla_context = nullptr; - return *anim_eval_context; -} - -/* Insert the specified keyframe value into a single F-Curve. */ -static bool insert_keyframe_value(ReportList *reports, - PointerRNA *ptr, - PropertyRNA *prop, - FCurve *fcu, - const AnimationEvalContext *anim_eval_context, - float curval, - eBezTriple_KeyframeType keytype, - eInsertKeyFlags flag) -{ - /* F-Curve not editable? */ - if (BKE_fcurve_is_keyframable(fcu) == 0) { - BKE_reportf( - reports, - RPT_ERROR, - "F-Curve with path '%s[%d]' cannot be keyframed, ensure that it is not locked or sampled, " - "and try removing F-Modifiers", - fcu->rna_path, - fcu->array_index); - return false; - } - - float cfra = anim_eval_context->eval_time; - - /* adjust frame on which to add keyframe */ - if ((flag & INSERTKEY_DRIVER) && (fcu->driver)) { - PathResolvedRNA anim_rna; - - if (RNA_path_resolved_create(ptr, prop, fcu->array_index, &anim_rna)) { - /* for making it easier to add corrective drivers... */ - cfra = evaluate_driver(&anim_rna, fcu->driver, fcu->driver, anim_eval_context); - } - else { - cfra = 0.0f; - } - } - - /* adjust coordinates for cycle aware insertion */ - if (flag & INSERTKEY_CYCLE_AWARE) { - if (remap_cyclic_keyframe_location(fcu, &cfra, &curval) != FCU_CYCLE_PERFECT) { - /* inhibit action from insert_vert_fcurve unless it's a perfect cycle */ - flag &= ~INSERTKEY_CYCLE_AWARE; - } - } - - /* only insert keyframes where they are needed */ - if (flag & INSERTKEY_NEEDED) { - /* check whether this curve really needs a new keyframe */ - static short insert_mode = new_key_needed(fcu, cfra, curval); - - /* only return success if keyframe added */ - if (insert_mode == KEYNEEDED_DONTADD) { - return false; - } - - /* insert new keyframe at current frame */ - if (insert_vert_fcurve(fcu, cfra, curval, keytype, flag) < 0) { - return false; - } - - /* delete keyframe immediately before/after newly added */ - switch (insert_mode) { - case KEYNEEDED_DELPREV: - BKE_fcurve_delete_key(fcu, fcu->totvert - 2); - BKE_fcurve_handles_recalc(fcu); - break; - case KEYNEEDED_DELNEXT: - BKE_fcurve_delete_key(fcu, 1); - BKE_fcurve_handles_recalc(fcu); - break; - } - - return true; - } - - /* just insert keyframe */ - return insert_vert_fcurve(fcu, cfra, curval, keytype, flag) >= 0; -} - -bool insert_keyframe_direct(ReportList *reports, - PointerRNA ptr, - PropertyRNA *prop, - FCurve *fcu, - const AnimationEvalContext *anim_eval_context, - eBezTriple_KeyframeType keytype, - NlaKeyframingContext *nla_context, - eInsertKeyFlags flag) -{ - - /* no F-Curve to add keyframe to? */ - if (fcu == nullptr) { - BKE_report(reports, RPT_ERROR, "No F-Curve to add keyframes to"); - return false; - } - - /* if no property given yet, try to validate from F-Curve info */ - if ((ptr.owner_id == nullptr) && (ptr.data == nullptr)) { - BKE_report( - reports, RPT_ERROR, "No RNA pointer available to retrieve values for keyframing from"); - return false; - } - if (prop == nullptr) { - PointerRNA tmp_ptr; - - /* try to get property we should be affecting */ - if (RNA_path_resolve_property(&ptr, fcu->rna_path, &tmp_ptr, &prop) == false) { - /* property not found... */ - const char *idname = (ptr.owner_id) ? ptr.owner_id->name : TIP_(""); - - BKE_reportf(reports, - RPT_ERROR, - "Could not insert keyframe, as RNA path is invalid for the given ID (ID = %s, " - "path = %s)", - idname, - fcu->rna_path); - return false; - } - - /* property found, so overwrite 'ptr' to make later code easier */ - ptr = tmp_ptr; - } - - /* update F-Curve flags to ensure proper behavior for property type */ - update_autoflags_fcurve_direct(fcu, prop); - - /* Obtain the value to insert. */ - const int index = fcu->array_index; - BLI_bitmap *successful_remaps = nullptr; - Vector values = get_keyframe_values(reports, - ptr, - prop, - index, - nla_context, - flag, - anim_eval_context, - nullptr, - &successful_remaps); - - float current_value = 0.0f; - if (index >= 0 && index < values.size()) { - current_value = values[index]; - } - - const bool curval_valid = BLI_BITMAP_TEST_BOOL(successful_remaps, index); - MEM_freeN(successful_remaps); - - /* This happens if NLA rejects this insertion. */ - if (!curval_valid) { - return false; - } - - return insert_keyframe_value( - reports, &ptr, prop, fcu, anim_eval_context, current_value, keytype, flag); -} - -/** Find or create the #FCurve based on the given path, and insert the specified value into it. */ -static bool insert_keyframe_fcurve_value(Main *bmain, - ReportList *reports, - PointerRNA *ptr, - PropertyRNA *prop, - bAction *act, - const char group[], - const char rna_path[], - int array_index, - const AnimationEvalContext *anim_eval_context, - float curval, - eBezTriple_KeyframeType keytype, - eInsertKeyFlags flag) -{ - /* make sure the F-Curve exists - * - if we're replacing keyframes only, DO NOT create new F-Curves if they do not exist yet - * but still try to get the F-Curve if it exists... - */ - const bool can_create_curve = (flag & (INSERTKEY_REPLACE | INSERTKEY_AVAILABLE)) == 0; - FCurve *fcu = can_create_curve ? - ED_action_fcurve_ensure(bmain, act, group, ptr, rna_path, array_index) : - ED_action_fcurve_find(act, rna_path, array_index); - - /* we may not have a F-Curve when we're replacing only... */ - if (!fcu) { - return false; - } - - const bool is_new_curve = (fcu->totvert == 0); - - /* set color mode if the F-Curve is new (i.e. without any keyframes) */ - if (is_new_curve && (flag & INSERTKEY_XYZ2RGB)) { - /* for Loc/Rot/Scale and also Color F-Curves, the color of the F-Curve in the Graph Editor, - * is determined by the array index for the F-Curve - */ - PropertySubType prop_subtype = RNA_property_subtype(prop); - if (ELEM(prop_subtype, PROP_TRANSLATION, PROP_XYZ, PROP_EULER, PROP_COLOR, PROP_COORDS)) { - fcu->color_mode = FCURVE_COLOR_AUTO_RGB; - } - else if (ELEM(prop_subtype, PROP_QUATERNION)) { - fcu->color_mode = FCURVE_COLOR_AUTO_YRGB; - } - } - - /* If the curve has only one key, make it cyclic if appropriate. */ - const bool is_cyclic_action = (flag & INSERTKEY_CYCLE_AWARE) && BKE_action_is_cyclic(act); - - if (is_cyclic_action && fcu->totvert == 1) { - make_new_fcurve_cyclic(act, fcu); - } - - /* update F-Curve flags to ensure proper behavior for property type */ - update_autoflags_fcurve_direct(fcu, prop); - - /* insert keyframe */ - const bool success = insert_keyframe_value( - reports, ptr, prop, fcu, anim_eval_context, curval, keytype, flag); - - /* If the curve is new, make it cyclic if appropriate. */ - if (is_cyclic_action && is_new_curve) { - make_new_fcurve_cyclic(act, fcu); - } - - return success; -} - -int insert_keyframe(Main *bmain, - ReportList *reports, - ID *id, - bAction *act, - const char group[], - const char rna_path[], - int array_index, - const AnimationEvalContext *anim_eval_context, - eBezTriple_KeyframeType keytype, - eInsertKeyFlags flag) -{ - /* validate pointer first - exit if failure */ - if (id == nullptr) { - BKE_reportf(reports, RPT_ERROR, "No ID block to insert keyframe in (path = %s)", rna_path); - return 0; - } - - if (!BKE_id_is_editable(bmain, id)) { - BKE_reportf(reports, RPT_ERROR, "'%s' on %s is not editable", rna_path, id->name + 2); - return 0; - } - - PointerRNA ptr; - PropertyRNA *prop = nullptr; - PointerRNA id_ptr = RNA_id_pointer_create(id); - if (RNA_path_resolve_property(&id_ptr, rna_path, &ptr, &prop) == false) { - BKE_reportf( - reports, - RPT_ERROR, - "Could not insert keyframe, as RNA path is invalid for the given ID (ID = %s, path = %s)", - (id) ? id->name : TIP_(""), - rna_path); - return 0; - } - - /* if no action is provided, keyframe to the default one attached to this ID-block */ - if (act == nullptr) { - /* get action to add F-Curve+keyframe to */ - act = ED_id_action_ensure(bmain, id); - - if (act == nullptr) { - BKE_reportf(reports, - RPT_ERROR, - "Could not insert keyframe, as this type does not support animation data (ID = " - "%s, path = %s)", - id->name, - rna_path); - return 0; - } - } - - /* apply NLA-mapping to frame to use (if applicable) */ - NlaKeyframingContext *nla_context = nullptr; - ListBase nla_cache = {nullptr, nullptr}; - AnimData *adt = BKE_animdata_from_id(id); - const AnimationEvalContext remapped_context = nla_time_remap( - anim_eval_context, &id_ptr, adt, act, &nla_cache, &nla_context); - - /* Obtain values to insert. */ - bool force_all; - BLI_bitmap *successful_remaps = nullptr; - Vector values = get_keyframe_values(reports, - ptr, - prop, - array_index, - nla_context, - flag, - anim_eval_context, - &force_all, - &successful_remaps); - - /* Key the entire array. */ - int key_count = 0; - if (array_index == -1 || force_all) { - /* In force mode, if any of the curves succeeds, drop the replace mode and restart. */ - if (force_all && (flag & (INSERTKEY_REPLACE | INSERTKEY_AVAILABLE)) != 0) { - int exclude = -1; - - for (array_index = 0; array_index < values.size(); array_index++) { - if (!BLI_BITMAP_TEST_BOOL(successful_remaps, array_index)) { - continue; - } - - if (insert_keyframe_fcurve_value(bmain, - reports, - &ptr, - prop, - act, - group, - rna_path, - array_index, - &remapped_context, - values[array_index], - keytype, - flag)) - { - key_count++; - exclude = array_index; - break; - } - } - - if (exclude != -1) { - flag &= ~(INSERTKEY_REPLACE | INSERTKEY_AVAILABLE); - - for (array_index = 0; array_index < values.size(); array_index++) { - if (!BLI_BITMAP_TEST_BOOL(successful_remaps, array_index)) { - continue; - } - - if (array_index != exclude) { - key_count += insert_keyframe_fcurve_value(bmain, - reports, - &ptr, - prop, - act, - group, - rna_path, - array_index, - &remapped_context, - values[array_index], - keytype, - flag); - } - } - } - } - /* Simply insert all channels. */ - else { - for (array_index = 0; array_index < values.size(); array_index++) { - if (!BLI_BITMAP_TEST_BOOL(successful_remaps, array_index)) { - continue; - } - - key_count += insert_keyframe_fcurve_value(bmain, - reports, - &ptr, - prop, - act, - group, - rna_path, - array_index, - &remapped_context, - values[array_index], - keytype, - flag); - } - } - } - /* Key a single index. */ - else { - if (array_index >= 0 && array_index < values.size() && - BLI_BITMAP_TEST_BOOL(successful_remaps, array_index)) - { - key_count += insert_keyframe_fcurve_value(bmain, - reports, - &ptr, - prop, - act, - group, - rna_path, - array_index, - &remapped_context, - values[array_index], - keytype, - flag); - } - } - - MEM_freeN(successful_remaps); - BKE_animsys_free_nla_keyframing_context_cache(&nla_cache); - - if (key_count > 0) { - if (act != nullptr) { - DEG_id_tag_update(&act->id, ID_RECALC_ANIMATION_NO_FLUSH); - } - if (adt != nullptr && adt->action != nullptr && adt->action != act) { - DEG_id_tag_update(&adt->action->id, ID_RECALC_ANIMATION_NO_FLUSH); - } - } - - return key_count; -} - -/* ************************************************** */ -/* KEYFRAME DELETION */ - -/* Main Keyframing API call: - * Use this when validation of necessary animation data isn't necessary as it - * already exists. It will delete a keyframe at the current frame. - * - * The flag argument is used for special settings that alter the behavior of - * the keyframe deletion. These include the quick refresh options. - */ - -static void deg_tag_after_keyframe_delete(Main *bmain, ID *id, AnimData *adt) -{ - if (adt->action == nullptr) { - /* In the case last f-curve was removed need to inform dependency graph - * about relations update, since it needs to get rid of animation operation - * for this data-block. */ - DEG_id_tag_update_ex(bmain, id, ID_RECALC_ANIMATION_NO_FLUSH); - DEG_relations_tag_update(bmain); - } - else { - DEG_id_tag_update_ex(bmain, &adt->action->id, ID_RECALC_ANIMATION_NO_FLUSH); - } -} - -int delete_keyframe(Main *bmain, - ReportList *reports, - ID *id, - bAction *act, - const char rna_path[], - int array_index, - float cfra) -{ - AnimData *adt = BKE_animdata_from_id(id); - - /* sanity checks */ - if (ELEM(nullptr, id, adt)) { - BKE_report(reports, RPT_ERROR, "No ID block and/or AnimData to delete keyframe from"); - return 0; - } - - /* validate pointer first - exit if failure */ - PointerRNA ptr; - PropertyRNA *prop; - PointerRNA id_ptr = RNA_id_pointer_create(id); - if (RNA_path_resolve_property(&id_ptr, rna_path, &ptr, &prop) == false) { - BKE_reportf( - reports, - RPT_ERROR, - "Could not delete keyframe, as RNA path is invalid for the given ID (ID = %s, path = %s)", - id->name, - rna_path); - return 0; - } - - /* get F-Curve - * NOTE: here is one of the places where we don't want new Action + F-Curve added! - * so 'add' var must be 0 - */ - if (act == nullptr) { - /* if no action is provided, use the default one attached to this ID-block - * - if it doesn't exist, then we're out of options... - */ - if (adt->action) { - act = adt->action; - - /* apply NLA-mapping to frame to use (if applicable) */ - cfra = BKE_nla_tweakedit_remap(adt, cfra, NLATIME_CONVERT_UNMAP); - } - else { - BKE_reportf(reports, RPT_ERROR, "No action to delete keyframes from for ID = %s", id->name); - return 0; - } - } - - int array_index_max = array_index + 1; - /* key entire array convenience method */ - if (array_index == -1) { - array_index = 0; - array_index_max = RNA_property_array_length(&ptr, prop); - - /* for single properties, increase max_index so that the property itself gets included, - * but don't do this for standard arrays since that can cause corruption issues - * (extra unused curves) - */ - if (array_index_max == array_index) { - array_index_max++; - } - } - - /* will only loop once unless the array index was -1 */ - int key_count = 0; - for (; array_index < array_index_max; array_index++) { - FCurve *fcu = ED_action_fcurve_find(act, rna_path, array_index); - - /* check if F-Curve exists and/or whether it can be edited */ - if (fcu == nullptr) { - continue; - } - - if (BKE_fcurve_is_protected(fcu)) { - BKE_reportf(reports, - RPT_WARNING, - "Not deleting keyframe for locked F-Curve '%s' for %s '%s'", - fcu->rna_path, - BKE_idtype_idcode_to_name(GS(id->name)), - id->name + 2); - continue; - } - - key_count += delete_keyframe_fcurve(adt, fcu, cfra); - } - if (key_count) { - deg_tag_after_keyframe_delete(bmain, id, adt); - } - /* return success/failure */ - return key_count; -} - -/* ************************************************** */ -/* KEYFRAME CLEAR */ - -/** - * Main Keyframing API call: - * Use this when validation of necessary animation data isn't necessary as it - * already exists. It will clear the current buttons fcurve(s). - * - * The flag argument is used for special settings that alter the behavior of - * the keyframe deletion. These include the quick refresh options. - * - * \return The number of f-curves removed. - */ -int clear_keyframe(Main *bmain, - ReportList *reports, - ID *id, - bAction *act, - const char rna_path[], - int array_index, - eInsertKeyFlags /*flag*/) -{ - AnimData *adt = BKE_animdata_from_id(id); - - /* sanity checks */ - if (ELEM(nullptr, id, adt)) { - BKE_report(reports, RPT_ERROR, "No ID block and/or AnimData to delete keyframe from"); - return 0; - } - - /* validate pointer first - exit if failure */ - PointerRNA ptr; - PropertyRNA *prop; - PointerRNA id_ptr = RNA_id_pointer_create(id); - if (RNA_path_resolve_property(&id_ptr, rna_path, &ptr, &prop) == false) { - BKE_reportf( - reports, - RPT_ERROR, - "Could not clear keyframe, as RNA path is invalid for the given ID (ID = %s, path = %s)", - id->name, - rna_path); - return 0; - } - - /* get F-Curve - * NOTE: here is one of the places where we don't want new Action + F-Curve added! - * so 'add' var must be 0 - */ - if (act == nullptr) { - /* if no action is provided, use the default one attached to this ID-block - * - if it doesn't exist, then we're out of options... - */ - if (adt->action) { - act = adt->action; - } - else { - BKE_reportf(reports, RPT_ERROR, "No action to delete keyframes from for ID = %s", id->name); - return 0; - } - } - - /* key entire array convenience method */ - int array_index_max = array_index + 1; - if (array_index == -1) { - array_index = 0; - array_index_max = RNA_property_array_length(&ptr, prop); - - /* for single properties, increase max_index so that the property itself gets included, - * but don't do this for standard arrays since that can cause corruption issues - * (extra unused curves) - */ - if (array_index_max == array_index) { - array_index_max++; - } - } - - int key_count = 0; - /* will only loop once unless the array index was -1 */ - for (; array_index < array_index_max; array_index++) { - FCurve *fcu = ED_action_fcurve_find(act, rna_path, array_index); - - /* check if F-Curve exists and/or whether it can be edited */ - if (fcu == nullptr) { - continue; - } - - if (BKE_fcurve_is_protected(fcu)) { - BKE_reportf(reports, - RPT_WARNING, - "Not clearing all keyframes from locked F-Curve '%s' for %s '%s'", - fcu->rna_path, - BKE_idtype_idcode_to_name(GS(id->name)), - id->name + 2); - continue; - } - - ANIM_fcurve_delete_from_animdata(nullptr, adt, fcu); - - /* return success */ - key_count++; - } - if (key_count) { - deg_tag_after_keyframe_delete(bmain, id, adt); - } - /* return success/failure */ - return key_count; -} - -} // namespace blender::animrig diff --git a/source/blender/animrig/intern/keyframing_auto.cc b/source/blender/animrig/intern/keyframing_auto.cc deleted file mode 100644 index a8e218fd4ad..00000000000 --- a/source/blender/animrig/intern/keyframing_auto.cc +++ /dev/null @@ -1,342 +0,0 @@ -/* SPDX-FileCopyrightText: 2023 Blender Foundation - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -/** \file - * \ingroup animrig - */ - -#include "BKE_animsys.h" -#include "BKE_context.h" -#include "BKE_fcurve.h" -#include "BKE_layer.h" -#include "BKE_object.hh" -#include "BKE_scene.h" - -#include "BLI_listbase.h" - -#include "DNA_scene_types.h" - -#include "RNA_path.hh" -#include "RNA_prototypes.h" - -#include "ED_keyframing.hh" -#include "ED_scene.hh" -#include "ED_transform.hh" - -#include "ANIM_keyframing.hh" - -#include "WM_api.hh" -#include "WM_types.hh" - -namespace blender::animrig { - -bool is_autokey_on(const Scene *scene) -{ - if (scene) { - return scene->toolsettings->autokey_mode & AUTOKEY_ON; - } - return U.autokey_mode & AUTOKEY_ON; -} - -bool is_autokey_mode(const Scene *scene, const eAutokey_Mode mode) -{ - if (scene) { - return scene->toolsettings->autokey_mode == mode; - } - return U.autokey_mode == mode; -} - -bool is_autokey_flag(const Scene *scene, const eAutokey_Flag flag) -{ - if (scene) { - return (scene->toolsettings->autokey_flag & flag) || (U.autokey_flag & flag); - } - return U.autokey_flag & flag; -} - -bool autokeyframe_cfra_can_key(const Scene *scene, ID *id) -{ - /* only filter if auto-key mode requires this */ - if (!is_autokey_on(scene)) { - return false; - } - - if (is_autokey_mode(scene, AUTOKEY_MODE_EDITKEYS)) { - /* Replace Mode: - * For whole block, only key if there's a keyframe on that frame already - * This is a valid assumption when we're blocking + tweaking - */ - const float cfra = BKE_scene_frame_get(scene); - return id_frame_has_keyframe(id, cfra); - } - - /* Normal Mode (or treat as being normal mode): - * - * Just in case the flags aren't set properly (i.e. only on/off is set, without a mode) - * let's set the "normal" flag too, so that it will all be sane everywhere... - */ - scene->toolsettings->autokey_mode = AUTOKEY_MODE_NORMAL; - - /* Can insert anytime we like... */ - return true; -} - -/** - * Auto-keyframing feature - for objects - * - * \param tmode: A transform mode. - * - * \note Context may not always be available, - * so must check before using it as it's a luxury for a few cases. - */ -void autokeyframe_object( - bContext *C, Scene *scene, ViewLayer *view_layer, Object *ob, const eTfmMode tmode) -{ - /* TODO: this should probably be done per channel instead. */ - ID *id = &ob->id; - if (!autokeyframe_cfra_can_key(scene, id)) { - return; - } - - ReportList *reports = CTX_wm_reports(C); - KeyingSet *active_ks = ANIM_scene_get_active_keyingset(scene); - Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); - const AnimationEvalContext anim_eval_context = BKE_animsys_eval_context_construct( - depsgraph, BKE_scene_frame_get(scene)); - eInsertKeyFlags flag = eInsertKeyFlags(0); - - /* Get flags used for inserting keyframes. */ - flag = ANIM_get_keyframing_flags(scene, true); - - /* Add data-source override for the object. */ - blender::Vector sources; - ANIM_relative_keyingset_add_source(sources, id); - - if (is_autokey_flag(scene, AUTOKEY_FLAG_ONLYKEYINGSET) && (active_ks)) { - /* Only insert into active keyingset - * NOTE: we assume here that the active Keying Set - * does not need to have its iterator overridden. - */ - ANIM_apply_keyingset( - C, &sources, active_ks, MODIFYKEY_MODE_INSERT, anim_eval_context.eval_time); - } - else if (is_autokey_flag(scene, AUTOKEY_FLAG_INSERTAVAIL)) { - AnimData *adt = ob->adt; - ToolSettings *ts = scene->toolsettings; - Main *bmain = CTX_data_main(C); - - /* only key on available channels */ - if (adt && adt->action) { - LISTBASE_FOREACH (FCurve *, fcu, &adt->action->curves) { - insert_keyframe(bmain, - reports, - id, - adt->action, - (fcu->grp ? fcu->grp->name : nullptr), - fcu->rna_path, - fcu->array_index, - &anim_eval_context, - eBezTriple_KeyframeType(ts->keyframe_type), - flag); - } - } - } - else if (is_autokey_flag(scene, AUTOKEY_FLAG_INSERTNEEDED)) { - bool do_loc = false, do_rot = false, do_scale = false; - - /* filter the conditions when this happens (assume that curarea->spacetype==SPACE_VIE3D) */ - if (tmode == TFM_TRANSLATION) { - do_loc = true; - } - else if (ELEM(tmode, TFM_ROTATION, TFM_TRACKBALL)) { - if (scene->toolsettings->transform_pivot_point == V3D_AROUND_ACTIVE) { - BKE_view_layer_synced_ensure(scene, view_layer); - if (ob != BKE_view_layer_active_object_get(view_layer)) { - do_loc = true; - } - } - else if (scene->toolsettings->transform_pivot_point == V3D_AROUND_CURSOR) { - do_loc = true; - } - - if ((scene->toolsettings->transform_flag & SCE_XFORM_AXIS_ALIGN) == 0) { - do_rot = true; - } - } - else if (tmode == TFM_RESIZE) { - if (scene->toolsettings->transform_pivot_point == V3D_AROUND_ACTIVE) { - BKE_view_layer_synced_ensure(scene, view_layer); - if (ob != BKE_view_layer_active_object_get(view_layer)) { - do_loc = true; - } - } - else if (scene->toolsettings->transform_pivot_point == V3D_AROUND_CURSOR) { - do_loc = true; - } - - if ((scene->toolsettings->transform_flag & SCE_XFORM_AXIS_ALIGN) == 0) { - do_scale = true; - } - } - - /* insert keyframes for the affected sets of channels using the builtin KeyingSets found */ - if (do_loc) { - KeyingSet *ks = ANIM_builtin_keyingset_get_named(ANIM_KS_LOCATION_ID); - ANIM_apply_keyingset(C, &sources, ks, MODIFYKEY_MODE_INSERT, anim_eval_context.eval_time); - } - if (do_rot) { - KeyingSet *ks = ANIM_builtin_keyingset_get_named(ANIM_KS_ROTATION_ID); - ANIM_apply_keyingset(C, &sources, ks, MODIFYKEY_MODE_INSERT, anim_eval_context.eval_time); - } - if (do_scale) { - KeyingSet *ks = ANIM_builtin_keyingset_get_named(ANIM_KS_SCALING_ID); - ANIM_apply_keyingset(C, &sources, ks, MODIFYKEY_MODE_INSERT, anim_eval_context.eval_time); - } - } - /* insert keyframe in all (transform) channels */ - else { - KeyingSet *ks = ANIM_builtin_keyingset_get_named(ANIM_KS_LOC_ROT_SCALE_ID); - ANIM_apply_keyingset(C, &sources, ks, MODIFYKEY_MODE_INSERT, anim_eval_context.eval_time); - } -} - -bool autokeyframe_object(bContext *C, Scene *scene, Object *ob, KeyingSet *ks) -{ - /* auto keyframing */ - if (!autokeyframe_cfra_can_key(scene, &ob->id)) { - return false; - } - - /* Now insert the key-frame(s) using the Keying Set: - * 1) Add data-source override for the Object. - * 2) Insert key-frames. - * 3) Free the extra info. - */ - blender::Vector sources; - ANIM_relative_keyingset_add_source(sources, &ob->id); - ANIM_apply_keyingset(C, &sources, ks, MODIFYKEY_MODE_INSERT, BKE_scene_frame_get(scene)); - - return true; -} - -bool autokeyframe_pchan(bContext *C, Scene *scene, Object *ob, bPoseChannel *pchan, KeyingSet *ks) -{ - if (!autokeyframe_cfra_can_key(scene, &ob->id)) { - return false; - } - - /* Now insert the keyframe(s) using the Keying Set: - * 1) Add data-source override for the pose-channel. - * 2) Insert key-frames. - * 3) Free the extra info. - */ - blender::Vector sources; - ANIM_relative_keyingset_add_source(sources, &ob->id, &RNA_PoseBone, pchan); - ANIM_apply_keyingset(C, &sources, ks, MODIFYKEY_MODE_INSERT, BKE_scene_frame_get(scene)); - - return true; -} - -bool autokeyframe_property(bContext *C, - Scene *scene, - PointerRNA *ptr, - PropertyRNA *prop, - const int rnaindex, - const float cfra, - const bool only_if_property_keyed) -{ - - Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); - const AnimationEvalContext anim_eval_context = BKE_animsys_eval_context_construct(depsgraph, - cfra); - bAction *action; - bool driven; - bool special; - bool changed = false; - - /* for entire array buttons we check the first component, it's not perfect - * but works well enough in typical cases */ - const int rnaindex_check = (rnaindex == -1) ? 0 : rnaindex; - FCurve *fcu = BKE_fcurve_find_by_rna_context_ui( - C, ptr, prop, rnaindex_check, nullptr, &action, &driven, &special); - - /* Only early out when we actually want an existing F-curve already - * (e.g. auto-keyframing from buttons). */ - if (fcu == nullptr && (driven || special || only_if_property_keyed)) { - return changed; - } - - if (special) { - /* NLA Strip property */ - if (is_autokey_on(scene)) { - ReportList *reports = CTX_wm_reports(C); - ToolSettings *ts = scene->toolsettings; - - changed = insert_keyframe_direct(reports, - *ptr, - prop, - fcu, - &anim_eval_context, - eBezTriple_KeyframeType(ts->keyframe_type), - nullptr, - eInsertKeyFlags(0)); - WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, nullptr); - } - } - else if (driven) { - /* Driver - Try to insert keyframe using the driver's input as the frame, - * making it easier to set up corrective drivers - */ - if (is_autokey_on(scene)) { - ReportList *reports = CTX_wm_reports(C); - ToolSettings *ts = scene->toolsettings; - - changed = insert_keyframe_direct(reports, - *ptr, - prop, - fcu, - &anim_eval_context, - eBezTriple_KeyframeType(ts->keyframe_type), - nullptr, - INSERTKEY_DRIVER); - WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, nullptr); - } - } - else { - ID *id = ptr->owner_id; - Main *bmain = CTX_data_main(C); - - /* TODO: this should probably respect the keyingset only option for anim */ - if (autokeyframe_cfra_can_key(scene, id)) { - ReportList *reports = CTX_wm_reports(C); - ToolSettings *ts = scene->toolsettings; - const eInsertKeyFlags flag = ANIM_get_keyframing_flags(scene, true); - char *path = RNA_path_from_ID_to_property(ptr, prop); - - if (only_if_property_keyed) { - /* NOTE: We use rnaindex instead of fcu->array_index, - * because a button may control all items of an array at once. - * E.g., color wheels (see #42567). */ - BLI_assert((fcu->array_index == rnaindex) || (rnaindex == -1)); - } - changed = insert_keyframe(bmain, - reports, - id, - action, - (fcu && fcu->grp) ? fcu->grp->name : nullptr, - fcu ? fcu->rna_path : path, - rnaindex, - &anim_eval_context, - eBezTriple_KeyframeType(ts->keyframe_type), - flag) != 0; - if (path) { - MEM_freeN(path); - } - WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, nullptr); - } - } - return changed; -} - -} // namespace blender::animrig \ No newline at end of file diff --git a/source/blender/animrig/intern/visualkey.cc b/source/blender/animrig/intern/visualkey.cc deleted file mode 100644 index d0ae468f5f3..00000000000 --- a/source/blender/animrig/intern/visualkey.cc +++ /dev/null @@ -1,288 +0,0 @@ -/* SPDX-FileCopyrightText: 2023 Blender Foundation - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -/** \file - * \ingroup animrig - */ - -#include -#include - -#include "ANIM_visualkey.hh" - -#include "BKE_animsys.h" -#include "BKE_armature.h" - -#include "BLI_math_matrix.h" -#include "BLI_math_rotation.h" -#include "BLI_math_vector.h" - -#include "DNA_constraint_types.h" -#include "DNA_object_types.h" -#include "DNA_rigidbody_types.h" - -#include "ED_keyframing.hh" - -#include "RNA_access.hh" -#include "RNA_prototypes.h" -#include "RNA_types.hh" - -namespace blender::animrig { - -/* internal status codes for visualkey_can_use */ -enum { - VISUALKEY_NONE = 0, - VISUALKEY_LOC, - VISUALKEY_ROT, - VISUALKEY_SCA, -}; - -/** - * This helper function determines if visual-keyframing should be used when - * inserting keyframes for the given channel. As visual-keyframing only works - * on Object and Pose-Channel blocks, this should only get called for those - * block-types, when using "standard" keying but 'Visual Keying' option in Auto-Keying - * settings is on. - */ -bool visualkey_can_use(PointerRNA *ptr, PropertyRNA *prop) -{ - bConstraint *con = nullptr; - bool has_rigidbody = false; - bool has_parent = false; - - /* validate data */ - if (ELEM(nullptr, ptr, ptr->data, prop)) { - return false; - } - - /* get first constraint and determine type of keyframe constraints to check for - * - constraints can be on either Objects or PoseChannels, so we only check if the - * ptr->type is RNA_Object or RNA_PoseBone, which are the RNA wrapping-info for - * those structs, allowing us to identify the owner of the data - */ - if (ptr->type == &RNA_Object) { - /* Object */ - Object *ob = static_cast(ptr->data); - RigidBodyOb *rbo = ob->rigidbody_object; - - con = static_cast(ob->constraints.first); - has_parent = (ob->parent != nullptr); - - /* active rigidbody objects only, as only those are affected by sim */ - has_rigidbody = ((rbo) && (rbo->type == RBO_TYPE_ACTIVE)); - } - else if (ptr->type == &RNA_PoseBone) { - /* Pose Channel */ - bPoseChannel *pchan = static_cast(ptr->data); - - if (pchan->constflag & (PCHAN_HAS_IK | PCHAN_INFLUENCED_BY_IK)) { - /* Spline IK cannot generally be keyed visually, because (at least with the default - * constraint settings) it requires non-uniform scaling that causes shearing in child bones, - * which cannot be represented by the bone's loc/rot/scale properties. */ - return true; - } - - con = static_cast(pchan->constraints.first); - has_parent = (pchan->parent != nullptr); - } - else { - BLI_assert(!"visualkey_can_use called for data-block that is not an Object or PoseBone."); - return false; - } - - /* Parent or rigidbody are always matching, no need to check further. */ - if (has_parent || has_rigidbody) { - return true; - } - - /* Only do visual keying on transforms. */ - const char *identifier = RNA_property_identifier(prop); - if (identifier == nullptr) { - printf("%s failed: nullptr identifier\n", __func__); - return false; - } - - short searchtype = VISUALKEY_NONE; - if (strstr(identifier, "location")) { - searchtype = VISUALKEY_LOC; - } - else if (strstr(identifier, "rotation")) { - searchtype = VISUALKEY_ROT; - } - else if (strstr(identifier, "scale")) { - searchtype = VISUALKEY_SCA; - } - else { - printf("%s failed: identifier - '%s'\n", __func__, identifier); - return false; - } - - /* Check constraints. */ - for (; con; con = con->next) { - /* only consider constraint if it is not disabled, and has influence */ - if (con->flag & CONSTRAINT_DISABLE) { - continue; - } - if (con->enforce == 0.0f) { - continue; - } - - /* some constraints may alter these transforms */ - switch (con->type) { - /* multi-transform constraints */ - case CONSTRAINT_TYPE_CHILDOF: - case CONSTRAINT_TYPE_ARMATURE: - return true; - case CONSTRAINT_TYPE_TRANSFORM: - case CONSTRAINT_TYPE_TRANSLIKE: - return true; - case CONSTRAINT_TYPE_FOLLOWPATH: - return true; - case CONSTRAINT_TYPE_KINEMATIC: - return true; - - /* Single-transform constraints. */ - case CONSTRAINT_TYPE_TRACKTO: - if (searchtype == VISUALKEY_ROT) { - return true; - } - break; - case CONSTRAINT_TYPE_DAMPTRACK: - if (searchtype == VISUALKEY_ROT) { - return true; - } - break; - case CONSTRAINT_TYPE_ROTLIMIT: - if (searchtype == VISUALKEY_ROT) { - return true; - } - break; - case CONSTRAINT_TYPE_LOCLIMIT: - if (searchtype == VISUALKEY_LOC) { - return true; - } - break; - case CONSTRAINT_TYPE_SIZELIMIT: - if (searchtype == VISUALKEY_SCA) { - return true; - } - break; - case CONSTRAINT_TYPE_DISTLIMIT: - if (searchtype == VISUALKEY_LOC) { - return true; - } - break; - case CONSTRAINT_TYPE_ROTLIKE: - if (searchtype == VISUALKEY_ROT) { - return true; - } - break; - case CONSTRAINT_TYPE_LOCLIKE: - if (searchtype == VISUALKEY_LOC) { - return true; - } - break; - case CONSTRAINT_TYPE_SIZELIKE: - if (searchtype == VISUALKEY_SCA) { - return true; - } - break; - case CONSTRAINT_TYPE_LOCKTRACK: - if (searchtype == VISUALKEY_ROT) { - return true; - } - break; - case CONSTRAINT_TYPE_MINMAX: - if (searchtype == VISUALKEY_LOC) { - return true; - } - break; - - default: - break; - } - } - - return false; -} - -/** - * This helper function extracts the value to use for visual-keyframing - * In the event that it is not possible to perform visual keying, try to fall-back - * to using the default method. Assumes that all data it has been passed is valid. - */ -Vector visualkey_get_values(PointerRNA *ptr, PropertyRNA *prop) -{ - Vector values; - const char *identifier = RNA_property_identifier(prop); - float tmat[4][4]; - int rotmode; - - /* handle for Objects or PoseChannels only - * - only Location, Rotation or Scale keyframes are supported currently - * - constraints can be on either Objects or PoseChannels, so we only check if the - * ptr->type is RNA_Object or RNA_PoseBone, which are the RNA wrapping-info for - * those structs, allowing us to identify the owner of the data - * - assume that array_index will be sane - */ - if (ptr->type == &RNA_Object) { - Object *ob = static_cast(ptr->data); - /* Loc code is specific... */ - if (strstr(identifier, "location")) { - values.extend({ob->object_to_world[3], 3}); - return values; - } - - copy_m4_m4(tmat, ob->object_to_world); - rotmode = ob->rotmode; - } - else if (ptr->type == &RNA_PoseBone) { - bPoseChannel *pchan = static_cast(ptr->data); - - BKE_armature_mat_pose_to_bone(pchan, pchan->pose_mat, tmat); - rotmode = pchan->rotmode; - - /* Loc code is specific... */ - if (strstr(identifier, "location")) { - /* only use for non-connected bones */ - if ((pchan->bone->parent == nullptr) || !(pchan->bone->flag & BONE_CONNECTED)) { - values.extend({tmat[3], 3}); - return values; - } - } - } - else { - return ANIM_setting_get_rna_values(ptr, prop); - } - - /* Rot/Scale code are common! */ - if (strstr(identifier, "rotation_euler")) { - values.resize(3); - mat4_to_eulO(values.data(), rotmode, tmat); - return values; - } - - if (strstr(identifier, "rotation_quaternion")) { - values.resize(4); - mat4_to_quat(values.data(), tmat); - return values; - } - - if (strstr(identifier, "rotation_axis_angle")) { - /* w = 0, x,y,z = 1,2,3 */ - values.resize(4); - mat4_to_axis_angle(&values[1], &values[0], tmat); - return values; - } - - if (strstr(identifier, "scale")) { - values.resize(3); - mat4_to_size(values.data(), tmat); - return values; - } - - /* as the function hasn't returned yet, read value from system in the default way */ - return ANIM_setting_get_rna_values(ptr, prop); -} -} // namespace blender::animrig \ No newline at end of file diff --git a/source/blender/blendthumb/src/blendthumb_extract.cc b/source/blender/blendthumb/src/blendthumb_extract.cc index aed6e0585b3..7fd9804e754 100644 --- a/source/blender/blendthumb/src/blendthumb_extract.cc +++ b/source/blender/blendthumb/src/blendthumb_extract.cc @@ -188,13 +188,13 @@ eThumbStatus blendthumb_create_thumb_from_file(FileReader *rawfile, Thumbnail *t else if (BLI_file_magic_is_gzip(header)) { file = BLI_filereader_new_gzip(rawfile); if (file != nullptr) { - rawfile = nullptr; /* The GZIP #FileReader takes ownership of raw-file. */ + rawfile = nullptr; /* The Gzip #FileReader takes ownership of raw-file. */ } } else if (BLI_file_magic_is_zstd(header)) { file = BLI_filereader_new_zstd(rawfile); if (file != nullptr) { - rawfile = nullptr; /* The ZSTD #FileReader takes ownership of raw-file. */ + rawfile = nullptr; /* The Zstd #FileReader takes ownership of raw-file. */ } } diff --git a/source/blender/blenfont/BLF_api.h b/source/blender/blenfont/BLF_api.h index 9838835349c..5f6060ef1fa 100644 --- a/source/blender/blenfont/BLF_api.h +++ b/source/blender/blenfont/BLF_api.h @@ -85,11 +85,6 @@ void BLF_aspect(int fontid, float x, float y, float z); void BLF_position(int fontid, float x, float y, float z); void BLF_size(int fontid, float size); -/** - * Weight class: 100 (Thin) - 400 (Normal) - 900 (Heavy). - */ -void BLF_character_weight(int fontid, int weight); - /* Goal: small but useful color API. */ void BLF_color4ubv(int fontid, const unsigned char rgba[4]); @@ -106,10 +101,10 @@ void BLF_color3fv_alpha(int fontid, const float rgb[3], float alpha); /** * Set a 4x4 matrix to be multiplied before draw the text. - * Remember that you need call `BLF_enable(BLF_MATRIX)` + * Remember that you need call BLF_enable(BLF_MATRIX) * to enable this. * - * The order of the matrix is column major (following the GPU module): + * The order of the matrix is like GL: * \code{.unparsed} * | m[0] m[4] m[8] m[12] | * | m[1] m[5] m[9] m[13] | diff --git a/source/blender/blenfont/intern/blf.cc b/source/blender/blenfont/intern/blf.cc index 1ab4bcb8b19..c7a8e91956d 100644 --- a/source/blender/blenfont/intern/blf.cc +++ b/source/blender/blenfont/intern/blf.cc @@ -7,7 +7,7 @@ * * Main BlenFont (BLF) API, public functions for font handling. * - * Wraps GPU display and FreeType. + * Wraps OpenGL and FreeType. */ #include @@ -324,14 +324,6 @@ void BLF_disable(int fontid, int option) } } -void BLF_character_weight(int fontid, int weight) -{ - FontBLF *font = blf_get(fontid); - if (font) { - font->char_weight = weight; - } -} - void BLF_aspect(int fontid, float x, float y, float z) { FontBLF *font = blf_get(fontid); @@ -529,7 +521,7 @@ void BLF_batch_draw_end() g_batch.enabled = false; } -static void blf_draw_gpu__start(const FontBLF *font) +static void blf_draw_gl__start(const FontBLF *font) { /* * The pixmap alignment hack is handle @@ -557,7 +549,7 @@ static void blf_draw_gpu__start(const FontBLF *font) } } -static void blf_draw_gpu__end(const FontBLF *font) +static void blf_draw_gl__end(const FontBLF *font) { if ((font->flags & (BLF_ROTATION | BLF_MATRIX | BLF_ASPECT)) != 0) { GPU_matrix_pop(); @@ -571,14 +563,14 @@ void BLF_draw_ex(int fontid, const char *str, const size_t str_len, ResultBLF *r BLF_RESULT_CHECK_INIT(r_info); if (font) { - blf_draw_gpu__start(font); + blf_draw_gl__start(font); if (font->flags & BLF_WORD_WRAP) { blf_font_draw__wrap(font, str, str_len, r_info); } else { blf_font_draw(font, str, str_len, r_info); } - blf_draw_gpu__end(font); + blf_draw_gl__end(font); } } void BLF_draw(int fontid, const char *str, const size_t str_len) @@ -603,9 +595,9 @@ int BLF_draw_mono(int fontid, const char *str, const size_t str_len, int cwidth, int columns = 0; if (font) { - blf_draw_gpu__start(font); + blf_draw_gl__start(font); columns = blf_font_draw_mono(font, str, str_len, cwidth, tab_columns); - blf_draw_gpu__end(font); + blf_draw_gl__end(font); } return columns; diff --git a/source/blender/blenfont/intern/blf_font.cc b/source/blender/blenfont/intern/blf_font.cc index cc7d76b70f9..a1839654480 100644 --- a/source/blender/blenfont/intern/blf_font.cc +++ b/source/blender/blenfont/intern/blf_font.cc @@ -75,8 +75,6 @@ static ft_pix blf_font_width_max_ft_pix(FontBLF *font); /** \name FreeType Caching * \{ */ -static bool blf_setup_face(FontBLF *font); - /** * Called when a face is removed by the cache. FreeType will call #FT_Done_Face. */ @@ -114,11 +112,6 @@ static FT_Error blf_cache_face_requester(FTC_FaceID faceID, font->face = *face; font->face->generic.data = font; font->face->generic.finalizer = blf_face_finalizer; - - /* More FontBLF setup now that we have a face. */ - if (!blf_setup_face(font)) { - err = FT_Err_Cannot_Open_Resource; - } } else { /* Clear this on error to avoid exception in FTC_Manager_LookupFace. */ @@ -1354,11 +1347,6 @@ static void blf_font_fill(FontBLF *font) font->clip_rec.ymax = 0; font->flags = 0; font->size = 0; - font->char_weight = 400; - font->char_slant = 0.0f; - font->char_width = 1.0f; - font->char_spacing = 0.0f; - BLI_listbase_clear(&font->cache); font->kerning_cache = nullptr; #if BLF_BLUR_ENABLE @@ -1377,211 +1365,6 @@ static void blf_font_fill(FontBLF *font) font->buf_info.col_init[3] = 0; } -/* Note that the data the following function creates is not yet used. - * But do not remove it as it will be used in the near future - Harley */ -static void blf_font_metrics(FT_Face face, FontMetrics *metrics) -{ - /* Members with non-zero defaults. */ - metrics->weight = 400; - metrics->width = 1.0f; - - TT_OS2 *os2_table = (TT_OS2 *)FT_Get_Sfnt_Table(face, FT_SFNT_OS2); - if (os2_table) { - /* The default (resting) font weight. */ - if (os2_table->usWeightClass >= 1 && os2_table->usWeightClass <= 1000) { - metrics->weight = short(os2_table->usWeightClass); - } - - /* Width value is one of integers 1-9 with known values. */ - if (os2_table->usWidthClass >= 1 && os2_table->usWidthClass <= 9) { - switch (os2_table->usWidthClass) { - case 1: - metrics->width = 0.5f; - break; - case 2: - metrics->width = 0.625f; - break; - case 3: - metrics->width = 0.75f; - break; - case 4: - metrics->width = 0.875f; - break; - case 5: - metrics->width = 1.0f; - break; - case 6: - metrics->width = 1.125f; - break; - case 7: - metrics->width = 1.25f; - break; - case 8: - metrics->width = 1.5f; - break; - case 9: - metrics->width = 2.0f; - break; - } - } - - metrics->strikeout_position = short(os2_table->yStrikeoutPosition); - metrics->strikeout_thickness = short(os2_table->yStrikeoutSize); - metrics->subscript_size = short(os2_table->ySubscriptYSize); - metrics->subscript_xoffset = short(os2_table->ySubscriptXOffset); - metrics->subscript_yoffset = short(os2_table->ySubscriptYOffset); - metrics->superscript_size = short(os2_table->ySuperscriptYSize); - metrics->superscript_xoffset = short(os2_table->ySuperscriptXOffset); - metrics->superscript_yoffset = short(os2_table->ySuperscriptYOffset); - metrics->family_class = short(os2_table->sFamilyClass); - metrics->selection_flags = short(os2_table->fsSelection); - metrics->first_charindex = short(os2_table->usFirstCharIndex); - metrics->last_charindex = short(os2_table->usLastCharIndex); - if (os2_table->version > 1) { - metrics->cap_height = short(os2_table->sCapHeight); - metrics->x_height = short(os2_table->sxHeight); - } - } - - /* The Post table usually contains a slant value, but in counter-clockwise degrees. */ - TT_Postscript *post_table = (TT_Postscript *)FT_Get_Sfnt_Table(face, FT_SFNT_POST); - if (post_table) { - if (post_table->italicAngle != 0) { - metrics->slant = float(post_table->italicAngle) / -65536.0f; - } - } - - /* Metrics copied from those gathered by FreeType. */ - metrics->units_per_EM = short(face->units_per_EM); - metrics->ascender = short(face->ascender); - metrics->descender = short(face->descender); - metrics->line_height = short(face->height); - metrics->max_advance_width = short(face->max_advance_width); - metrics->max_advance_height = short(face->max_advance_height); - metrics->underline_position = short(face->underline_position); - metrics->underline_thickness = short(face->underline_thickness); - metrics->num_glyphs = int(face->num_glyphs); - - if (metrics->cap_height == 0) { - /* Calculate or guess cap height if it is not set in the font. */ - FT_UInt gi = FT_Get_Char_Index(face, uint('H')); - if (gi && FT_Load_Glyph(face, gi, FT_LOAD_NO_SCALE | FT_LOAD_NO_BITMAP) == FT_Err_Ok) { - metrics->cap_height = short(face->glyph->metrics.height); - } - else { - metrics->cap_height = short(float(metrics->units_per_EM) * 0.7f); - } - } - - if (metrics->x_height == 0) { - /* Calculate or guess x-height if it is not set in the font. */ - FT_UInt gi = FT_Get_Char_Index(face, uint('x')); - if (gi && FT_Load_Glyph(face, gi, FT_LOAD_NO_SCALE | FT_LOAD_NO_BITMAP) == FT_Err_Ok) { - metrics->x_height = short(face->glyph->metrics.height); - } - else { - metrics->x_height = short(float(metrics->units_per_EM) * 0.5f); - } - } - - FT_UInt gi = FT_Get_Char_Index(face, uint('o')); - if (gi && FT_Load_Glyph(face, gi, FT_LOAD_NO_SCALE | FT_LOAD_NO_BITMAP) == FT_Err_Ok) { - metrics->o_proportion = float(face->glyph->metrics.width) / float(face->glyph->metrics.height); - } - - if (metrics->ascender == 0) { - /* Set a sane value for ascender if not set in the font. */ - metrics->ascender = short(float(metrics->units_per_EM) * 0.8f); - } - - if (metrics->descender == 0) { - /* Set a sane value for descender if not set in the font. */ - metrics->descender = metrics->ascender - metrics->units_per_EM; - } - - if (metrics->weight == 400 && face->style_flags & FT_STYLE_FLAG_BOLD) { - /* Normal weight yet this is an bold font, so set a sane weight value. */ - metrics->weight = 700; - } - - if (metrics->slant == 0.0f && face->style_flags & FT_STYLE_FLAG_ITALIC) { - /* No slant yet this is an italic font, so set a sane slant value. */ - metrics->slant = 8.0f; - } - - if (metrics->underline_position == 0) { - metrics->underline_position = short(float(metrics->units_per_EM) * -0.2f); - } - - if (metrics->underline_thickness == 0) { - metrics->underline_thickness = short(float(metrics->units_per_EM) * 0.07f); - } - - if (metrics->strikeout_position == 0) { - metrics->strikeout_position = short(float(metrics->x_height) * 0.6f); - } - - if (metrics->strikeout_thickness == 0) { - metrics->strikeout_thickness = metrics->underline_thickness; - } - - if (metrics->subscript_size == 0) { - metrics->subscript_size = short(float(metrics->units_per_EM) * 0.6f); - } - - if (metrics->subscript_yoffset == 0) { - metrics->subscript_yoffset = short(float(metrics->units_per_EM) * 0.075f); - } - - if (metrics->superscript_size == 0) { - metrics->superscript_size = short(float(metrics->units_per_EM) * 0.6f); - } - - if (metrics->superscript_yoffset == 0) { - metrics->superscript_yoffset = short(float(metrics->units_per_EM) * 0.35f); - } - - metrics->valid = true; -} - -/** - * Extra FontBLF setup needed after it gets a Face. Called from - * both blf_ensure_face and from the blf_cache_face_requester callback. - */ -static bool blf_setup_face(FontBLF *font) -{ - font->face_flags = font->face->face_flags; - - if (FT_HAS_MULTIPLE_MASTERS(font) && !font->variations) { - FT_Get_MM_Var(font->face, &(font->variations)); - } - - if (!font->metrics.valid) { - blf_font_metrics(font->face, &font->metrics); - font->char_weight = font->metrics.weight; - font->char_slant = font->metrics.slant; - font->char_width = font->metrics.width; - font->char_spacing = font->metrics.spacing; - } - - if (FT_IS_FIXED_WIDTH(font)) { - font->flags |= BLF_MONOSPACED; - } - - if (FT_HAS_KERNING(font) && !font->kerning_cache) { - /* Create kerning cache table and fill with value indicating "unset". */ - font->kerning_cache = static_cast( - MEM_mallocN(sizeof(KerningCacheBLF), __func__)); - for (uint i = 0; i < KERNING_CACHE_TABLE_SIZE; i++) { - for (uint j = 0; j < KERNING_CACHE_TABLE_SIZE; j++) { - font->kerning_cache->ascii_table[i][j] = KERNING_ENTRY_UNSET; - } - } - } - - return true; -} - bool blf_ensure_face(FontBLF *font) { if (font->face) { @@ -1663,8 +1446,37 @@ bool blf_ensure_face(FontBLF *font) font->ft_size = font->face->size; } - /* Setup Font details that require having a Face. */ - return blf_setup_face(font); + font->face_flags = font->face->face_flags; + + if (FT_HAS_MULTIPLE_MASTERS(font)) { + FT_Get_MM_Var(font->face, &(font->variations)); + } + + /* Save TrueType table with bits to quickly test most unicode block coverage. */ + TT_OS2 *os2_table = (TT_OS2 *)FT_Get_Sfnt_Table(font->face, FT_SFNT_OS2); + if (os2_table) { + font->unicode_ranges[0] = uint(os2_table->ulUnicodeRange1); + font->unicode_ranges[1] = uint(os2_table->ulUnicodeRange2); + font->unicode_ranges[2] = uint(os2_table->ulUnicodeRange3); + font->unicode_ranges[3] = uint(os2_table->ulUnicodeRange4); + } + + if (FT_IS_FIXED_WIDTH(font)) { + font->flags |= BLF_MONOSPACED; + } + + if (FT_HAS_KERNING(font) && !font->kerning_cache) { + /* Create kerning cache table and fill with value indicating "unset". */ + font->kerning_cache = static_cast( + MEM_mallocN(sizeof(KerningCacheBLF), __func__)); + for (uint i = 0; i < KERNING_CACHE_TABLE_SIZE; i++) { + for (uint j = 0; j < KERNING_CACHE_TABLE_SIZE; j++) { + font->kerning_cache->ascii_table[i][j] = KERNING_ENTRY_UNSET; + } + } + } + + return true; } struct FaceDetails { @@ -1761,15 +1573,6 @@ static FontBLF *blf_font_new_impl(const char *filepath, blf_font_free(font); return nullptr; } - - /* Save TrueType table with bits to quickly test most unicode block coverage. */ - TT_OS2 *os2_table = (TT_OS2 *)FT_Get_Sfnt_Table(font->face, FT_SFNT_OS2); - if (os2_table) { - font->unicode_ranges[0] = uint(os2_table->ulUnicodeRange1); - font->unicode_ranges[1] = uint(os2_table->ulUnicodeRange2); - font->unicode_ranges[2] = uint(os2_table->ulUnicodeRange3); - font->unicode_ranges[3] = uint(os2_table->ulUnicodeRange4); - } } /* Detect "Last resort" fonts. They have everything. Usually except last 5 bits. */ diff --git a/source/blender/blenfont/intern/blf_glyph.cc b/source/blender/blenfont/intern/blf_glyph.cc index d5fba595516..b3741b09356 100644 --- a/source/blender/blenfont/intern/blf_glyph.cc +++ b/source/blender/blenfont/intern/blf_glyph.cc @@ -58,14 +58,6 @@ static FT_Fixed to_16dot16(double val) return (FT_Fixed)lround(val * 65536.0); } -/** - * Convert a floating point value to a FreeType 16.16 fixed point value. - */ -static float from_16dot16(FT_Fixed value) -{ - return float(value) / 65536.0f; -} - /** \} */ /* -------------------------------------------------------------------- */ @@ -821,15 +813,11 @@ static const FT_Var_Axis *blf_var_axis_by_tag(const FT_MM_Var *variations, /** * Convert a float factor to a fixed-point design coordinate. - * Currently unused because we are only dealing with known axes - * with specific functions, but this would be needed for unregistered, - * custom, or private tags. These are all uppercase axis tags. * * \param axis: Pointer to a design space axis structure. * \param factor: -1 to 1 with 0 meaning "default" */ -[[maybe_unused]] static FT_Fixed blf_factor_to_coordinate(const FT_Var_Axis *axis, - const float factor) +static FT_Fixed blf_factor_to_coordinate(const FT_Var_Axis *axis, const float factor) { FT_Fixed value = axis->def; if (factor > 0) { @@ -844,20 +832,16 @@ static const FT_Var_Axis *blf_var_axis_by_tag(const FT_MM_Var *variations, } /** - * Alter a face variation axis by a factor. - * Currently unused because we are only dealing with known axes - * with specific functions, but this would be needed for unregistered, - * custom, or private tags. These are all uppercase axis tags. + * Alter a face variation axis by a factor * * \param coords: array of design coordinates, per axis. * \param tag: Axis tag, e.g. #BLF_VARIATION_AXIS_WEIGHT. * \param factor: -1 to 1 with 0 meaning "default" - * \return success if able to set this value. */ -[[maybe_unused]] static bool blf_glyph_set_variation_normalized(const FontBLF *font, - FT_Fixed coords[], - const uint32_t tag, - const float factor) +static bool blf_glyph_set_variation_normalized(const FontBLF *font, + FT_Fixed coords[], + const uint32_t tag, + const float factor) { int axis_index; const FT_Var_Axis *axis = blf_var_axis_by_tag(font->variations, tag, &axis_index); @@ -874,116 +858,23 @@ static const FT_Var_Axis *blf_var_axis_by_tag(const FT_MM_Var *variations, * \param coords: Array of design coordinates, per axis. * \param tag: Axis tag, e.g. #BLF_VARIATION_AXIS_OPTSIZE. * \param value: New float value. Converted to 16.16 and clamped within allowed range. - * \return success if able to set this value. */ static bool blf_glyph_set_variation_float(FontBLF *font, FT_Fixed coords[], uint32_t tag, - float *value) + float value) { int axis_index; const FT_Var_Axis *axis = blf_var_axis_by_tag(font->variations, tag, &axis_index); if (axis && (axis_index < BLF_VARIATIONS_MAX)) { - FT_Fixed int_value = to_16dot16(*value); + FT_Fixed int_value = to_16dot16(value); CLAMP(int_value, axis->minimum, axis->maximum); coords[axis_index] = int_value; - *value = from_16dot16(int_value); return true; } return false; } -/** - * Set the #BLF_VARIATION_AXIS_WEIGHT (Weight) axis to a specific weight value. - * - * \param coords: Array of design coordinates, per axis. - * \param weight: Weight class value (1-1000 allowed, 100-900 typical). - * \return value set (could be clamped), or current weight if the axis does not exist. - */ -static float blf_glyph_set_variation_weight(FontBLF *font, - FT_Fixed coords[], - float current_weight, - float target_weight) -{ - float value = target_weight; - if (blf_glyph_set_variation_float(font, coords, BLF_VARIATION_AXIS_WEIGHT, &value)) { - return value; - } - return current_weight; -} - -/** - * Set the #BLF_VARIATION_AXIS_SLANT (Slant) axis to a specific slant value. - * - * \param coords: Array of design coordinates, per axis. - * \param degrees: Slant in clockwise (opposite to spec) degrees. - * \return value set (could be clamped), or current slant if the axis does not exist. - */ -static float blf_glyph_set_variation_slant(FontBLF *font, - FT_Fixed coords[], - float current_degrees, - float target_degrees) -{ - float value = -target_degrees; - if (blf_glyph_set_variation_float(font, coords, BLF_VARIATION_AXIS_SLANT, &value)) { - return -value; - } - return current_degrees; -} - -/** - * Set the #BLF_VARIATION_AXIS_WIDTH (Width) axis to a specific width value. - * - * \param coords: Array of design coordinates, per axis. - * \param width: Glyph width value. 1.0 is normal, as per spec (which uses percent). - * \return value set (could be clamped), or current width if the axis does not exist. - */ -static float blf_glyph_set_variation_width(FontBLF *font, - FT_Fixed coords[], - float current_width, - float target_width) -{ - float value = target_width * 100.0f; - if (blf_glyph_set_variation_float(font, coords, BLF_VARIATION_AXIS_WIDTH, &value)) { - return value / 100.0f; - } - return current_width; -} - -/** - * Set the proposed #BLF_VARIATION_AXIS_SPACING (Spacing) axis to a specific value. - * - * \param coords: Array of design coordinates, per axis. - * \param spacing: Glyph spacing value. 0.0 is normal, as per spec. - * \return value set (could be clamped), or current spacing if the axis does not exist. - */ -static float blf_glyph_set_variation_spacing(FontBLF *font, - FT_Fixed coords[], - float current_spacing, - float target_spacing) -{ - float value = target_spacing; - if (blf_glyph_set_variation_float(font, coords, BLF_VARIATION_AXIS_SPACING, &value)) { - return value; - } - return current_spacing; -} - -/** - * Set the #BLF_VARIATION_AXIS_OPTSIZE (Optical Size) axis to a specific size value. - * - * \param coords: Array of design coordinates, per axis. - * \param points: Non-zero size in typographic points. - * \return success if able to set this value. - */ -static bool blf_glyph_set_variation_optical_size(FontBLF *font, - FT_Fixed coords[], - const float points) -{ - float value = points; - return blf_glyph_set_variation_float(font, coords, BLF_VARIATION_AXIS_OPTSIZE, &value); -} - /** \} */ /* -------------------------------------------------------------------- */ @@ -994,15 +885,14 @@ static bool blf_glyph_set_variation_optical_size(FontBLF *font, * Adjust the glyph's weight by a factor. * Used for fonts without #BLF_VARIATION_AXIS_WEIGHT variable axis. * - * \param width: -500 (make thinner) <= 0 (normal) => 500 (add boldness). + * \param factor: -1 (min stroke width) <= 0 (normal) => 1 (max boldness). */ -static bool blf_glyph_transform_weight(FT_GlyphSlot glyph, float width, bool monospaced) +static bool blf_glyph_transform_weight(FT_GlyphSlot glyph, float factor, bool monospaced) { if (glyph->format == FT_GLYPH_FORMAT_OUTLINE) { const FontBLF *font = (FontBLF *)glyph->face->generic.data; const FT_Pos average_width = font->ft_size->metrics.height; - float factor = width * 0.000225f; - FT_Pos change = (FT_Pos)(float(average_width) * factor); + FT_Pos change = (FT_Pos)(float(average_width) * factor * 0.12f); FT_Outline_EmboldenXY(&glyph->outline, change, 0); if (monospaced) { /* Widened fixed-pitch font needs a nudge left. */ @@ -1018,23 +908,19 @@ static bool blf_glyph_transform_weight(FT_GlyphSlot glyph, float width, bool mon } /** - * Adjust the glyph's slant by a number of degrees. + * Adjust the glyph's slant by a factor. * Used for fonts without #BLF_VARIATION_AXIS_SLANT variable axis. * - * \param degrees: amount of tilt to add in clockwise degrees. + * \param factor: -1 (max right-leaning) <= 0 (no slant) => 1 (max left-leaning). + * + * \note that left-leaning italics are possible in some RTL writing systems. */ -static bool blf_glyph_transform_slant(FT_GlyphSlot glyph, float degrees) +static bool blf_glyph_transform_slant(FT_GlyphSlot glyph, float factor) { if (glyph->format == FT_GLYPH_FORMAT_OUTLINE) { - FT_Matrix transform = {to_16dot16(1), to_16dot16(degrees * 0.0225f), 0, to_16dot16(1)}; + /* Slant angle is counter-clockwise as per OTF specs. */ + FT_Matrix transform = {to_16dot16(1), to_16dot16(-factor / 4.0f), 0, to_16dot16(1)}; FT_Outline_Transform(&glyph->outline, &transform); - if (degrees < 0.0f) { - /* Leftward slant could interfere with prior characters to nudge right. */ - const FontBLF *font = (FontBLF *)glyph->face->generic.data; - const FT_Pos average_width = font->ft_size->metrics.height; - FT_Pos change = (FT_Pos)(float(average_width) * degrees * -0.01f); - FT_Outline_Translate(&glyph->outline, change, 0); - } return true; } return false; @@ -1044,12 +930,12 @@ static bool blf_glyph_transform_slant(FT_GlyphSlot glyph, float degrees) * Adjust the glyph width by factor. * Used for fonts without #BLF_VARIATION_AXIS_WIDTH variable axis. * - * \param factor: -1 (subtract width) <= 0 (normal) => 1 (add width). + * \param factor: -1 (min width) <= 0 (normal) => 1 (max width). */ static bool blf_glyph_transform_width(FT_GlyphSlot glyph, float factor) { if (glyph->format == FT_GLYPH_FORMAT_OUTLINE) { - float scale = factor + 1.0f; + float scale = (factor * 0.4f) + 1.0f; /* 0.6f - 1.4f */ FT_Matrix matrix = {to_16dot16(scale), 0, 0, to_16dot16(1)}; FT_Outline_Transform(&glyph->outline, &matrix); glyph->advance.x = (FT_Pos)(double(glyph->advance.x) * scale); @@ -1062,7 +948,7 @@ static bool blf_glyph_transform_width(FT_GlyphSlot glyph, float factor) * Adjust the glyph spacing by factor. * Used for fonts without #BLF_VARIATION_AXIS_SPACING variable axis. * - * \param factor: -1 (make tighter) <= 0 (normal) => 1 (add space). + * \param factor: -1 (min tightness) <= 0 (normal) => 1 (max looseness). */ static bool blf_glyph_transform_spacing(FT_GlyphSlot glyph, float factor) { @@ -1125,23 +1011,21 @@ static FT_GlyphSlot blf_glyph_render(FontBLF *settings_font, blf_ensure_size(glyph_font); - /* Default style values of the font containing the glyph. */ - float weight = glyph_font->metrics.weight; - float slant = glyph_font->metrics.slant; - float width = glyph_font->metrics.width; - float spacing = glyph_font->metrics.spacing; + /* We need to keep track if changes are still needed. */ + bool weight_done = false; + bool slant_done = false; + bool width_done = false; + bool spacing_done = false; - /* Style targets are on the settings_font. */ - float weight_target = float(settings_font->char_weight); - if (settings_font->flags & BLF_BOLD) { - weight_target = MIN2(weight_target + 300.0f, 900.0f); - } - float slant_target = settings_font->char_slant; - if (settings_font->flags & BLF_ITALIC) { - slant_target = MIN2(slant_target + 8.0f, 15.0f); - } - float width_target = settings_font->char_width; - float spacing_target = settings_font->char_spacing; + /* Treat bold as 75% of maximum weight. */ + float weight = (settings_font->flags & BLF_BOLD) ? 0.75f : settings_font->char_weight; + + /* Treat italics as 75% of maximum rightward slant. Note that slant angle is in + * counter-clockwise degrees per OTF spec, so negative. */ + float slant = (settings_font->flags & BLF_ITALIC) ? -0.75f : settings_font->char_slant; + + float width = settings_font->char_width; + float spacing = settings_font->char_spacing; /* Font variations need to be set before glyph loading. Even if new value is zero. */ @@ -1150,13 +1034,17 @@ static FT_GlyphSlot blf_glyph_render(FontBLF *settings_font, /* Load current design coordinates. */ FT_Get_Var_Design_Coordinates(glyph_font->face, BLF_VARIATIONS_MAX, &coords[0]); /* Update design coordinates with new values. */ - - weight = blf_glyph_set_variation_weight(glyph_font, coords, weight, weight_target); - slant = blf_glyph_set_variation_slant(glyph_font, coords, slant, slant_target); - width = blf_glyph_set_variation_width(glyph_font, coords, width, width_target); - spacing = blf_glyph_set_variation_spacing(glyph_font, coords, spacing, spacing_target); - blf_glyph_set_variation_optical_size(glyph_font, coords, settings_font->size); - + weight_done = blf_glyph_set_variation_normalized( + glyph_font, coords, BLF_VARIATION_AXIS_WEIGHT, weight); + slant_done = blf_glyph_set_variation_normalized( + glyph_font, coords, BLF_VARIATION_AXIS_SLANT, slant); + width_done = blf_glyph_set_variation_normalized( + glyph_font, coords, BLF_VARIATION_AXIS_WIDTH, width); + spacing_done = blf_glyph_set_variation_normalized( + glyph_font, coords, BLF_VARIATION_AXIS_SPACING, spacing); + /* Optical size, if available, is set to current font size. */ + blf_glyph_set_variation_float( + glyph_font, coords, BLF_VARIATION_AXIS_OPTSIZE, settings_font->size); /* Save updated design coordinates. */ FT_Set_Var_Design_Coordinates(glyph_font->face, BLF_VARIATIONS_MAX, &coords[0]); } @@ -1175,17 +1063,17 @@ static FT_GlyphSlot blf_glyph_render(FontBLF *settings_font, /* Fallback glyph transforms, but only if required and not yet done. */ - if (weight != weight_target) { - blf_glyph_transform_weight(glyph, weight_target - weight, FT_IS_FIXED_WIDTH(glyph_font)); + if (weight != 0.0f && !weight_done) { + blf_glyph_transform_weight(glyph, weight, FT_IS_FIXED_WIDTH(glyph_font)); } - if (slant != slant_target) { - blf_glyph_transform_slant(glyph, slant_target - slant); + if (slant != 0.0f && !slant_done) { + blf_glyph_transform_slant(glyph, slant); } - if (width != width_target) { - blf_glyph_transform_width(glyph, width_target - width); + if (width != 0.0f && !width_done) { + blf_glyph_transform_width(glyph, width); } - if (spacing != spacing_target) { - blf_glyph_transform_spacing(glyph, spacing_target - spacing); + if (spacing != 0.0f && !spacing_done) { + blf_glyph_transform_spacing(glyph, spacing); } FT_Outline_Translate(&glyph->outline, (FT_Pos)subpixel, 0); diff --git a/source/blender/blenfont/intern/blf_internal_types.h b/source/blender/blenfont/intern/blf_internal_types.h index 3783246296e..4785de08463 100644 --- a/source/blender/blenfont/intern/blf_internal_types.h +++ b/source/blender/blenfont/intern/blf_internal_types.h @@ -116,7 +116,7 @@ typedef struct GlyphCacheBLF { /** Font size. */ float size; - int char_weight; + float char_weight; float char_slant; float char_width; float char_spacing; @@ -209,72 +209,6 @@ typedef struct FontBufInfoBLF { } FontBufInfoBLF; -typedef struct FontMetrics { - /** Indicate that these values have been properly loaded. */ - bool valid; - /** This font's default weight, 100-900, 400 is normal. */ - short weight; - /** This font's default width, 1 is normal, 2 is twice as wide. */ - float width; - /** This font's slant in clockwise degrees, 0 being upright. */ - float slant; - /** This font's default spacing, 1 is normal. */ - float spacing; - - /** Number of font units in an EM square. 2048, 1024, 1000 are typical. */ - short units_per_EM; /* */ - /** Design classification from OS/2 sFamilyClass. */ - short family_class; - /** Style classification from OS/2 fsSelection. */ - short selection_flags; - /** Total number of glyphs in the font. */ - int num_glyphs; - /** Minimum Unicode index, typically 0x0020. */ - short first_charindex; - /** Maximum Unicode index, or 0xFFFF if greater than. */ - short last_charindex; - - /** - * Positive number of font units from baseline to top of typical capitals. Can be slightly more - * than cap height when head serifs, terminals, or apexes extend above cap line. */ - short ascender; - /** Negative (!) number of font units from baseline to bottom of letters like `gjpqy`. */ - short descender; - /** Positive number of font units between consecutive baselines. */ - short line_height; - /** Font units from baseline to lowercase mean line, typically to top of "x". */ - short x_height; - /** Font units from baseline to top of capital letters, specifically "H". */ - short cap_height; - /** Ratio width to height of lowercase "O". Reliable indication of font proportion. */ - float o_proportion; - /** Font unit maximum horizontal advance for all glyphs in font. Can help with wrapping. */ - short max_advance_width; - /** As above but only for vertical layout fonts, otherwise is set to line_height value. */ - short max_advance_height; - - /** Negative (!) number of font units below baseline to center (!) of underlining stem. */ - short underline_position; - /** thickness of the underline in font units. */ - short underline_thickness; - /** Positive number of font units above baseline to the top (!) of strikeout stroke. */ - short strikeout_position; - /** thickness of the strikeout line in font units. */ - short strikeout_thickness; - /** EM size font units of recommended subscript letters. */ - short subscript_size; - /** Horizontal offset before first subscript character, typically 0. */ - short subscript_xoffset; - /** Positive number of font units above baseline for subscript characters. */ - short subscript_yoffset; - /** EM size font units of recommended superscript letters. */ - short superscript_size; - /** Horizontal offset before first superscript character, typically 0. */ - short superscript_xoffset; - /** Positive (!) number of font units below baseline for subscript characters. */ - short superscript_yoffset; -} FontMetrics; - typedef struct FontBLF { /** Full path to font file or NULL if from memory. */ char *filepath; @@ -324,7 +258,7 @@ typedef struct FontBLF { /** * Multiplied this matrix with the current one before draw the text! - * see #blf_draw_gpu__start. + * see #blf_draw_gl__start. */ float m[16]; @@ -340,11 +274,11 @@ typedef struct FontBLF { /** Axes data for Adobe MM, TrueType GX, or OpenType variation fonts. */ FT_MM_Var *variations; - /** Character variations. */ - int char_weight; /* 100 - 900, 400 = normal. */ - float char_slant; /* Slant in clockwise degrees. 0.0 = upright. */ - float char_width; /* Factor of normal character width. 1.0 = normal. */ - float char_spacing; /* Factor of normal normal spacing. 0.0 = normal. */ + /** Character variation; 0=default, -1=min, +1=max. */ + float char_weight; + float char_slant; + float char_width; + float char_spacing; /** Max texture size. */ int tex_size_max; @@ -373,9 +307,6 @@ typedef struct FontBLF { /** Copy of the font->face->face_flags, in case we don't have a face loaded. */ FT_Long face_flags; - /** Details about the font's design and style and sizes (in un-sized font units). */ - FontMetrics metrics; - /** Data for buffer usage (drawing into a texture buffer) */ FontBufInfoBLF buf_info; diff --git a/source/blender/blenkernel/BKE_animsys.h b/source/blender/blenkernel/BKE_animsys.h index a6093adef9d..54e7eaf50df 100644 --- a/source/blender/blenkernel/BKE_animsys.h +++ b/source/blender/blenkernel/BKE_animsys.h @@ -9,7 +9,6 @@ */ #include "BLI_bitmap.h" -#include "BLI_span.hh" #include "BLI_sys_types.h" /* for bool */ #ifdef __cplusplus @@ -96,7 +95,7 @@ void BKE_keyingsets_foreach_id(struct LibraryForeachIDData *data, void BKE_keyingset_free_path(struct KeyingSet *ks, struct KS_Path *ksp); /* Free data for KeyingSet but not set itself */ -void BKE_keyingset_free_paths(struct KeyingSet *ks); +void BKE_keyingset_free(struct KeyingSet *ks); /* Free all the KeyingSets in the given list */ void BKE_keyingsets_free(struct ListBase *list); @@ -251,7 +250,8 @@ struct NlaKeyframingContext *BKE_animsys_get_nla_keyframing_context( * * \param context: Context to use (may be NULL). * \param prop_ptr: Property about to be keyframed. - * \param[in,out] values: Span of property values to adjust. + * \param[in,out] values: Array of property values to adjust. + * \param count: Number of values in the array. * \param index: Index of the element about to be updated, or -1. * \param[out] r_force_all: Set to true if all channels must be inserted. May be NULL. * \param[out] r_successful_remaps: Bits will be enabled for indices that are both intended to be @@ -261,7 +261,8 @@ struct NlaKeyframingContext *BKE_animsys_get_nla_keyframing_context( void BKE_animsys_nla_remap_keyframe_values(struct NlaKeyframingContext *context, struct PointerRNA *prop_ptr, struct PropertyRNA *prop, - const blender::MutableSpan values, + float *values, + int count, int index, const struct AnimationEvalContext *anim_eval_context, bool *r_force_all, diff --git a/source/blender/blenkernel/BKE_attribute.h b/source/blender/blenkernel/BKE_attribute.h index 40596bc38e1..906d66f99f2 100644 --- a/source/blender/blenkernel/BKE_attribute.h +++ b/source/blender/blenkernel/BKE_attribute.h @@ -31,9 +31,8 @@ typedef enum eAttrDomain { ATTR_DOMAIN_CORNER = 3, /* Mesh Corner */ ATTR_DOMAIN_CURVE = 4, /* A single curve in a larger curve data-block */ ATTR_DOMAIN_INSTANCE = 5, /* Instance */ - ATTR_DOMAIN_LAYER = 6, /* A layer in a grease pencil data-block */ } eAttrDomain; -#define ATTR_DOMAIN_NUM 7 +#define ATTR_DOMAIN_NUM 6 typedef enum eAttrDomainMask { ATTR_DOMAIN_MASK_POINT = (1 << 0), @@ -41,8 +40,7 @@ typedef enum eAttrDomainMask { ATTR_DOMAIN_MASK_FACE = (1 << 2), ATTR_DOMAIN_MASK_CORNER = (1 << 3), ATTR_DOMAIN_MASK_CURVE = (1 << 4), - ATTR_DOMAIN_MASK_GREASE_PENCIL_LAYER = (1 << 6), - ATTR_DOMAIN_MASK_ALL = (1 << 7) - 1 + ATTR_DOMAIN_MASK_ALL = (1 << 5) - 1 } eAttrDomainMask; ENUM_OPERATORS(eAttrDomainMask, ATTR_DOMAIN_MASK_ALL); @@ -124,7 +122,7 @@ void BKE_id_attributes_default_color_set(struct ID *id, const char *name); const struct CustomDataLayer *BKE_id_attributes_color_find(const struct ID *id, const char *name); -void BKE_id_attribute_calc_unique_name(struct ID *id, const char *name, char *outname); +bool BKE_id_attribute_calc_unique_name(struct ID *id, const char *name, char *outname); const char *BKE_uv_map_vert_select_name_get(const char *uv_map_name, char *buffer); const char *BKE_uv_map_edge_select_name_get(const char *uv_map_name, char *buffer); diff --git a/source/blender/blenkernel/BKE_attribute.hh b/source/blender/blenkernel/BKE_attribute.hh index ebd2137b2f1..d79cc855ca7 100644 --- a/source/blender/blenkernel/BKE_attribute.hh +++ b/source/blender/blenkernel/BKE_attribute.hh @@ -808,6 +808,60 @@ eCustomDataType attribute_data_type_highest_complexity(Span dat */ eAttrDomain attribute_domain_highest_priority(Span domains); +/** + * A basic container around DNA CustomData so that its users + * don't have to implement special copy and move constructors. + */ +class CustomDataAttributes { + /** + * #CustomData needs a size to be freed, and unfortunately it isn't stored in the struct + * itself, so keep track of the size here so this class can implement its own destructor. + * If the implementation of the attribute storage changes, this could be removed. + */ + int size_; + + public: + CustomData data; + + CustomDataAttributes(); + ~CustomDataAttributes(); + CustomDataAttributes(const CustomDataAttributes &other); + CustomDataAttributes(CustomDataAttributes &&other); + + CustomDataAttributes &operator=(const CustomDataAttributes &other); + CustomDataAttributes &operator=(CustomDataAttributes &&other); + + void reallocate(int size); + + void clear(); + + std::optional get_for_read(const AttributeIDRef &attribute_id) const; + + /** + * Return a virtual array for a stored attribute, or a single value virtual array with the + * default value if the attribute doesn't exist. If no default value is provided, the default + * value for the type will be used. + */ + blender::GVArray get_for_read(const AttributeIDRef &attribute_id, + eCustomDataType data_type, + const void *default_value) const; + + template + blender::VArray get_for_read(const AttributeIDRef &attribute_id, const T &default_value) const + { + const blender::CPPType &cpp_type = blender::CPPType::get(); + const eCustomDataType type = blender::bke::cpp_type_to_custom_data_type(cpp_type); + GVArray varray = this->get_for_read(attribute_id, type, &default_value); + return varray.typed(); + } + + std::optional get_for_write(const AttributeIDRef &attribute_id); + bool create(const AttributeIDRef &attribute_id, eCustomDataType data_type); + bool remove(const AttributeIDRef &attribute_id); + + bool foreach_attribute(const AttributeForeachCallback callback, eAttrDomain domain) const; +}; + /* -------------------------------------------------------------------- */ /** \name #AttributeIDRef Inline Methods * \{ */ @@ -903,13 +957,4 @@ void copy_attributes(const AttributeAccessor src_attributes, const Set &skip, MutableAttributeAccessor dst_attributes); -void copy_attributes_group_to_group(AttributeAccessor src_attributes, - eAttrDomain domain, - const AnonymousAttributePropagationInfo &propagation_info, - const Set &skip, - OffsetIndices src_offsets, - OffsetIndices dst_offsets, - const IndexMask &selection, - MutableAttributeAccessor dst_attributes); - } // namespace blender::bke diff --git a/source/blender/blenkernel/BKE_blender_version.h b/source/blender/blenkernel/BKE_blender_version.h index 960e1922aad..030e21d41df 100644 --- a/source/blender/blenkernel/BKE_blender_version.h +++ b/source/blender/blenkernel/BKE_blender_version.h @@ -21,15 +21,15 @@ extern "C" { */ /** Blender major and minor version. */ -#define BLENDER_VERSION 401 +#define BLENDER_VERSION 400 /** Blender patch version for bug-fix releases. */ #define BLENDER_VERSION_PATCH 0 /** Blender release cycle stage: alpha/beta/rc/release. */ -#define BLENDER_VERSION_CYCLE alpha +#define BLENDER_VERSION_CYCLE beta /* Blender file format version. */ #define BLENDER_FILE_VERSION BLENDER_VERSION -#define BLENDER_FILE_SUBVERSION 3 +#define BLENDER_FILE_SUBVERSION 34 /* Minimum Blender version that supports reading file written with the current * version. Older Blender versions will test this and cancel loading the file, showing a warning to diff --git a/source/blender/blenkernel/BKE_compute_contexts.hh b/source/blender/blenkernel/BKE_compute_contexts.hh index f8b0260e6fc..a8c2db2ab06 100644 --- a/source/blender/blenkernel/BKE_compute_contexts.hh +++ b/source/blender/blenkernel/BKE_compute_contexts.hh @@ -30,11 +30,6 @@ class ModifierComputeContext : public ComputeContext { public: ModifierComputeContext(const ComputeContext *parent, std::string modifier_name); - StringRefNull modifier_name() const - { - return modifier_name_; - } - private: void print_current_in_line(std::ostream &stream) const override; }; diff --git a/source/blender/blenkernel/BKE_crazyspace.hh b/source/blender/blenkernel/BKE_crazyspace.hh index f158af024eb..b1df5f94c1c 100644 --- a/source/blender/blenkernel/BKE_crazyspace.hh +++ b/source/blender/blenkernel/BKE_crazyspace.hh @@ -53,9 +53,6 @@ GeometryDeformation get_evaluated_curves_deformation(const Depsgraph &depsgraph, const Object &ob_orig); GeometryDeformation get_evaluated_grease_pencil_drawing_deformation(const Object *ob_eval, const Object &ob_orig, - int layer_index); -GeometryDeformation get_evaluated_grease_pencil_drawing_deformation(const Depsgraph &depsgraph, - const Object &ob_orig, - int layer_index); + int drawing_index); } // namespace blender::bke::crazyspace diff --git a/source/blender/blenkernel/BKE_curves.h b/source/blender/blenkernel/BKE_curves.h index 344a733c1f4..ab1f84591ff 100644 --- a/source/blender/blenkernel/BKE_curves.h +++ b/source/blender/blenkernel/BKE_curves.h @@ -5,7 +5,6 @@ #pragma once #include "DNA_curves_types.h" -#include "DNA_object_types.h" /* #BoundBox. */ /** \file * \ingroup bke @@ -25,7 +24,7 @@ struct Scene; void *BKE_curves_add(struct Main *bmain, const char *name); -BoundBox BKE_curves_boundbox_get(struct Object *ob); +struct BoundBox *BKE_curves_boundbox_get(struct Object *ob); bool BKE_curves_attribute_required(const struct Curves *curves, const char *name); diff --git a/source/blender/blenkernel/BKE_curves.hh b/source/blender/blenkernel/BKE_curves.hh index ac2137c9f72..c26eae2739a 100644 --- a/source/blender/blenkernel/BKE_curves.hh +++ b/source/blender/blenkernel/BKE_curves.hh @@ -344,10 +344,8 @@ class CurvesGeometry : public ::CurvesGeometry { public: /** - * Change the number of curves and/or points. - * - * \warning To avoid redundant writes, newly created attribute values are not initialized. - * They must be initialized by the caller afterwards. + * Change the number of elements. New values for existing attributes should be properly + * initialized afterwards. */ void resize(int points_num, int curves_num); @@ -656,9 +654,11 @@ void set_handle_position(const float3 &position, * points are referred to as the control points, and the middle points are the corresponding * handles. */ -template -void evaluate_segment( - const T &point_0, const T &point_1, const T &point_2, const T &point_3, MutableSpan result); +void evaluate_segment(const float3 &point_0, + const float3 &point_1, + const float3 &point_2, + const float3 &point_3, + MutableSpan result); /** * Calculate all evaluated points for the Bezier curve. diff --git a/source/blender/blenkernel/BKE_curves_utils.hh b/source/blender/blenkernel/BKE_curves_utils.hh index 0da0585108a..4c8af98d18e 100644 --- a/source/blender/blenkernel/BKE_curves_utils.hh +++ b/source/blender/blenkernel/BKE_curves_utils.hh @@ -470,6 +470,31 @@ class IndexRangeCyclic { /** \name Utility Functions * \{ */ +/** + * Copy the provided point attribute values between all curves in the #curve_ranges index + * ranges, assuming that all curves have the same number of control points in #src_curves + * and #dst_curves. + */ +void copy_point_data(OffsetIndices src_points_by_curve, + OffsetIndices dst_points_by_curve, + const IndexMask &src_curve_selection, + GSpan src, + GMutableSpan dst); + +template +void copy_point_data(OffsetIndices src_points_by_curve, + OffsetIndices dst_points_by_curve, + const IndexMask &src_curve_selection, + Span src, + MutableSpan dst) +{ + copy_point_data(src_points_by_curve, + dst_points_by_curve, + src_curve_selection, + GSpan(src), + GMutableSpan(dst)); +} + void fill_points(OffsetIndices points_by_curve, const IndexMask &curve_selection, GPointer value, diff --git a/source/blender/blenkernel/BKE_customdata.h b/source/blender/blenkernel/BKE_customdata.h index f7a654bb3ba..d9b89c0515d 100644 --- a/source/blender/blenkernel/BKE_customdata.h +++ b/source/blender/blenkernel/BKE_customdata.h @@ -197,10 +197,7 @@ bool CustomData_merge_layout(const struct CustomData *source, * the #CD_CONSTRUCT behavior, so trivial types must be initialized by the caller. After being * resized, the #CustomData does not contain any referenced layers. */ -void CustomData_realloc(struct CustomData *data, - int old_size, - int new_size, - eCDAllocType alloctype = CD_CONSTRUCT); +void CustomData_realloc(struct CustomData *data, int old_size, int new_size); /** * BMesh version of CustomData_merge_layout; merges the layouts of source and `dest`, diff --git a/source/blender/blenkernel/BKE_editmesh.h b/source/blender/blenkernel/BKE_editmesh.h index a9a6c4d52e6..8f903d9cb08 100644 --- a/source/blender/blenkernel/BKE_editmesh.h +++ b/source/blender/blenkernel/BKE_editmesh.h @@ -120,7 +120,11 @@ const float (*BKE_editmesh_vert_coords_when_deformed(struct Depsgraph *depsgraph int *r_vert_len, bool *r_is_alloc))[3]; -void BKE_editmesh_lnorspace_update(BMEditMesh *em); +void BKE_editmesh_lnorspace_update(BMEditMesh *em, struct Mesh *me); +/** + * If auto-smooth not already set, set it. + */ +void BKE_editmesh_ensure_autosmooth(BMEditMesh *em, struct Mesh *me); struct BoundBox *BKE_editmesh_cage_boundbox_get(struct Object *object, BMEditMesh *em); #ifdef __cplusplus diff --git a/source/blender/blenkernel/BKE_geometry_fields.hh b/source/blender/blenkernel/BKE_geometry_fields.hh index 6e0b8b8879f..ddc4f2563fe 100644 --- a/source/blender/blenkernel/BKE_geometry_fields.hh +++ b/source/blender/blenkernel/BKE_geometry_fields.hh @@ -21,17 +21,14 @@ namespace blender::bke { class CurvesGeometry; class GeometryFieldInput; -namespace greasepencil { -class Drawing; -} class MeshFieldContext : public fn::FieldContext { private: const Mesh &mesh_; - eAttrDomain domain_; + const eAttrDomain domain_; public: - MeshFieldContext(const Mesh &mesh, eAttrDomain domain); + MeshFieldContext(const Mesh &mesh, const eAttrDomain domain); const Mesh &mesh() const { return mesh_; @@ -46,10 +43,10 @@ class MeshFieldContext : public fn::FieldContext { class CurvesFieldContext : public fn::FieldContext { private: const CurvesGeometry &curves_; - eAttrDomain domain_; + const eAttrDomain domain_; public: - CurvesFieldContext(const CurvesGeometry &curves, eAttrDomain domain); + CurvesFieldContext(const CurvesGeometry &curves, const eAttrDomain domain); const CurvesGeometry &curves() const { @@ -75,53 +72,6 @@ class PointCloudFieldContext : public fn::FieldContext { } }; -class GreasePencilFieldContext : public fn::FieldContext { - private: - const GreasePencil &grease_pencil_; - - public: - GreasePencilFieldContext(const GreasePencil &grease_pencil) : grease_pencil_(grease_pencil) {} - - const GreasePencil &grease_pencil() const - { - return grease_pencil_; - } -}; - -class GreasePencilLayerFieldContext : public fn::FieldContext { - private: - const GreasePencil &grease_pencil_; - eAttrDomain domain_; - int layer_index_; - - public: - GreasePencilLayerFieldContext(const GreasePencil &grease_pencil, - eAttrDomain domain, - int layer_index) - : grease_pencil_(grease_pencil), domain_(domain), layer_index_(layer_index) - { - } - - const GreasePencil &grease_pencil() const - { - return grease_pencil_; - } - - eAttrDomain domain() const - { - return domain_; - } - - int layer_index() const - { - return layer_index_; - } - - GVArray get_varray_for_input(const fn::FieldInput &field_input, - const IndexMask &mask, - ResourceScope &scope) const; -}; - class InstancesFieldContext : public fn::FieldContext { private: const Instances &instances_; @@ -136,8 +86,8 @@ class InstancesFieldContext : public fn::FieldContext { }; /** - * A field context that can represent meshes, curves, point clouds, instances or grease pencil - * layers, used for field inputs that can work for multiple geometry types. + * A field context that can represent meshes, curves, point clouds, or instances, + * used for field inputs that can work for multiple geometry types. */ class GeometryFieldContext : public fn::FieldContext { private: @@ -148,28 +98,13 @@ class GeometryFieldContext : public fn::FieldContext { */ const void *geometry_; const GeometryComponent::Type type_; - eAttrDomain domain_; - /** - * Only used when the type is grease pencil and the domain is either points or curves - * (not layers). - */ - int grease_pencil_layer_index_; + const eAttrDomain domain_; friend GeometryFieldInput; public: - GeometryFieldContext(const GeometryFieldContext &other, eAttrDomain domain); GeometryFieldContext(const GeometryComponent &component, eAttrDomain domain); - GeometryFieldContext(const void *geometry, - GeometryComponent::Type type, - eAttrDomain domain, - int grease_pencil_layer_index); - GeometryFieldContext(const Mesh &mesh, eAttrDomain domain); - GeometryFieldContext(const CurvesGeometry &curves, eAttrDomain domain); - GeometryFieldContext(const GreasePencil &grease_pencil); - GeometryFieldContext(const GreasePencil &grease_pencil, eAttrDomain domain, int layer_index); - GeometryFieldContext(const PointCloud &points); - GeometryFieldContext(const Instances &instances); + GeometryFieldContext(const void *geometry, GeometryComponent::Type type, eAttrDomain domain); const void *geometry() const { @@ -186,21 +121,17 @@ class GeometryFieldContext : public fn::FieldContext { return domain_; } - int grease_pencil_layer_index() const - { - BLI_assert(this->type_ == GeometryComponent::Type::GreasePencil); - BLI_assert(ELEM(this->domain_, ATTR_DOMAIN_LAYER, ATTR_DOMAIN_CURVE, ATTR_DOMAIN_POINT)); - return grease_pencil_layer_index_; - } - std::optional attributes() const; const Mesh *mesh() const; const CurvesGeometry *curves() const; const PointCloud *pointcloud() const; - const GreasePencil *grease_pencil() const; - const greasepencil::Drawing *grease_pencil_layer_drawing() const; const Instances *instances() const; - const CurvesGeometry *curves_or_strokes() const; + + private: + GeometryFieldContext(const Mesh &mesh, eAttrDomain domain); + GeometryFieldContext(const CurvesGeometry &curves, eAttrDomain domain); + GeometryFieldContext(const PointCloud &points); + GeometryFieldContext(const Instances &instances); }; class GeometryFieldInput : public fn::FieldInput { @@ -316,25 +247,6 @@ class AttributeExistsFieldInput final : public bke::GeometryFieldInput { const IndexMask &mask) const final; }; -class NamedLayerSelectionFieldInput final : public bke::GeometryFieldInput { - private: - std::string layer_name_; - - public: - NamedLayerSelectionFieldInput(std::string layer_name) - : bke::GeometryFieldInput(CPPType::get(), "Named Layer node"), - layer_name_(std::move(layer_name)) - { - category_ = Category::Generated; - } - - GVArray get_varray_for_context(const bke::GeometryFieldContext &context, - const IndexMask &mask) const final; - uint64_t hash() const override; - bool is_equal_to(const fn::FieldNode &other) const override; - std::optional preferred_domain(const GeometryComponent &component) const override; -}; - class IDAttributeFieldInput : public GeometryFieldInput { public: IDAttributeFieldInput() : GeometryFieldInput(CPPType::get()) @@ -351,7 +263,7 @@ class IDAttributeFieldInput : public GeometryFieldInput { bool is_equal_to(const fn::FieldNode &other) const override; }; -VArray curve_normals_varray(const CurvesGeometry &curves, eAttrDomain domain); +VArray curve_normals_varray(const CurvesGeometry &curves, const eAttrDomain domain); VArray mesh_normals_varray(const Mesh &mesh, const IndexMask &mask, eAttrDomain domain); @@ -382,7 +294,7 @@ class AnonymousAttributeFieldInput : public GeometryFieldInput { std::string producer_name) : GeometryFieldInput(type, anonymous_id->user_name()), anonymous_id_(std::move(anonymous_id)), - producer_name_(std::move(producer_name)) + producer_name_(producer_name) { category_ = Category::AnonymousAttribute; } @@ -424,12 +336,12 @@ class CurveLengthFieldInput final : public CurvesFieldInput { bool try_capture_field_on_geometry(GeometryComponent &component, const AttributeIDRef &attribute_id, - eAttrDomain domain, + const eAttrDomain domain, const fn::GField &field); bool try_capture_field_on_geometry(GeometryComponent &component, const AttributeIDRef &attribute_id, - eAttrDomain domain, + const eAttrDomain domain, const fn::Field &selection, const fn::GField &field); diff --git a/source/blender/blenkernel/BKE_geometry_set.hh b/source/blender/blenkernel/BKE_geometry_set.hh index 5022728e834..064a795f74e 100644 --- a/source/blender/blenkernel/BKE_geometry_set.hh +++ b/source/blender/blenkernel/BKE_geometry_set.hh @@ -29,7 +29,6 @@ class ComponentAttributeProviders; class CurvesEditHints; class Instances; class GeometryComponent; -class GreasePencilEditHints; } // namespace blender::bke namespace blender::bke { @@ -229,11 +228,6 @@ struct GeometrySet { bool include_instances, AttributeForeachCallback callback) const; - static void propagate_attributes_from_layer_to_instances( - const AttributeAccessor src_attributes, - MutableAttributeAccessor dst_attributes, - const AnonymousAttributePropagationInfo &propagation_info); - void gather_attributes_for_propagation(Span component_types, GeometryComponent::Type dst_component_type, bool include_instances, @@ -674,10 +668,6 @@ class GeometryComponentEditData final : public GeometryComponent { * example, when the curves have been converted to a mesh. */ std::unique_ptr curves_edit_hints_; - /** - * Information about how drawings on the grease pencil layers are manipulated during evaluation. - */ - std::unique_ptr grease_pencil_edit_hints_; GeometryComponentEditData(); @@ -693,15 +683,15 @@ class GeometryComponentEditData final : public GeometryComponent { * lost, which would make curves sculpt mode fall back to using original curve positions instead * of deformed ones. */ - static void remember_deformed_positions_if_necessary(GeometrySet &geometry); + static void remember_deformed_curve_positions_if_necessary(GeometrySet &geometry); static constexpr inline GeometryComponent::Type static_type = GeometryComponent::Type::Edit; }; /** * A geometry component that stores #GreasePencil data. - * The attributes on this component are only on the layer domain. Each individual layer represents - * a #CurvesGeometry with its own curve and point domain. See #CurveComponent. + * This component does not implement an attribute API, because the #GreasePencil data itself does + * not store any attributes, only the individual drawings within it. */ class GreasePencilComponent : public GeometryComponent { private: @@ -735,9 +725,6 @@ class GreasePencilComponent : public GeometryComponent { void ensure_owns_direct_data() override; static constexpr inline GeometryComponent::Type static_type = Type::GreasePencil; - - std::optional attributes() const final; - std::optional attributes_for_write() final; }; } // namespace blender::bke diff --git a/source/blender/blenkernel/BKE_global.h b/source/blender/blenkernel/BKE_global.h index 17b5621f14d..ad11f72984f 100644 --- a/source/blender/blenkernel/BKE_global.h +++ b/source/blender/blenkernel/BKE_global.h @@ -37,9 +37,9 @@ typedef struct Global { struct Main *pr_main; /** Last saved location for images. */ - char filepath_last_image[/*FILE_MAX*/ 1024]; + char ima[1024]; /* 1024 = FILE_MAX */ /** Last used location for library link/append. */ - char filepath_last_library[/*FILE_MAX*/ 1024]; + char lib[1024]; /** * Strings of recently opened files to show in the file menu. diff --git a/source/blender/blenkernel/BKE_gpencil_modifier_legacy.h b/source/blender/blenkernel/BKE_gpencil_modifier_legacy.h index e6cf3829414..5ec8785fabd 100644 --- a/source/blender/blenkernel/BKE_gpencil_modifier_legacy.h +++ b/source/blender/blenkernel/BKE_gpencil_modifier_legacy.h @@ -274,7 +274,7 @@ void BKE_gpencil_modifier_free_ex(struct GpencilModifierData *md, int flag); */ void BKE_gpencil_modifier_free(struct GpencilModifierData *md); /* check unique name */ -void BKE_gpencil_modifier_unique_name(struct ListBase *modifiers, struct GpencilModifierData *gmd); +bool BKE_gpencil_modifier_unique_name(struct ListBase *modifiers, struct GpencilModifierData *gmd); /** * Check if grease pencil modifier depends on time. * \param md: Modifier data. diff --git a/source/blender/blenkernel/BKE_grease_pencil.hh b/source/blender/blenkernel/BKE_grease_pencil.hh index 6a06190e723..5ad18f1a138 100644 --- a/source/blender/blenkernel/BKE_grease_pencil.hh +++ b/source/blender/blenkernel/BKE_grease_pencil.hh @@ -11,11 +11,8 @@ #include -#include "BLI_array_utils.hh" -#include "BLI_color.hh" #include "BLI_function_ref.hh" #include "BLI_map.hh" -#include "BLI_math_matrix_types.hh" #include "BLI_math_vector_types.hh" #include "BLI_shared_cache.hh" #include "BLI_utility_mixins.hh" @@ -23,7 +20,6 @@ #include "DNA_gpencil_legacy_types.h" #include "DNA_grease_pencil_types.h" -#include "DNA_object_types.h" /* #BoundBox. */ struct Main; struct Depsgraph; @@ -36,12 +32,30 @@ namespace blender::bke { namespace greasepencil { -struct DrawingTransforms { - float4x4 world_space_to_layer_space; - float4x4 layer_space_to_world_space; +/** + * A single point for a stroke that is currently being drawn. + */ +struct StrokePoint { + float3 position; + float radius; + float opacity; + float4 color; +}; - DrawingTransforms() = default; - DrawingTransforms(const Object &grease_pencil_ob); +/** + * Stroke cache for a stroke that is currently being drawn. + */ +struct StrokeCache { + Vector points; + Vector triangles; + int mat = 0; + + void clear() + { + this->points.clear_and_shrink(); + this->triangles.clear_and_shrink(); + this->mat = 0; + } }; class DrawingRuntime { @@ -88,12 +102,6 @@ class Drawing : public ::GreasePencilDrawing { VArray opacities() const; MutableSpan opacities_for_write(); - /** - * Vertex colors of the points. Default is black. - */ - VArray vertex_colors() const; - MutableSpan vertex_colors_for_write(); - /** * Add a user for this drawing. When a drawing has multiple users, both users are allowed to * modify this drawings data. @@ -111,24 +119,6 @@ class Drawing : public ::GreasePencilDrawing { bool has_users() const; }; -class DrawingReference : public ::GreasePencilDrawingReference { - public: - DrawingReference(); - DrawingReference(const DrawingReference &other); - ~DrawingReference(); -}; - -const Drawing *get_eval_grease_pencil_layer_drawing(const GreasePencil &grease_pencil, - int layer_index); -Drawing *get_eval_grease_pencil_layer_drawing_for_write(GreasePencil &grease_pencil, - int layer_index); -/** - * Copies the drawings from one array to another. Assumes that \a dst_drawings is allocated but not - * initialized, e.g. it will allocate new drawings and store the pointers. - */ -void copy_drawing_array(Span src_drawings, - MutableSpan dst_drawings); - class LayerGroup; class Layer; @@ -148,8 +138,7 @@ class Layer; bool is_editable() const; \ bool is_selected() const; \ void set_selected(bool selected); \ - bool use_onion_skinning() const; \ - bool is_child_of(const LayerGroup &group) const; + bool use_onion_skinning() const; /* Implements the forwarding of the methods defined by #TREENODE_COMMON_METHODS. */ #define TREENODE_COMMON_METHODS_FORWARD_IMPL(class_name) \ @@ -192,10 +181,6 @@ class Layer; inline bool class_name::use_onion_skinning() const \ { \ return this->as_node().use_onion_skinning(); \ - } \ - inline bool class_name::is_child_of(const LayerGroup &group) const \ - { \ - return this->as_node().is_child_of(group); \ } /** @@ -506,10 +491,16 @@ class LayerGroup : public ::GreasePencilLayerTreeGroup { Span groups_for_write(); /** - * Returns a pointer to the node with \a name. If no such node was found, returns nullptr. + * Returns a pointer to the layer with \a name. If no such layer was found, returns nullptr. */ - const TreeNode *find_node_by_name(StringRefNull name) const; - TreeNode *find_node_by_name(StringRefNull name); + const Layer *find_layer_by_name(StringRefNull name) const; + Layer *find_layer_by_name(StringRefNull name); + + /** + * Returns a pointer to the group with \a name. If no such group was found, returns nullptr. + */ + const LayerGroup *find_group_by_name(StringRefNull name) const; + LayerGroup *find_group_by_name(StringRefNull name); /** * Print the nodes. For debugging purposes. @@ -521,7 +512,6 @@ class LayerGroup : public ::GreasePencilLayerTreeGroup { * Adds a new layer named \a name at the end of this group and returns it. */ Layer &add_layer(StringRefNull name); - Layer &add_layer(const Layer &duplicate_layer); /** * Adds a new group named \a name at the end of this group and returns it. */ @@ -621,16 +611,6 @@ inline bool TreeNode::use_onion_skinning() const { return ((this->flag & GP_LAYER_TREE_NODE_USE_ONION_SKINNING) != 0); } -inline bool TreeNode::is_child_of(const LayerGroup &group) const -{ - if (const LayerGroup *parent = this->parent_group()) { - if (parent == &group) { - return true; - } - return parent->is_child_of(group); - } - return false; -} inline StringRefNull TreeNode::name() const { return (this->GreasePencilLayerTreeNode::name != nullptr) ? @@ -674,6 +654,7 @@ void legacy_gpencil_frame_to_grease_pencil_drawing(const bGPDframe &gpf, void legacy_gpencil_to_grease_pencil(Main &main, GreasePencil &grease_pencil, bGPdata &gpd); } // namespace convert + } // namespace greasepencil class GreasePencilRuntime { @@ -682,39 +663,19 @@ class GreasePencilRuntime { * Allocated and freed by the drawing code. See `DRW_grease_pencil_batch_cache_*` functions. */ void *batch_cache = nullptr; + bke::greasepencil::StrokeCache stroke_cache; /* The frame on which the object was evaluated (only valid for evaluated object). */ int eval_frame; public: GreasePencilRuntime() {} ~GreasePencilRuntime() {} -}; - -class GreasePencilDrawingEditHints { - public: - std::optional> positions; -}; - -/** - * Used to propagate deformation data through modifier evaluation. - */ -class GreasePencilEditHints { - public: - /** - * Original data that the edit hints below are meant to be used for. - */ - const GreasePencil &grease_pencil_id_orig; - - GreasePencilEditHints(const GreasePencil &grease_pencil_id_orig) - : grease_pencil_id_orig(grease_pencil_id_orig) - { - } /** - * Array of #GreasePencilDrawingEditHints. There is one edit hint for each evaluated drawing. - * Note: The index for each element is the layer index. + * A buffer for a single stroke while drawing. */ - std::optional> drawing_hints; + Span stroke_buffer() const; + bool has_stroke_buffer() const; }; } // namespace blender::bke @@ -728,16 +689,6 @@ inline const blender::bke::greasepencil::Drawing &GreasePencilDrawing::wrap() co return *reinterpret_cast(this); } -inline blender::bke::greasepencil::DrawingReference &GreasePencilDrawingReference::wrap() -{ - return *reinterpret_cast(this); -} -inline const blender::bke::greasepencil::DrawingReference &GreasePencilDrawingReference::wrap() - const -{ - return *reinterpret_cast(this); -} - inline GreasePencilFrame GreasePencilFrame::null() { return GreasePencilFrame{-1, 0, 0}; @@ -811,32 +762,18 @@ inline bool GreasePencil::has_active_layer() const void *BKE_grease_pencil_add(Main *bmain, const char *name); GreasePencil *BKE_grease_pencil_new_nomain(); GreasePencil *BKE_grease_pencil_copy_for_eval(const GreasePencil *grease_pencil_src); -BoundBox BKE_grease_pencil_boundbox_get(Object *ob); +BoundBox *BKE_grease_pencil_boundbox_get(Object *ob); void BKE_grease_pencil_data_update(Depsgraph *depsgraph, Scene *scene, Object *object); -void BKE_grease_pencil_duplicate_drawing_array(const GreasePencil *grease_pencil_src, - GreasePencil *grease_pencil_dst); -int BKE_grease_pencil_object_material_index_get(Object *ob, Material *ma); int BKE_grease_pencil_object_material_index_get_by_name(Object *ob, const char *name); Material *BKE_grease_pencil_object_material_new(Main *bmain, Object *ob, const char *name, int *r_index); -Material *BKE_grease_pencil_object_material_from_brush_get(Object *ob, Brush *brush); Material *BKE_grease_pencil_object_material_ensure_by_name(Main *bmain, Object *ob, const char *name, int *r_index); -Material *BKE_grease_pencil_brush_material_get(Brush *brush); -Material *BKE_grease_pencil_object_material_ensure_from_brush(Main *bmain, - Object *ob, - Brush *brush); -Material *BKE_grease_pencil_object_material_ensure_from_active_input_brush(Main *bmain, - Object *ob, - Brush *brush); -Material *BKE_grease_pencil_object_material_ensure_from_active_input_material(Object *ob); -Material *BKE_grease_pencil_object_material_ensure_active(Object *ob); -void BKE_grease_pencil_material_remap(GreasePencil *grease_pencil, const uint *remap, int totcol); bool BKE_grease_pencil_references_cyclic_check(const GreasePencil *id_reference, const GreasePencil *grease_pencil); diff --git a/source/blender/blenkernel/BKE_instances.hh b/source/blender/blenkernel/BKE_instances.hh index c148d5d4c93..a6416c559b9 100644 --- a/source/blender/blenkernel/BKE_instances.hh +++ b/source/blender/blenkernel/BKE_instances.hh @@ -63,7 +63,6 @@ class InstanceReference { InstanceReference(Object &object); InstanceReference(Collection &collection); InstanceReference(GeometrySet geometry_set); - InstanceReference(std::unique_ptr geometry_set); InstanceReference(const InstanceReference &other); InstanceReference(InstanceReference &&other); @@ -89,31 +88,27 @@ class Instances { * Contains the data that is used by the individual instances. * Actual instances store an index ("handle") into this vector. */ - Vector references_; + blender::Vector references_; /** Indices into `references_`. Determines what data is instanced. */ - Vector reference_handles_; + blender::Vector reference_handles_; /** Transformation of the instances. */ - Vector transforms_; + blender::Vector transforms_; /* These almost unique ids are generated based on the `id` attribute, which might not contain * unique ids at all. They are *almost* unique, because under certain very unlikely * circumstances, they are not unique. Code using these ids should not crash when they are not * unique but can generally expect them to be unique. */ mutable std::mutex almost_unique_ids_mutex_; - mutable Array almost_unique_ids_; + mutable blender::Array almost_unique_ids_; - CustomData attributes_; + CustomDataAttributes attributes_; public: - Instances(); - Instances(Instances &&other); + Instances() = default; Instances(const Instances &other); - ~Instances(); - - Instances &operator=(const Instances &other); - Instances &operator=(Instances &&other); + void reserve(int min_capacity); /** * Resize the transform, handles, and attributes to the specified capacity. * @@ -134,9 +129,9 @@ class Instances { * argument. For adding many instances, using #resize and accessing the transform array * directly is preferred. */ - void add_instance(int instance_handle, const float4x4 &transform); + void add_instance(int instance_handle, const blender::float4x4 &transform); - Span references() const; + blender::Span references() const; void remove_unused_references(); /** @@ -152,10 +147,10 @@ class Instances { */ GeometrySet &geometry_set_from_reference(int reference_index); - Span reference_handles() const; - MutableSpan reference_handles(); - MutableSpan transforms(); - Span transforms() const; + blender::Span reference_handles() const; + blender::MutableSpan reference_handles(); + blender::MutableSpan transforms(); + blender::Span transforms() const; int instances_num() const; int references_num() const; @@ -164,20 +159,21 @@ class Instances { * Remove the indices that are not contained in the mask input, and remove unused instance * references afterwards. */ - void remove(const IndexMask &mask, const AnonymousAttributePropagationInfo &propagation_info); + void remove(const blender::IndexMask &mask, + const blender::bke::AnonymousAttributePropagationInfo &propagation_info); /** * Get an id for every instance. These can be used for e.g. motion blur. */ - Span almost_unique_ids() const; + blender::Span almost_unique_ids() const; - bke::AttributeAccessor attributes() const; - bke::MutableAttributeAccessor attributes_for_write(); + blender::bke::AttributeAccessor attributes() const; + blender::bke::MutableAttributeAccessor attributes_for_write(); - CustomData &custom_data_attributes(); - const CustomData &custom_data_attributes() const; + CustomDataAttributes &custom_data_attributes(); + const CustomDataAttributes &custom_data_attributes() const; void foreach_referenced_geometry( - FunctionRef callback) const; + blender::FunctionRef callback) const; bool owns_direct_data() const; void ensure_owns_direct_data(); @@ -187,11 +183,6 @@ class Instances { /** \name #InstanceReference Inline Methods * \{ */ -inline InstanceReference::InstanceReference(std::unique_ptr geometry_set) - : type_(Type::GeometrySet), data_(nullptr), geometry_set_(std::move(geometry_set)) -{ -} - inline InstanceReference::InstanceReference(Object &object) : type_(Type::Object), data_(&object) { } @@ -265,12 +256,12 @@ inline const GeometrySet &InstanceReference::geometry_set() const return *geometry_set_; } -inline CustomData &Instances::custom_data_attributes() +inline CustomDataAttributes &Instances::custom_data_attributes() { return attributes_; } -inline const CustomData &Instances::custom_data_attributes() const +inline const CustomDataAttributes &Instances::custom_data_attributes() const { return attributes_; } diff --git a/source/blender/blenkernel/BKE_mesh.h b/source/blender/blenkernel/BKE_mesh.h index 70efdc0c6e6..1ab2a3a7647 100644 --- a/source/blender/blenkernel/BKE_mesh.h +++ b/source/blender/blenkernel/BKE_mesh.h @@ -13,7 +13,6 @@ #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" -#include "DNA_object_types.h" /* #BoundBox. */ #include "BKE_customdata.h" @@ -77,9 +76,6 @@ void BKE_mesh_tag_topology_changed(struct Mesh *mesh); */ void BKE_mesh_tag_edges_split(struct Mesh *mesh); -/** Call when changing "sharp_face" or "sharp_edge" data. */ -void BKE_mesh_tag_sharpness_changed(struct Mesh *mesh); - /** * Call when face vertex order has changed but positions and faces haven't changed */ @@ -227,7 +223,7 @@ bool BKE_mesh_material_index_used(struct Mesh *me, short index); void BKE_mesh_material_index_clear(struct Mesh *me); void BKE_mesh_material_remap(struct Mesh *me, const unsigned int *remap, unsigned int remap_len); void BKE_mesh_smooth_flag_set(struct Mesh *me, bool use_smooth); -void BKE_mesh_sharp_edges_set_from_angle(struct Mesh *me, float angle); +void BKE_mesh_auto_smooth_flag_set(struct Mesh *me, bool use_auto_smooth, float auto_smooth_angle); /** * Used for unit testing; compares two meshes, checking only @@ -237,7 +233,7 @@ void BKE_mesh_sharp_edges_set_from_angle(struct Mesh *me, float angle); */ const char *BKE_mesh_cmp(struct Mesh *me1, struct Mesh *me2, float thresh); -BoundBox BKE_mesh_boundbox_get(struct Object *ob); +struct BoundBox *BKE_mesh_boundbox_get(struct Object *ob); void BKE_mesh_texspace_calc(struct Mesh *me); void BKE_mesh_texspace_ensure(struct Mesh *me); @@ -331,6 +327,11 @@ bool BKE_mesh_vert_normals_are_dirty(const struct Mesh *mesh); /** Return true if the mesh face normals either are not stored or are dirty. */ bool BKE_mesh_face_normals_are_dirty(const struct Mesh *mesh); +/** + * Called after calculating all modifiers. + */ +void BKE_mesh_ensure_normals_for_display(struct Mesh *mesh); + /** * References a contiguous loop-fan with normal offset vars. */ @@ -454,6 +455,18 @@ void BKE_mesh_normals_loop_to_vertex(int numVerts, */ bool BKE_mesh_has_custom_loop_normals(struct Mesh *me); +void BKE_mesh_calc_normals_split(struct Mesh *mesh); +/** + * Compute 'split' (aka loop, or per face corner's) normals. + * + * \param r_lnors_spacearr: Allows to get computed loop normal space array. + * That data, among other things, contains 'smooth fan' info, useful e.g. + * to split geometry along sharp edges. + */ +void BKE_mesh_calc_normals_split_ex(const struct Mesh *mesh, + struct MLoopNorSpaceArray *r_lnors_spacearr, + float (*r_corner_normals)[3]); + /** * Higher level functions hiding most of the code needed around call to * #normals_loop_custom_set(). @@ -636,7 +649,8 @@ void BKE_mesh_calc_edges(struct Mesh *mesh, bool keep_existing_edges, bool selec void BKE_mesh_calc_edges_tessface(struct Mesh *mesh); /* In DerivedMesh.cc */ -void BKE_mesh_wrapper_deferred_finalize_mdata(struct Mesh *me_eval); +void BKE_mesh_wrapper_deferred_finalize_mdata(struct Mesh *me_eval, + const struct CustomData_MeshMasks *cd_mask_finalize); /* **** Depsgraph evaluation **** */ diff --git a/source/blender/blenkernel/BKE_mesh.hh b/source/blender/blenkernel/BKE_mesh.hh index e7a9605844d..fb7e3d9549d 100644 --- a/source/blender/blenkernel/BKE_mesh.hh +++ b/source/blender/blenkernel/BKE_mesh.hh @@ -162,6 +162,8 @@ void normals_calc_loop(Span vert_positions, const bool *sharp_edges, const bool *sharp_faces, const short2 *clnors_data, + bool use_split_normals, + float split_angle, CornerNormalSpaceArray *r_lnors_spacearr, MutableSpan r_loop_normals); diff --git a/source/blender/blenkernel/BKE_mesh_legacy_convert.hh b/source/blender/blenkernel/BKE_mesh_legacy_convert.hh index b10870e5ad4..d5d9be43abe 100644 --- a/source/blender/blenkernel/BKE_mesh_legacy_convert.hh +++ b/source/blender/blenkernel/BKE_mesh_legacy_convert.hh @@ -13,7 +13,6 @@ #include "BLI_utildefines.h" struct CustomData; -struct Main; struct Mesh; struct MFace; @@ -109,8 +108,6 @@ void BKE_mesh_calc_edges_legacy(Mesh *me); void BKE_mesh_do_versions_cd_flag_init(Mesh *mesh); -void BKE_main_mesh_legacy_convert_auto_smooth(Main &bmain); - /* Inlines */ /* NOTE(@sybren): Instead of -1 that function uses ORIGINDEX_NONE as defined in BKE_customdata.h, diff --git a/source/blender/blenkernel/BKE_mesh_remap.hh b/source/blender/blenkernel/BKE_mesh_remap.hh index 13c877339be..73058279014 100644 --- a/source/blender/blenkernel/BKE_mesh_remap.hh +++ b/source/blender/blenkernel/BKE_mesh_remap.hh @@ -144,6 +144,9 @@ enum { MREMAP_MODE_TOPOLOGY = MREMAP_MODE_VERT | MREMAP_MODE_EDGE | MREMAP_MODE_LOOP | MREMAP_MODE_POLY, }; +void BKE_mesh_remap_calc_source_cddata_masks_from_map_modes( + int vert_mode, int edge_mode, int loop_mode, int face_mode, CustomData_MeshMasks *cddata_mask); + /** * Compute a value of the difference between both given meshes. * The smaller the result, the better the match. @@ -173,6 +176,7 @@ void BKE_mesh_remap_calc_verts_from_mesh(int mode, float ray_radius, const float (*vert_positions_dst)[3], int numverts_dst, + bool dirty_nors_dst, const Mesh *me_src, Mesh *me_dst, MeshPairRemap *r_map); @@ -185,6 +189,7 @@ void BKE_mesh_remap_calc_edges_from_mesh(int mode, int numverts_dst, const blender::int2 *edges_dst, int numedges_dst, + bool dirty_nors_dst, const Mesh *me_src, Mesh *me_dst, MeshPairRemap *r_map); @@ -193,12 +198,19 @@ void BKE_mesh_remap_calc_loops_from_mesh(int mode, const SpaceTransform *space_transform, float max_dist, float ray_radius, - const Mesh *mesh_dst, + Mesh *mesh_dst, const float (*vert_positions_dst)[3], int numverts_dst, + const blender::int2 *edges_dst, + int numedges_dst, const int *corner_verts_dst, + const int *corner_edges_dst, int numloops_dst, const blender::OffsetIndices faces_dst, + CustomData *ldata_dst, + bool use_split_nors_dst, + float split_angle_dst, + bool dirty_nors_dst, const Mesh *me_src, MeshRemapIslandsCalc gen_islands_src, float islands_precision_src, diff --git a/source/blender/blenkernel/BKE_mesh_runtime.hh b/source/blender/blenkernel/BKE_mesh_runtime.hh index e2da362b739..62e8eba3171 100644 --- a/source/blender/blenkernel/BKE_mesh_runtime.hh +++ b/source/blender/blenkernel/BKE_mesh_runtime.hh @@ -33,7 +33,8 @@ bool BKE_mesh_runtime_ensure_edit_data(Mesh *mesh); * For "smaller" changes to meshes like updating positions, consider calling a more specific update * function like #BKE_mesh_tag_positions_changed. * - * Also note that some derived caches like #CD_TANGENT are stored directly in #CustomData. + * Also note that some derived caches like #CD_NORMAL and #CD_TANGENT are stored directly in + * #CustomData. */ void BKE_mesh_runtime_clear_geometry(Mesh *mesh); diff --git a/source/blender/blenkernel/BKE_mesh_types.hh b/source/blender/blenkernel/BKE_mesh_types.hh index d5799b96c9d..416a4500a67 100644 --- a/source/blender/blenkernel/BKE_mesh_types.hh +++ b/source/blender/blenkernel/BKE_mesh_types.hh @@ -43,31 +43,6 @@ enum eMeshWrapperType { namespace blender::bke { -/** - * The complexity requirement of attribute domains needed to process normals. - * See #Mesh::normals_domain(). - */ -enum class MeshNormalDomain : int8_t { - /** - * The mesh is completely smooth shaded; either all faces or edges are sharp. - * Only #Mesh::face_normals() is necessary. This case is generally the best - * for performance, since no mixing is necessary and multithreading is simple. - */ - Face, - /** - * The mesh is completely smooth shaded; there are no sharp face or edges. Only - * #Mesh::vert_normals() is necessary. Calculating face normals is still necessary though, - * since they have to be mixed to become vertex normals. - */ - Point, - /** - * The mesh has mixed smooth and sharp shading. In order to split the normals on each side of - * sharp edges, they need to be processed per-face-corner. Normals can be retrieved with - * #Mesh::corner_normals(). - */ - Corner, -}; - /** * Cache of a mesh's loose edges, accessed with #Mesh::loose_edges(). * */ @@ -161,10 +136,9 @@ struct MeshRuntime { */ SubsurfRuntimeData *subsurf_runtime_data = nullptr; - /** Caches for lazily computed normals. */ + /** Caches for lazily computed vertex and face normals. */ SharedCache> vert_normals_cache; SharedCache> face_normals_cache; - SharedCache> corner_normals_cache; /** * Cache of offsets for vert to face/corner maps. The same offsets array is used to group diff --git a/source/blender/blenkernel/BKE_modifier.h b/source/blender/blenkernel/BKE_modifier.h index b1c0edeef92..b30f726e82e 100644 --- a/source/blender/blenkernel/BKE_modifier.h +++ b/source/blender/blenkernel/BKE_modifier.h @@ -424,7 +424,7 @@ void BKE_modifier_remove_from_list(struct Object *ob, struct ModifierData *md); /* Generate new UUID for the given modifier. */ void BKE_modifier_session_uuid_generate(struct ModifierData *md); -void BKE_modifier_unique_name(struct ListBase *modifiers, struct ModifierData *md); +bool BKE_modifier_unique_name(struct ListBase *modifiers, struct ModifierData *md); struct ModifierData *BKE_modifier_copy_ex(const struct ModifierData *md, int flag); diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h index 574bcc60eba..ef3ec6466a8 100644 --- a/source/blender/blenkernel/BKE_node.h +++ b/source/blender/blenkernel/BKE_node.h @@ -114,7 +114,7 @@ using NodeGeometryExecFunction = void (*)(blender::nodes::GeoNodeExecParams para using NodeDeclareFunction = void (*)(blender::nodes::NodeDeclarationBuilder &builder); using NodeDeclareDynamicFunction = void (*)(const bNodeTree &tree, const bNode &node, - blender::nodes::NodeDeclarationBuilder &builder); + blender::nodes::NodeDeclaration &r_declaration); using SocketGetCPPValueFunction = void (*)(const void *socket_value, void *r_value); using SocketGetGeometryNodesCPPValueFunction = void (*)(const void *socket_value, void *r_value); @@ -362,22 +362,17 @@ typedef struct bNodeType { /* Execute a geometry node. */ NodeGeometryExecFunction geometry_node_execute; - /** - * Declares which sockets and panels the node has. It has to be able to generate a declaration - * with and without a specific node context. If the declaration depends on the node, but the node - * is not provided, then the declaration should be generated as much as possible and everything - * that depends on the node context should be skipped. - */ + /* Declares which sockets the node has. */ NodeDeclareFunction declare; - /** - * Declaration of the node outside of any context. If the node declaration is never dependent on - * the node context, this declaration is also shared with the corresponding node instances. - * Otherwise, it mainly allows checking what sockets a node will have, without having to create - * the node. In this case, the static declaration is mostly just a hint, and does not have to - * match with the final node. + * Declare which sockets the node has for declarations that aren't static per node type. + * In other words, defining this callback means that different nodes of this type can have + * different declarations and different sockets. */ - NodeDeclarationHandle *static_declaration; + NodeDeclareDynamicFunction declare_dynamic; + + /* Declaration to be used when it is not dynamic. */ + NodeDeclarationHandle *fixed_declaration; /** * Add to the list of search names and operations gathered by node link drag searching. @@ -1314,8 +1309,6 @@ void BKE_nodetree_remove_layer_n(struct bNodeTree *ntree, struct Scene *scene, i #define GEO_NODE_TOOL_SET_FACE_SET 2113 #define GEO_NODE_POINTS_TO_CURVES 2114 #define GEO_NODE_INPUT_EDGE_SMOOTH 2115 -#define GEO_NODE_SPLIT_TO_INSTANCES 2116 -#define GEO_NODE_INPUT_NAMED_LAYER_SELECTION 2117 /** \} */ diff --git a/source/blender/blenkernel/BKE_node.hh b/source/blender/blenkernel/BKE_node.hh index 0b8e9b87841..0c2ceea97c4 100644 --- a/source/blender/blenkernel/BKE_node.hh +++ b/source/blender/blenkernel/BKE_node.hh @@ -328,9 +328,6 @@ extern bNodeTreeType NodeTreeTypeUndefined; extern bNodeType NodeTypeUndefined; extern bNodeSocketType NodeSocketTypeUndefined; -std::optional socket_type_to_custom_data_type(eNodeSocketDatatype type); -std::optional custom_data_type_to_socket_type(eCustomDataType type); - /** * Contains information about a specific kind of zone (e.g. simulation or repeat zone in geometry * nodes). This allows writing code that works for all kinds of zones automatically, reducing diff --git a/source/blender/blenkernel/BKE_node_runtime.hh b/source/blender/blenkernel/BKE_node_runtime.hh index 07da363415f..c3c1ce2e6eb 100644 --- a/source/blender/blenkernel/BKE_node_runtime.hh +++ b/source/blender/blenkernel/BKE_node_runtime.hh @@ -553,37 +553,19 @@ inline void bNodeTree::ensure_interface_cache() const this->tree_interface.ensure_items_cache(); } -inline blender::Span bNodeTree::interface_inputs() +inline blender::Span bNodeTree::interface_inputs() const { BLI_assert(this->tree_interface.items_cache_is_available()); return this->tree_interface.runtime->inputs_; } -inline blender::Span bNodeTree::interface_inputs() const -{ - BLI_assert(this->tree_interface.items_cache_is_available()); - return this->tree_interface.runtime->inputs_; -} - -inline blender::Span bNodeTree::interface_outputs() +inline blender::Span bNodeTree::interface_outputs() const { BLI_assert(this->tree_interface.items_cache_is_available()); return this->tree_interface.runtime->outputs_; } -inline blender::Span bNodeTree::interface_outputs() const -{ - BLI_assert(this->tree_interface.items_cache_is_available()); - return this->tree_interface.runtime->outputs_; -} - -inline blender::Span bNodeTree::interface_items() -{ - BLI_assert(this->tree_interface.items_cache_is_available()); - return this->tree_interface.runtime->items_; -} - -inline blender::Span bNodeTree::interface_items() const +inline blender::Span bNodeTree::interface_items() const { BLI_assert(this->tree_interface.items_cache_is_available()); return this->tree_interface.runtime->items_; diff --git a/source/blender/blenkernel/BKE_node_tree_anonymous_attributes.hh b/source/blender/blenkernel/BKE_node_tree_anonymous_attributes.hh index 30a0344ef78..a9425b1b4f1 100644 --- a/source/blender/blenkernel/BKE_node_tree_anonymous_attributes.hh +++ b/source/blender/blenkernel/BKE_node_tree_anonymous_attributes.hh @@ -76,5 +76,6 @@ struct AnonymousAttributeInferencingResult { Array get_relations_by_node(const bNodeTree &tree, ResourceScope &scope); bool update_anonymous_attribute_relations(bNodeTree &tree); +bool is_possible_field_socket(eNodeSocketDatatype type); } // namespace blender::bke::anonymous_attribute_inferencing diff --git a/source/blender/blenkernel/BKE_node_tree_update.h b/source/blender/blenkernel/BKE_node_tree_update.h index 053773c6a76..0969e3a22e9 100644 --- a/source/blender/blenkernel/BKE_node_tree_update.h +++ b/source/blender/blenkernel/BKE_node_tree_update.h @@ -35,6 +35,7 @@ void BKE_ntree_update_tag_all(struct bNodeTree *ntree); void BKE_ntree_update_tag_node_property(struct bNodeTree *ntree, struct bNode *node); void BKE_ntree_update_tag_node_new(struct bNodeTree *ntree, struct bNode *node); void BKE_ntree_update_tag_node_removed(struct bNodeTree *ntree); +void BKE_ntree_update_tag_node_reordered(struct bNodeTree *ntree); void BKE_ntree_update_tag_node_mute(struct bNodeTree *ntree, struct bNode *node); void BKE_ntree_update_tag_node_internal_link(struct bNodeTree *ntree, struct bNode *node); diff --git a/source/blender/blenkernel/BKE_object.hh b/source/blender/blenkernel/BKE_object.hh index a8009d73a2f..53e9a934f15 100644 --- a/source/blender/blenkernel/BKE_object.hh +++ b/source/blender/blenkernel/BKE_object.hh @@ -9,13 +9,10 @@ * \brief General operations, lookup, etc. for blender objects. */ -#include - #include "BLI_compiler_attrs.h" #include "BLI_sys_types.h" #include "DNA_object_enums.h" -#include "DNA_object_types.h" /* #BoundBox. */ #include "DNA_userdef_enums.h" struct Base; @@ -26,17 +23,11 @@ struct GpencilModifierData; struct HookGpencilModifierData; struct HookModifierData; struct ID; -struct KDTree_3d; -struct KeyBlock; -struct Lattice; -struct LinkNode; struct Main; struct Mesh; struct ModifierData; struct MovieClip; struct Object; -struct PartEff; -struct ParticleSystem; struct RegionView3D; struct RigidBodyWorld; struct Scene; @@ -44,7 +35,7 @@ struct SubsurfModifierData; struct View3D; struct ViewLayer; -void BKE_object_workob_clear(Object *workob); +void BKE_object_workob_clear(struct Object *workob); /** * For calculation of the inverse parent transform, only used for editor. * @@ -53,30 +44,34 @@ void BKE_object_workob_clear(Object *workob); * - #DEG_relations_tag_update(bmain); * - #BKE_scene_graph_update_tagged(depsgraph, bmain); */ -void BKE_object_workob_calc_parent(Depsgraph *depsgraph, Scene *scene, Object *ob, Object *workob); +void BKE_object_workob_calc_parent(struct Depsgraph *depsgraph, + struct Scene *scene, + struct Object *ob, + struct Object *workob); -void BKE_object_transform_copy(Object *ob_tar, const Object *ob_src); -void BKE_object_copy_softbody(Object *ob_dst, const Object *ob_src, int flag); -ParticleSystem *BKE_object_copy_particlesystem(ParticleSystem *psys, int flag); -void BKE_object_copy_particlesystems(Object *ob_dst, const Object *ob_src, int flag); -void BKE_object_free_particlesystems(Object *ob); -void BKE_object_free_softbody(Object *ob); -void BKE_object_free_curve_cache(Object *ob); +void BKE_object_transform_copy(struct Object *ob_tar, const struct Object *ob_src); +void BKE_object_copy_softbody(struct Object *ob_dst, const struct Object *ob_src, int flag); +struct ParticleSystem *BKE_object_copy_particlesystem(struct ParticleSystem *psys, int flag); +void BKE_object_copy_particlesystems(struct Object *ob_dst, const struct Object *ob_src, int flag); +void BKE_object_free_particlesystems(struct Object *ob); +void BKE_object_free_softbody(struct Object *ob); +void BKE_object_free_curve_cache(struct Object *ob); /** * Free data derived from mesh, called when mesh changes or is freed. */ -void BKE_object_free_derived_caches(Object *ob); -void BKE_object_free_caches(Object *object); +void BKE_object_free_derived_caches(struct Object *ob); +void BKE_object_free_caches(struct Object *object); -void BKE_object_modifier_hook_reset(Object *ob, HookModifierData *hmd); -void BKE_object_modifier_gpencil_hook_reset(Object *ob, HookGpencilModifierData *hmd); +void BKE_object_modifier_hook_reset(struct Object *ob, struct HookModifierData *hmd); +void BKE_object_modifier_gpencil_hook_reset(struct Object *ob, + struct HookGpencilModifierData *hmd); /** * \return True if the object's type supports regular modifiers (not grease pencil modifiers). */ -bool BKE_object_supports_modifiers(const Object *ob); -bool BKE_object_support_modifier_type_check(const Object *ob, int modifier_type); +bool BKE_object_supports_modifiers(const struct Object *ob); +bool BKE_object_support_modifier_type_check(const struct Object *ob, int modifier_type); /* Active modifier. */ @@ -86,8 +81,8 @@ bool BKE_object_support_modifier_type_check(const Object *ob, int modifier_type) * \param md: If nullptr, only clear the active modifier, otherwise * it must be in the #Object.modifiers list. */ -void BKE_object_modifier_set_active(Object *ob, ModifierData *md); -ModifierData *BKE_object_active_modifier(const Object *ob); +void BKE_object_modifier_set_active(struct Object *ob, struct ModifierData *md); +struct ModifierData *BKE_object_active_modifier(const struct Object *ob); /** * Copy a single modifier. @@ -100,15 +95,18 @@ ModifierData *BKE_object_active_modifier(const Object *ob); * which particle system to use or add in `ob_dst`, and it's placement in the stack, etc. If used * more than once, this function should preferably be called in stack order. */ -bool BKE_object_copy_modifier( - Main *bmain, Scene *scene, Object *ob_dst, const Object *ob_src, ModifierData *md); +bool BKE_object_copy_modifier(struct Main *bmain, + struct Scene *scene, + struct Object *ob_dst, + const struct Object *ob_src, + struct ModifierData *md); /** * Copy a single GPencil modifier. * * \note *Do not* use this function to copy a whole modifier stack. Use * `BKE_object_modifier_stack_copy` instead. */ -bool BKE_object_copy_gpencil_modifier(Object *ob_dst, GpencilModifierData *gmd_src); +bool BKE_object_copy_gpencil_modifier(struct Object *ob_dst, struct GpencilModifierData *gmd_src); /** * Copy the whole stack of modifiers from one object into another. * @@ -118,51 +116,53 @@ bool BKE_object_copy_gpencil_modifier(Object *ob_dst, GpencilModifierData *gmd_s * \param do_copy_all: If true, even modifiers that should not support copying (like Hook one) * will be duplicated. */ -bool BKE_object_modifier_stack_copy(Object *ob_dst, - const Object *ob_src, +bool BKE_object_modifier_stack_copy(struct Object *ob_dst, + const struct Object *ob_src, bool do_copy_all, int flag_subdata); -void BKE_object_link_modifiers(Object *ob_dst, const Object *ob_src); -void BKE_object_free_modifiers(Object *ob, int flag); -void BKE_object_free_shaderfx(Object *ob, int flag); +void BKE_object_link_modifiers(struct Object *ob_dst, const struct Object *ob_src); +void BKE_object_free_modifiers(struct Object *ob, int flag); +void BKE_object_free_shaderfx(struct Object *ob, int flag); -bool BKE_object_exists_check(Main *bmain, const Object *obtest); +bool BKE_object_exists_check(struct Main *bmain, const struct Object *obtest); /** * Actual check for internal data, not context or flags. */ -bool BKE_object_is_in_editmode(const Object *ob); -bool BKE_object_is_in_editmode_vgroup(const Object *ob); -bool BKE_object_is_in_wpaint_select_vert(const Object *ob); -bool BKE_object_has_mode_data(const Object *ob, eObjectMode object_mode); -bool BKE_object_is_mode_compat(const Object *ob, eObjectMode object_mode); +bool BKE_object_is_in_editmode(const struct Object *ob); +bool BKE_object_is_in_editmode_vgroup(const struct Object *ob); +bool BKE_object_is_in_wpaint_select_vert(const struct Object *ob); +bool BKE_object_has_mode_data(const struct Object *ob, eObjectMode object_mode); +bool BKE_object_is_mode_compat(const struct Object *ob, eObjectMode object_mode); -bool BKE_object_data_is_in_editmode(const Object *ob, const ID *id); +bool BKE_object_data_is_in_editmode(const struct Object *ob, const struct ID *id); -char *BKE_object_data_editmode_flush_ptr_get(ID *id); +char *BKE_object_data_editmode_flush_ptr_get(struct ID *id); /** * Updates select_id of all objects in the given \a bmain. */ -void BKE_object_update_select_id(Main *bmain); +void BKE_object_update_select_id(struct Main *bmain); -enum eObjectVisibilityResult { +typedef enum eObjectVisibilityResult { OB_VISIBLE_SELF = 1, OB_VISIBLE_PARTICLES = 2, OB_VISIBLE_INSTANCES = 4, OB_VISIBLE_ALL = (OB_VISIBLE_SELF | OB_VISIBLE_PARTICLES | OB_VISIBLE_INSTANCES), -}; +} eObjectVisibilityResult; /** * Return which parts of the object are visible, as evaluated by depsgraph. */ -int BKE_object_visibility(const Object *ob, int dag_eval_mode); +int BKE_object_visibility(const struct Object *ob, int dag_eval_mode); /** * More general add: creates minimum required data, but without vertices etc. * * \param bmain: The main to add the object to. May be null for #LIB_ID_CREATE_NO_MAIN behavior. */ -Object *BKE_object_add_only_object(Main *bmain, int type, const char *name) ATTR_RETURNS_NONNULL; +struct Object *BKE_object_add_only_object(struct Main *bmain, + int type, + const char *name) ATTR_RETURNS_NONNULL; /** * General add: to scene, with layer from area and default name. * @@ -171,18 +171,22 @@ Object *BKE_object_add_only_object(Main *bmain, int type, const char *name) ATTR * * \note Creates minimum required data, but without vertices etc. */ -Object *BKE_object_add(Main *bmain, - Scene *scene, - ViewLayer *view_layer, - int type, - const char *name) ATTR_NONNULL(1, 2, 3) ATTR_RETURNS_NONNULL; +struct Object *BKE_object_add(struct Main *bmain, + struct Scene *scene, + struct ViewLayer *view_layer, + int type, + const char *name) ATTR_NONNULL(1, 2, 3) ATTR_RETURNS_NONNULL; /** * Add a new object, using another one as a reference * * \param ob_src: object to use to determine the collections of the new object. */ -Object *BKE_object_add_from( - Main *bmain, Scene *scene, ViewLayer *view_layer, int type, const char *name, Object *ob_src) +struct Object *BKE_object_add_from(struct Main *bmain, + struct Scene *scene, + struct ViewLayer *view_layer, + int type, + const char *name, + struct Object *ob_src) ATTR_NONNULL(1, 2, 3, 6) ATTR_RETURNS_NONNULL; /** * Add a new object, but assign the given data-block as the `ob->data` @@ -193,27 +197,28 @@ Object *BKE_object_add_from( * \param do_id_user: If true, #id_us_plus() will be called on data when * assigning it to the object. */ -Object *BKE_object_add_for_data(Main *bmain, - const Scene *scene, - ViewLayer *view_layer, - int type, - const char *name, - ID *data, - bool do_id_user) ATTR_RETURNS_NONNULL; -void *BKE_object_obdata_add_from_type(Main *bmain, int type, const char *name) ATTR_NONNULL(1); +struct Object *BKE_object_add_for_data(struct Main *bmain, + const struct Scene *scene, + struct ViewLayer *view_layer, + int type, + const char *name, + struct ID *data, + bool do_id_user) ATTR_RETURNS_NONNULL; +void *BKE_object_obdata_add_from_type(struct Main *bmain, int type, const char *name) + ATTR_NONNULL(1); /** * Return -1 on failure. */ -int BKE_object_obdata_to_type(const ID *id) ATTR_NONNULL(1); +int BKE_object_obdata_to_type(const struct ID *id) ATTR_NONNULL(1); /** * Returns true if the Object is from an external blend file (libdata). */ -bool BKE_object_is_libdata(const Object *ob); +bool BKE_object_is_libdata(const struct Object *ob); /** * Returns true if the Object data is from an external blend file (libdata). */ -bool BKE_object_obdata_is_libdata(const Object *ob); +bool BKE_object_obdata_is_libdata(const struct Object *ob); /** * Perform deep-copy of object and its 'children' data-blocks (obdata, materials, actions, etc.). @@ -226,21 +231,21 @@ bool BKE_object_obdata_is_libdata(const Object *ob); * \note Caller MUST free \a newid pointers itself (#BKE_main_id_newptr_and_tag_clear()) and call * updates of DEG too (#DAG_relations_tag_update()). */ -Object *BKE_object_duplicate(Main *bmain, - Object *ob, - eDupli_ID_Flags dupflag, - uint duplicate_options); +struct Object *BKE_object_duplicate(struct Main *bmain, + struct Object *ob, + eDupli_ID_Flags dupflag, + uint duplicate_options); /** * Use with newly created objects to set their size (used to apply scene-scale). */ -void BKE_object_obdata_size_init(Object *ob, float size); +void BKE_object_obdata_size_init(struct Object *ob, float size); -void BKE_object_scale_to_mat3(Object *ob, float r_mat[3][3]); -void BKE_object_rot_to_mat3(const Object *ob, float r_mat[3][3], bool use_drot); -void BKE_object_mat3_to_rot(Object *ob, float r_mat[3][3], bool use_compat); -void BKE_object_to_mat3(Object *ob, float r_mat[3][3]); -void BKE_object_to_mat4(Object *ob, float r_mat[4][4]); +void BKE_object_scale_to_mat3(struct Object *ob, float r_mat[3][3]); +void BKE_object_rot_to_mat3(const struct Object *ob, float r_mat[3][3], bool use_drot); +void BKE_object_mat3_to_rot(struct Object *ob, float r_mat[3][3], bool use_compat); +void BKE_object_to_mat3(struct Object *ob, float r_mat[3][3]); +void BKE_object_to_mat4(struct Object *ob, float r_mat[4][4]); /** * Applies the global transformation \a mat to the \a ob using a relative parent space if * supplied. @@ -251,13 +256,16 @@ void BKE_object_to_mat4(Object *ob, float r_mat[4][4]); * \param use_compat: true to ensure that rotations are set using the * min difference between the old and new orientation. */ -void BKE_object_apply_mat4_ex(Object *ob, +void BKE_object_apply_mat4_ex(struct Object *ob, const float mat[4][4], - Object *parent, + struct Object *parent, const float parentinv[4][4], bool use_compat); /** See #BKE_object_apply_mat4_ex */ -void BKE_object_apply_mat4(Object *ob, const float mat[4][4], bool use_compat, bool use_parent); +void BKE_object_apply_mat4(struct Object *ob, + const float mat[4][4], + bool use_compat, + bool use_parent); /** * Use parent's world location and rotation as the child's origin. The parent inverse will @@ -267,13 +275,13 @@ void BKE_object_apply_mat4(Object *ob, const float mat[4][4], bool use_compat, b * Assumes the object's world matrix has no shear. * Assumes parent exists. */ -void BKE_object_apply_parent_inverse(Object *ob); +void BKE_object_apply_parent_inverse(struct Object *ob); -void BKE_object_matrix_local_get(Object *ob, float r_mat[4][4]); +void BKE_object_matrix_local_get(struct Object *ob, float r_mat[4][4]); -bool BKE_object_pose_context_check(const Object *ob); +bool BKE_object_pose_context_check(const struct Object *ob); -Object *BKE_object_pose_armature_get(Object *ob); +struct Object *BKE_object_pose_armature_get(struct Object *ob); /** * A version of #BKE_object_pose_armature_get with an additional check. * When `ob` isn't an armature: only return the referenced pose object @@ -282,73 +290,79 @@ Object *BKE_object_pose_armature_get(Object *ob); * \note Some callers need to check that pose bones are selectable * which isn't the case when the object using the armature isn't in weight-paint mode. */ -Object *BKE_object_pose_armature_get_with_wpaint_check(Object *ob); -Object *BKE_object_pose_armature_get_visible(Object *ob, - const Scene *scene, - ViewLayer *view_layer, - View3D *v3d); +struct Object *BKE_object_pose_armature_get_with_wpaint_check(struct Object *ob); +struct Object *BKE_object_pose_armature_get_visible(struct Object *ob, + const struct Scene *scene, + struct ViewLayer *view_layer, + struct View3D *v3d); /** * Access pose array with special check to get pose object when in weight paint mode. */ -Object **BKE_object_pose_array_get_ex(const Scene *scene, - ViewLayer *view_layer, - View3D *v3d, - unsigned int *r_objects_len, - bool unique); -Object **BKE_object_pose_array_get_unique(const Scene *scene, - ViewLayer *view_layer, - View3D *v3d, +struct Object **BKE_object_pose_array_get_ex(const struct Scene *scene, + struct ViewLayer *view_layer, + struct View3D *v3d, + unsigned int *r_objects_len, + bool unique); +struct Object **BKE_object_pose_array_get_unique(const struct Scene *scene, + struct ViewLayer *view_layer, + struct View3D *v3d, + unsigned int *r_objects_len); +struct Object **BKE_object_pose_array_get(const struct Scene *scene, + struct ViewLayer *view_layer, + struct View3D *v3d, unsigned int *r_objects_len); -Object **BKE_object_pose_array_get(const Scene *scene, - ViewLayer *view_layer, - View3D *v3d, - unsigned int *r_objects_len); -Base **BKE_object_pose_base_array_get_ex(const Scene *scene, - ViewLayer *view_layer, - View3D *v3d, - unsigned int *r_bases_len, - bool unique); -Base **BKE_object_pose_base_array_get_unique(const Scene *scene, - ViewLayer *view_layer, - View3D *v3d, +struct Base **BKE_object_pose_base_array_get_ex(const struct Scene *scene, + struct ViewLayer *view_layer, + struct View3D *v3d, + unsigned int *r_bases_len, + bool unique); +struct Base **BKE_object_pose_base_array_get_unique(const struct Scene *scene, + struct ViewLayer *view_layer, + struct View3D *v3d, + unsigned int *r_bases_len); +struct Base **BKE_object_pose_base_array_get(const struct Scene *scene, + struct ViewLayer *view_layer, + struct View3D *v3d, unsigned int *r_bases_len); -Base **BKE_object_pose_base_array_get(const Scene *scene, - ViewLayer *view_layer, - View3D *v3d, - unsigned int *r_bases_len); -void BKE_object_get_parent_matrix(Object *ob, Object *par, float r_parentmat[4][4]); +void BKE_object_get_parent_matrix(struct Object *ob, struct Object *par, float r_parentmat[4][4]); /** * Compute object world transform and store it in `ob->object_to_world`. */ -void BKE_object_where_is_calc(Depsgraph *depsgraph, Scene *scene, Object *ob); -void BKE_object_where_is_calc_ex( - Depsgraph *depsgraph, Scene *scene, RigidBodyWorld *rbw, Object *ob, float r_originmat[3][3]); -void BKE_object_where_is_calc_time(Depsgraph *depsgraph, Scene *scene, Object *ob, float ctime); +void BKE_object_where_is_calc(struct Depsgraph *depsgraph, struct Scene *scene, struct Object *ob); +void BKE_object_where_is_calc_ex(struct Depsgraph *depsgraph, + struct Scene *scene, + struct RigidBodyWorld *rbw, + struct Object *ob, + float r_originmat[3][3]); +void BKE_object_where_is_calc_time(struct Depsgraph *depsgraph, + struct Scene *scene, + struct Object *ob, + float ctime); /** * Calculate object transformation matrix without recalculating dependencies and * constraints -- assume dependencies are already solved by depsgraph. * No changes to object and its parent would be done. * Used for bundles orientation in 3d space relative to parented blender camera. */ -void BKE_object_where_is_calc_mat4(Object *ob, float r_obmat[4][4]); +void BKE_object_where_is_calc_mat4(struct Object *ob, float r_obmat[4][4]); /* Possibly belong in own module? */ -BoundBox *BKE_boundbox_alloc_unit(); -void BKE_boundbox_init_from_minmax(BoundBox *bb, const float min[3], const float max[3]); -void BKE_boundbox_calc_center_aabb(const BoundBox *bb, float r_cent[3]); -void BKE_boundbox_calc_size_aabb(const BoundBox *bb, float r_size[3]); -void BKE_boundbox_minmax(const BoundBox *bb, +struct BoundBox *BKE_boundbox_alloc_unit(void); +void BKE_boundbox_init_from_minmax(struct BoundBox *bb, const float min[3], const float max[3]); +void BKE_boundbox_calc_center_aabb(const struct BoundBox *bb, float r_cent[3]); +void BKE_boundbox_calc_size_aabb(const struct BoundBox *bb, float r_size[3]); +void BKE_boundbox_minmax(const struct BoundBox *bb, const float obmat[4][4], float r_min[3], float r_max[3]); -std::optional BKE_object_boundbox_get(Object *ob); -void BKE_object_dimensions_get(Object *ob, float r_vec[3]); +const struct BoundBox *BKE_object_boundbox_get(struct Object *ob); +void BKE_object_dimensions_get(struct Object *ob, float r_vec[3]); /** * The original scale and object matrix can be passed in so any difference * of the objects matrix and the final matrix can be accounted for, @@ -357,21 +371,21 @@ void BKE_object_dimensions_get(Object *ob, float r_vec[3]); * Re-using these values from the object causes a feedback loop * when multiple values are modified at once in some situations. see: #69536. */ -void BKE_object_dimensions_set_ex(Object *ob, +void BKE_object_dimensions_set_ex(struct Object *ob, const float value[3], int axis_mask, const float ob_scale_orig[3], const float ob_obmat_orig[4][4]); -void BKE_object_dimensions_set(Object *ob, const float value[3], int axis_mask); +void BKE_object_dimensions_set(struct Object *ob, const float value[3], int axis_mask); -void BKE_object_empty_draw_type_set(Object *ob, int value); +void BKE_object_empty_draw_type_set(struct Object *ob, int value); -void BKE_object_boundbox_calc_from_mesh(Object *ob, const Mesh *me_eval); -bool BKE_object_boundbox_calc_from_evaluated_geometry(Object *ob); -void BKE_object_minmax(Object *ob, float r_min[3], float r_max[3], bool use_hidden); -bool BKE_object_minmax_dupli(Depsgraph *depsgraph, - Scene *scene, - Object *ob, +void BKE_object_boundbox_calc_from_mesh(struct Object *ob, const struct Mesh *me_eval); +bool BKE_object_boundbox_calc_from_evaluated_geometry(struct Object *ob); +void BKE_object_minmax(struct Object *ob, float r_min[3], float r_max[3], bool use_hidden); +bool BKE_object_minmax_dupli(struct Depsgraph *depsgraph, + struct Scene *scene, + struct Object *ob, float r_min[3], float r_max[3], bool use_hidden); @@ -381,40 +395,40 @@ bool BKE_object_minmax_dupli(Depsgraph *depsgraph, * \note This is not part of the calculation used by #BKE_object_boundbox_get * as these bounds represent the extents of visual guides (use for viewport culling for e.g.) */ -bool BKE_object_minmax_empty_drawtype(const Object *ob, float r_min[3], float r_max[3]); +bool BKE_object_minmax_empty_drawtype(const struct Object *ob, float r_min[3], float r_max[3]); /** * Sometimes min-max isn't enough, we need to loop over each point. */ -void BKE_object_foreach_display_point(Object *ob, +void BKE_object_foreach_display_point(struct Object *ob, const float obmat[4][4], void (*func_cb)(const float[3], void *), void *user_data); -void BKE_scene_foreach_display_point(Depsgraph *depsgraph, +void BKE_scene_foreach_display_point(struct Depsgraph *depsgraph, void (*func_cb)(const float[3], void *), void *user_data); -bool BKE_object_parent_loop_check(const Object *parent, const Object *ob); +bool BKE_object_parent_loop_check(const struct Object *parent, const struct Object *ob); -void *BKE_object_tfm_backup(Object *ob); -void BKE_object_tfm_restore(Object *ob, void *obtfm_pt); +void *BKE_object_tfm_backup(struct Object *ob); +void BKE_object_tfm_restore(struct Object *ob, void *obtfm_pt); -struct ObjectTfmProtectedChannels { +typedef struct ObjectTfmProtectedChannels { float loc[3], dloc[3]; float scale[3], dscale[3]; float rot[3], drot[3]; float quat[4], dquat[4]; float rotAxis[3], drotAxis[3]; float rotAngle, drotAngle; -}; +} ObjectTfmProtectedChannels; -void BKE_object_tfm_protected_backup(const Object *ob, ObjectTfmProtectedChannels *obtfm); +void BKE_object_tfm_protected_backup(const struct Object *ob, ObjectTfmProtectedChannels *obtfm); -void BKE_object_tfm_protected_restore(Object *ob, +void BKE_object_tfm_protected_restore(struct Object *ob, const ObjectTfmProtectedChannels *obtfm, short protectflag); -void BKE_object_tfm_copy(Object *object_dst, const Object *object_src); +void BKE_object_tfm_copy(struct Object *object_dst, const struct Object *object_src); /** * Restore the object->data to a non-modifier evaluated state. @@ -424,42 +438,52 @@ void BKE_object_tfm_copy(Object *object_dst, const Object *object_src); * For example, we need to call this before #BKE_mesh_new_from_object(), * in case we removed/added modifiers in the evaluated object. */ -void BKE_object_eval_reset(Object *ob_eval); +void BKE_object_eval_reset(struct Object *ob_eval); /* Dependency graph evaluation callbacks. */ -void BKE_object_eval_local_transform(Depsgraph *depsgraph, Object *ob); -void BKE_object_eval_parent(Depsgraph *depsgraph, Object *ob); -void BKE_object_eval_constraints(Depsgraph *depsgraph, Scene *scene, Object *ob); -void BKE_object_eval_transform_final(Depsgraph *depsgraph, Object *ob); +void BKE_object_eval_local_transform(struct Depsgraph *depsgraph, struct Object *ob); +void BKE_object_eval_parent(struct Depsgraph *depsgraph, struct Object *ob); +void BKE_object_eval_constraints(struct Depsgraph *depsgraph, + struct Scene *scene, + struct Object *ob); +void BKE_object_eval_transform_final(struct Depsgraph *depsgraph, struct Object *ob); -void BKE_object_eval_uber_transform(Depsgraph *depsgraph, Object *object); -void BKE_object_eval_uber_data(Depsgraph *depsgraph, Scene *scene, Object *ob); +void BKE_object_eval_uber_transform(struct Depsgraph *depsgraph, struct Object *object); +void BKE_object_eval_uber_data(struct Depsgraph *depsgraph, + struct Scene *scene, + struct Object *ob); -void BKE_object_eval_light_linking(Depsgraph *depsgraph, Object *object); +void BKE_object_eval_light_linking(struct Depsgraph *depsgraph, struct Object *object); /** * Assign #Object.data after modifier stack evaluation. */ -void BKE_object_eval_assign_data(Object *object, ID *data, bool is_owned); +void BKE_object_eval_assign_data(struct Object *object, struct ID *data, bool is_owned); -void BKE_object_sync_to_original(Depsgraph *depsgraph, Object *object); +void BKE_object_sync_to_original(struct Depsgraph *depsgraph, struct Object *object); -void BKE_object_eval_ptcache_reset(Depsgraph *depsgraph, Scene *scene, Object *object); +void BKE_object_eval_ptcache_reset(struct Depsgraph *depsgraph, + struct Scene *scene, + struct Object *object); -void BKE_object_eval_transform_all(Depsgraph *depsgraph, Scene *scene, Object *object); +void BKE_object_eval_transform_all(struct Depsgraph *depsgraph, + struct Scene *scene, + struct Object *object); -void BKE_object_data_select_update(Depsgraph *depsgraph, ID *object_data); -void BKE_object_select_update(Depsgraph *depsgraph, Object *object); +void BKE_object_data_select_update(struct Depsgraph *depsgraph, struct ID *object_data); +void BKE_object_select_update(struct Depsgraph *depsgraph, struct Object *object); -void BKE_object_eval_eval_base_flags(Depsgraph *depsgraph, - Scene *scene, +void BKE_object_eval_eval_base_flags(struct Depsgraph *depsgraph, + struct Scene *scene, int view_layer_index, - Object *object, + struct Object *object, int base_index, bool is_from_set); -void BKE_object_handle_data_update(Depsgraph *depsgraph, Scene *scene, Object *ob); +void BKE_object_handle_data_update(struct Depsgraph *depsgraph, + struct Scene *scene, + struct Object *ob); /** * \warning "scene" here may not be the scene object actually resides in. * When dealing with background-sets, "scene" is actually the active scene. @@ -467,7 +491,7 @@ void BKE_object_handle_data_update(Depsgraph *depsgraph, Scene *scene, Object *o * rigid bodies depend on their world so use #BKE_object_handle_update_ex() * to also pass along the current rigid body world. */ -void BKE_object_handle_update(Depsgraph *depsgraph, Scene *scene, Object *ob); +void BKE_object_handle_update(struct Depsgraph *depsgraph, struct Scene *scene, struct Object *ob); /** * The main object update call, for object matrix, constraints, keys and modifiers. * Requires flags to be set! @@ -475,21 +499,21 @@ void BKE_object_handle_update(Depsgraph *depsgraph, Scene *scene, Object *ob); * Ideally we shouldn't have to pass the rigid body world, * but need bigger restructuring to avoid id. */ -void BKE_object_handle_update_ex(Depsgraph *depsgraph, - Scene *scene, - Object *ob, - RigidBodyWorld *rbw); +void BKE_object_handle_update_ex(struct Depsgraph *depsgraph, + struct Scene *scene, + struct Object *ob, + struct RigidBodyWorld *rbw); -void BKE_object_sculpt_data_create(Object *ob); +void BKE_object_sculpt_data_create(struct Object *ob); -bool BKE_object_obdata_texspace_get(Object *ob, +bool BKE_object_obdata_texspace_get(struct Object *ob, char **r_texspace_flag, float **r_texspace_location, float **r_texspace_size); -Mesh *BKE_object_get_evaluated_mesh_no_subsurf(const Object *object); +struct Mesh *BKE_object_get_evaluated_mesh_no_subsurf(const struct Object *object); /** Get evaluated mesh for given object. */ -Mesh *BKE_object_get_evaluated_mesh(const Object *object); +struct Mesh *BKE_object_get_evaluated_mesh(const struct Object *object); /** * Get mesh which is not affected by modifiers: * - For original objects it will be same as `object->data`, and it is a mesh @@ -497,34 +521,37 @@ Mesh *BKE_object_get_evaluated_mesh(const Object *object); * - For copied-on-write objects it will give pointer to a copied-on-write * mesh which corresponds to original object's mesh. */ -Mesh *BKE_object_get_pre_modified_mesh(const Object *object); +struct Mesh *BKE_object_get_pre_modified_mesh(const struct Object *object); /** * Get a mesh which corresponds to the very original mesh from #Main. * - For original objects it will be object->data. * - For evaluated objects it will be same mesh as corresponding original * object uses as data. */ -Mesh *BKE_object_get_original_mesh(const Object *object); +struct Mesh *BKE_object_get_original_mesh(const struct Object *object); -Mesh *BKE_object_get_editmesh_eval_final(const Object *object); -Mesh *BKE_object_get_editmesh_eval_cage(const Object *object); +struct Mesh *BKE_object_get_editmesh_eval_final(const struct Object *object); +struct Mesh *BKE_object_get_editmesh_eval_cage(const struct Object *object); /* Lattice accessors. * These functions return either the regular lattice, or the edit-mode lattice, * whichever is currently in use. */ -Lattice *BKE_object_get_lattice(const Object *object); -Lattice *BKE_object_get_evaluated_lattice(const Object *object); +struct Lattice *BKE_object_get_lattice(const struct Object *object); +struct Lattice *BKE_object_get_evaluated_lattice(const struct Object *object); -int BKE_object_insert_ptcache(Object *ob); -void BKE_object_delete_ptcache(Object *ob, int index); -KeyBlock *BKE_object_shapekey_insert(Main *bmain, Object *ob, const char *name, bool from_mix); -bool BKE_object_shapekey_remove(Main *bmain, Object *ob, KeyBlock *kb); -bool BKE_object_shapekey_free(Main *bmain, Object *ob); +int BKE_object_insert_ptcache(struct Object *ob); +void BKE_object_delete_ptcache(struct Object *ob, int index); +struct KeyBlock *BKE_object_shapekey_insert(struct Main *bmain, + struct Object *ob, + const char *name, + bool from_mix); +bool BKE_object_shapekey_remove(struct Main *bmain, struct Object *ob, struct KeyBlock *kb); +bool BKE_object_shapekey_free(struct Main *bmain, struct Object *ob); -bool BKE_object_flag_test_recursive(const Object *ob, short flag); +bool BKE_object_flag_test_recursive(const struct Object *ob, short flag); -bool BKE_object_is_child_recursive(const Object *ob_parent, const Object *ob_child); +bool BKE_object_is_child_recursive(const struct Object *ob_parent, const struct Object *ob_child); /** * Most important if this is modified it should _always_ return true, in certain @@ -532,13 +559,13 @@ bool BKE_object_is_child_recursive(const Object *ob_parent, const Object *ob_chi * * \return #ModifierMode flag. */ -int BKE_object_is_modified(Scene *scene, Object *ob); +int BKE_object_is_modified(struct Scene *scene, struct Object *ob); /** * Test if object is affected by deforming modifiers (for motion blur). again * most important is to avoid false positives, this is to skip computations * and we can still if there was actual deformation afterwards. */ -int BKE_object_is_deform_modified(Scene *scene, Object *ob); +int BKE_object_is_deform_modified(struct Scene *scene, struct Object *ob); /** * Check of objects moves in time. @@ -555,31 +582,33 @@ int BKE_object_is_deform_modified(Scene *scene, Object *ob); * * \note This function does not consider physics systems. */ -bool BKE_object_moves_in_time(const Object *object, bool recurse_parent); +bool BKE_object_moves_in_time(const struct Object *object, bool recurse_parent); /** Return the number of scenes using (instantiating) that object in their collections. */ -int BKE_object_scenes_users_get(Main *bmain, Object *ob); +int BKE_object_scenes_users_get(struct Main *bmain, struct Object *ob); -MovieClip *BKE_object_movieclip_get(Scene *scene, Object *ob, bool use_default); +struct MovieClip *BKE_object_movieclip_get(struct Scene *scene, + struct Object *ob, + bool use_default); -void BKE_object_runtime_reset(Object *object); +void BKE_object_runtime_reset(struct Object *object); /** * Reset all pointers which we don't want to be shared when copying the object. */ -void BKE_object_runtime_reset_on_copy(Object *object, int flag); +void BKE_object_runtime_reset_on_copy(struct Object *object, int flag); /** * The function frees memory used by the runtime data, but not the runtime field itself. * * All runtime data is cleared to ensure it's not used again, * in keeping with other `_free_data(..)` functions. */ -void BKE_object_runtime_free_data(Object *object); +void BKE_object_runtime_free_data(struct Object *object); -void BKE_object_batch_cache_dirty_tag(Object *ob); +void BKE_object_batch_cache_dirty_tag(struct Object *ob); /* this function returns a superset of the scenes selection based on relationships */ -enum eObRelationTypes { +typedef enum eObRelationTypes { OB_REL_NONE = 0, /* Just the selection as is. */ OB_REL_PARENT = (1 << 0), /* Immediate parent. */ OB_REL_PARENT_RECURSIVE = (1 << 1), /* Parents up to root of selection tree. */ @@ -587,13 +616,13 @@ enum eObRelationTypes { OB_REL_CHILDREN_RECURSIVE = (1 << 3), /* All children. */ OB_REL_MOD_ARMATURE = (1 << 4), /* Armatures related to the selected objects. */ // OB_REL_SCENE_CAMERA = (1 << 5), /* You might want the scene camera too even if unselected? */ -}; +} eObRelationTypes; -enum eObjectSet { +typedef enum eObjectSet { OB_SET_SELECTED, /* Selected Objects. */ OB_SET_VISIBLE, /* Visible Objects. */ OB_SET_ALL, /* All Objects. */ -}; +} eObjectSet; /** * Iterates over all objects of the given scene layer. @@ -602,15 +631,15 @@ enum eObjectSet { * If #OB_SET_VISIBLE or#OB_SET_SELECTED are collected, * then also add related objects according to the given \a includeFilter. */ -LinkNode *BKE_object_relational_superset(const Scene *scene, - ViewLayer *view_layer, - eObjectSet objectSet, - eObRelationTypes includeFilter); +struct LinkNode *BKE_object_relational_superset(const struct Scene *scene, + struct ViewLayer *view_layer, + eObjectSet objectSet, + eObRelationTypes includeFilter); /** * \return All groups this object is a part of, caller must free. */ -LinkNode *BKE_object_groups(Main *bmain, Scene *scene, Object *ob); -void BKE_object_groups_clear(Main *bmain, Scene *scene, Object *object); +struct LinkNode *BKE_object_groups(struct Main *bmain, struct Scene *scene, struct Object *ob); +void BKE_object_groups_clear(struct Main *bmain, struct Scene *scene, struct Object *object); /** * Return a KDTree_3d from the deformed object (in world-space). @@ -621,22 +650,24 @@ void BKE_object_groups_clear(Main *bmain, Scene *scene, Object *object); * \param r_tot: * \return The KD-tree or nullptr if it can't be created. */ -KDTree_3d *BKE_object_as_kdtree(Object *ob, int *r_tot); +struct KDTree_3d *BKE_object_as_kdtree(struct Object *ob, int *r_tot); /** * \note this function should eventually be replaced by depsgraph functionality. * Avoid calling this in new code unless there is a very good reason for it! */ -bool BKE_object_modifier_update_subframe(Depsgraph *depsgraph, - Scene *scene, - Object *ob, +bool BKE_object_modifier_update_subframe(struct Depsgraph *depsgraph, + struct Scene *scene, + struct Object *ob, bool update_mesh, int parent_recursion, float frame, int type); -bool BKE_object_empty_image_frame_is_visible_in_view3d(const Object *ob, const RegionView3D *rv3d); -bool BKE_object_empty_image_data_is_visible_in_view3d(const Object *ob, const RegionView3D *rv3d); +bool BKE_object_empty_image_frame_is_visible_in_view3d(const struct Object *ob, + const struct RegionView3D *rv3d); +bool BKE_object_empty_image_data_is_visible_in_view3d(const struct Object *ob, + const struct RegionView3D *rv3d); /** * This is an utility function for Python's object.to_mesh() (the naming is not very clear though). @@ -650,9 +681,11 @@ bool BKE_object_empty_image_data_is_visible_in_view3d(const Object *ob, const Re * * NOTE: Dependency graph argument is required when preserve_all_data_layers is truth, and is * ignored otherwise. */ -Mesh *BKE_object_to_mesh(Depsgraph *depsgraph, Object *object, bool preserve_all_data_layers); +struct Mesh *BKE_object_to_mesh(struct Depsgraph *depsgraph, + struct Object *object, + bool preserve_all_data_layers); -void BKE_object_to_mesh_clear(Object *object); +void BKE_object_to_mesh_clear(struct Object *object); /** * This is an utility function for Python's `object.to_curve()`. @@ -664,11 +697,13 @@ void BKE_object_to_mesh_clear(Object *object); * If apply_modifiers is true and the object is a curve one, then spline deform modifiers are * applied on the curve control points. */ -Curve *BKE_object_to_curve(Object *object, Depsgraph *depsgraph, bool apply_modifiers); +struct Curve *BKE_object_to_curve(struct Object *object, + struct Depsgraph *depsgraph, + bool apply_modifiers); -void BKE_object_to_curve_clear(Object *object); +void BKE_object_to_curve_clear(struct Object *object); -void BKE_object_check_uuids_unique_and_report(const Object *object); +void BKE_object_check_uuids_unique_and_report(const struct Object *object); /** * Return the last subsurf modifier of an object, this does not check whether modifiers on top of @@ -677,10 +712,11 @@ void BKE_object_check_uuids_unique_and_report(const Object *object); * This does not check if the modifier is enabled as it is assumed that the caller verified that it * is enabled for its evaluation mode. */ -SubsurfModifierData *BKE_object_get_last_subsurf_modifier(const Object *ob); +struct SubsurfModifierData *BKE_object_get_last_subsurf_modifier(const struct Object *ob); -void BKE_object_replace_data_on_shallow_copy(Object *ob, ID *new_data); +void BKE_object_replace_data_on_shallow_copy(struct Object *ob, struct ID *new_data); -PartEff *BKE_object_do_version_give_parteff_245(Object *ob); +struct PartEff; +struct PartEff *BKE_object_do_version_give_parteff_245(struct Object *ob); -bool BKE_object_supports_material_slots(Object *ob); +bool BKE_object_supports_material_slots(struct Object *ob); diff --git a/source/blender/blenkernel/BKE_pbvh_api.hh b/source/blender/blenkernel/BKE_pbvh_api.hh index f1473b8c1c4..1fc8da036f7 100644 --- a/source/blender/blenkernel/BKE_pbvh_api.hh +++ b/source/blender/blenkernel/BKE_pbvh_api.hh @@ -9,12 +9,12 @@ * \brief A BVH for high poly meshes. */ -#include #include #include "BLI_bitmap.h" #include "BLI_compiler_compat.h" #include "BLI_function_ref.hh" +#include "BLI_ghash.h" #include "BLI_math_vector_types.hh" #include "BLI_offset_indices.hh" #include "BLI_vector.hh" @@ -89,11 +89,7 @@ struct PBVHPixelsNode { void *node_data = nullptr; }; -class PBVHAttrReq { - public: - PBVHAttrReq() = default; - PBVHAttrReq(const eAttrDomain domain, const eCustomDataType type) : domain(domain), type(type) {} - +struct PBVHAttrReq { std::string name; eAttrDomain domain; eCustomDataType type; @@ -305,8 +301,7 @@ bool BKE_pbvh_node_find_nearest_to_ray(PBVH *pbvh, void BKE_pbvh_set_frustum_planes(PBVH *pbvh, PBVHFrustumPlanes *planes); void BKE_pbvh_get_frustum_planes(PBVH *pbvh, PBVHFrustumPlanes *planes); -void BKE_pbvh_draw_cb(const Mesh &mesh, - PBVH *pbvh, +void BKE_pbvh_draw_cb(PBVH *pbvh, bool update_only_visible, PBVHFrustumPlanes *update_frustum, PBVHFrustumPlanes *draw_frustum, @@ -403,12 +398,8 @@ void BKE_pbvh_node_get_grids(PBVH *pbvh, int *maxgrid, int *gridsize, CCGElem ***r_griddata); -void BKE_pbvh_node_num_verts(const PBVH *pbvh, - const PBVHNode *node, - int *r_uniquevert, - int *r_totvert); -blender::Span BKE_pbvh_node_get_vert_indices(const PBVHNode *node); -blender::Span BKE_pbvh_node_get_unique_vert_indices(const PBVHNode *node); +void BKE_pbvh_node_num_verts(PBVH *pbvh, PBVHNode *node, int *r_uniquevert, int *r_totvert); +const int *BKE_pbvh_node_get_vert_indices(PBVHNode *node); void BKE_pbvh_node_get_loops(PBVH *pbvh, PBVHNode *node, const int **r_loop_indices, @@ -433,10 +424,9 @@ bool BKE_pbvh_node_frustum_contain_AABB(PBVHNode *node, PBVHFrustumPlanes *frust */ bool BKE_pbvh_node_frustum_exclude_AABB(PBVHNode *node, PBVHFrustumPlanes *frustum); -const blender::Set &BKE_pbvh_bmesh_node_unique_verts(PBVHNode *node); -const blender::Set &BKE_pbvh_bmesh_node_other_verts(PBVHNode *node); -const blender::Set &BKE_pbvh_bmesh_node_faces(PBVHNode *node); - +GSet *BKE_pbvh_bmesh_node_unique_verts(PBVHNode *node); +GSet *BKE_pbvh_bmesh_node_other_verts(PBVHNode *node); +GSet *BKE_pbvh_bmesh_node_faces(PBVHNode *node); /** * In order to perform operations on the original node coordinates * (currently just ray-cast), store the node's triangles and vertices. @@ -518,10 +508,8 @@ struct PBVHVertexIter { bool is_mesh; /* bmesh */ - std::optional::Iterator> bm_unique_verts; - std::optional::Iterator> bm_unique_verts_end; - std::optional::Iterator> bm_other_verts; - std::optional::Iterator> bm_other_verts_end; + GSetIterator bm_unique_verts; + GSetIterator bm_other_verts; CustomData *bm_vdata; int cd_vert_mask_offset; @@ -531,7 +519,7 @@ struct PBVHVertexIter { float *co; const float *no; const float *fno; - float mask; + const float *mask; bool visible; }; @@ -561,7 +549,7 @@ void pbvh_vertex_iter_init(PBVH *pbvh, PBVHNode *node, PBVHVertexIter *vi, int m vi.grid = CCG_elem_next(&vi.key, vi.grid); \ vi.co = CCG_elem_co(&vi.key, vi.grid); \ vi.fno = CCG_elem_no(&vi.key, vi.grid); \ - vi.mask = vi.key.has_mask ? *CCG_elem_mask(&vi.key, vi.grid) : 0.0f; \ + vi.mask = vi.key.has_mask ? CCG_elem_mask(&vi.key, vi.grid) : NULL; \ vi.index++; \ vi.vertex.i++; \ vi.visible = true; \ @@ -579,16 +567,18 @@ void pbvh_vertex_iter_init(PBVH *pbvh, PBVHNode *node, PBVHVertexIter *vi, int m vi.co = vi.vert_positions[vi.vert_indices[vi.gx]]; \ vi.no = vi.vert_normals[vi.vert_indices[vi.gx]]; \ vi.index = vi.vertex.i = vi.vert_indices[vi.i]; \ - vi.mask = vi.vmask ? vi.vmask[vi.index] : 0.0f; \ + if (vi.vmask) { \ + vi.mask = &vi.vmask[vi.index]; \ + } \ } \ else { \ - if (*vi.bm_unique_verts != *vi.bm_unique_verts_end) { \ - vi.bm_vert = **vi.bm_unique_verts; \ - (*vi.bm_unique_verts)++; \ + if (!BLI_gsetIterator_done(&vi.bm_unique_verts)) { \ + vi.bm_vert = (BMVert *)BLI_gsetIterator_getKey(&vi.bm_unique_verts); \ + BLI_gsetIterator_step(&vi.bm_unique_verts); \ } \ else { \ - vi.bm_vert = **vi.bm_other_verts; \ - (*vi.bm_other_verts)++; \ + vi.bm_vert = (BMVert *)BLI_gsetIterator_getKey(&vi.bm_other_verts); \ + BLI_gsetIterator_step(&vi.bm_other_verts); \ } \ vi.visible = !BM_elem_flag_test_bool(vi.bm_vert, BM_ELEM_HIDDEN); \ if (mode == PBVH_ITER_UNIQUE && !vi.visible) { \ @@ -598,7 +588,7 @@ void pbvh_vertex_iter_init(PBVH *pbvh, PBVHNode *node, PBVHVertexIter *vi, int m vi.fno = vi.bm_vert->no; \ vi.vertex = BKE_pbvh_make_vref((intptr_t)vi.bm_vert); \ vi.index = BM_elem_index_get(vi.bm_vert); \ - vi.mask = BM_ELEM_CD_GET_FLOAT(vi.bm_vert, vi.cd_vert_mask_offset); \ + vi.mask = (float *)BM_ELEM_CD_GET_VOID_P(vi.bm_vert, vi.cd_vert_mask_offset); \ } #define BKE_pbvh_vertex_iter_end \ @@ -623,7 +613,7 @@ struct PBVHFaceIter { const PBVHNode *node_; PBVHType pbvh_type_; int verts_size_; - std::optional::Iterator> bm_faces_iter_; + GSetIterator bm_faces_iter_; int cd_hide_poly_, cd_face_set_; bool *hide_poly_; int *face_sets_; @@ -698,19 +688,22 @@ bool BKE_pbvh_get_color_layer(Mesh *me, CustomDataLayer **r_layer, eAttrDomain * /* Swaps colors at each element in indices (of domain pbvh->vcol_domain) * with values in colors. */ void BKE_pbvh_swap_colors(PBVH *pbvh, - blender::Span indices, - blender::MutableSpan r_colors); + const int *indices, + const int indices_num, + float (*colors)[4]); /* Stores colors from the elements in indices (of domain pbvh->vcol_domain) * into colors. */ void BKE_pbvh_store_colors(PBVH *pbvh, - blender::Span indices, - blender::MutableSpan r_colors); + const int *indices, + const int indices_num, + float (*colors)[4]); /* Like BKE_pbvh_store_colors but handles loop->vert conversion */ void BKE_pbvh_store_colors_vertex(PBVH *pbvh, - blender::Span indices, - blender::MutableSpan r_colors); + const int *indices, + const int indices_num, + float (*colors)[4]); bool BKE_pbvh_is_drawing(const PBVH *pbvh); diff --git a/source/blender/blenkernel/BKE_pointcloud.h b/source/blender/blenkernel/BKE_pointcloud.h index 83c8ec822fc..1c90e185ddf 100644 --- a/source/blender/blenkernel/BKE_pointcloud.h +++ b/source/blender/blenkernel/BKE_pointcloud.h @@ -9,8 +9,6 @@ * \brief General operations for point clouds. */ -#include "DNA_object_types.h" /* #BoundBox. */ - #ifdef __cplusplus # include @@ -76,7 +74,7 @@ struct PointCloud *BKE_pointcloud_new_nomain(int totpoint); void BKE_pointcloud_nomain_to_pointcloud(struct PointCloud *pointcloud_src, struct PointCloud *pointcloud_dst); -BoundBox BKE_pointcloud_boundbox_get(struct Object *ob); +struct BoundBox *BKE_pointcloud_boundbox_get(struct Object *ob); bool BKE_pointcloud_attribute_required(const struct PointCloud *pointcloud, const char *name); diff --git a/source/blender/blenkernel/BKE_report.h b/source/blender/blenkernel/BKE_report.h index 13b3938d166..e5d95b2bde5 100644 --- a/source/blender/blenkernel/BKE_report.h +++ b/source/blender/blenkernel/BKE_report.h @@ -18,47 +18,21 @@ extern "C" { #endif -/** Reporting Information and Errors. +/* Reporting Information and Errors * - * These functions are thread-safe, unless otherwise specified. - * - * These functions also accept nullptr in case no error reporting is needed. The message are only - * printed to the console then. */ + * These functions also accept NULL in case no error reporting + * is needed. */ /* Report structures are stored in DNA. */ -/** Initialize a #ReportList struct. - * - * \note: Not thread-safe, should only be called from the 'owner' thread of the report list. - */ void BKE_reports_init(ReportList *reports, int flag); /** - * Fully release any internal resources used by this #ReportList, as acquired by #BKE_reports_init. - * - * Also calls #BKE_reports_clear. The given `reports` should not be used anymore unless it is - * re-initialized first. - * - * \note: Not thread-safe, should only be called from the current owner of the report list, once - * no other concurrent access is possible. - */ -void BKE_reports_free(ReportList *reports); - -/** - * Only frees the list of reports in given \a reports. Use #BKE_reports_free to fully cleanup all - * allocated resources. - * + * Only frees the list \a reports. * To make displayed reports disappear, either remove window-manager reports * (#wmWindowManager.reports, or #CTX_wm_reports()), or use #WM_report_banners_cancel(). */ void BKE_reports_clear(ReportList *reports); -/** Moves all reports from `reports_src` to `reports_dst`. */ -void BKE_reports_move_to_reports(ReportList *reports_dst, ReportList *reports_src); - -/** (Un)lock given `reports`, in case external code needs to access its data. */ -void BKE_reports_lock(ReportList *reports); -void BKE_reports_unlock(ReportList *reports); - void BKE_report(ReportList *reports, eReportType type, const char *message); void BKE_reportf(ReportList *reports, eReportType type, const char *format, ...) ATTR_PRINTF_FORMAT(3, 4); diff --git a/source/blender/blenkernel/BKE_screen.hh b/source/blender/blenkernel/BKE_screen.hh index 5e3ce2a1419..a85ea5c9ebf 100644 --- a/source/blender/blenkernel/BKE_screen.hh +++ b/source/blender/blenkernel/BKE_screen.hh @@ -39,7 +39,6 @@ struct View3DShading; struct WorkSpace; struct bContext; struct bScreen; -struct uiBlock; struct uiLayout; struct uiList; struct wmGizmoMap; @@ -340,27 +339,6 @@ enum { PANEL_TYPE_NO_SEARCH = (1 << 7), }; -typedef struct Panel_Runtime { - /* Applied to Panel.ofsx, but saved separately so we can track changes between redraws. */ - int region_ofsx = 0; - - /** - * Pointer for storing which data the panel corresponds to. - * Useful when there can be multiple instances of the same panel type. - * - * \note A panel and its sub-panels share the same custom data pointer. - * This avoids freeing the same pointer twice when panels are removed. - */ - PointerRNA *custom_data_ptr = nullptr; - - /* Pointer to the panel's block. Useful when changes to panel #uiBlocks - * need some context from traversal of the panel "tree". */ - uiBlock *block = nullptr; - - /* Non-owning pointer. The context is stored in the block. */ - bContextStore *context = nullptr; -} Panel_Runtime; - /* #uiList types. */ /** Draw an item in the `ui_list`. */ @@ -553,11 +531,6 @@ ARegion *BKE_area_region_copy(const SpaceType *st, const ARegion *region); */ void BKE_area_region_free(SpaceType *st, ARegion *region); void BKE_area_region_panels_free(ListBase *panels); -/** - * Create and free panels. - */ -Panel *BKE_panel_new(PanelType *panel_type); -void BKE_panel_free(Panel *panel); /** * Doesn't free the area itself. */ diff --git a/source/blender/blenkernel/BKE_shader_fx.h b/source/blender/blenkernel/BKE_shader_fx.h index 6dc2ee94804..c4bc041c580 100644 --- a/source/blender/blenkernel/BKE_shader_fx.h +++ b/source/blender/blenkernel/BKE_shader_fx.h @@ -157,7 +157,7 @@ void BKE_shaderfx_free(struct ShaderFxData *fx); /** * Check unique name. */ -void BKE_shaderfx_unique_name(struct ListBase *shaderfx, struct ShaderFxData *fx); +bool BKE_shaderfx_unique_name(struct ListBase *shaderfx, struct ShaderFxData *fx); bool BKE_shaderfx_depends_ontime(struct ShaderFxData *fx); /** * Check whether given shaderfx is not local (i.e. from linked data) when the object is a library diff --git a/source/blender/blenkernel/BKE_subdiv_modifier.hh b/source/blender/blenkernel/BKE_subdiv_modifier.hh index e6a269be308..a6416be3f4e 100644 --- a/source/blender/blenkernel/BKE_subdiv_modifier.hh +++ b/source/blender/blenkernel/BKE_subdiv_modifier.hh @@ -45,6 +45,7 @@ struct SubsurfRuntimeData { bool has_gpu_subdiv; int resolution; bool use_optimal_display; + bool calc_loop_normals; bool use_loop_normals; /* Cached from the draw code for stats display. */ diff --git a/source/blender/blenkernel/intern/DerivedMesh.cc b/source/blender/blenkernel/intern/DerivedMesh.cc index a9c8b9868e9..d45100b565d 100644 --- a/source/blender/blenkernel/intern/DerivedMesh.cc +++ b/source/blender/blenkernel/intern/DerivedMesh.cc @@ -20,6 +20,7 @@ #include "DNA_object_types.h" #include "DNA_scene_types.h" +#include "BLI_array.h" #include "BLI_bitmap.h" #include "BLI_blenlib.h" #include "BLI_linklist.h" @@ -89,8 +90,10 @@ using blender::bke::MeshComponent; #endif static void mesh_init_origspace(Mesh *mesh); -static void editbmesh_calc_modifier_final_normals(Mesh *mesh_final); -static void editbmesh_calc_modifier_final_normals_or_defer(Mesh *mesh_final); +static void editbmesh_calc_modifier_final_normals(Mesh *mesh_final, + const CustomData_MeshMasks *final_datamask); +static void editbmesh_calc_modifier_final_normals_or_defer( + Mesh *mesh_final, const CustomData_MeshMasks *final_datamask); /* -------------------------------------------------------------------- */ @@ -446,6 +449,60 @@ static void add_orco_mesh( } } +static bool mesh_has_modifier_final_normals(const Mesh *mesh_input, + const CustomData_MeshMasks *final_datamask, + Mesh *mesh_final) +{ + /* Test if mesh has the required loop normals, in case an additional modifier + * evaluation from another instance or from an operator requests it but the + * initial normals were not loop normals. */ + const bool calc_loop_normals = ((mesh_input->flag & ME_AUTOSMOOTH) != 0 || + (final_datamask->lmask & CD_MASK_NORMAL) != 0); + + return (!calc_loop_normals || CustomData_has_layer(&mesh_final->loop_data, CD_NORMAL)); +} + +static void mesh_calc_modifier_final_normals(const Mesh *mesh_input, + const CustomData_MeshMasks *final_datamask, + const bool sculpt_dyntopo, + Mesh *mesh_final) +{ + /* Compute normals. */ + const bool calc_loop_normals = ((mesh_input->flag & ME_AUTOSMOOTH) != 0 || + (final_datamask->lmask & CD_MASK_NORMAL) != 0); + + /* Needed as `final_datamask` is not preserved outside modifier stack evaluation. */ + SubsurfRuntimeData *subsurf_runtime_data = mesh_final->runtime->subsurf_runtime_data; + if (subsurf_runtime_data) { + subsurf_runtime_data->calc_loop_normals = calc_loop_normals; + } + + if (calc_loop_normals) { + /* Compute loop normals (NOTE: will compute face and vert normals as well, if needed!). In case + * of deferred CPU subdivision, this will be computed when the wrapper is generated. */ + if (!subsurf_runtime_data || subsurf_runtime_data->resolution == 0) { + BKE_mesh_calc_normals_split(mesh_final); + } + } + else { + if (sculpt_dyntopo == false) { + /* without this, drawing ngon tri's faces will show ugly tessellated face + * normals and will also have to calculate normals on the fly, try avoid + * this where possible since calculating face normals isn't fast, + * note that this isn't a problem for subsurf (only quads) or edit-mode + * which deals with drawing differently. */ + BKE_mesh_ensure_normals_for_display(mesh_final); + } + + /* Some modifiers, like data-transfer, may generate those data as temp layer, + * we do not want to keep them, as they are used by display code when available + * (i.e. even if auto-smooth is disabled). */ + if (CustomData_has_layer(&mesh_final->loop_data, CD_NORMAL)) { + CustomData_free_layers(&mesh_final->loop_data, CD_NORMAL, mesh_final->totloop); + } + } +} + /* Does final touches to the final evaluated mesh, making sure it is perfectly usable. * * This is needed because certain information is not passed along intermediate meshes allocated @@ -460,10 +517,11 @@ static void mesh_calc_finalize(const Mesh *mesh_input, Mesh *mesh_eval) mesh_eval->edit_mesh = mesh_input->edit_mesh; } -void BKE_mesh_wrapper_deferred_finalize_mdata(Mesh *me_eval) +void BKE_mesh_wrapper_deferred_finalize_mdata(Mesh *me_eval, + const CustomData_MeshMasks *cd_mask_finalize) { if (me_eval->runtime->wrapper_type_finalize & (1 << ME_WRAPPER_TYPE_BMESH)) { - editbmesh_calc_modifier_final_normals(me_eval); + editbmesh_calc_modifier_final_normals(me_eval, cd_mask_finalize); me_eval->runtime->wrapper_type_finalize = eMeshWrapperType( me_eval->runtime->wrapper_type_finalize & ~(1 << ME_WRAPPER_TYPE_BMESH)); } @@ -967,6 +1025,7 @@ static void mesh_calc_modifiers(Depsgraph *depsgraph, /* Compute normals. */ if (is_own_mesh) { + mesh_calc_modifier_final_normals(mesh_input, &final_datamask, sculpt_dyntopo, mesh_final); mesh_calc_finalize(mesh_input, mesh_final); } else { @@ -978,6 +1037,8 @@ static void mesh_calc_modifiers(Depsgraph *depsgraph, * Isolate since computing normals is multithreaded and we are holding a lock. */ blender::threading::isolate_task([&] { mesh_final = BKE_mesh_copy_for_eval(mesh_input); + mesh_calc_modifier_final_normals( + mesh_input, &final_datamask, sculpt_dyntopo, mesh_final); mesh_calc_finalize(mesh_input, mesh_final); runtime->mesh_eval = mesh_final; }); @@ -987,6 +1048,13 @@ static void mesh_calc_modifiers(Depsgraph *depsgraph, mesh_final = runtime->mesh_eval; } } + else if (!mesh_has_modifier_final_normals(mesh_input, &final_datamask, runtime->mesh_eval)) { + /* Modifier stack was (re-)evaluated with a request for additional normals + * different than the instanced mesh, can't instance anymore now. */ + mesh_final = BKE_mesh_copy_for_eval(mesh_input); + mesh_calc_modifier_final_normals(mesh_input, &final_datamask, sculpt_dyntopo, mesh_final); + mesh_calc_finalize(mesh_input, mesh_final); + } else { /* Already finalized by another instance, reuse. */ mesh_final = runtime->mesh_eval; @@ -1035,25 +1103,39 @@ bool editbmesh_modifier_is_enabled(const Scene *scene, return true; } -static void editbmesh_calc_modifier_final_normals(Mesh *mesh_final) +static void editbmesh_calc_modifier_final_normals(Mesh *mesh_final, + const CustomData_MeshMasks *final_datamask) { - switch (mesh_final->runtime->wrapper_type) { - case ME_WRAPPER_TYPE_SUBD: - case ME_WRAPPER_TYPE_MDATA: - break; - case ME_WRAPPER_TYPE_BMESH: { - BMEditMesh *em = mesh_final->edit_mesh; - blender::bke::EditMeshData *emd = mesh_final->runtime->edit_data; - if (!emd->vertexCos.is_empty()) { - BKE_editmesh_cache_ensure_vert_normals(em, emd); - BKE_editmesh_cache_ensure_face_normals(em, emd); - } - return; + const bool calc_loop_normals = ((mesh_final->flag & ME_AUTOSMOOTH) != 0 || + (final_datamask->lmask & CD_MASK_NORMAL) != 0); + + SubsurfRuntimeData *subsurf_runtime_data = mesh_final->runtime->subsurf_runtime_data; + if (subsurf_runtime_data) { + subsurf_runtime_data->calc_loop_normals = calc_loop_normals; + } + + if (calc_loop_normals) { + /* Compute loop normals. In case of deferred CPU subdivision, this will be computed when the + * wrapper is generated. */ + if (!subsurf_runtime_data || subsurf_runtime_data->resolution == 0) { + BKE_mesh_calc_normals_split(mesh_final); + } + } + else { + /* Same as #mesh_calc_modifiers. + * If using loop normals, face normals have already been computed. */ + BKE_mesh_ensure_normals_for_display(mesh_final); + + /* Some modifiers, like data-transfer, may generate those data, we do not want to keep them, + * as they are used by display code when available (i.e. even if auto-smooth is disabled). */ + if (CustomData_has_layer(&mesh_final->loop_data, CD_NORMAL)) { + CustomData_free_layers(&mesh_final->loop_data, CD_NORMAL, mesh_final->totloop); } } } -static void editbmesh_calc_modifier_final_normals_or_defer(Mesh *mesh_final) +static void editbmesh_calc_modifier_final_normals_or_defer( + Mesh *mesh_final, const CustomData_MeshMasks *final_datamask) { if (mesh_final->runtime->wrapper_type != ME_WRAPPER_TYPE_MDATA) { /* Generated at draw time. */ @@ -1062,7 +1144,7 @@ static void editbmesh_calc_modifier_final_normals_or_defer(Mesh *mesh_final) return; } - editbmesh_calc_modifier_final_normals(mesh_final); + editbmesh_calc_modifier_final_normals(mesh_final, final_datamask); } static MutableSpan mesh_wrapper_vert_coords_ensure_for_write(Mesh *mesh) @@ -1280,9 +1362,9 @@ static void editbmesh_calc_modifiers(Depsgraph *depsgraph, } /* Compute normals. */ - editbmesh_calc_modifier_final_normals_or_defer(mesh_final); + editbmesh_calc_modifier_final_normals_or_defer(mesh_final, &final_datamask); if (mesh_cage && (mesh_cage != mesh_final)) { - editbmesh_calc_modifier_final_normals_or_defer(mesh_cage); + editbmesh_calc_modifier_final_normals_or_defer(mesh_cage, &final_datamask); } /* Return final mesh. */ diff --git a/source/blender/blenkernel/intern/action.cc b/source/blender/blenkernel/intern/action.cc index 1d27d71b862..465a3e73392 100644 --- a/source/blender/blenkernel/intern/action.cc +++ b/source/blender/blenkernel/intern/action.cc @@ -29,7 +29,7 @@ #include "BLI_math_rotation.h" #include "BLI_math_vector.h" #include "BLI_session_uuid.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_utildefines.h" #include "BLT_translation.h" diff --git a/source/blender/blenkernel/intern/action_mirror.cc b/source/blender/blenkernel/intern/action_mirror.cc index b3fd24ae09a..86d3350397e 100644 --- a/source/blender/blenkernel/intern/action_mirror.cc +++ b/source/blender/blenkernel/intern/action_mirror.cc @@ -20,7 +20,7 @@ #include "BLI_blenlib.h" #include "BLI_math_matrix.h" #include "BLI_math_vector.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_utildefines.h" #include "BKE_action.h" diff --git a/source/blender/blenkernel/intern/anim_sys.cc b/source/blender/blenkernel/intern/anim_sys.cc index 782b999b1f9..f38d5c73784 100644 --- a/source/blender/blenkernel/intern/anim_sys.cc +++ b/source/blender/blenkernel/intern/anim_sys.cc @@ -20,7 +20,7 @@ #include "BLI_listbase.h" #include "BLI_math_rotation.h" #include "BLI_math_vector.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_utildefines.h" #include "BLT_translation.h" @@ -255,7 +255,7 @@ void BKE_keyingsets_foreach_id(LibraryForeachIDData *data, const ListBase *keyin /* Freeing Tools --------------------------- */ -void BKE_keyingset_free_paths(KeyingSet *ks) +void BKE_keyingset_free(KeyingSet *ks) { KS_Path *ksp, *kspn; @@ -281,11 +281,11 @@ void BKE_keyingsets_free(ListBase *list) } /* loop over KeyingSets freeing them - * - BKE_keyingset_free_paths() doesn't free the set itself, but it frees its sub-data + * - BKE_keyingset_free() doesn't free the set itself, but it frees its sub-data */ for (ks = static_cast(list->first); ks; ks = ksn) { ksn = ks->next; - BKE_keyingset_free_paths(ks); + BKE_keyingset_free(ks); BLI_freelinkN(list, ks); } } @@ -3709,13 +3709,13 @@ NlaKeyframingContext *BKE_animsys_get_nla_keyframing_context( void BKE_animsys_nla_remap_keyframe_values(NlaKeyframingContext *context, PointerRNA *prop_ptr, PropertyRNA *prop, - const blender::MutableSpan values, + float *values, + int count, int index, const AnimationEvalContext *anim_eval_context, bool *r_force_all, BLI_bitmap *r_successful_remaps) { - const int count = values.size(); BLI_bitmap_set_all(r_successful_remaps, false, count); if (r_force_all != nullptr) { @@ -3782,7 +3782,7 @@ void BKE_animsys_nla_remap_keyframe_values(NlaKeyframingContext *context, } NlaEvalChannelSnapshot *blended_necs = nlaeval_snapshot_ensure_channel(&blended_snapshot, nec); - std::copy(values.begin(), values.end(), blended_necs->values); + memcpy(blended_necs->values, values, sizeof(float) * count); /* Force all channels to be remapped for quaternions in a Combine or Replace strip, otherwise it * will always fail. See nlaevalchan_combine_quaternion_handle_undefined_blend_values(). diff --git a/source/blender/blenkernel/intern/appdir.cc b/source/blender/blenkernel/intern/appdir.cc index 36e869d4dbc..f5149a15d76 100644 --- a/source/blender/blenkernel/intern/appdir.cc +++ b/source/blender/blenkernel/intern/appdir.cc @@ -18,7 +18,7 @@ #include "BLI_path_util.h" #include "BLI_string.h" #include "BLI_string_utf8.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_tempfile.h" #include "BLI_utildefines.h" @@ -34,8 +34,8 @@ #include "CLG_log.h" #ifdef WIN32 -# include "utf_winfunc.hh" -# include "utfconv.hh" +# include "utf_winfunc.h" +# include "utfconv.h" # include # ifdef _WIN32_IE # undef _WIN32_IE diff --git a/source/blender/blenkernel/intern/armature.cc b/source/blender/blenkernel/intern/armature.cc index 44222170a1e..2a7a9969098 100644 --- a/source/blender/blenkernel/intern/armature.cc +++ b/source/blender/blenkernel/intern/armature.cc @@ -2956,15 +2956,17 @@ void BKE_pchan_minmax(const Object *ob, const bArmature *arm = static_cast(ob->data); Object *ob_custom = (arm->flag & ARM_NO_CUSTOM) ? nullptr : pchan->custom; const bPoseChannel *pchan_tx = (ob_custom && pchan->custom_tx) ? pchan->custom_tx : pchan; + const BoundBox *bb_custom = nullptr; + BoundBox bb_custom_buf; - std::optional bb_custom; if (ob_custom) { float min[3], max[3]; if (use_empty_drawtype && (ob_custom->type == OB_EMPTY) && BKE_object_minmax_empty_drawtype(ob_custom, min, max)) { - bb_custom.emplace(); - BKE_boundbox_init_from_minmax(&bb_custom.value(), min, max); + memset(&bb_custom_buf, 0x0, sizeof(bb_custom_buf)); + BKE_boundbox_init_from_minmax(&bb_custom_buf, min, max); + bb_custom = &bb_custom_buf; } else { bb_custom = BKE_object_boundbox_get(ob_custom); @@ -2982,7 +2984,7 @@ void BKE_pchan_minmax(const Object *ob, pchan->custom_translation[1], pchan->custom_translation[2]); mul_m4_series(mat, ob->object_to_world, tmp, rmat, smat); - BKE_boundbox_minmax(&bb_custom.value(), mat, r_min, r_max); + BKE_boundbox_minmax(bb_custom, mat, r_min, r_max); } else { float vec[3]; diff --git a/source/blender/blenkernel/intern/asset.cc b/source/blender/blenkernel/intern/asset.cc index b78710c6c2a..56281a8ee8e 100644 --- a/source/blender/blenkernel/intern/asset.cc +++ b/source/blender/blenkernel/intern/asset.cc @@ -15,7 +15,7 @@ #include "BLI_string.h" #include "BLI_string_ref.hh" #include "BLI_string_utf8.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_uuid.h" #include "BKE_asset.h" diff --git a/source/blender/blenkernel/intern/attribute.cc b/source/blender/blenkernel/intern/attribute.cc index 68c20c91d4e..bdc552c3c6f 100644 --- a/source/blender/blenkernel/intern/attribute.cc +++ b/source/blender/blenkernel/intern/attribute.cc @@ -23,7 +23,7 @@ #include "BLI_index_range.hh" #include "BLI_string.h" #include "BLI_string_utf8.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLT_translation.h" @@ -32,7 +32,6 @@ #include "BKE_curves.hh" #include "BKE_customdata.h" #include "BKE_editmesh.h" -#include "BKE_grease_pencil.hh" #include "BKE_mesh.hh" #include "BKE_pointcloud.h" #include "BKE_report.h" @@ -91,12 +90,6 @@ static void get_domains(const ID *id, DomainInfo info[ATTR_DOMAIN_NUM]) info[ATTR_DOMAIN_CURVE].length = curves->geometry.curve_num; break; } - case ID_GP: { - GreasePencil *grease_pencil = (GreasePencil *)id; - info[ATTR_DOMAIN_LAYER].customdata = &grease_pencil->layers_data; - info[ATTR_DOMAIN_LAYER].length = grease_pencil->layers().size(); - break; - } default: break; } @@ -123,10 +116,6 @@ static std::optional get_attribute_acces CurvesGeometry &curves = curves_id.geometry.wrap(); return curves.attributes_for_write(); } - case ID_GP: { - GreasePencil &grease_pencil = reinterpret_cast(id); - return grease_pencil.attributes_for_write(); - } default: { BLI_assert_unreachable(); return {}; @@ -263,7 +252,7 @@ static bool unique_name_cb(void *arg, const char *name) return false; } -void BKE_id_attribute_calc_unique_name(ID *id, const char *name, char *outname) +bool BKE_id_attribute_calc_unique_name(ID *id, const char *name, char *outname) { AttrUniqueData data{id}; @@ -274,7 +263,7 @@ void BKE_id_attribute_calc_unique_name(ID *id, const char *name, char *outname) BLI_strncpy_utf8(outname, (name && name[0]) ? name : IFACE_("Attribute"), name_maxncpy); const char *defname = ""; /* Dummy argument, never used as `name` is never zero length. */ - BLI_uniquename_cb(unique_name_cb, &data, defname, '.', outname, name_maxncpy); + return BLI_uniquename_cb(unique_name_cb, &data, defname, '.', outname, name_maxncpy); } CustomDataLayer *BKE_id_attribute_new(ID *id, @@ -740,9 +729,6 @@ int *BKE_id_attributes_active_index_p(ID *id) case ID_CV: { return &((Curves *)id)->attributes_active_index; } - case ID_GP: { - return &((GreasePencil *)id)->attributes_active_index; - } default: return nullptr; } diff --git a/source/blender/blenkernel/intern/attribute_access.cc b/source/blender/blenkernel/intern/attribute_access.cc index 9a91e540367..9e905533506 100644 --- a/source/blender/blenkernel/intern/attribute_access.cc +++ b/source/blender/blenkernel/intern/attribute_access.cc @@ -147,18 +147,16 @@ static int attribute_domain_priority(const eAttrDomain domain) switch (domain) { case ATTR_DOMAIN_INSTANCE: return 0; - case ATTR_DOMAIN_LAYER: - return 1; case ATTR_DOMAIN_CURVE: - return 2; + return 1; case ATTR_DOMAIN_FACE: - return 3; + return 2; case ATTR_DOMAIN_EDGE: - return 4; + return 3; case ATTR_DOMAIN_POINT: - return 5; + return 4; case ATTR_DOMAIN_CORNER: - return 6; + return 5; default: /* Domain not supported in nodes yet. */ BLI_assert_unreachable(); @@ -594,6 +592,155 @@ bool CustomDataAttributeProvider::foreach_attribute(const void *owner, return true; } +CustomDataAttributes::CustomDataAttributes() +{ + CustomData_reset(&data); + size_ = 0; +} + +CustomDataAttributes::~CustomDataAttributes() +{ + CustomData_free(&data, size_); +} + +CustomDataAttributes::CustomDataAttributes(const CustomDataAttributes &other) +{ + size_ = other.size_; + CustomData_copy(&other.data, &data, CD_MASK_ALL, size_); +} + +CustomDataAttributes::CustomDataAttributes(CustomDataAttributes &&other) +{ + size_ = other.size_; + data = other.data; + CustomData_reset(&other.data); + other.size_ = 0; +} + +CustomDataAttributes &CustomDataAttributes::operator=(const CustomDataAttributes &other) +{ + if (this == &other) { + return *this; + } + this->~CustomDataAttributes(); + new (this) CustomDataAttributes(other); + return *this; +} + +CustomDataAttributes &CustomDataAttributes::operator=(CustomDataAttributes &&other) +{ + if (this == &other) { + return *this; + } + this->~CustomDataAttributes(); + new (this) CustomDataAttributes(std::move(other)); + return *this; +} + +std::optional CustomDataAttributes::get_for_read(const AttributeIDRef &attribute_id) const +{ + for (const CustomDataLayer &layer : Span(data.layers, data.totlayer)) { + if (custom_data_layer_matches_attribute_id(layer, attribute_id)) { + const CPPType *cpp_type = custom_data_type_to_cpp_type((eCustomDataType)layer.type); + BLI_assert(cpp_type != nullptr); + return GSpan(*cpp_type, layer.data, size_); + } + } + return {}; +} + +GVArray CustomDataAttributes::get_for_read(const AttributeIDRef &attribute_id, + const eCustomDataType data_type, + const void *default_value) const +{ + const CPPType *type = blender::bke::custom_data_type_to_cpp_type(data_type); + + std::optional attribute = this->get_for_read(attribute_id); + if (!attribute) { + const int domain_num = this->size_; + return GVArray::ForSingle( + *type, domain_num, (default_value == nullptr) ? type->default_value() : default_value); + } + + if (attribute->type() == *type) { + return GVArray::ForSpan(*attribute); + } + const blender::bke::DataTypeConversions &conversions = + blender::bke::get_implicit_type_conversions(); + return conversions.try_convert(GVArray::ForSpan(*attribute), *type); +} + +std::optional CustomDataAttributes::get_for_write(const AttributeIDRef &attribute_id) +{ + for (CustomDataLayer &layer : MutableSpan(data.layers, data.totlayer)) { + if (custom_data_layer_matches_attribute_id(layer, attribute_id)) { + const CPPType *cpp_type = custom_data_type_to_cpp_type((eCustomDataType)layer.type); + BLI_assert(cpp_type != nullptr); + return GMutableSpan(*cpp_type, layer.data, size_); + } + } + return {}; +} + +bool CustomDataAttributes::create(const AttributeIDRef &attribute_id, + const eCustomDataType data_type) +{ + void *result = add_generic_custom_data_layer( + data, data_type, CD_SET_DEFAULT, size_, attribute_id); + return result != nullptr; +} + +bool CustomDataAttributes::remove(const AttributeIDRef &attribute_id) +{ + for (const int i : IndexRange(data.totlayer)) { + const CustomDataLayer &layer = data.layers[i]; + if (custom_data_layer_matches_attribute_id(layer, attribute_id)) { + CustomData_free_layer(&data, eCustomDataType(layer.type), size_, i); + return true; + } + } + return false; +} + +void CustomDataAttributes::reallocate(const int size) +{ + const int old_size = size_; + size_ = size; + CustomData_realloc(&data, old_size, size_); + if (size_ > old_size) { + /* Fill default new values. */ + const int new_elements_num = size_ - old_size; + this->foreach_attribute( + [&](const bke::AttributeIDRef &id, const bke::AttributeMetaData /*meta_data*/) { + GMutableSpan new_data = this->get_for_write(id)->take_back(new_elements_num); + const CPPType &type = new_data.type(); + type.fill_assign_n(type.default_value(), new_data.data(), new_data.size()); + return true; + }, + /* Dummy. */ + ATTR_DOMAIN_POINT); + } +} + +void CustomDataAttributes::clear() +{ + CustomData_free(&data, size_); + size_ = 0; +} + +bool CustomDataAttributes::foreach_attribute(const AttributeForeachCallback callback, + const eAttrDomain domain) const +{ + for (const CustomDataLayer &layer : Span(data.layers, data.totlayer)) { + AttributeMetaData meta_data{domain, (eCustomDataType)layer.type}; + const AttributeIDRef attribute_id = attribute_id_from_custom_data_layer(layer); + if (!callback(attribute_id, meta_data)) { + return false; + } + } + return true; +} + /* -------------------------------------------------------------------- */ /** \name Attribute API * \{ */ @@ -1012,38 +1159,4 @@ void copy_attributes(const AttributeAccessor src_attributes, dst_attributes); } -void copy_attributes_group_to_group(const AttributeAccessor src_attributes, - const eAttrDomain domain, - const AnonymousAttributePropagationInfo &propagation_info, - const Set &skip, - const OffsetIndices src_offsets, - const OffsetIndices dst_offsets, - const IndexMask &selection, - MutableAttributeAccessor dst_attributes) -{ - if (selection.is_empty()) { - return; - } - src_attributes.for_all([&](const AttributeIDRef &id, const AttributeMetaData meta_data) { - if (meta_data.domain != domain) { - return true; - } - if (id.is_anonymous() && !propagation_info.propagate(id.anonymous_id())) { - return true; - } - if (skip.contains(id.name())) { - return true; - } - const GVArraySpan src = *src_attributes.lookup(id, domain); - bke::GSpanAttributeWriter dst = dst_attributes.lookup_or_add_for_write_only_span( - id, domain, meta_data.data_type); - if (!dst) { - return true; - } - array_utils::copy_group_to_group(src_offsets, dst_offsets, selection, src, dst.span); - dst.finish(); - return true; - }); -} - } // namespace blender::bke diff --git a/source/blender/blenkernel/intern/attribute_access_intern.hh b/source/blender/blenkernel/intern/attribute_access_intern.hh index 890ba7c9944..2f25841946a 100644 --- a/source/blender/blenkernel/intern/attribute_access_intern.hh +++ b/source/blender/blenkernel/intern/attribute_access_intern.hh @@ -128,8 +128,8 @@ class DynamicAttributesProvider { class CustomDataAttributeProvider final : public DynamicAttributesProvider { private: static constexpr uint64_t supported_types_mask = CD_MASK_PROP_ALL; - eAttrDomain domain_; - CustomDataAccessInfo custom_data_access_; + const eAttrDomain domain_; + const CustomDataAccessInfo custom_data_access_; public: CustomDataAttributeProvider(const eAttrDomain domain, diff --git a/source/blender/blenkernel/intern/bake_geometry_nodes_modifier.cc b/source/blender/blenkernel/intern/bake_geometry_nodes_modifier.cc index 569cf5ebd28..fe53d78abff 100644 --- a/source/blender/blenkernel/intern/bake_geometry_nodes_modifier.cc +++ b/source/blender/blenkernel/intern/bake_geometry_nodes_modifier.cc @@ -18,7 +18,7 @@ #include "BLI_hash_md5.h" #include "BLI_path_util.h" #include "BLI_string.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "MOD_nodes.hh" diff --git a/source/blender/blenkernel/intern/bake_items_paths.cc b/source/blender/blenkernel/intern/bake_items_paths.cc index 6945dacebfc..2c7055fa494 100644 --- a/source/blender/blenkernel/intern/bake_items_paths.cc +++ b/source/blender/blenkernel/intern/bake_items_paths.cc @@ -7,7 +7,7 @@ #include "BLI_fileops.hh" #include "BLI_path_util.h" #include "BLI_string.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" namespace blender::bke::bake { diff --git a/source/blender/blenkernel/intern/blender.cc b/source/blender/blenkernel/intern/blender.cc index 32d954a2292..fa576b424e8 100644 --- a/source/blender/blenkernel/intern/blender.cc +++ b/source/blender/blenkernel/intern/blender.cc @@ -181,7 +181,7 @@ void BKE_blender_globals_init() BKE_blender_globals_main_replace(BKE_main_new()); - STRNCPY(G.filepath_last_image, "//"); + STRNCPY(G.ima, "//"); #ifndef WITH_PYTHON_SECURITY /* default */ G.f |= G_FLAG_SCRIPT_AUTOEXEC; diff --git a/source/blender/blenkernel/intern/constraint.cc b/source/blender/blenkernel/intern/constraint.cc index 7f1cfed2e84..d3922fbb954 100644 --- a/source/blender/blenkernel/intern/constraint.cc +++ b/source/blender/blenkernel/intern/constraint.cc @@ -23,7 +23,7 @@ #include "BLI_math_matrix.h" #include "BLI_math_rotation.h" #include "BLI_math_vector.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_utildefines.h" #include "BLT_translation.h" diff --git a/source/blender/blenkernel/intern/crazyspace.cc b/source/blender/blenkernel/intern/crazyspace.cc index d7c062e6136..6a92514130f 100644 --- a/source/blender/blenkernel/intern/crazyspace.cc +++ b/source/blender/blenkernel/intern/crazyspace.cc @@ -670,88 +670,35 @@ GeometryDeformation get_evaluated_curves_deformation(const Depsgraph &depsgraph, GeometryDeformation get_evaluated_grease_pencil_drawing_deformation(const Object *ob_eval, const Object &ob_orig, - const int layer_index) + const int drawing_index) { BLI_assert(ob_orig.type == OB_GREASE_PENCIL); - const GreasePencil &grease_pencil = *static_cast(ob_eval->data); const GreasePencil &grease_pencil_orig = *static_cast(ob_orig.data); - const int eval_frame = grease_pencil.runtime->eval_frame; - const Span layers_orig = grease_pencil_orig.layers(); - BLI_assert(layer_index >= 0 && layer_index < layers_orig.size()); - const int drawing_index = layers_orig[layer_index]->drawing_index_at(eval_frame); - if (drawing_index == -1) { - return {}; - } const GreasePencilDrawingBase *drawing_base = grease_pencil_orig.drawing(drawing_index); - if (drawing_base->type != GP_DRAWING) { - return {}; - } - const bke::greasepencil::Drawing &drawing_orig = - reinterpret_cast(drawing_base)->wrap(); GeometryDeformation deformation; - /* Use the undeformed positions by default. */ - deformation.positions = drawing_orig.strokes().positions(); + if (drawing_base->type == GP_DRAWING) { + const auto *drawing = reinterpret_cast(drawing_base); + /* Use the undeformed positions by default. */ + deformation.positions = drawing->wrap().strokes().positions(); + } + else if (drawing_base->type == GP_DRAWING_REFERENCE) { + /* TODO */ + } if (ob_eval == nullptr) { return deformation; } const GeometrySet *geometry_eval = ob_eval->runtime.geometry_set_eval; - if (geometry_eval == nullptr || !geometry_eval->has()) { + if (geometry_eval == nullptr) { return deformation; } - /* If there are edit hints, use the positions of those. */ - const GeometryComponentEditData &edit_component_eval = - *geometry_eval->get_component(); - const GreasePencilEditHints *edit_hints = edit_component_eval.grease_pencil_edit_hints_.get(); - if (edit_hints != nullptr && &edit_hints->grease_pencil_id_orig == &grease_pencil_orig && - edit_hints->drawing_hints.has_value()) - { - BLI_assert(edit_hints->drawing_hints->size() == layers_orig.size()); - const GreasePencilDrawingEditHints &drawing_hints = - edit_hints->drawing_hints.value()[layer_index]; - if (drawing_hints.positions.has_value()) { - deformation.positions = *drawing_hints.positions; - return deformation; - } - } + /* TODO: Read `GeometryComponentEditData` from `geometry_eval` and populate deformation with it. + */ - /* Otherwise use the positions of the evaluated drawing if the number of points match. */ - if (const GreasePencilComponent *grease_pencil_component_eval = - geometry_eval->get_component()) - { - if (const GreasePencil *grease_pencil_eval = grease_pencil_component_eval->get()) { - Span layers_eval = grease_pencil_eval->layers(); - if (layers_eval.size() != layers_orig.size()) { - const bke::greasepencil::Layer *layer_eval = layers_eval[layer_index]; - const int drawing_index_eval = layer_eval->drawing_index_at(eval_frame); - if (drawing_index_eval != -1) { - const GreasePencilDrawingBase *drawing_base_eval = grease_pencil_eval->drawing( - drawing_index_eval); - if (drawing_base_eval->type != GP_DRAWING) { - return deformation; - } - const bke::greasepencil::Drawing &drawing_eval = - reinterpret_cast(drawing_base_eval)->wrap(); - if (drawing_eval.strokes().points_num() == drawing_orig.strokes().points_num()) { - deformation.positions = drawing_eval.strokes().positions(); - return deformation; - } - } - } - } - } return deformation; } -GeometryDeformation get_evaluated_grease_pencil_drawing_deformation(const Depsgraph &depsgraph, - const Object &ob_orig, - const int layer_index) -{ - const Object *ob_eval = DEG_get_evaluated_object(&depsgraph, const_cast(&ob_orig)); - return get_evaluated_grease_pencil_drawing_deformation(ob_eval, ob_orig, layer_index); -} - } // namespace blender::bke::crazyspace diff --git a/source/blender/blenkernel/intern/curve_bezier.cc b/source/blender/blenkernel/intern/curve_bezier.cc index 3223944f819..5152a6e2b97 100644 --- a/source/blender/blenkernel/intern/curve_bezier.cc +++ b/source/blender/blenkernel/intern/curve_bezier.cc @@ -208,47 +208,31 @@ void calculate_auto_handles(const bool cyclic, positions_right.last()); } -template -void evaluate_segment_ex( - const T &point_0, const T &point_1, const T &point_2, const T &point_3, MutableSpan result) -{ - BLI_assert(result.size() > 0); - const float inv_len = 1.0f / float(result.size()); - const float inv_len_squared = inv_len * inv_len; - const float inv_len_cubed = inv_len_squared * inv_len; - - const T rt1 = 3.0f * (point_1 - point_0) * inv_len; - const T rt2 = 3.0f * (point_0 - 2.0f * point_1 + point_2) * inv_len_squared; - const T rt3 = (point_3 - point_0 + 3.0f * (point_1 - point_2)) * inv_len_cubed; - - T q0 = point_0; - T q1 = rt1 + rt2 + rt3; - T q2 = 2.0f * rt2 + 6.0f * rt3; - T q3 = 6.0f * rt3; - for (const int i : result.index_range()) { - result[i] = q0; - q0 += q1; - q1 += q2; - q2 += q3; - } -} -template<> void evaluate_segment(const float3 &point_0, const float3 &point_1, const float3 &point_2, const float3 &point_3, MutableSpan result) { - evaluate_segment_ex(point_0, point_1, point_2, point_3, result); -} -template<> -void evaluate_segment(const float2 &point_0, - const float2 &point_1, - const float2 &point_2, - const float2 &point_3, - MutableSpan result) -{ - evaluate_segment_ex(point_0, point_1, point_2, point_3, result); + BLI_assert(result.size() > 0); + const float inv_len = 1.0f / float(result.size()); + const float inv_len_squared = inv_len * inv_len; + const float inv_len_cubed = inv_len_squared * inv_len; + + const float3 rt1 = 3.0f * (point_1 - point_0) * inv_len; + const float3 rt2 = 3.0f * (point_0 - 2.0f * point_1 + point_2) * inv_len_squared; + const float3 rt3 = (point_3 - point_0 + 3.0f * (point_1 - point_2)) * inv_len_cubed; + + float3 q0 = point_0; + float3 q1 = rt1 + rt2 + rt3; + float3 q2 = 2.0f * rt2 + 6.0f * rt3; + float3 q3 = 6.0f * rt3; + for (const int i : result.index_range()) { + result[i] = q0; + q0 += q1; + q1 += q2; + q2 += q3; + } } void calculate_evaluated_positions(const Span positions, diff --git a/source/blender/blenkernel/intern/curve_to_mesh_convert.cc b/source/blender/blenkernel/intern/curve_to_mesh_convert.cc index 7dd5eed1df8..94f487fc723 100644 --- a/source/blender/blenkernel/intern/curve_to_mesh_convert.cc +++ b/source/blender/blenkernel/intern/curve_to_mesh_convert.cc @@ -804,6 +804,8 @@ Mesh *curve_to_mesh_sweep(const CurvesGeometry &main, CustomData_free_layer_named(&mesh->vert_data, "position", 0); mesh->totvert = offsets.vert.last(); + mesh->flag |= ME_AUTOSMOOTH; + mesh->smoothresh = DEG2RADF(180.0f); MutableSpan edges = mesh->edges_for_write(); MutableSpan face_offsets = mesh->face_offsets_for_write(); MutableSpan corner_verts = mesh->corner_verts_for_write(); diff --git a/source/blender/blenkernel/intern/curves.cc b/source/blender/blenkernel/intern/curves.cc index 67a9ca6f9db..449bd0eca13 100644 --- a/source/blender/blenkernel/intern/curves.cc +++ b/source/blender/blenkernel/intern/curves.cc @@ -170,21 +170,28 @@ void *BKE_curves_add(Main *bmain, const char *name) return curves; } -BoundBox BKE_curves_boundbox_get(Object *ob) +BoundBox *BKE_curves_boundbox_get(Object *ob) { using namespace blender; BLI_assert(ob->type == OB_CURVES); const Curves *curves_id = static_cast(ob->data); - const bke::CurvesGeometry &curves = curves_id->geometry.wrap(); - BoundBox bb; - if (const std::optional> bounds = curves.bounds_min_max()) { - BKE_boundbox_init_from_minmax(&bb, bounds->min, bounds->max); + if (ob->runtime.bb != nullptr && (ob->runtime.bb->flag & BOUNDBOX_DIRTY) == 0) { + return ob->runtime.bb; } - else { - BKE_boundbox_init_from_minmax(&bb, float3(-1), float3(1)); + + if (ob->runtime.bb == nullptr) { + ob->runtime.bb = MEM_cnew(__func__); + const bke::CurvesGeometry &curves = curves_id->geometry.wrap(); + if (const std::optional> bounds = curves.bounds_min_max()) { + BKE_boundbox_init_from_minmax(ob->runtime.bb, bounds->min, bounds->max); + } + else { + BKE_boundbox_init_from_minmax(ob->runtime.bb, float3(-1), float3(1)); + } } - return bb; + + return ob->runtime.bb; } bool BKE_curves_attribute_required(const Curves * /*curves*/, const char *name) diff --git a/source/blender/blenkernel/intern/curves_geometry.cc b/source/blender/blenkernel/intern/curves_geometry.cc index 84909f4dd09..3d50f7f9685 100644 --- a/source/blender/blenkernel/intern/curves_geometry.cc +++ b/source/blender/blenkernel/intern/curves_geometry.cc @@ -677,7 +677,7 @@ Span CurvesGeometry::evaluated_positions() const }); }; auto evaluate_poly = [&](const IndexMask &selection) { - array_utils::copy_group_to_group( + curves::copy_point_data( points_by_curve, evaluated_points_by_curve, selection, positions, evaluated_positions); }; auto evaluate_bezier = [&](const IndexMask &selection) { diff --git a/source/blender/blenkernel/intern/curves_utils.cc b/source/blender/blenkernel/intern/curves_utils.cc index 68ff6719cfd..37ce8603e9d 100644 --- a/source/blender/blenkernel/intern/curves_utils.cc +++ b/source/blender/blenkernel/intern/curves_utils.cc @@ -10,6 +10,20 @@ namespace blender::bke::curves { +void copy_point_data(const OffsetIndices src_points_by_curve, + const OffsetIndices dst_points_by_curve, + const IndexMask &src_curve_selection, + const GSpan src, + GMutableSpan dst) +{ + src_curve_selection.foreach_index(GrainSize(512), [&](const int i) { + const IndexRange src_points = src_points_by_curve[i]; + const IndexRange dst_points = dst_points_by_curve[i]; + /* The arrays might be large, so a threaded copy might make sense here too. */ + dst.slice(dst_points).copy_from(src.slice(src_points)); + }); +} + void fill_points(const OffsetIndices points_by_curve, const IndexMask &curve_selection, const GPointer value, diff --git a/source/blender/blenkernel/intern/customdata.cc b/source/blender/blenkernel/intern/customdata.cc index 0788076332a..3efffe4ac11 100644 --- a/source/blender/blenkernel/intern/customdata.cc +++ b/source/blender/blenkernel/intern/customdata.cc @@ -33,7 +33,7 @@ #include "BLI_string.h" #include "BLI_string_ref.hh" #include "BLI_string_utf8.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_utildefines.h" #ifndef NDEBUG @@ -2414,13 +2414,15 @@ static void ensure_layer_data_is_mutable(CustomDataLayer &layer, const int totel } } -[[maybe_unused]] static bool layer_is_mutable(CustomDataLayer &layer) +#ifndef NDEBUG +static bool layer_is_mutable(CustomDataLayer &layer) { if (layer.sharing_info == nullptr) { return true; } return layer.sharing_info->is_mutable(); } +#endif void CustomData_ensure_data_is_mutable(CustomDataLayer *layer, const int totelem) { @@ -2434,10 +2436,7 @@ void CustomData_ensure_layers_are_mutable(struct CustomData *data, int totelem) } } -void CustomData_realloc(CustomData *data, - const int old_size, - const int new_size, - const eCDAllocType alloctype) +void CustomData_realloc(CustomData *data, const int old_size, const int new_size) { BLI_assert(new_size >= 0); for (int i = 0; i < data->totlayer; i++) { @@ -2470,25 +2469,10 @@ void CustomData_realloc(CustomData *data, } if (new_size > old_size) { - const int new_elements_num = new_size - old_size; - void *new_elements_begin = POINTER_OFFSET(layer->data, old_size_in_bytes); - switch (alloctype) { - case CD_CONSTRUCT: { - /* Initialize new values for non-trivial types. */ - if (typeInfo->construct) { - typeInfo->construct(new_elements_begin, new_elements_num); - } - break; - } - case CD_SET_DEFAULT: { - if (typeInfo->set_default_value) { - typeInfo->set_default_value(new_elements_begin, new_elements_num); - } - else { - memset(new_elements_begin, 0, typeInfo->size * new_elements_num); - } - break; - } + /* Initialize new values for non-trivial types. */ + if (typeInfo->construct) { + const int new_elements_num = new_size - old_size; + typeInfo->construct(POINTER_OFFSET(layer->data, old_size_in_bytes), new_elements_num); } } } diff --git a/source/blender/blenkernel/intern/data_transfer.cc b/source/blender/blenkernel/intern/data_transfer.cc index 7912f3844b6..bbc2ccc0526 100644 --- a/source/blender/blenkernel/intern/data_transfer.cc +++ b/source/blender/blenkernel/intern/data_transfer.cc @@ -70,7 +70,7 @@ void BKE_object_data_transfer_dttypes_to_cdmask(const int dtdata_types, r_data_masks->lmask |= CD_MASK_PROP_FLOAT2; } else if (cddata_type == CD_FAKE_LNOR) { - r_data_masks->lmask |= CD_MASK_CUSTOMLOOPNORMAL; + r_data_masks->lmask |= CD_MASK_NORMAL | CD_MASK_CUSTOMLOOPNORMAL; } } } @@ -353,6 +353,60 @@ static void data_transfer_mesh_attributes_transfer_default_color_string( /* ********** */ +/* Generic pre/post processing, only used by custom loop normals currently. */ + +static void data_transfer_dtdata_type_preprocess(const Mesh *me_src, + Mesh *me_dst, + const int dtdata_type, + const bool dirty_nors_dst) +{ + if (dtdata_type == DT_TYPE_LNOR) { + /* Compute custom normals into regular loop normals, which will be used for the transfer. */ + CustomData *ldata_dst = &me_dst->loop_data; + + const bool use_split_nors_dst = (me_dst->flag & ME_AUTOSMOOTH) != 0; + const float split_angle_dst = me_dst->smoothresh; + + /* This should be ensured by cddata_masks we pass to code generating/giving us me_src now. */ + BLI_assert(CustomData_get_layer(&me_src->loop_data, CD_NORMAL) != nullptr); + (void)me_src; + + const blender::short2 *custom_nors_dst = static_cast( + CustomData_get_layer(ldata_dst, CD_CUSTOMLOOPNORMAL)); + + /* Cache loop nors into a temp CDLayer. */ + blender::float3 *loop_nors_dst = static_cast( + CustomData_get_layer_for_write(ldata_dst, CD_NORMAL, me_dst->totloop)); + const bool do_loop_nors_dst = (loop_nors_dst == nullptr); + if (do_loop_nors_dst) { + loop_nors_dst = static_cast( + CustomData_add_layer(ldata_dst, CD_NORMAL, CD_SET_DEFAULT, me_dst->totloop)); + CustomData_set_layer_flag(ldata_dst, CD_NORMAL, CD_FLAG_TEMPORARY); + } + if (dirty_nors_dst || do_loop_nors_dst) { + const bool *sharp_edges = static_cast( + CustomData_get_layer_named(&me_dst->edge_data, CD_PROP_BOOL, "sharp_edge")); + const bool *sharp_faces = static_cast( + CustomData_get_layer_named(&me_dst->face_data, CD_PROP_BOOL, "sharp_face")); + blender::bke::mesh::normals_calc_loop(me_dst->vert_positions(), + me_dst->edges(), + me_dst->faces(), + me_dst->corner_verts(), + me_dst->corner_edges(), + me_dst->corner_to_face_map(), + me_dst->vert_normals(), + me_dst->face_normals(), + sharp_edges, + sharp_faces, + custom_nors_dst, + use_split_nors_dst, + split_angle_dst, + nullptr, + {loop_nors_dst, me_dst->totloop}); + } + } +} + static void data_transfer_dtdata_type_postprocess(Mesh *me_dst, const int dtdata_type, const bool changed) @@ -1298,6 +1352,8 @@ bool BKE_object_data_transfer_ex(Depsgraph *depsgraph, SpaceTransform auto_space_transform; const Mesh *me_src; + /* Assumed always true if not using an evaluated mesh as destination. */ + bool dirty_nors_dst = true; const MDeformVert *mdef = nullptr; int vg_idx = -1; @@ -1314,6 +1370,7 @@ bool BKE_object_data_transfer_ex(Depsgraph *depsgraph, BLI_assert((ob_src != ob_dst) && (ob_src->type == OB_MESH) && (ob_dst->type == OB_MESH)); if (me_dst) { + dirty_nors_dst = BKE_mesh_vert_normals_are_dirty(me_dst); /* Never create needed custom layers on passed destination mesh * (assumed to *not* be ob_dst->data, aka modifier case). */ use_create = false; @@ -1367,6 +1424,8 @@ bool BKE_object_data_transfer_ex(Depsgraph *depsgraph, continue; } + data_transfer_dtdata_type_preprocess(me_src, me_dst, dtdata_type, dirty_nors_dst); + cddata_type = BKE_object_data_transfer_dttype_to_cdtype(dtdata_type); fromto_idx = BKE_object_data_transfer_dttype_to_srcdst_index(dtdata_type); @@ -1421,6 +1480,7 @@ bool BKE_object_data_transfer_ex(Depsgraph *depsgraph, ray_radius, reinterpret_cast(positions_dst.data()), num_verts_dst, + dirty_nors_dst, me_src, me_dst, &geom_map[VDATA]); @@ -1500,6 +1560,7 @@ bool BKE_object_data_transfer_ex(Depsgraph *depsgraph, num_verts_dst, edges_dst.data(), edges_dst.size(), + dirty_nors_dst, me_src, me_dst, &geom_map[EDATA]); @@ -1547,8 +1608,11 @@ bool BKE_object_data_transfer_ex(Depsgraph *depsgraph, if (DT_DATATYPE_IS_LOOP(dtdata_type)) { const blender::Span positions_dst = me_dst->vert_positions(); const int num_verts_dst = me_dst->totvert; + const blender::Span edges_dst = me_dst->edges(); const blender::OffsetIndices faces_dst = me_dst->faces(); const blender::Span corner_verts_dst = me_dst->corner_verts(); + const blender::Span corner_edges_dst = me_dst->corner_edges(); + CustomData *ldata_dst = &me_dst->loop_data; MeshRemapIslandsCalc island_callback = data_transfer_get_loop_islands_generator(cddata_type); @@ -1586,9 +1650,16 @@ bool BKE_object_data_transfer_ex(Depsgraph *depsgraph, me_dst, reinterpret_cast(positions_dst.data()), num_verts_dst, + edges_dst.data(), + edges_dst.size(), corner_verts_dst.data(), + corner_edges_dst.data(), corner_verts_dst.size(), faces_dst, + ldata_dst, + (me_dst->flag & ME_AUTOSMOOTH) != 0, + me_dst->smoothresh, + dirty_nors_dst, me_src, island_callback, islands_handling_precision, diff --git a/source/blender/blenkernel/intern/deform.cc b/source/blender/blenkernel/intern/deform.cc index 52bac5d890f..419919a54bb 100644 --- a/source/blender/blenkernel/intern/deform.cc +++ b/source/blender/blenkernel/intern/deform.cc @@ -24,7 +24,7 @@ #include "BLI_listbase.h" #include "BLI_math_vector.h" #include "BLI_string.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_utildefines.h" #include "BLT_translation.h" diff --git a/source/blender/blenkernel/intern/displist.cc b/source/blender/blenkernel/intern/displist.cc index 4583b73c63a..f018bb97a9c 100644 --- a/source/blender/blenkernel/intern/displist.cc +++ b/source/blender/blenkernel/intern/displist.cc @@ -758,6 +758,9 @@ static blender::bke::GeometrySet curve_calc_modifiers_post(Depsgraph *depsgraph, if (geometry_set.has_mesh()) { Mesh *final_mesh = geometry_set.get_mesh_for_write(); + + BKE_mesh_ensure_normals_for_display(final_mesh); + STRNCPY(final_mesh->id.name, cu->id.name); *((short *)final_mesh->id.name) = ID_ME; } diff --git a/source/blender/blenkernel/intern/dynamicpaint.cc b/source/blender/blenkernel/intern/dynamicpaint.cc index 311539b0b84..ef281d0cbf7 100644 --- a/source/blender/blenkernel/intern/dynamicpaint.cc +++ b/source/blender/blenkernel/intern/dynamicpaint.cc @@ -17,7 +17,7 @@ #include "BLI_math_geom.h" #include "BLI_math_matrix.h" #include "BLI_math_vector.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_task.h" #include "BLI_threads.h" #include "BLI_utildefines.h" diff --git a/source/blender/blenkernel/intern/editmesh.cc b/source/blender/blenkernel/intern/editmesh.cc index fef5ab7d5d3..f34fc1b3db9 100644 --- a/source/blender/blenkernel/intern/editmesh.cc +++ b/source/blender/blenkernel/intern/editmesh.cc @@ -252,9 +252,33 @@ float (*BKE_editmesh_vert_coords_alloc_orco(BMEditMesh *em, int *r_vert_len))[3] return BM_mesh_vert_coords_alloc(em->bm, r_vert_len); } -void BKE_editmesh_lnorspace_update(BMEditMesh *em) +void BKE_editmesh_lnorspace_update(BMEditMesh *em, Mesh *me) { - BM_lnorspace_update(em->bm); + BMesh *bm = em->bm; + + /* We need to create custom-loop-normals (CLNORS) data if none exist yet, + * otherwise there is no way to edit them. + * Similar code to #MESH_OT_customdata_custom_splitnormals_add operator, + * we want to keep same shading in case we were using auto-smooth so far. + * NOTE: there is a problem here, which is that if someone starts a normal editing operation on + * previously auto-smooth-ed mesh, and cancel that operation, generated CLNORS data remain, + * with related sharp edges (and hence auto-smooth is 'lost'). + * Not sure how critical this is, and how to fix that issue? */ + if (!CustomData_has_layer(&bm->ldata, CD_CUSTOMLOOPNORMAL)) { + if (me->flag & ME_AUTOSMOOTH) { + BM_edges_sharp_from_angle_set(bm, me->smoothresh); + } + } + + BM_lnorspace_update(bm); +} + +void BKE_editmesh_ensure_autosmooth(BMEditMesh *em, Mesh *me) +{ + if (!(me->flag & ME_AUTOSMOOTH)) { + me->flag |= ME_AUTOSMOOTH; + BKE_editmesh_lnorspace_update(em, me); + } } BoundBox *BKE_editmesh_cage_boundbox_get(Object *object, BMEditMesh * /*em*/) diff --git a/source/blender/blenkernel/intern/fcurve.cc b/source/blender/blenkernel/intern/fcurve.cc index cd14f1d7870..776a0632c95 100644 --- a/source/blender/blenkernel/intern/fcurve.cc +++ b/source/blender/blenkernel/intern/fcurve.cc @@ -23,7 +23,7 @@ #include "BLI_ghash.h" #include "BLI_math_vector.h" #include "BLI_sort_utils.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BKE_anim_data.h" #include "BKE_animsys.h" @@ -1975,7 +1975,7 @@ static float fcurve_eval_keyframes_interpolate(const FCurve *fcu, const float eps = 1.e-8f; uint a; - /* Evaluation-time occurs somewhere in the middle of the curve. */ + /* Evaltime occurs somewhere in the middle of the curve. */ bool exact = false; /* Use binary search to find appropriate keyframes... @@ -2020,7 +2020,7 @@ static float fcurve_eval_keyframes_interpolate(const FCurve *fcu, return 0.0f; } - /* Evaluation-time occurs within the interval defined by these two keyframes. */ + /* Evaltime occurs within the interval defined by these two keyframes. */ const float begin = prevbezt->vec[1][1]; const float change = bezt->vec[1][1] - prevbezt->vec[1][1]; const float duration = bezt->vec[1][0] - prevbezt->vec[1][0]; @@ -2351,10 +2351,10 @@ float evaluate_fcurve_driver(PathResolvedRNA *anim_rna, float evaltime = anim_eval_context->eval_time; /* If there is a driver (only if this F-Curve is acting as 'driver'), - * evaluate it to find value to use as `evaltime` since drivers essentially act as alternative + * evaluate it to find value to use as "evaltime" since drivers essentially act as alternative * input (i.e. in place of 'time') for F-Curves. */ if (fcu->driver) { - /* Evaluation-time now serves as input for the curve. */ + /* Evaltime now serves as input for the curve. */ evaltime = evaluate_driver(anim_rna, fcu->driver, driver_orig, anim_eval_context); /* Only do a default 1-1 mapping if it's unlikely that anything else will set a value... */ diff --git a/source/blender/blenkernel/intern/fcurve_driver.cc b/source/blender/blenkernel/intern/fcurve_driver.cc index e36852e2878..b28fadf90c8 100644 --- a/source/blender/blenkernel/intern/fcurve_driver.cc +++ b/source/blender/blenkernel/intern/fcurve_driver.cc @@ -20,7 +20,7 @@ #include "BLI_math_rotation.h" #include "BLI_math_vector.h" #include "BLI_string_utf8.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_threads.h" #include "BLI_utildefines.h" diff --git a/source/blender/blenkernel/intern/freestyle.cc b/source/blender/blenkernel/intern/freestyle.cc index 590383341af..f93143d58fc 100644 --- a/source/blender/blenkernel/intern/freestyle.cc +++ b/source/blender/blenkernel/intern/freestyle.cc @@ -13,7 +13,7 @@ #include "BLI_blenlib.h" #include "BLI_math_rotation.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLT_translation.h" diff --git a/source/blender/blenkernel/intern/geometry_component_edit_data.cc b/source/blender/blenkernel/intern/geometry_component_edit_data.cc index 7a022d78b5c..0994dc55472 100644 --- a/source/blender/blenkernel/intern/geometry_component_edit_data.cc +++ b/source/blender/blenkernel/intern/geometry_component_edit_data.cc @@ -4,7 +4,6 @@ #include "BKE_curves.hh" #include "BKE_geometry_set.hh" -#include "BKE_grease_pencil.hh" namespace blender::bke { @@ -16,10 +15,6 @@ GeometryComponent *GeometryComponentEditData::copy() const if (curves_edit_hints_) { new_component->curves_edit_hints_ = std::make_unique(*curves_edit_hints_); } - if (grease_pencil_edit_hints_) { - new_component->grease_pencil_edit_hints_ = std::make_unique( - *grease_pencil_edit_hints_); - } return new_component; } @@ -37,18 +32,24 @@ void GeometryComponentEditData::clear() { BLI_assert(this->is_mutable() || this->is_expired()); curves_edit_hints_.reset(); - grease_pencil_edit_hints_.reset(); } -static void remember_deformed_curve_positions_if_necessary( - const Curves *curves_id, GeometryComponentEditData &edit_component) +void GeometryComponentEditData::remember_deformed_curve_positions_if_necessary( + GeometrySet &geometry) { + /* This component should be created at the start of object evaluation if it's necessary. */ + if (!geometry.has()) { + return; + } + GeometryComponentEditData &edit_component = + geometry.get_component_for_write(); if (!edit_component.curves_edit_hints_) { return; } if (edit_component.curves_edit_hints_->positions.has_value()) { return; } + const Curves *curves_id = geometry.get_curves(); if (curves_id == nullptr) { return; } @@ -61,57 +62,4 @@ static void remember_deformed_curve_positions_if_necessary( edit_component.curves_edit_hints_->positions->as_mutable_span().copy_from(curves.positions()); } -static void remember_deformed_grease_pencil_if_necessary(const GreasePencil *grease_pencil, - GeometryComponentEditData &edit_component) -{ - if (!edit_component.grease_pencil_edit_hints_) { - return; - } - if (edit_component.grease_pencil_edit_hints_->drawing_hints.has_value()) { - return; - } - if (grease_pencil == nullptr) { - return; - } - const GreasePencil &orig_grease_pencil = - edit_component.grease_pencil_edit_hints_->grease_pencil_id_orig; - const Span layers = grease_pencil->layers(); - const Span orig_layers = orig_grease_pencil.layers(); - const int layers_num = layers.size(); - if (layers_num != orig_layers.size()) { - return; - } - edit_component.grease_pencil_edit_hints_->drawing_hints.emplace(layers_num); - MutableSpan all_hints = - *edit_component.grease_pencil_edit_hints_->drawing_hints; - for (const int layer_index : layers.index_range()) { - const bke::greasepencil::Drawing *drawing = greasepencil::get_eval_grease_pencil_layer_drawing( - *grease_pencil, layer_index); - const bke::greasepencil::Layer &orig_layer = *orig_layers[layer_index]; - const bke::greasepencil::Drawing *orig_drawing = orig_grease_pencil.get_drawing_at( - &orig_layer, grease_pencil->runtime->eval_frame); - GreasePencilDrawingEditHints &drawing_hints = all_hints[layer_index]; - - if (!drawing || !orig_drawing) { - continue; - } - if (drawing->strokes().points_num() != orig_drawing->strokes().points_num()) { - continue; - } - drawing_hints.positions.emplace(drawing->strokes().positions()); - } -} - -void GeometryComponentEditData::remember_deformed_positions_if_necessary(GeometrySet &geometry) -{ - /* This component should be created at the start of object evaluation if it's necessary. */ - if (!geometry.has()) { - return; - } - GeometryComponentEditData &edit_component = - geometry.get_component_for_write(); - remember_deformed_curve_positions_if_necessary(geometry.get_curves(), edit_component); - remember_deformed_grease_pencil_if_necessary(geometry.get_grease_pencil(), edit_component); -} - } // namespace blender::bke diff --git a/source/blender/blenkernel/intern/geometry_component_grease_pencil.cc b/source/blender/blenkernel/intern/geometry_component_grease_pencil.cc index ff8308ea39c..80480243eb6 100644 --- a/source/blender/blenkernel/intern/geometry_component_grease_pencil.cc +++ b/source/blender/blenkernel/intern/geometry_component_grease_pencil.cc @@ -6,10 +6,6 @@ #include "BKE_grease_pencil.hh" #include "BKE_lib_id.h" -#include "DNA_grease_pencil_types.h" - -#include "attribute_access_intern.hh" - namespace blender::bke { /* -------------------------------------------------------------------- */ @@ -99,103 +95,4 @@ void GreasePencilComponent::ensure_owns_direct_data() } } -static ComponentAttributeProviders create_attribute_providers_for_grease_pencil() -{ - static CustomDataAccessInfo layers_access = { - [](void *owner) -> CustomData * { - GreasePencil &grease_pencil = *static_cast(owner); - return &grease_pencil.layers_data; - }, - [](const void *owner) -> const CustomData * { - const GreasePencil &grease_pencil = *static_cast(owner); - return &grease_pencil.layers_data; - }, - [](const void *owner) -> int { - const GreasePencil &grease_pencil = *static_cast(owner); - return grease_pencil.layers().size(); - }}; - - static CustomDataAttributeProvider layer_custom_data(ATTR_DOMAIN_LAYER, layers_access); - - return ComponentAttributeProviders({}, {&layer_custom_data}); -} - -static GVArray adapt_grease_pencil_attribute_domain(const GreasePencil & /*grease_pencil*/, - const GVArray &varray, - const eAttrDomain from, - const eAttrDomain to) -{ - if (from == to) { - return varray; - } - return {}; -} - -static AttributeAccessorFunctions get_grease_pencil_accessor_functions() -{ - static const ComponentAttributeProviders providers = - create_attribute_providers_for_grease_pencil(); - AttributeAccessorFunctions fn = - attribute_accessor_functions::accessor_functions_for_providers(); - fn.domain_size = [](const void *owner, const eAttrDomain domain) { - if (owner == nullptr) { - return 0; - } - const GreasePencil &grease_pencil = *static_cast(owner); - switch (domain) { - case ATTR_DOMAIN_LAYER: - return int(grease_pencil.layers().size()); - default: - return 0; - } - }; - fn.domain_supported = [](const void * /*owner*/, const eAttrDomain domain) { - return domain == ATTR_DOMAIN_LAYER; - }; - fn.adapt_domain = [](const void *owner, - const GVArray &varray, - const eAttrDomain from_domain, - const eAttrDomain to_domain) -> GVArray { - if (owner == nullptr) { - return {}; - } - const GreasePencil &grease_pencil = *static_cast(owner); - return adapt_grease_pencil_attribute_domain(grease_pencil, varray, from_domain, to_domain); - }; - return fn; -} - -static const AttributeAccessorFunctions &get_grease_pencil_accessor_functions_ref() -{ - static const AttributeAccessorFunctions fn = get_grease_pencil_accessor_functions(); - return fn; -} - -} // namespace blender::bke - -blender::bke::AttributeAccessor GreasePencil::attributes() const -{ - return blender::bke::AttributeAccessor(this, - blender::bke::get_grease_pencil_accessor_functions_ref()); -} - -blender::bke::MutableAttributeAccessor GreasePencil::attributes_for_write() -{ - return blender::bke::MutableAttributeAccessor( - this, blender::bke::get_grease_pencil_accessor_functions_ref()); -} - -namespace blender::bke { - -std::optional GreasePencilComponent::attributes() const -{ - return AttributeAccessor(grease_pencil_, get_grease_pencil_accessor_functions_ref()); -} - -std::optional GreasePencilComponent::attributes_for_write() -{ - GreasePencil *grease_pencil = this->get_for_write(); - return MutableAttributeAccessor(grease_pencil, get_grease_pencil_accessor_functions_ref()); -} - } // namespace blender::bke diff --git a/source/blender/blenkernel/intern/geometry_component_instances.cc b/source/blender/blenkernel/intern/geometry_component_instances.cc index d019b65f84f..81fe794bb63 100644 --- a/source/blender/blenkernel/intern/geometry_component_instances.cc +++ b/source/blender/blenkernel/intern/geometry_component_instances.cc @@ -169,11 +169,11 @@ static ComponentAttributeProviders create_attribute_providers_for_instances() static CustomDataAccessInfo instance_custom_data_access = { [](void *owner) -> CustomData * { Instances *instances = static_cast(owner); - return &instances->custom_data_attributes(); + return &instances->custom_data_attributes().data; }, [](const void *owner) -> const CustomData * { const Instances *instances = static_cast(owner); - return &instances->custom_data_attributes(); + return &instances->custom_data_attributes().data; }, [](const void *owner) -> int { const Instances *instances = static_cast(owner); diff --git a/source/blender/blenkernel/intern/geometry_component_mesh.cc b/source/blender/blenkernel/intern/geometry_component_mesh.cc index 5b2744abdab..ace3d1ccd79 100644 --- a/source/blender/blenkernel/intern/geometry_component_mesh.cc +++ b/source/blender/blenkernel/intern/geometry_component_mesh.cc @@ -858,13 +858,6 @@ static void tag_component_positions_changed(void *owner) } } -static void tag_component_sharpness_changed(void *owner) -{ - if (Mesh *mesh = static_cast(owner)) { - BKE_mesh_tag_sharpness_changed(mesh); - } -} - /** * This provider makes vertex groups available as float attributes. */ @@ -1082,7 +1075,7 @@ static ComponentAttributeProviders create_attribute_providers_for_mesh() BuiltinAttributeProvider::Creatable, BuiltinAttributeProvider::Deletable, face_access, - tag_component_sharpness_changed); + nullptr); static BuiltinCustomDataLayerProvider sharp_edge("sharp_edge", ATTR_DOMAIN_EDGE, @@ -1091,7 +1084,7 @@ static ComponentAttributeProviders create_attribute_providers_for_mesh() BuiltinAttributeProvider::Creatable, BuiltinAttributeProvider::Deletable, edge_access, - tag_component_sharpness_changed); + nullptr); static MeshVertexGroupsAttributeProvider vertex_groups; static CustomDataAttributeProvider corner_custom_data(ATTR_DOMAIN_CORNER, corner_access); diff --git a/source/blender/blenkernel/intern/geometry_fields.cc b/source/blender/blenkernel/intern/geometry_fields.cc index f1ee5c44833..31645c0f33b 100644 --- a/source/blender/blenkernel/intern/geometry_fields.cc +++ b/source/blender/blenkernel/intern/geometry_fields.cc @@ -8,7 +8,6 @@ #include "BKE_curves.hh" #include "BKE_geometry_fields.hh" #include "BKE_geometry_set.hh" -#include "BKE_grease_pencil.hh" #include "BKE_instances.hh" #include "BKE_mesh.hh" #include "BKE_pointcloud.h" @@ -35,50 +34,15 @@ CurvesFieldContext::CurvesFieldContext(const CurvesGeometry &curves, const eAttr BLI_assert(curves.attributes().domain_supported(domain)); } -GVArray GreasePencilLayerFieldContext::get_varray_for_input(const fn::FieldInput &field_input, - const IndexMask &mask, - ResourceScope &scope) const -{ - if (const CurvesFieldInput *curves_field_input = dynamic_cast( - &field_input)) - { - if (const bke::greasepencil::Drawing *drawing = - bke::greasepencil::get_eval_grease_pencil_layer_drawing(this->grease_pencil(), - this->layer_index())) - { - if (drawing->strokes().attributes().domain_supported(this->domain())) { - const CurvesFieldContext context{drawing->strokes(), this->domain()}; - return curves_field_input->get_varray_for_context(context, mask, scope); - } - } - return {}; - } - return field_input.get_varray_for_context(*this, mask, scope); -} - -GeometryFieldContext::GeometryFieldContext(const GeometryFieldContext &other, - const eAttrDomain domain) - : geometry_(other.geometry_), - type_(other.type_), - domain_(domain), - grease_pencil_layer_index_(other.grease_pencil_layer_index_) -{ -} - GeometryFieldContext::GeometryFieldContext(const void *geometry, const GeometryComponent::Type type, - const eAttrDomain domain, - const int grease_pencil_layer_index) - : geometry_(geometry), - type_(type), - domain_(domain), - grease_pencil_layer_index_(grease_pencil_layer_index) + const eAttrDomain domain) + : geometry_(geometry), type_(type), domain_(domain) { BLI_assert(ELEM(type, GeometryComponent::Type::Mesh, GeometryComponent::Type::Curve, GeometryComponent::Type::PointCloud, - GeometryComponent::Type::GreasePencil, GeometryComponent::Type::Instance)); } @@ -104,14 +68,6 @@ GeometryFieldContext::GeometryFieldContext(const GeometryComponent &component, geometry_ = pointcloud_component.get(); break; } - case GeometryComponent::Type::GreasePencil: { - const GreasePencilComponent &grease_pencil_component = - static_cast(component); - geometry_ = grease_pencil_component.get(); - /* Need to use another constructor for other domains. */ - BLI_assert(domain == ATTR_DOMAIN_LAYER); - break; - } case GeometryComponent::Type::Instance: { const InstancesComponent &instances_component = static_cast( component); @@ -120,6 +76,7 @@ GeometryFieldContext::GeometryFieldContext(const GeometryComponent &component, } case GeometryComponent::Type::Volume: case GeometryComponent::Type::Edit: + case GeometryComponent::Type::GreasePencil: BLI_assert_unreachable(); break; } @@ -137,21 +94,6 @@ GeometryFieldContext::GeometryFieldContext(const PointCloud &points) : geometry_(&points), type_(GeometryComponent::Type::PointCloud), domain_(ATTR_DOMAIN_POINT) { } -GeometryFieldContext::GeometryFieldContext(const GreasePencil &grease_pencil) - : geometry_(&grease_pencil), - type_(GeometryComponent::Type::GreasePencil), - domain_(ATTR_DOMAIN_LAYER) -{ -} -GeometryFieldContext::GeometryFieldContext(const GreasePencil &grease_pencil, - const eAttrDomain domain, - const int layer_index) - : geometry_(&grease_pencil), - type_(GeometryComponent::Type::GreasePencil), - domain_(domain), - grease_pencil_layer_index_(layer_index) -{ -} GeometryFieldContext::GeometryFieldContext(const Instances &instances) : geometry_(&instances), type_(GeometryComponent::Type::Instance), @@ -170,17 +112,6 @@ std::optional GeometryFieldContext::attributes() const if (const PointCloud *pointcloud = this->pointcloud()) { return pointcloud->attributes(); } - if (const GreasePencil *grease_pencil = this->grease_pencil()) { - if (domain_ == ATTR_DOMAIN_LAYER) { - return grease_pencil->attributes(); - } - else if (const greasepencil::Drawing *drawing = - greasepencil::get_eval_grease_pencil_layer_drawing(*grease_pencil, - grease_pencil_layer_index_)) - { - return drawing->strokes().attributes(); - } - } if (const Instances *instances = this->instances()) { return instances->attributes(); } @@ -204,32 +135,6 @@ const PointCloud *GeometryFieldContext::pointcloud() const static_cast(geometry_) : nullptr; } -const GreasePencil *GeometryFieldContext::grease_pencil() const -{ - return this->type() == GeometryComponent::Type::GreasePencil ? - static_cast(geometry_) : - nullptr; -} -const greasepencil::Drawing *GeometryFieldContext::grease_pencil_layer_drawing() const -{ - if (!(this->type() == GeometryComponent::Type::GreasePencil) || - !ELEM(domain_, ATTR_DOMAIN_CURVE, ATTR_DOMAIN_POINT)) - { - return nullptr; - } - return greasepencil::get_eval_grease_pencil_layer_drawing(*this->grease_pencil(), - this->grease_pencil_layer_index_); -} -const CurvesGeometry *GeometryFieldContext::curves_or_strokes() const -{ - if (const CurvesGeometry *curves = this->curves()) { - return curves; - } - if (const greasepencil::Drawing *drawing = this->grease_pencil_layer_drawing()) { - return &drawing->strokes(); - } - return nullptr; -} const Instances *GeometryFieldContext::instances() const { return this->type() == GeometryComponent::Type::Instance ? @@ -258,19 +163,6 @@ GVArray GeometryFieldInput::get_varray_for_context(const fn::FieldContext &conte { return this->get_varray_for_context({point_context->pointcloud()}, mask); } - if (const GreasePencilFieldContext *grease_pencil_context = - dynamic_cast(&context)) - { - return this->get_varray_for_context({grease_pencil_context->grease_pencil()}, mask); - } - if (const GreasePencilLayerFieldContext *grease_pencil_context = - dynamic_cast(&context)) - { - return this->get_varray_for_context({grease_pencil_context->grease_pencil(), - grease_pencil_context->domain(), - grease_pencil_context->layer_index()}, - mask); - } if (const InstancesFieldContext *instances_context = dynamic_cast( &context)) { @@ -314,7 +206,7 @@ GVArray CurvesFieldInput::get_varray_for_context(const fn::FieldContext &context if (const GeometryFieldContext *geometry_context = dynamic_cast( &context)) { - if (const CurvesGeometry *curves = geometry_context->curves_or_strokes()) { + if (const CurvesGeometry *curves = geometry_context->curves()) { return this->get_varray_for_context(*curves, geometry_context->domain(), mask); } } @@ -373,31 +265,8 @@ GVArray AttributeFieldInput::get_varray_for_context(const GeometryFieldContext & const IndexMask & /*mask*/) const { const eCustomDataType data_type = cpp_type_to_custom_data_type(*type_); - const eAttrDomain domain = context.domain(); - if (const GreasePencil *grease_pencil = context.grease_pencil()) { - const AttributeAccessor layer_attributes = grease_pencil->attributes(); - if (domain == ATTR_DOMAIN_LAYER) { - return *layer_attributes.lookup(name_, data_type); - } - else if (ELEM(domain, ATTR_DOMAIN_POINT, ATTR_DOMAIN_CURVE)) { - const int layer_index = context.grease_pencil_layer_index(); - const AttributeAccessor curves_attributes = *context.attributes(); - if (const GAttributeReader reader = curves_attributes.lookup(name_, domain, data_type)) { - return *reader; - } - /* Lookup attribute on the layer domain if it does not exist on points or curves. */ - if (const GAttributeReader reader = layer_attributes.lookup(name_)) { - const CPPType &cpp_type = reader.varray.type(); - BUFFER_FOR_CPP_TYPE_VALUE(cpp_type, value); - BLI_SCOPED_DEFER([&]() { cpp_type.destruct(value); }); - reader.varray.get_to_uninitialized(layer_index, value); - const int domain_size = curves_attributes.domain_size(domain); - return GVArray::ForSingle(cpp_type, domain_size, value); - } - } - } - else if (auto attributes = context.attributes()) { - return *attributes->lookup(name_, domain, data_type); + if (auto attributes = context.attributes()) { + return *attributes->lookup(name_, context.domain(), data_type); } return {}; } @@ -526,60 +395,6 @@ std::optional AnonymousAttributeFieldInput::preferred_domain( return meta_data->domain; } -GVArray NamedLayerSelectionFieldInput::get_varray_for_context( - const bke::GeometryFieldContext &context, const IndexMask &mask) const -{ - using namespace bke::greasepencil; - const eAttrDomain domain = context.domain(); - if (!ELEM(domain, ATTR_DOMAIN_POINT, ATTR_DOMAIN_CURVE, ATTR_DOMAIN_LAYER)) { - return {}; - } - - const GreasePencil &grease_pencil = *context.grease_pencil(); - if (!context.grease_pencil()) { - return {}; - } - - IndexMaskMemory memory; - const IndexMask layer_indices = grease_pencil.layer_selection_by_name(layer_name_, memory); - if (layer_indices.is_empty()) { - return {}; - } - - if (domain == ATTR_DOMAIN_LAYER) { - Array selection(mask.min_array_size()); - layer_indices.to_bools(selection); - return VArray::ForContainer(std::move(selection)); - } - - if (!layer_indices.contains(context.grease_pencil_layer_index())) { - return {}; - } - - return VArray::ForSingle(true, mask.min_array_size()); -} - -uint64_t NamedLayerSelectionFieldInput::hash() const -{ - return get_default_hash_2(layer_name_, type_); -} - -bool NamedLayerSelectionFieldInput::is_equal_to(const fn::FieldNode &other) const -{ - if (const NamedLayerSelectionFieldInput *other_named_layer = - dynamic_cast(&other)) - { - return layer_name_ == other_named_layer->layer_name_; - } - return false; -} - -std::optional NamedLayerSelectionFieldInput::preferred_domain( - const bke::GeometryComponent & /*component*/) const -{ - return ATTR_DOMAIN_LAYER; -} - } // namespace blender::bke /* -------------------------------------------------------------------- */ @@ -594,7 +409,7 @@ GVArray NormalFieldInput::get_varray_for_context(const GeometryFieldContext &con if (const Mesh *mesh = context.mesh()) { return mesh_normals_varray(*mesh, mask, context.domain()); } - if (const CurvesGeometry *curves = context.curves_or_strokes()) { + if (const CurvesGeometry *curves = context.curves()) { return curve_normals_varray(*curves, context.domain()); } return {}; @@ -664,13 +479,13 @@ static bool try_add_shared_field_attribute(MutableAttributeAccessor attributes, return attributes.add(id_to_create, domain, data_type, init); } -static bool try_capture_field_on_geometry(MutableAttributeAccessor attributes, - const GeometryFieldContext &field_context, - const AttributeIDRef &attribute_id, - const eAttrDomain domain, - const fn::Field &selection, - const fn::GField &field) +bool try_capture_field_on_geometry(GeometryComponent &component, + const AttributeIDRef &attribute_id, + const eAttrDomain domain, + const fn::Field &selection, + const fn::GField &field) { + MutableAttributeAccessor attributes = *component.attributes_for_write(); const int domain_size = attributes.domain_size(domain); const CPPType &type = field.cpp_type(); const eCustomDataType data_type = bke::cpp_type_to_custom_data_type(type); @@ -679,6 +494,7 @@ static bool try_capture_field_on_geometry(MutableAttributeAccessor attributes, return attributes.add(attribute_id, domain, data_type, AttributeInitConstruct{}); } + const bke::GeometryFieldContext field_context{component, domain}; const bke::AttributeValidator validator = attributes.lookup_validator(attribute_id); const std::optional meta_data = attributes.lookup_meta_data(attribute_id); @@ -688,6 +504,7 @@ static bool try_capture_field_on_geometry(MutableAttributeAccessor attributes, /* We are writing to an attribute that exists already with the correct domain and type. */ if (attribute_matches) { if (GSpanAttributeWriter dst_attribute = attributes.lookup_for_write_span(attribute_id)) { + const bke::GeometryFieldContext field_context{component, domain}; fn::FieldEvaluator evaluator{field_context, domain_size}; evaluator.add(validator.validate_field_if_necessary(field)); evaluator.set_selection(selection); @@ -745,51 +562,6 @@ static bool try_capture_field_on_geometry(MutableAttributeAccessor attributes, return false; } -bool try_capture_field_on_geometry(GeometryComponent &component, - const AttributeIDRef &attribute_id, - const eAttrDomain domain, - const fn::Field &selection, - const fn::GField &field) -{ - if (component.type() == GeometryComponent::Type::GreasePencil && - ELEM(domain, ATTR_DOMAIN_POINT, ATTR_DOMAIN_CURVE)) - { - /* Capture the field on every layer individually. */ - auto &grease_pencil_component = static_cast(component); - GreasePencil *grease_pencil = grease_pencil_component.get_for_write(); - if (grease_pencil == nullptr) { - return false; - } - bool any_success = false; - threading::parallel_for(grease_pencil->layers().index_range(), 8, [&](const IndexRange range) { - for (const int layer_index : range) { - if (greasepencil::Drawing *drawing = - greasepencil::get_eval_grease_pencil_layer_drawing_for_write(*grease_pencil, - layer_index)) - { - const GeometryFieldContext field_context{*grease_pencil, domain, layer_index}; - const bool success = try_capture_field_on_geometry( - drawing->strokes_for_write().attributes_for_write(), - field_context, - attribute_id, - domain, - selection, - field); - if (success & !any_success) { - any_success = true; - } - } - } - }); - return any_success; - } - - MutableAttributeAccessor attributes = *component.attributes_for_write(); - const GeometryFieldContext field_context{component, domain}; - return try_capture_field_on_geometry( - attributes, field_context, attribute_id, domain, selection, field); -} - bool try_capture_field_on_geometry(GeometryComponent &component, const AttributeIDRef &attribute_id, const eAttrDomain domain, @@ -806,9 +578,6 @@ std::optional try_detect_field_domain(const GeometryComponent &comp if (component_type == GeometryComponent::Type::PointCloud) { return ATTR_DOMAIN_POINT; } - if (component_type == GeometryComponent::Type::GreasePencil) { - return ATTR_DOMAIN_LAYER; - } if (component_type == GeometryComponent::Type::Instance) { return ATTR_DOMAIN_INSTANCE; } diff --git a/source/blender/blenkernel/intern/geometry_set.cc b/source/blender/blenkernel/intern/geometry_set.cc index 915df3f1078..53d1472f29d 100644 --- a/source/blender/blenkernel/intern/geometry_set.cc +++ b/source/blender/blenkernel/intern/geometry_set.cc @@ -240,9 +240,6 @@ std::ostream &operator<<(std::ostream &stream, const GeometrySet &geometry_set) parts.append(std::to_string(curves->geometry.point_num) + " control points"); parts.append(std::to_string(curves->geometry.curve_num) + " curves"); } - if (const GreasePencil *grease_pencil = geometry_set.get_grease_pencil()) { - parts.append(std::to_string(grease_pencil->layers().size()) + " grease pencil layers"); - } if (const PointCloud *point_cloud = geometry_set.get_pointcloud()) { parts.append(std::to_string(point_cloud->totpoint) + " points"); } @@ -605,33 +602,6 @@ void GeometrySet::attribute_foreach(const Span componen } } -void GeometrySet::propagate_attributes_from_layer_to_instances( - const AttributeAccessor src_attributes, - MutableAttributeAccessor dst_attributes, - const AnonymousAttributePropagationInfo &propagation_info) -{ - src_attributes.for_all([&](const AttributeIDRef &id, const AttributeMetaData meta_data) { - if (id.is_anonymous() && !propagation_info.propagate(id.anonymous_id())) { - return true; - } - const GAttributeReader src = src_attributes.lookup(id, ATTR_DOMAIN_LAYER); - if (src.sharing_info && src.varray.is_span()) { - const AttributeInitShared init(src.varray.get_internal_span().data(), *src.sharing_info); - if (dst_attributes.add(id, ATTR_DOMAIN_INSTANCE, meta_data.data_type, init)) { - return true; - } - } - GSpanAttributeWriter dst = dst_attributes.lookup_or_add_for_write_only_span( - id, ATTR_DOMAIN_INSTANCE, meta_data.data_type); - if (!dst) { - return true; - } - array_utils::copy(src.varray, dst.span); - dst.finish(); - return true; - }); -} - void GeometrySet::gather_attributes_for_propagation( const Span component_types, const GeometryComponent::Type dst_component_type, diff --git a/source/blender/blenkernel/intern/gpencil_geom_legacy.cc b/source/blender/blenkernel/intern/gpencil_geom_legacy.cc index cfe499a3a63..f5f59c3cb28 100644 --- a/source/blender/blenkernel/intern/gpencil_geom_legacy.cc +++ b/source/blender/blenkernel/intern/gpencil_geom_legacy.cc @@ -28,7 +28,7 @@ #include "BLI_math_vector_types.hh" #include "BLI_polyfill_2d.h" #include "BLI_span.hh" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "DNA_gpencil_legacy_types.h" #include "DNA_gpencil_modifier_types.h" diff --git a/source/blender/blenkernel/intern/gpencil_legacy.cc b/source/blender/blenkernel/intern/gpencil_legacy.cc index 2464befbd00..af41913f580 100644 --- a/source/blender/blenkernel/intern/gpencil_legacy.cc +++ b/source/blender/blenkernel/intern/gpencil_legacy.cc @@ -19,7 +19,7 @@ #include "BLI_blenlib.h" #include "BLI_math_matrix.h" #include "BLI_math_vector.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLT_translation.h" diff --git a/source/blender/blenkernel/intern/gpencil_modifier_legacy.cc b/source/blender/blenkernel/intern/gpencil_modifier_legacy.cc index 7ec791aadc8..25c840025e0 100644 --- a/source/blender/blenkernel/intern/gpencil_modifier_legacy.cc +++ b/source/blender/blenkernel/intern/gpencil_modifier_legacy.cc @@ -14,7 +14,7 @@ #include "BLI_math_geom.h" #include "BLI_math_matrix.h" #include "BLI_math_vector.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_utildefines.h" #include "BLT_translation.h" @@ -421,18 +421,19 @@ void BKE_gpencil_modifier_free(GpencilModifierData *md) BKE_gpencil_modifier_free_ex(md, 0); } -void BKE_gpencil_modifier_unique_name(ListBase *modifiers, GpencilModifierData *gmd) +bool BKE_gpencil_modifier_unique_name(ListBase *modifiers, GpencilModifierData *gmd) { if (modifiers && gmd) { const GpencilModifierTypeInfo *gmti = BKE_gpencil_modifier_get_info( GpencilModifierType(gmd->type)); - BLI_uniquename(modifiers, - gmd, - DATA_(gmti->name), - '.', - offsetof(GpencilModifierData, name), - sizeof(gmd->name)); + return BLI_uniquename(modifiers, + gmd, + DATA_(gmti->name), + '.', + offsetof(GpencilModifierData, name), + sizeof(gmd->name)); } + return false; } bool BKE_gpencil_modifier_depends_ontime(GpencilModifierData *md) diff --git a/source/blender/blenkernel/intern/grease_pencil.cc b/source/blender/blenkernel/intern/grease_pencil.cc index 98f5937351a..f7390a97971 100644 --- a/source/blender/blenkernel/intern/grease_pencil.cc +++ b/source/blender/blenkernel/intern/grease_pencil.cc @@ -31,7 +31,7 @@ #include "BLI_stack.hh" #include "BLI_string.h" #include "BLI_string_ref.hh" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_vector_set.hh" #include "BLI_virtual_array.hh" @@ -41,11 +41,9 @@ #include "DNA_ID.h" #include "DNA_ID_enums.h" -#include "DNA_brush_types.h" #include "DNA_grease_pencil_types.h" #include "DNA_material_types.h" #include "DNA_modifier_types.h" -#include "DNA_scene_types.h" #include "DEG_depsgraph.hh" #include "DEG_depsgraph_query.hh" @@ -70,14 +68,11 @@ static void grease_pencil_init_data(ID *id) using namespace blender::bke; GreasePencil *grease_pencil = reinterpret_cast(id); + grease_pencil->runtime = MEM_new(__func__); grease_pencil->root_group_ptr = MEM_new(__func__); grease_pencil->active_layer = nullptr; grease_pencil->flag |= GREASE_PENCIL_ANIM_CHANNEL_EXPANDED; - - CustomData_reset(&grease_pencil->layers_data); - - grease_pencil->runtime = MEM_new(__func__); } static void grease_pencil_copy_data(Main * /*bmain*/, @@ -94,7 +89,29 @@ static void grease_pencil_copy_data(Main * /*bmain*/, grease_pencil_dst->material_array = static_cast( MEM_dupallocN(grease_pencil_src->material_array)); - BKE_grease_pencil_duplicate_drawing_array(grease_pencil_src, grease_pencil_dst); + /* Duplicate drawing array. */ + grease_pencil_dst->drawing_array_num = grease_pencil_src->drawing_array_num; + grease_pencil_dst->drawing_array = MEM_cnew_array( + grease_pencil_src->drawing_array_num, __func__); + for (int i = 0; i < grease_pencil_src->drawing_array_num; i++) { + const GreasePencilDrawingBase *src_drawing_base = grease_pencil_src->drawing_array[i]; + switch (src_drawing_base->type) { + case GP_DRAWING: { + const GreasePencilDrawing *src_drawing = reinterpret_cast( + src_drawing_base); + grease_pencil_dst->drawing_array[i] = reinterpret_cast( + MEM_new(__func__, src_drawing->wrap())); + break; + } + case GP_DRAWING_REFERENCE: { + const GreasePencilDrawingReference *src_drawing_reference = + reinterpret_cast(src_drawing_base); + grease_pencil_dst->drawing_array[i] = reinterpret_cast( + MEM_dupallocN(src_drawing_reference)); + break; + } + } + } /* Duplicate layer tree. */ grease_pencil_dst->root_group_ptr = MEM_new( @@ -102,17 +119,10 @@ static void grease_pencil_copy_data(Main * /*bmain*/, /* Set active layer. */ if (grease_pencil_src->has_active_layer()) { - bke::greasepencil::TreeNode *active_node = grease_pencil_dst->find_node_by_name( - grease_pencil_src->active_layer->wrap().name()); - BLI_assert(active_node && active_node->is_layer()); - grease_pencil_dst->set_active_layer(&active_node->as_layer()); + grease_pencil_dst->set_active_layer( + grease_pencil_dst->find_layer_by_name(grease_pencil_src->active_layer->wrap().name())); } - CustomData_copy(&grease_pencil_src->layers_data, - &grease_pencil_dst->layers_data, - CD_MASK_ALL, - grease_pencil_dst->layers().size()); - /* Make sure the runtime pointer exists. */ grease_pencil_dst->runtime = MEM_new(__func__); } @@ -124,8 +134,6 @@ static void grease_pencil_free_data(ID *id) MEM_SAFE_FREE(grease_pencil->material_array); - CustomData_free(&grease_pencil->layers_data, grease_pencil->layers().size()); - free_drawing_array(*grease_pencil); MEM_delete(&grease_pencil->root_group()); @@ -154,20 +162,10 @@ static void grease_pencil_blend_write(BlendWriter *writer, ID *id, const void *i { GreasePencil *grease_pencil = reinterpret_cast(id); - blender::Vector layers_data_layers; - CustomData_blend_write_prepare(grease_pencil->layers_data, layers_data_layers); - /* Write LibData */ BLO_write_id_struct(writer, GreasePencil, id_address, &grease_pencil->id); BKE_id_blend_write(writer, &grease_pencil->id); - CustomData_blend_write(writer, - &grease_pencil->layers_data, - layers_data_layers, - grease_pencil->layers().size(), - CD_MASK_ALL, - id); - /* Write drawings. */ write_drawing_array(*grease_pencil, writer); /* Write layer tree. */ @@ -188,8 +186,6 @@ static void grease_pencil_blend_read_data(BlendDataReader *reader, ID *id) /* Read layer tree. */ read_layer_tree(*grease_pencil, reader); - CustomData_blend_read(reader, &grease_pencil->layers_data, grease_pencil->layers().size()); - /* Read materials. */ BLO_read_pointer_array(reader, reinterpret_cast(&grease_pencil->material_array)); @@ -227,15 +223,8 @@ IDTypeInfo IDType_ID_GP = { namespace blender::bke::greasepencil { -DrawingTransforms::DrawingTransforms(const Object &grease_pencil_ob) -{ - this->layer_space_to_world_space = float4x4_view(grease_pencil_ob.object_to_world); - this->world_space_to_layer_space = math::invert(this->layer_space_to_world_space); -} - -static const std::string ATTR_RADIUS = "radius"; static const std::string ATTR_OPACITY = "opacity"; -static const std::string ATTR_VERTEX_COLOR = "vertex_color"; +static const std::string ATTR_RADIUS = "radius"; /* Curves attributes getters */ static int domain_num(const CurvesGeometry &curves, const eAttrDomain domain) @@ -386,20 +375,6 @@ MutableSpan Drawing::opacities_for_write() this->strokes_for_write(), ATTR_DOMAIN_POINT, ATTR_OPACITY, 1.0f); } -VArray Drawing::vertex_colors() const -{ - return *this->strokes().attributes().lookup_or_default( - ATTR_VERTEX_COLOR, ATTR_DOMAIN_POINT, ColorGeometry4f(0.0f, 0.0f, 0.0f, 0.0f)); -} - -MutableSpan Drawing::vertex_colors_for_write() -{ - return get_mutable_attribute(this->strokes_for_write(), - ATTR_DOMAIN_POINT, - ATTR_VERTEX_COLOR, - ColorGeometry4f(0.0f, 0.0f, 0.0f, 0.0f)); -} - void Drawing::tag_positions_changed() { this->strokes_for_write().tag_positions_changed(); @@ -411,72 +386,6 @@ void Drawing::tag_topology_changed() this->tag_positions_changed(); } -DrawingReference::DrawingReference() -{ - this->base.type = GP_DRAWING_REFERENCE; - this->base.flag = 0; - - this->id_reference = nullptr; -} - -DrawingReference::DrawingReference(const DrawingReference &other) -{ - this->base.type = GP_DRAWING_REFERENCE; - this->base.flag = other.base.flag; - - this->id_reference = other.id_reference; -} - -DrawingReference::~DrawingReference() {} - -const Drawing *get_eval_grease_pencil_layer_drawing(const GreasePencil &grease_pencil, - const int layer_index) -{ - BLI_assert(layer_index >= 0 && layer_index < grease_pencil.layers().size()); - const Layer &layer = *grease_pencil.layers()[layer_index]; - const int drawing_index = layer.drawing_index_at(grease_pencil.runtime->eval_frame); - if (drawing_index == -1) { - return nullptr; - } - const GreasePencilDrawingBase *drawing_base = grease_pencil.drawing(drawing_index); - if (drawing_base->type != GP_DRAWING) { - return nullptr; - } - const Drawing &drawing = reinterpret_cast(drawing_base)->wrap(); - return &drawing; -} - -Drawing *get_eval_grease_pencil_layer_drawing_for_write(GreasePencil &grease_pencil, - const int layer) -{ - return const_cast(get_eval_grease_pencil_layer_drawing(grease_pencil, layer)); -} - -void copy_drawing_array(Span src_drawings, - MutableSpan dst_drawings) -{ - BLI_assert(src_drawings.size() == dst_drawings.size()); - for (const int i : src_drawings.index_range()) { - const GreasePencilDrawingBase *src_drawing_base = src_drawings[i]; - switch (src_drawing_base->type) { - case GP_DRAWING: { - const GreasePencilDrawing *src_drawing = reinterpret_cast( - src_drawing_base); - dst_drawings[i] = reinterpret_cast( - MEM_new(__func__, src_drawing->wrap())); - break; - } - case GP_DRAWING_REFERENCE: { - const GreasePencilDrawingReference *src_drawing_reference = - reinterpret_cast(src_drawing_base); - dst_drawings[i] = reinterpret_cast( - MEM_new(__func__, src_drawing_reference->wrap())); - break; - } - } - } -} - TreeNode::TreeNode() { this->next = this->prev = nullptr; @@ -512,7 +421,6 @@ TreeNode::~TreeNode() void TreeNode::set_name(StringRefNull name) { - MEM_SAFE_FREE(this->GreasePencilLayerTreeNode::name); this->GreasePencilLayerTreeNode::name = BLI_strdup(name.c_str()); } @@ -893,12 +801,6 @@ Layer &LayerGroup::add_layer(StringRefNull name) return this->add_node(new_layer->as_node()).as_layer(); } -Layer &LayerGroup::add_layer(const Layer &duplicate_layer) -{ - Layer *new_layer = MEM_new(__func__, duplicate_layer); - return this->add_node(new_layer->as_node()).as_layer(); -} - LayerGroup &LayerGroup::add_group(StringRefNull name) { LayerGroup *new_group = MEM_new(__func__, name); @@ -1007,21 +909,41 @@ Span LayerGroup::groups_for_write() return this->runtime->layer_group_cache_.as_span(); } -const TreeNode *LayerGroup::find_node_by_name(const StringRefNull name) const +const Layer *LayerGroup::find_layer_by_name(const StringRefNull name) const { - for (const TreeNode *node : this->nodes()) { - if (StringRef(node->name()) == StringRef(name)) { - return node; + for (const Layer *layer : this->layers()) { + if (StringRef(layer->name()) == StringRef(name)) { + return layer; } } return nullptr; } -TreeNode *LayerGroup::find_node_by_name(const StringRefNull name) +Layer *LayerGroup::find_layer_by_name(const StringRefNull name) { - for (TreeNode *node : this->nodes_for_write()) { - if (StringRef(node->name()) == StringRef(name)) { - return node; + for (Layer *layer : this->layers_for_write()) { + if (StringRef(layer->name()) == StringRef(name)) { + return layer; + } + } + return nullptr; +} + +const LayerGroup *LayerGroup::find_group_by_name(StringRefNull name) const +{ + for (const LayerGroup *group : this->groups()) { + if (StringRef(group->name()) == StringRef(name)) { + return group; + } + } + return nullptr; +} + +LayerGroup *LayerGroup::find_group_by_name(StringRefNull name) +{ + for (LayerGroup *group : this->groups_for_write()) { + if (StringRef(group->name()) == StringRef(name)) { + return group; } } return nullptr; @@ -1118,27 +1040,30 @@ GreasePencil *BKE_grease_pencil_new_nomain() GreasePencil *BKE_grease_pencil_copy_for_eval(const GreasePencil *grease_pencil_src) { - GreasePencil *grease_pencil = reinterpret_cast( + return reinterpret_cast( BKE_id_copy_ex(nullptr, &grease_pencil_src->id, nullptr, LIB_ID_COPY_LOCALIZE)); - grease_pencil->runtime->eval_frame = grease_pencil_src->runtime->eval_frame; - return grease_pencil; } -BoundBox BKE_grease_pencil_boundbox_get(Object *ob) +BoundBox *BKE_grease_pencil_boundbox_get(Object *ob) { using namespace blender; BLI_assert(ob->type == OB_GREASE_PENCIL); const GreasePencil *grease_pencil = static_cast(ob->data); + if (ob->runtime.bb != nullptr && (ob->runtime.bb->flag & BOUNDBOX_DIRTY) == 0) { + return ob->runtime.bb; + } + if (ob->runtime.bb == nullptr) { + ob->runtime.bb = MEM_cnew(__func__); + } - BoundBox bb; if (const std::optional> bounds = grease_pencil->bounds_min_max()) { - BKE_boundbox_init_from_minmax(&bb, bounds->min, bounds->max); + BKE_boundbox_init_from_minmax(ob->runtime.bb, bounds->min, bounds->max); } else { - BKE_boundbox_init_from_minmax(&bb, float3(-1), float3(1)); + BKE_boundbox_init_from_minmax(ob->runtime.bb, float3(-1), float3(1)); } - return bb; + return ob->runtime.bb; } static void grease_pencil_evaluate_modifiers(Depsgraph *depsgraph, @@ -1184,65 +1109,32 @@ void BKE_grease_pencil_data_update(Depsgraph *depsgraph, Scene *scene, Object *o /* Evaluate modifiers. */ GreasePencil *grease_pencil = static_cast(object->data); - /* Store the frame that this grease pencil is evaluated on. */ - grease_pencil->runtime->eval_frame = int(DEG_get_ctime(depsgraph)); GeometrySet geometry_set = GeometrySet::from_grease_pencil(grease_pencil, GeometryOwnershipType::ReadOnly); - /* Only add the edit hint component in edit mode for now so users can properly select deformed - * drawings. */ - if (object->mode == OB_MODE_EDIT) { - GeometryComponentEditData &edit_component = - geometry_set.get_component_for_write(); - edit_component.grease_pencil_edit_hints_ = std::make_unique( - *static_cast(DEG_get_original_object(object)->data)); - } grease_pencil_evaluate_modifiers(depsgraph, scene, object, geometry_set); if (!geometry_set.has_grease_pencil()) { - GreasePencil *empty_grease_pencil = BKE_grease_pencil_new_nomain(); - empty_grease_pencil->runtime->eval_frame = int(DEG_get_ctime(depsgraph)); - geometry_set.replace_grease_pencil(empty_grease_pencil); + geometry_set.replace_grease_pencil(BKE_grease_pencil_new_nomain()); } /* For now the evaluated data is not const. We could use #get_grease_pencil_for_write, but that * would result in a copy when it's shared. So for now, we use a const_cast here. */ GreasePencil *grease_pencil_eval = const_cast(geometry_set.get_grease_pencil()); + /* Store the frame that this grease pencil data was evaluated on. */ + grease_pencil_eval->runtime->eval_frame = int(DEG_get_ctime(depsgraph)); + /* Assign evaluated object. */ BKE_object_eval_assign_data(object, &grease_pencil_eval->id, false); object->runtime.geometry_set_eval = new GeometrySet(std::move(geometry_set)); } -void BKE_grease_pencil_duplicate_drawing_array(const GreasePencil *grease_pencil_src, - GreasePencil *grease_pencil_dst) -{ - using namespace blender; - grease_pencil_dst->drawing_array_num = grease_pencil_src->drawing_array_num; - grease_pencil_dst->drawing_array = MEM_cnew_array( - grease_pencil_src->drawing_array_num, __func__); - bke::greasepencil::copy_drawing_array(grease_pencil_src->drawings(), - grease_pencil_dst->drawings()); -} - /** \} */ /* ------------------------------------------------------------------- */ /** \name Grease Pencil material functions * \{ */ -int BKE_grease_pencil_object_material_index_get(Object *ob, Material *ma) -{ - short *totcol = BKE_object_material_len_p(ob); - Material *read_ma = nullptr; - for (short i = 0; i < *totcol; i++) { - read_ma = BKE_object_material_get(ob, i + 1); - if (ma == read_ma) { - return i; - } - } - return -1; -} - int BKE_grease_pencil_object_material_index_get_by_name(Object *ob, const char *name) { short *totcol = BKE_object_material_len_p(ob); @@ -1274,18 +1166,6 @@ Material *BKE_grease_pencil_object_material_new(Main *bmain, return ma; } -Material *BKE_grease_pencil_object_material_from_brush_get(Object *ob, Brush *brush) -{ - if ((brush) && (brush->gpencil_settings) && - (brush->gpencil_settings->flag & GP_BRUSH_MATERIAL_PINNED)) - { - Material *ma = BKE_grease_pencil_brush_material_get(brush); - return ma; - } - - return BKE_object_material_get(ob, ob->actcol); -} - Material *BKE_grease_pencil_object_material_ensure_by_name(Main *bmain, Object *ob, const char *name, @@ -1299,95 +1179,6 @@ Material *BKE_grease_pencil_object_material_ensure_by_name(Main *bmain, return BKE_grease_pencil_object_material_new(bmain, ob, name, r_index); } -Material *BKE_grease_pencil_brush_material_get(Brush *brush) -{ - if (brush == nullptr) { - return nullptr; - } - if (brush->gpencil_settings == nullptr) { - return nullptr; - } - return brush->gpencil_settings->material; -} - -Material *BKE_grease_pencil_object_material_ensure_from_brush(Main *bmain, - Object *ob, - Brush *brush) -{ - if (brush->gpencil_settings->flag & GP_BRUSH_MATERIAL_PINNED) { - Material *ma = BKE_grease_pencil_brush_material_get(brush); - - /* check if the material is already on object material slots and add it if missing */ - if (ma && BKE_grease_pencil_object_material_index_get(ob, ma) < 0) { - BKE_object_material_slot_add(bmain, ob); - BKE_object_material_assign(bmain, ob, ma, ob->totcol, BKE_MAT_ASSIGN_USERPREF); - } - - return ma; - } - - /* Use the active material instead. */ - return BKE_object_material_get(ob, ob->actcol); -} - -Material *BKE_grease_pencil_object_material_ensure_from_active_input_brush(Main *bmain, - Object *ob, - Brush *brush) -{ - if (brush == nullptr) { - return BKE_grease_pencil_object_material_ensure_from_active_input_material(ob); - } - if (Material *ma = BKE_grease_pencil_object_material_ensure_from_brush(bmain, ob, brush)) { - return ma; - } - if (brush->gpencil_settings->flag & GP_BRUSH_MATERIAL_PINNED) { - /* It is easier to just unpin a null material, instead of setting a new one. */ - brush->gpencil_settings->flag &= ~GP_BRUSH_MATERIAL_PINNED; - } - return BKE_grease_pencil_object_material_ensure_from_active_input_material(ob); -} - -Material *BKE_grease_pencil_object_material_ensure_from_active_input_material(Object *ob) -{ - if (Material *ma = BKE_object_material_get(ob, ob->actcol)) { - return ma; - } - return BKE_material_default_gpencil(); -} - -Material *BKE_grease_pencil_object_material_ensure_active(Object *ob) -{ - Material *ma = BKE_grease_pencil_object_material_ensure_from_active_input_material(ob); - if (ma->gp_style == nullptr) { - BKE_gpencil_material_attr_init(ma); - } - return ma; -} - -void BKE_grease_pencil_material_remap(GreasePencil *grease_pencil, const uint *remap, int totcol) -{ - using namespace blender::bke; - - for (GreasePencilDrawingBase *base : grease_pencil->drawings()) { - if (base->type != GP_DRAWING) { - continue; - } - greasepencil::Drawing &drawing = reinterpret_cast(base)->wrap(); - MutableAttributeAccessor attributes = drawing.strokes_for_write().attributes_for_write(); - SpanAttributeWriter material_indices = attributes.lookup_or_add_for_write_span( - "material_index", ATTR_DOMAIN_CURVE); - if (!material_indices) { - return; - } - for (const int i : material_indices.span.index_range()) { - BLI_assert(blender::IndexRange(totcol).contains(remap[material_indices.span[i]])); - UNUSED_VARS_NDEBUG(totcol); - material_indices.span[i] = remap[material_indices.span[i]]; - } - material_indices.finish(); - } -} - /** \} */ /* ------------------------------------------------------------------- */ @@ -1444,6 +1235,23 @@ void BKE_grease_pencil_batch_cache_free(GreasePencil *grease_pencil) /** \} */ +/* ------------------------------------------------------------------- */ +/** \name Grease Pencil runtime API + * \{ */ + +bool blender::bke::GreasePencilRuntime::has_stroke_buffer() const +{ + return this->stroke_cache.points.size() > 0; +} + +blender::Span blender::bke::GreasePencilRuntime:: + stroke_buffer() const +{ + return this->stroke_cache.points.as_span(); +} + +/** \} */ + /* ------------------------------------------------------------------- */ /** \name Grease Pencil data-block API * \{ */ @@ -1678,7 +1486,7 @@ static void remove_drawings_unchecked(GreasePencil &grease_pencil, case GP_DRAWING_REFERENCE: { GreasePencilDrawingReference *drawing_reference_to_remove = reinterpret_cast(drawing_base_to_remove); - MEM_delete(&drawing_reference_to_remove->wrap()); + MEM_freeN(drawing_reference_to_remove); break; } } @@ -1706,24 +1514,6 @@ void GreasePencil::remove_drawings_with_no_users() remove_drawings_unchecked(*this, drawings_to_be_removed.as_span()); } -void GreasePencil::update_drawing_users_for_layer(const blender::bke::greasepencil::Layer &layer) -{ - using namespace blender; - for (auto [key, value] : layer.frames().items()) { - if (value.drawing_index > 0 && value.drawing_index < this->drawings().size()) { - GreasePencilDrawingBase *drawing_base = this->drawing(value.drawing_index); - if (drawing_base->type != GP_DRAWING) { - continue; - } - bke::greasepencil::Drawing &drawing = - reinterpret_cast(drawing_base)->wrap(); - if (!drawing.has_users()) { - drawing.add_user(); - } - } - } -} - void GreasePencil::move_frames(blender::bke::greasepencil::Layer &layer, const blender::Map &frame_number_destinations) { @@ -1828,18 +1618,57 @@ enum ForeachDrawingMode { EDITABLE, }; +static void foreach_drawing_ex( + GreasePencil &grease_pencil, + const int frame, + const ForeachDrawingMode mode, + blender::FunctionRef function) +{ + using namespace blender::bke::greasepencil; + + blender::Span drawings = grease_pencil.drawings(); + for (const Layer *layer : grease_pencil.layers()) { + switch (mode) { + case VISIBLE: { + if (!layer->is_visible()) { + continue; + } + break; + } + case EDITABLE: { + if (!layer->is_editable()) { + continue; + } + break; + } + } + + int index = layer->drawing_index_at(frame); + if (index == -1) { + continue; + } + GreasePencilDrawingBase *drawing_base = drawings[index]; + if (drawing_base->type == GP_DRAWING) { + GreasePencilDrawing *drawing = reinterpret_cast(drawing_base); + function(index, drawing->wrap()); + } + else if (drawing_base->type == GP_DRAWING_REFERENCE) { + /* TODO: Drawing references are not implemented yet. */ + BLI_assert_unreachable(); + } + } +} + static void foreach_drawing_ex( const GreasePencil &grease_pencil, const int frame, const ForeachDrawingMode mode, - blender::FunctionRef function) + blender::FunctionRef function) { using namespace blender::bke::greasepencil; blender::Span drawings = grease_pencil.drawings(); - blender::Span layers = grease_pencil.layers(); - for (const int layer_i : layers.index_range()) { - const Layer *layer = layers[layer_i]; + for (const Layer *layer : grease_pencil.layers()) { switch (mode) { case VISIBLE: { if (!layer->is_visible()) { @@ -1863,7 +1692,7 @@ static void foreach_drawing_ex( if (drawing_base->type == GP_DRAWING) { const GreasePencilDrawing *drawing = reinterpret_cast( drawing_base); - function(layer_i, drawing->wrap()); + function(index, drawing->wrap()); } else if (drawing_base->type == GP_DRAWING_REFERENCE) { /* TODO: Drawing references are not implemented yet. */ @@ -1874,38 +1703,23 @@ static void foreach_drawing_ex( void GreasePencil::foreach_visible_drawing( const int frame, - blender::FunctionRef function) + blender::FunctionRef function) { - foreach_drawing_ex( - *this, - frame, - VISIBLE, - [&](const int layer_index, const blender::bke::greasepencil::Drawing &drawing) { - /* We const_cast here to be able to implement `foreach_drawing_ex` only once. */ - function(layer_index, const_cast(drawing)); - }); + foreach_drawing_ex(*this, frame, VISIBLE, function); } void GreasePencil::foreach_visible_drawing( const int frame, - blender::FunctionRef function) - const + blender::FunctionRef function) const { foreach_drawing_ex(*this, frame, VISIBLE, function); } void GreasePencil::foreach_editable_drawing( const int frame, - blender::FunctionRef function) + blender::FunctionRef function) { - foreach_drawing_ex( - *this, - frame, - EDITABLE, - [&](const int layer_index, const blender::bke::greasepencil::Drawing &drawing) { - /* We const_cast here to be able to implement `foreach_drawing_ex` only once. */ - function(layer_index, const_cast(drawing)); - }); + foreach_drawing_ex(*this, frame, EDITABLE, function); } std::optional> GreasePencil::bounds_min_max() const @@ -1914,7 +1728,7 @@ std::optional> GreasePencil::bounds_min_max() c std::optional> bounds; this->foreach_visible_drawing( this->runtime->eval_frame, - [&](const int /*layer_index*/, const bke::greasepencil::Drawing &drawing) { + [&](int /*drawing_index*/, const bke::greasepencil::Drawing &drawing) { const bke::CurvesGeometry &curves = drawing.strokes(); bounds = bounds::merge(bounds, curves.bounds_min_max()); }); @@ -2004,11 +1818,11 @@ static bool check_unique_node_cb(void *arg, const char *name) return names.contains(name); } -static void unique_node_name_ex(VectorSet &names, +static bool unique_node_name_ex(VectorSet &names, const char *default_name, char *name) { - BLI_uniquename_cb(check_unique_node_cb, &names, default_name, '.', name, MAX_NAME); + return BLI_uniquename_cb(check_unique_node_cb, &names, default_name, '.', name, MAX_NAME); } static std::string unique_node_name(const GreasePencil &grease_pencil, @@ -2016,10 +1830,9 @@ static std::string unique_node_name(const GreasePencil &grease_pencil, blender::StringRefNull name) { using namespace blender; - char unique_name[MAX_NAME]; - BLI_strncpy(unique_name, name.c_str(), MAX_NAME); + std::string unique_name(name.c_str()); VectorSet names = get_node_names(grease_pencil); - unique_node_name_ex(names, default_name, unique_name); + unique_node_name_ex(names, default_name, unique_name.data()); return unique_name; } @@ -2035,45 +1848,12 @@ static std::string unique_layer_group_name(const GreasePencil &grease_pencil, return unique_node_name(grease_pencil, DATA_("GP_Group"), name); } -blender::bke::greasepencil::Layer &GreasePencil::add_layer(const blender::StringRefNull name) -{ - using namespace blender; - std::string unique_name = unique_layer_name(*this, name); - const int numLayers = layers().size(); - CustomData_realloc(&layers_data, numLayers, numLayers + 1); - return root_group().add_layer(unique_name); -} - blender::bke::greasepencil::Layer &GreasePencil::add_layer( blender::bke::greasepencil::LayerGroup &parent_group, const blender::StringRefNull name) { using namespace blender; - blender::bke::greasepencil::Layer &new_layer = add_layer(name); - move_node_into(new_layer.as_node(), parent_group); - return new_layer; -} - -blender::bke::greasepencil::Layer &GreasePencil::add_layer( - const blender::bke::greasepencil::Layer &duplicate_layer) -{ - using namespace blender; - std::string unique_name = unique_layer_name(*this, duplicate_layer.name()); - const int numLayers = layers().size(); - CustomData_realloc(&layers_data, numLayers, numLayers + 1); - bke::greasepencil::Layer &new_layer = root_group().add_layer(duplicate_layer); - this->update_drawing_users_for_layer(new_layer); - new_layer.set_name(unique_name); - return new_layer; -} - -blender::bke::greasepencil::Layer &GreasePencil::add_layer( - blender::bke::greasepencil::LayerGroup &parent_group, - const blender::bke::greasepencil::Layer &duplicate_layer) -{ - using namespace blender; - bke::greasepencil::Layer &new_layer = add_layer(duplicate_layer); - move_node_into(new_layer.as_node(), parent_group); - return new_layer; + std::string unique_name = unique_layer_name(*this, name); + return parent_group.add_layer(unique_name); } blender::bke::greasepencil::LayerGroup &GreasePencil::add_layer_group( @@ -2084,159 +1864,80 @@ blender::bke::greasepencil::LayerGroup &GreasePencil::add_layer_group( return parent_group.add_group(unique_name); } -static void reorder_customdata(CustomData &data, const Span new_by_old_map) -{ - CustomData new_data; - CustomData_copy_layout(&data, &new_data, CD_MASK_ALL, CD_CONSTRUCT, new_by_old_map.size()); - - for (const int old_i : new_by_old_map.index_range()) { - const int new_i = new_by_old_map[old_i]; - CustomData_copy_data(&data, &new_data, old_i, new_i, 1); - } - CustomData_free(&data, new_by_old_map.size()); - data = new_data; -} - -static void reorder_layer_data(GreasePencil &grease_pencil, - const blender::FunctionRef do_layer_order_changes) -{ - using namespace blender; - Span layers = grease_pencil.layers(); - - /* Stash the initial layer order that we can refer back to later */ - Map old_layer_index_by_layer; - old_layer_index_by_layer.reserve(layers.size()); - for (const int i : layers.index_range()) { - old_layer_index_by_layer.add_new(layers[i], i); - } - - /* Execute the callback that changes the order of the layers. */ - do_layer_order_changes(); - layers = grease_pencil.layers(); - BLI_assert(layers.size() == old_layer_index_by_layer.size()); - - /* Compose the mapping from old layer indices to new layer indices */ - Array new_by_old_map(layers.size()); - for (const int layer_i_new : layers.index_range()) { - const bke::greasepencil::Layer *layer = layers[layer_i_new]; - BLI_assert(old_layer_index_by_layer.contains(layer)); - const int layer_i_old = old_layer_index_by_layer.pop(layer); - new_by_old_map[layer_i_old] = layer_i_new; - } - BLI_assert(old_layer_index_by_layer.is_empty()); - - /* Use the mapping to re-order the custom data */ - reorder_customdata(grease_pencil.layers_data, new_by_old_map); -} - void GreasePencil::move_node_up(blender::bke::greasepencil::TreeNode &node, const int step) { - using namespace blender; - if (!node.parent_group()) { - return; + if (node.parent_group()) { + node.parent_group()->move_node_up(node, step); } - reorder_layer_data(*this, [&]() { node.parent_group()->move_node_up(node, step); }); } void GreasePencil::move_node_down(blender::bke::greasepencil::TreeNode &node, const int step) { - using namespace blender; - if (!node.parent_group()) { - return; + if (node.parent_group()) { + node.parent_group()->move_node_down(node, step); } - reorder_layer_data(*this, [&]() { node.parent_group()->move_node_down(node, step); }); } void GreasePencil::move_node_top(blender::bke::greasepencil::TreeNode &node) { - using namespace blender; - if (!node.parent_group()) { - return; + if (node.parent_group()) { + node.parent_group()->move_node_top(node); } - reorder_layer_data(*this, [&]() { node.parent_group()->move_node_top(node); }); } void GreasePencil::move_node_bottom(blender::bke::greasepencil::TreeNode &node) { - using namespace blender; - if (!node.parent_group()) { - return; + if (node.parent_group()) { + node.parent_group()->move_node_bottom(node); } - reorder_layer_data(*this, [&]() { node.parent_group()->move_node_bottom(node); }); } void GreasePencil::move_node_after(blender::bke::greasepencil::TreeNode &node, blender::bke::greasepencil::TreeNode &target_node) { - using namespace blender; if (!target_node.parent_group() || !node.parent_group()) { return; } - reorder_layer_data(*this, [&]() { - node.parent_group()->unlink_node(node); - target_node.parent_group()->add_node_after(node, target_node); - }); + node.parent_group()->unlink_node(node); + target_node.parent_group()->add_node_after(node, target_node); } - void GreasePencil::move_node_before(blender::bke::greasepencil::TreeNode &node, blender::bke::greasepencil::TreeNode &target_node) { - using namespace blender; if (!target_node.parent_group() || !node.parent_group()) { return; } - reorder_layer_data(*this, [&]() { - node.parent_group()->unlink_node(node); - target_node.parent_group()->add_node_before(node, target_node); - }); + node.parent_group()->unlink_node(node); + target_node.parent_group()->add_node_before(node, target_node); } - void GreasePencil::move_node_into(blender::bke::greasepencil::TreeNode &node, blender::bke::greasepencil::LayerGroup &parent_group) { - using namespace blender; - if (!node.parent_group()) { - return; - } - reorder_layer_data(*this, [&]() { + if (node.parent_group()) { node.parent_group()->unlink_node(node); - parent_group.add_node(node); - }); + } + parent_group.add_node(node); } -const blender::bke::greasepencil::TreeNode *GreasePencil::find_node_by_name( +const blender::bke::greasepencil::Layer *GreasePencil::find_layer_by_name( const blender::StringRefNull name) const { - return this->root_group().find_node_by_name(name); + return this->root_group().find_layer_by_name(name); } -blender::bke::greasepencil::TreeNode *GreasePencil::find_node_by_name( +blender::bke::greasepencil::Layer *GreasePencil::find_layer_by_name( const blender::StringRefNull name) { - return this->root_group().find_node_by_name(name); + return this->root_group().find_layer_by_name(name); } -blender::IndexMask GreasePencil::layer_selection_by_name(const blender::StringRefNull name, - blender::IndexMaskMemory &memory) const +const blender::bke::greasepencil::LayerGroup *GreasePencil::find_layer_group_by_name( + blender::StringRefNull name) const { - using namespace blender::bke::greasepencil; - const TreeNode *node = this->find_node_by_name(name); - if (!node) { - return {}; - } + return this->root_group().find_group_by_name(name); +} - if (node->is_layer()) { - const int64_t index = this->layers().first_index(&node->as_layer()); - return blender::IndexMask::from_indices(Span{index}, memory); - } - else if (node->is_group()) { - blender::Vector layer_indices; - for (const int64_t layer_index : this->layers().index_range()) { - const Layer &layer = *this->layers()[layer_index]; - if (layer.is_child_of(node->as_group())) { - layer_indices.append(layer_index); - } - } - return blender::IndexMask::from_indices(layer_indices.as_span(), memory); - } - return {}; +blender::bke::greasepencil::LayerGroup *GreasePencil::find_layer_group_by_name( + blender::StringRefNull name) +{ + return this->root_group().find_group_by_name(name); } void GreasePencil::rename_node(blender::bke::greasepencil::TreeNode &node, @@ -2250,29 +1951,6 @@ void GreasePencil::rename_node(blender::bke::greasepencil::TreeNode &node, unique_layer_group_name(*this, new_name)); } -static void shrink_customdata(CustomData &data, const int index_to_remove, const int size) -{ - using namespace blender; - CustomData new_data; - CustomData_copy_layout(&data, &new_data, CD_MASK_ALL, CD_CONSTRUCT, size); - CustomData_realloc(&new_data, size, size - 1); - - const IndexRange range_before(index_to_remove); - const IndexRange range_after(index_to_remove + 1, size - index_to_remove - 1); - - if (!range_before.is_empty()) { - CustomData_copy_data( - &data, &new_data, range_before.start(), range_before.start(), range_before.size()); - } - if (!range_after.is_empty()) { - CustomData_copy_data( - &data, &new_data, range_after.start(), range_after.start() - 1, range_after.size()); - } - - CustomData_free(&data, size); - data = new_data; -} - void GreasePencil::remove_layer(blender::bke::greasepencil::Layer &layer) { using namespace blender::bke::greasepencil; @@ -2296,10 +1974,6 @@ void GreasePencil::remove_layer(blender::bke::greasepencil::Layer &layer) } } - /* Remove all the layer attributes and shrink the `CustomData`. */ - const int64_t layer_index = this->layers().first_index(&layer); - shrink_customdata(this->layers_data, layer_index, this->layers().size()); - /* Unlink the layer from the parent group. */ layer.parent_group().unlink_node(layer.as_node()); @@ -2395,7 +2069,7 @@ static void free_drawing_array(GreasePencil &grease_pencil) case GP_DRAWING_REFERENCE: { GreasePencilDrawingReference *drawing_reference = reinterpret_cast(drawing_base); - MEM_delete(&drawing_reference->wrap()); + MEM_freeN(drawing_reference); break; } } diff --git a/source/blender/blenkernel/intern/grease_pencil_convert_legacy.cc b/source/blender/blenkernel/intern/grease_pencil_convert_legacy.cc index 058cee519e8..5c196b7065b 100644 --- a/source/blender/blenkernel/intern/grease_pencil_convert_legacy.cc +++ b/source/blender/blenkernel/intern/grease_pencil_convert_legacy.cc @@ -129,14 +129,8 @@ void legacy_gpencil_frame_to_grease_pencil_drawing(const bGPDframe &gpf, /* Do first point. */ const bGPDspoint &first_pt = stroke_points.first(); stroke_positions.first() = float3(first_pt.x, first_pt.y, first_pt.z); - /* Previously, Grease Pencil used a radius convention where 1 `px` = 0.001 units. This `px` was - * the brush size which would be stored in the stroke thickness and then scaled by the point - * pressure factor. Finally, the render engine would divide this thickness value by 2000 (we're - * going from a thickness to a radius, hence the factor of two) to convert back into blender - * units. - * Store the radius now directly in blender units. This makes it consistent with how hair - * curves handle the radius. */ - stroke_radii.first() = gps->thickness * first_pt.pressure / 2000.0f; + /* Store the actual radius of the stroke (without layer adjustment). */ + stroke_radii.first() = gps->thickness * first_pt.pressure; stroke_opacities.first() = first_pt.strength; stroke_deltatimes.first() = 0; stroke_rotations.first() = first_pt.uv_rot; @@ -149,7 +143,8 @@ void legacy_gpencil_frame_to_grease_pencil_drawing(const bGPDframe &gpf, const bGPDspoint &pt_prev = stroke_points[point_i - 1]; const bGPDspoint &pt = stroke_points[point_i]; stroke_positions[point_i] = float3(pt.x, pt.y, pt.z); - stroke_radii[point_i] = gps->thickness * pt.pressure / 2000.0f; + /* Store the actual radius of the stroke (without layer adjustment). */ + stroke_radii[point_i] = gps->thickness * pt.pressure; stroke_opacities[point_i] = pt.strength; stroke_deltatimes[point_i] = pt.time - pt_prev.time; stroke_rotations[point_i] = pt.uv_rot; @@ -193,7 +188,7 @@ void legacy_gpencil_to_grease_pencil(Main &bmain, GreasePencil &grease_pencil, b LISTBASE_FOREACH_INDEX (bGPDlayer *, gpl, &gpd.layers, layer_idx) { /* Create a new layer. */ Layer &new_layer = grease_pencil.add_layer( - StringRefNull(gpl->info, BLI_strnlen(gpl->info, 128))); + grease_pencil.root_group(), StringRefNull(gpl->info, BLI_strnlen(gpl->info, 128))); /* Flags. */ new_layer.set_visible((gpl->flag & GP_LAYER_HIDE) == 0); diff --git a/source/blender/blenkernel/intern/grease_pencil_test.cc b/source/blender/blenkernel/intern/grease_pencil_test.cc index e159448fa44..e1fdc3da991 100644 --- a/source/blender/blenkernel/intern/grease_pencil_test.cc +++ b/source/blender/blenkernel/intern/grease_pencil_test.cc @@ -64,8 +64,8 @@ TEST(greasepencil, remove_drawings) GreasePencilDrawing *drawing = reinterpret_cast(grease_pencil.drawing(1)); drawing->wrap().strokes_for_write().resize(0, 10); - Layer &layer1 = grease_pencil.add_layer("Layer1"); - Layer &layer2 = grease_pencil.add_layer("Layer2"); + Layer &layer1 = grease_pencil.add_layer(grease_pencil.root_group(), "Layer1"); + Layer &layer2 = grease_pencil.add_layer(grease_pencil.root_group(), "Layer2"); layer1.add_frame(0, 0); layer1.add_frame(10, 1); @@ -93,49 +93,35 @@ TEST(greasepencil, remove_drawings) /* --------------------------------------------------------------------------------------------- */ /* Layer Tree Tests. */ -struct GreasePencilHelper : public ::GreasePencil { - GreasePencilHelper() - { - this->root_group_ptr = MEM_new(__func__); - this->active_layer = nullptr; - - CustomData_reset(&this->layers_data); - - this->runtime = MEM_new(__func__); - } - - ~GreasePencilHelper() - { - CustomData_free(&this->layers_data, this->layers().size()); - MEM_delete(&this->root_group()); - MEM_delete(this->runtime); - this->runtime = nullptr; - } -}; - TEST(greasepencil, layer_tree_empty) { - GreasePencilHelper grease_pencil; + GreasePencil grease_pencil; + grease_pencil.root_group_ptr = MEM_new(__func__); EXPECT_EQ(grease_pencil.root_group().num_nodes_total(), 0); + MEM_delete(&grease_pencil.root_group()); } TEST(greasepencil, layer_tree_build_simple) { - GreasePencilHelper grease_pencil; + GreasePencil grease_pencil; + grease_pencil.root_group_ptr = MEM_new(__func__); LayerGroup &group = grease_pencil.add_layer_group(grease_pencil.root_group(), "Group1"); grease_pencil.add_layer(group, "Layer1"); grease_pencil.add_layer(group, "Layer2"); EXPECT_EQ(grease_pencil.root_group().num_nodes_total(), 3); + MEM_delete(&grease_pencil.root_group()); } struct GreasePencilLayerTreeExample { StringRefNull names[7] = {"Group1", "Layer1", "Layer2", "Group2", "Layer3", "Layer4", "Layer5"}; const bool is_layer[7] = {false, true, true, false, true, true, true}; - GreasePencilHelper grease_pencil; + GreasePencil grease_pencil; GreasePencilLayerTreeExample() { + grease_pencil.root_group_ptr = MEM_new(__func__); + LayerGroup &group = grease_pencil.add_layer_group(grease_pencil.root_group(), names[0]); grease_pencil.add_layer(group, names[1]); grease_pencil.add_layer(group, names[2]); @@ -144,7 +130,12 @@ struct GreasePencilLayerTreeExample { grease_pencil.add_layer(group2, names[4]); grease_pencil.add_layer(group2, names[5]); - grease_pencil.add_layer(names[6]); + grease_pencil.add_layer(grease_pencil.root_group(), names[6]); + } + + ~GreasePencilLayerTreeExample() + { + MEM_delete(&grease_pencil.root_group()); } }; @@ -189,29 +180,6 @@ TEST(greasepencil, layer_tree_node_types) } } -TEST(greasepencil, layer_tree_is_child_of) -{ - GreasePencilLayerTreeExample ex; - - EXPECT_FALSE(ex.grease_pencil.root_group().is_child_of(ex.grease_pencil.root_group())); - - const LayerGroup &group1 = ex.grease_pencil.find_node_by_name("Group1")->as_group(); - const LayerGroup &group2 = ex.grease_pencil.find_node_by_name("Group2")->as_group(); - const Layer &layer1 = ex.grease_pencil.find_node_by_name("Layer1")->as_layer(); - const Layer &layer3 = ex.grease_pencil.find_node_by_name("Layer3")->as_layer(); - const Layer &layer5 = ex.grease_pencil.find_node_by_name("Layer5")->as_layer(); - - EXPECT_TRUE(layer1.is_child_of(ex.grease_pencil.root_group())); - EXPECT_TRUE(layer1.is_child_of(group1)); - EXPECT_TRUE(layer3.is_child_of(group1)); - EXPECT_FALSE(layer5.is_child_of(group1)); - - EXPECT_TRUE(layer3.is_child_of(group2)); - EXPECT_FALSE(layer1.is_child_of(group2)); - - EXPECT_TRUE(layer5.is_child_of(ex.grease_pencil.root_group())); -} - /* --------------------------------------------------------------------------------------------- */ /* Frames Tests. */ diff --git a/source/blender/blenkernel/intern/image.cc b/source/blender/blenkernel/intern/image.cc index e229a541b7c..3c8c40f05d4 100644 --- a/source/blender/blenkernel/intern/image.cc +++ b/source/blender/blenkernel/intern/image.cc @@ -22,7 +22,7 @@ #include #include "BLI_array.hh" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "CLG_log.h" diff --git a/source/blender/blenkernel/intern/image_save.cc b/source/blender/blenkernel/intern/image_save.cc index e94b6134c0c..d5c3460d0d0 100644 --- a/source/blender/blenkernel/intern/image_save.cc +++ b/source/blender/blenkernel/intern/image_save.cc @@ -165,10 +165,10 @@ bool BKE_image_save_options_init(ImageSaveOptions *opts, /* check for empty path */ if (guess_path && opts->filepath[0] == 0) { - const bool is_prev_save = !STREQ(G.filepath_last_image, "//"); + const bool is_prev_save = !STREQ(G.ima, "//"); if (opts->save_as_render) { if (is_prev_save) { - STRNCPY(opts->filepath, G.filepath_last_image); + STRNCPY(opts->filepath, G.ima); } else { BLI_path_join(opts->filepath, sizeof(opts->filepath), "//", DATA_("untitled")); @@ -178,8 +178,7 @@ bool BKE_image_save_options_init(ImageSaveOptions *opts, else { BLI_path_join(opts->filepath, sizeof(opts->filepath), "//", ima->id.name + 2); BLI_path_make_safe(opts->filepath); - BLI_path_abs(opts->filepath, - is_prev_save ? G.filepath_last_image : BKE_main_blendfile_path(bmain)); + BLI_path_abs(opts->filepath, is_prev_save ? G.ima : BKE_main_blendfile_path(bmain)); } /* append UDIM marker if not present */ diff --git a/source/blender/blenkernel/intern/instances.cc b/source/blender/blenkernel/intern/instances.cc index 86d9c7eb7b9..fe4d4974ae7 100644 --- a/source/blender/blenkernel/intern/instances.cc +++ b/source/blender/blenkernel/intern/instances.cc @@ -43,71 +43,36 @@ bool operator==(const InstanceReference &a, const InstanceReference &b) return a.type_ == b.type_ && a.data_ == b.data_; } -Instances::Instances() -{ - CustomData_reset(&attributes_); -} - -Instances::Instances(Instances &&other) - : references_(std::move(other.references_)), - reference_handles_(std::move(other.reference_handles_)), - transforms_(std::move(other.transforms_)), - almost_unique_ids_(std::move(other.almost_unique_ids_)), - attributes_(other.attributes_) -{ - CustomData_reset(&other.attributes_); -} - Instances::Instances(const Instances &other) : references_(other.references_), reference_handles_(other.reference_handles_), transforms_(other.transforms_), - almost_unique_ids_(other.almost_unique_ids_) + almost_unique_ids_(other.almost_unique_ids_), + attributes_(other.attributes_) { - CustomData_copy(&other.attributes_, &attributes_, CD_MASK_ALL, other.instances_num()); } -Instances::~Instances() +void Instances::reserve(int min_capacity) { - CustomData_free(&attributes_, this->instances_num()); -} - -Instances &Instances::operator=(const Instances &other) -{ - if (this == &other) { - return *this; - } - std::destroy_at(this); - new (this) Instances(other); - return *this; -} - -Instances &Instances::operator=(Instances &&other) -{ - if (this == &other) { - return *this; - } - std::destroy_at(this); - new (this) Instances(std::move(other)); - return *this; + reference_handles_.reserve(min_capacity); + transforms_.reserve(min_capacity); + attributes_.reallocate(min_capacity); } void Instances::resize(int capacity) { - const int old_size = this->instances_num(); reference_handles_.resize(capacity); transforms_.resize(capacity); - CustomData_realloc(&attributes_, old_size, capacity, CD_SET_DEFAULT); + attributes_.reallocate(capacity); } void Instances::add_instance(const int instance_handle, const float4x4 &transform) { BLI_assert(instance_handle >= 0); BLI_assert(instance_handle < references_.size()); - const int old_size = this->instances_num(); reference_handles_.append(instance_handle); transforms_.append(transform); - CustomData_realloc(&attributes_, old_size, transforms_.size()); + attributes_.reallocate(this->instances_num()); } Span Instances::reference_handles() const @@ -173,25 +138,37 @@ void Instances::remove(const IndexMask &mask, return; } - const int new_size = mask.size(); + const Span old_handles = this->reference_handles(); + Vector new_handles(mask.size()); + array_utils::gather(old_handles, mask, new_handles.as_mutable_span()); + reference_handles_ = std::move(new_handles); - Instances new_instances; - new_instances.references_ = std::move(references_); - new_instances.reference_handles_.resize(new_size); - new_instances.transforms_.resize(new_size); - array_utils::gather( - reference_handles_.as_span(), mask, new_instances.reference_handles_.as_mutable_span()); - array_utils::gather(transforms_.as_span(), mask, new_instances.transforms_.as_mutable_span()); + const Span old_tansforms = this->transforms(); + Vector new_transforms(mask.size()); + array_utils::gather(old_tansforms, mask, new_transforms.as_mutable_span()); + transforms_ = std::move(new_transforms); - gather_attributes(this->attributes(), - ATTR_DOMAIN_INSTANCE, - propagation_info, - {"position"}, - mask, - new_instances.attributes_for_write()); + const bke::CustomDataAttributes &src_attributes = attributes_; - *this = std::move(new_instances); + bke::CustomDataAttributes dst_attributes; + dst_attributes.reallocate(mask.size()); + src_attributes.foreach_attribute( + [&](const bke::AttributeIDRef &id, const bke::AttributeMetaData &meta_data) { + if (id.is_anonymous() && !propagation_info.propagate(id.anonymous_id())) { + return true; + } + + GSpan src = *src_attributes.get_for_read(id); + dst_attributes.create(id, meta_data.data_type); + GMutableSpan dst = *dst_attributes.get_for_write(id); + array_utils::gather(src, mask, dst); + + return true; + }, + ATTR_DOMAIN_INSTANCE); + + attributes_ = std::move(dst_attributes); this->remove_unused_references(); } @@ -357,9 +334,9 @@ static Array generate_unique_instance_ids(Span original_ids) Span Instances::almost_unique_ids() const { std::lock_guard lock(almost_unique_ids_mutex_); - bke::AttributeReader instance_ids_attribute = this->attributes().lookup("id"); - if (instance_ids_attribute) { - Span instance_ids = instance_ids_attribute.varray.get_internal_span(); + std::optional instance_ids_gspan = attributes_.get_for_read("id"); + if (instance_ids_gspan) { + Span instance_ids = instance_ids_gspan->typed(); if (almost_unique_ids_.size() != instance_ids.size()) { almost_unique_ids_ = generate_unique_instance_ids(instance_ids); } diff --git a/source/blender/blenkernel/intern/ipo.cc b/source/blender/blenkernel/intern/ipo.cc index c8d643ff56c..4a86fa59ea4 100644 --- a/source/blender/blenkernel/intern/ipo.cc +++ b/source/blender/blenkernel/intern/ipo.cc @@ -38,7 +38,7 @@ #include "BLI_blenlib.h" #include "BLI_dynstr.h" #include "BLI_endian_switch.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_utildefines.h" #include "BLT_translation.h" diff --git a/source/blender/blenkernel/intern/key.cc b/source/blender/blenkernel/intern/key.cc index acc8d441bf2..d1a978ed04b 100644 --- a/source/blender/blenkernel/intern/key.cc +++ b/source/blender/blenkernel/intern/key.cc @@ -16,7 +16,7 @@ #include "BLI_endian_switch.h" #include "BLI_math_matrix.h" #include "BLI_math_vector.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_utildefines.h" #include "BLT_translation.h" @@ -2287,6 +2287,8 @@ void BKE_keyblock_mesh_calc_normals(const KeyBlock *kb, sharp_edges, sharp_faces, clnors, + (mesh->flag & ME_AUTOSMOOTH) != 0, + mesh->smoothresh, nullptr, {reinterpret_cast(r_loop_normals), corner_verts.size()}); } diff --git a/source/blender/blenkernel/intern/layer.cc b/source/blender/blenkernel/intern/layer.cc index e6ec1fff536..a5fac388d3c 100644 --- a/source/blender/blenkernel/intern/layer.cc +++ b/source/blender/blenkernel/intern/layer.cc @@ -17,7 +17,7 @@ #include "BLI_mempool.h" #include "BLI_string.h" #include "BLI_string_utf8.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_threads.h" #include "BLT_translation.h" diff --git a/source/blender/blenkernel/intern/lib_id.cc b/source/blender/blenkernel/intern/lib_id.cc index 35ec2fcd05f..0e46ee400f6 100644 --- a/source/blender/blenkernel/intern/lib_id.cc +++ b/source/blender/blenkernel/intern/lib_id.cc @@ -35,7 +35,7 @@ #include "BLI_ghash.h" #include "BLI_linklist.h" #include "BLI_memarena.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLT_translation.h" diff --git a/source/blender/blenkernel/intern/lightprobe.cc b/source/blender/blenkernel/intern/lightprobe.cc index 902f8724f17..f4543bde815 100644 --- a/source/blender/blenkernel/intern/lightprobe.cc +++ b/source/blender/blenkernel/intern/lightprobe.cc @@ -86,17 +86,17 @@ void BKE_lightprobe_type_set(LightProbe *probe, const short lightprobe_type) probe->type = lightprobe_type; switch (probe->type) { - case LIGHTPROBE_TYPE_VOLUME: + case LIGHTPROBE_TYPE_GRID: probe->distinf = 0.3f; probe->falloff = 1.0f; probe->clipsta = 0.01f; break; - case LIGHTPROBE_TYPE_PLANE: + case LIGHTPROBE_TYPE_PLANAR: probe->distinf = 0.1f; probe->falloff = 0.5f; probe->clipsta = 0.001f; break; - case LIGHTPROBE_TYPE_SPHERE: + case LIGHTPROBE_TYPE_CUBE: probe->attenuation_type = LIGHTPROBE_SHAPE_ELIPSOID; break; default: diff --git a/source/blender/blenkernel/intern/linestyle.cc b/source/blender/blenkernel/intern/linestyle.cc index 565f373636d..eaa858e642e 100644 --- a/source/blender/blenkernel/intern/linestyle.cc +++ b/source/blender/blenkernel/intern/linestyle.cc @@ -19,7 +19,7 @@ #include "BLI_blenlib.h" #include "BLI_math_rotation.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_utildefines.h" #include "BLT_translation.h" diff --git a/source/blender/blenkernel/intern/main_namemap.cc b/source/blender/blenkernel/intern/main_namemap.cc index 6391771f5a3..828dab51c3c 100644 --- a/source/blender/blenkernel/intern/main_namemap.cc +++ b/source/blender/blenkernel/intern/main_namemap.cc @@ -20,7 +20,7 @@ #include "BLI_set.hh" #include "BLI_string.h" #include "BLI_string_utf8.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "DNA_ID.h" diff --git a/source/blender/blenkernel/intern/mask.cc b/source/blender/blenkernel/intern/mask.cc index 1058bdfd75a..efbc5fb8416 100644 --- a/source/blender/blenkernel/intern/mask.cc +++ b/source/blender/blenkernel/intern/mask.cc @@ -20,7 +20,7 @@ #include "BLI_math_matrix.h" #include "BLI_math_vector.h" #include "BLI_string.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_utildefines.h" #include "BLT_translation.h" diff --git a/source/blender/blenkernel/intern/material.cc b/source/blender/blenkernel/intern/material.cc index 992b1be8e82..894181646a6 100644 --- a/source/blender/blenkernel/intern/material.cc +++ b/source/blender/blenkernel/intern/material.cc @@ -53,7 +53,6 @@ #include "BKE_displist.h" #include "BKE_editmesh.h" #include "BKE_gpencil_legacy.h" -#include "BKE_grease_pencil.hh" #include "BKE_icons.h" #include "BKE_idtype.h" #include "BKE_image.h" @@ -1113,9 +1112,6 @@ void BKE_object_material_remap(Object *ob, const uint *remap) else if (ob->type == OB_GPENCIL_LEGACY) { BKE_gpencil_material_remap(static_cast(ob->data), remap, ob->totcol); } - else if (ob->type == OB_GREASE_PENCIL) { - BKE_grease_pencil_material_remap(static_cast(ob->data), remap, ob->totcol); - } else { /* add support for this object data! */ BLI_assert(matar == nullptr); diff --git a/source/blender/blenkernel/intern/mball.cc b/source/blender/blenkernel/intern/mball.cc index 564d8983524..f537ddf7fe7 100644 --- a/source/blender/blenkernel/intern/mball.cc +++ b/source/blender/blenkernel/intern/mball.cc @@ -32,7 +32,7 @@ #include "BLI_math_matrix.h" #include "BLI_math_rotation.h" #include "BLI_math_vector.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_utildefines.h" #include "BLT_translation.h" diff --git a/source/blender/blenkernel/intern/mball_tessellate.cc b/source/blender/blenkernel/intern/mball_tessellate.cc index 60d5d0bcd28..03fe73575e8 100644 --- a/source/blender/blenkernel/intern/mball_tessellate.cc +++ b/source/blender/blenkernel/intern/mball_tessellate.cc @@ -27,7 +27,7 @@ #include "BLI_math_rotation.h" #include "BLI_math_vector.h" #include "BLI_memarena.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_utildefines.h" #include "BKE_displist.h" diff --git a/source/blender/blenkernel/intern/mesh.cc b/source/blender/blenkernel/intern/mesh.cc index 7638788febe..7ad21a02b66 100644 --- a/source/blender/blenkernel/intern/mesh.cc +++ b/source/blender/blenkernel/intern/mesh.cc @@ -972,6 +972,7 @@ void BKE_mesh_copy_parameters(Mesh *me_dst, const Mesh *me_src) /* Copy general settings. */ me_dst->editflag = me_src->editflag; me_dst->flag = me_src->flag; + me_dst->smoothresh = me_src->smoothresh; me_dst->remesh_voxel_size = me_src->remesh_voxel_size; me_dst->remesh_voxel_adaptivity = me_src->remesh_voxel_adaptivity; me_dst->remesh_mode = me_src->remesh_mode; @@ -1154,20 +1155,28 @@ void BKE_mesh_ensure_default_orig_index_customdata_no_check(Mesh *mesh) ensure_orig_index_layer(mesh->face_data, mesh->faces_num); } -BoundBox BKE_mesh_boundbox_get(Object *ob) +BoundBox *BKE_mesh_boundbox_get(Object *ob) { - Mesh *me = static_cast(ob->data); - float min[3], max[3]; + /* This is Object-level data access, + * DO NOT touch to Mesh's bb, would be totally thread-unsafe. */ + if (ob->runtime.bb == nullptr || ob->runtime.bb->flag & BOUNDBOX_DIRTY) { + Mesh *me = static_cast(ob->data); + float min[3], max[3]; - INIT_MINMAX(min, max); - if (!BKE_mesh_wrapper_minmax(me, min, max)) { - min[0] = min[1] = min[2] = -1.0f; - max[0] = max[1] = max[2] = 1.0f; + INIT_MINMAX(min, max); + if (!BKE_mesh_wrapper_minmax(me, min, max)) { + min[0] = min[1] = min[2] = -1.0f; + max[0] = max[1] = max[2] = 1.0f; + } + + if (ob->runtime.bb == nullptr) { + ob->runtime.bb = (BoundBox *)MEM_mallocN(sizeof(*ob->runtime.bb), __func__); + } + BKE_boundbox_init_from_minmax(ob->runtime.bb, min, max); + ob->runtime.bb->flag &= ~BOUNDBOX_DIRTY; } - BoundBox bb; - BKE_boundbox_init_from_minmax(&bb, min, max); - return bb; + return ob->runtime.bb; } void BKE_mesh_texspace_calc(Mesh *me) @@ -1428,11 +1437,9 @@ void BKE_mesh_smooth_flag_set(Mesh *me, const bool use_smooth) using namespace blender::bke; MutableAttributeAccessor attributes = me->attributes_for_write(); if (use_smooth) { - attributes.remove("sharp_edge"); attributes.remove("sharp_face"); } else { - attributes.remove("sharp_edge"); SpanAttributeWriter sharp_faces = attributes.lookup_or_add_for_write_only_span( "sharp_face", ATTR_DOMAIN_FACE); sharp_faces.span.fill(true); @@ -1440,33 +1447,17 @@ void BKE_mesh_smooth_flag_set(Mesh *me, const bool use_smooth) } } -void BKE_mesh_sharp_edges_set_from_angle(Mesh *me, const float angle) +void BKE_mesh_auto_smooth_flag_set(Mesh *me, + const bool use_auto_smooth, + const float auto_smooth_angle) { - using namespace blender; - using namespace blender::bke; - bke::MutableAttributeAccessor attributes = me->attributes_for_write(); - if (angle >= M_PI) { - attributes.remove("sharp_edge"); - attributes.remove("sharp_face"); - return; + if (use_auto_smooth) { + me->flag |= ME_AUTOSMOOTH; + me->smoothresh = auto_smooth_angle; } - if (angle == 0.0f) { - BKE_mesh_smooth_flag_set(me, false); - return; + else { + me->flag &= ~ME_AUTOSMOOTH; } - bke::SpanAttributeWriter sharp_edges = attributes.lookup_or_add_for_write_span( - "sharp_edge", ATTR_DOMAIN_EDGE); - const bool *sharp_faces = static_cast( - CustomData_get_layer_named(&me->face_data, CD_PROP_BOOL, "sharp_face")); - bke::mesh::edges_sharp_from_angle_set(me->faces(), - me->corner_verts(), - me->corner_edges(), - me->face_normals(), - me->corner_to_face_map(), - sharp_faces, - angle, - sharp_edges.span); - sharp_edges.finish(); } void BKE_mesh_looptri_get_real_edges(const blender::int2 *edges, @@ -1523,6 +1514,20 @@ void BKE_mesh_transform(Mesh *me, const float mat[4][4], bool do_keys) } } + /* don't update normals, caller can do this explicitly. + * We do update loop normals though, those may not be auto-generated + * (see e.g. STL import script)! */ + float(*lnors)[3] = (float(*)[3])CustomData_get_layer_for_write( + &me->loop_data, CD_NORMAL, me->totloop); + if (lnors) { + float m3[3][3]; + + copy_m3_m4(m3, mat); + normalize_m3(m3); + for (int i = 0; i < me->totloop; i++, lnors++) { + mul_m3_v3(m3, *lnors); + } + } BKE_mesh_tag_positions_changed(me); } @@ -1734,6 +1739,63 @@ void BKE_mesh_vert_coords_apply_with_mat4(Mesh *mesh, BKE_mesh_tag_positions_changed(mesh); } +static float (*ensure_corner_normal_layer(Mesh &mesh))[3] +{ + float(*r_loop_normals)[3]; + if (CustomData_has_layer(&mesh.loop_data, CD_NORMAL)) { + r_loop_normals = (float(*)[3])CustomData_get_layer_for_write( + &mesh.loop_data, CD_NORMAL, mesh.totloop); + memset(r_loop_normals, 0, sizeof(float[3]) * mesh.totloop); + } + else { + r_loop_normals = (float(*)[3])CustomData_add_layer( + &mesh.loop_data, CD_NORMAL, CD_SET_DEFAULT, mesh.totloop); + CustomData_set_layer_flag(&mesh.loop_data, CD_NORMAL, CD_FLAG_TEMPORARY); + } + return r_loop_normals; +} + +void BKE_mesh_calc_normals_split_ex(const Mesh *mesh, + MLoopNorSpaceArray *r_lnors_spacearr, + float (*r_corner_normals)[3]) +{ + /* Note that we enforce computing clnors when the clnor space array is requested by caller here. + * However, we obviously only use the auto-smooth angle threshold + * only in case auto-smooth is enabled. */ + const bool use_split_normals = (r_lnors_spacearr != nullptr) || + ((mesh->flag & ME_AUTOSMOOTH) != 0); + const float split_angle = (mesh->flag & ME_AUTOSMOOTH) != 0 ? mesh->smoothresh : float(M_PI); + + const blender::short2 *clnors = static_cast( + CustomData_get_layer(&mesh->loop_data, CD_CUSTOMLOOPNORMAL)); + const bool *sharp_edges = static_cast( + CustomData_get_layer_named(&mesh->edge_data, CD_PROP_BOOL, "sharp_edge")); + const bool *sharp_faces = static_cast( + CustomData_get_layer_named(&mesh->face_data, CD_PROP_BOOL, "sharp_face")); + + blender::bke::mesh::normals_calc_loop( + mesh->vert_positions(), + mesh->edges(), + mesh->faces(), + mesh->corner_verts(), + mesh->corner_edges(), + mesh->corner_to_face_map(), + mesh->vert_normals(), + mesh->face_normals(), + sharp_edges, + sharp_faces, + clnors, + use_split_normals, + split_angle, + nullptr, + {reinterpret_cast(r_corner_normals), mesh->totloop}); +} + +void BKE_mesh_calc_normals_split(Mesh *mesh) +{ + BKE_mesh_calc_normals_split_ex(mesh, nullptr, ensure_corner_normal_layer(*mesh)); +} + /* **** Depsgraph evaluation **** */ void BKE_mesh_eval_geometry(Depsgraph *depsgraph, Mesh *mesh) diff --git a/source/blender/blenkernel/intern/mesh_fair.cc b/source/blender/blenkernel/intern/mesh_fair.cc index 7dd7f4cfdbe..e589fb0f58f 100644 --- a/source/blender/blenkernel/intern/mesh_fair.cc +++ b/source/blender/blenkernel/intern/mesh_fair.cc @@ -57,7 +57,7 @@ class FairingContext { float r_adj_prev[3]) = 0; /* Get the other vertex index for a loop. */ - virtual int other_vertex_index_from_loop(const int loop, const int v) = 0; + virtual int other_vertex_index_from_loop(const int loop, const uint v) = 0; int vertex_count_get() { @@ -234,7 +234,7 @@ class MeshFairingContext : public FairingContext { copy_v3_v3(r_adj_prev, co_[adjecent_verts[1]]); } - int other_vertex_index_from_loop(const int loop, const int v) override + int other_vertex_index_from_loop(const int loop, const uint v) override { const blender::int2 &edge = edges_[corner_edges_[loop]]; return blender::bke::mesh::edge_other_vert(edge, v); @@ -299,7 +299,7 @@ class BMeshFairingContext : public FairingContext { copy_v3_v3(r_adj_prev, bmloop_[loop]->prev->v->co); } - int other_vertex_index_from_loop(const int loop, const int v) override + int other_vertex_index_from_loop(const int loop, const uint v) override { BMLoop *l = bmloop_[loop]; BMVert *bmvert = BM_vert_at_index(bm, v); diff --git a/source/blender/blenkernel/intern/mesh_iterators.cc b/source/blender/blenkernel/intern/mesh_iterators.cc index ff16d6cdf72..052cd3d7316 100644 --- a/source/blender/blenkernel/intern/mesh_iterators.cc +++ b/source/blender/blenkernel/intern/mesh_iterators.cc @@ -167,7 +167,9 @@ void BKE_mesh_foreach_mapped_loop(Mesh *mesh, /* XXX: investigate using EditMesh data. */ blender::Span corner_normals; if (flag & MESH_FOREACH_USE_NORMAL) { - corner_normals = mesh->corner_normals(); + corner_normals = { + static_cast(CustomData_get_layer(&mesh->loop_data, CD_NORMAL)), + mesh->totloop}; } int f_idx; @@ -192,7 +194,9 @@ void BKE_mesh_foreach_mapped_loop(Mesh *mesh, else { blender::Span corner_normals; if (flag & MESH_FOREACH_USE_NORMAL) { - corner_normals = mesh->corner_normals(); + corner_normals = { + static_cast(CustomData_get_layer(&mesh->loop_data, CD_NORMAL)), + mesh->totloop}; } const blender::Span positions = mesh->vert_positions(); diff --git a/source/blender/blenkernel/intern/mesh_legacy_convert.cc b/source/blender/blenkernel/intern/mesh_legacy_convert.cc index 6729cff9754..f45dcbf1c17 100644 --- a/source/blender/blenkernel/intern/mesh_legacy_convert.cc +++ b/source/blender/blenkernel/intern/mesh_legacy_convert.cc @@ -21,7 +21,6 @@ #include "BLI_map.hh" #include "BLI_math_geom.h" #include "BLI_math_matrix.h" -#include "BLI_math_rotation.h" #include "BLI_math_vector_types.hh" #include "BLI_memarena.h" #include "BLI_multi_value_map.hh" @@ -34,17 +33,10 @@ #include "BKE_attribute.hh" #include "BKE_customdata.h" #include "BKE_global.h" -#include "BKE_idprop.hh" #include "BKE_main.h" #include "BKE_mesh.hh" #include "BKE_mesh_legacy_convert.hh" -#include "BKE_modifier.h" #include "BKE_multires.hh" -#include "BKE_node.hh" -#include "BKE_node_runtime.hh" -#include "BKE_node_tree_update.h" - -#include "BLT_translation.h" using blender::MutableSpan; using blender::Span; @@ -2096,7 +2088,7 @@ void BKE_mesh_legacy_convert_polys_to_offsets(Mesh *mesh) else { /* Reorder mesh polygons to match the order of their loops. */ Array orig_indices(polys.size()); - array_utils::fill_index_range(orig_indices); + std::iota(orig_indices.begin(), orig_indices.end(), 0); std::stable_sort(orig_indices.begin(), orig_indices.end(), [polys](const int a, const int b) { return polys[a].loopstart < polys[b].loopstart; }); @@ -2124,174 +2116,3 @@ void BKE_mesh_legacy_convert_polys_to_offsets(Mesh *mesh) } /** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Auto Smooth Conversion - * \{ */ - -static bNodeTree *add_auto_smooth_node_tree(Main &bmain) -{ - bNodeTree *group = ntreeAddTree(&bmain, DATA_("Auto Smooth"), "GeometryNodeTree"); - - group->tree_interface.add_socket(DATA_("Geometry"), - "", - "NodeSocketGeometry", - NODE_INTERFACE_SOCKET_INPUT | NODE_INTERFACE_SOCKET_OUTPUT, - nullptr); - bNodeTreeInterfaceSocket *angle_io_socket = group->tree_interface.add_socket( - DATA_("Angle"), "", "NodeSocketFloat", NODE_INTERFACE_SOCKET_INPUT, nullptr); - auto &angle_data = *static_cast(angle_io_socket->socket_data); - angle_data.min = 0.0f; - angle_data.max = DEG2RADF(180.0f); - angle_data.subtype = PROP_ANGLE; - - bNode *group_output = nodeAddNode(nullptr, group, "NodeGroupOutput"); - group_output->locx = 480.0f; - group_output->locy = -100.0f; - bNode *group_input_angle = nodeAddNode(nullptr, group, "NodeGroupInput"); - group_input_angle->locx = -420.0f; - group_input_angle->locy = -300.0f; - LISTBASE_FOREACH (bNodeSocket *, socket, &group_input_angle->outputs) { - if (!STREQ(socket->identifier, "Socket_1")) { - socket->flag |= SOCK_HIDDEN; - } - } - bNode *group_input_mesh = nodeAddNode(nullptr, group, "NodeGroupInput"); - group_input_mesh->locx = -60.0f; - group_input_mesh->locy = -100.0f; - LISTBASE_FOREACH (bNodeSocket *, socket, &group_input_mesh->outputs) { - if (!STREQ(socket->identifier, "Socket_0")) { - socket->flag |= SOCK_HIDDEN; - } - } - bNode *shade_smooth_edge = nodeAddNode(nullptr, group, "GeometryNodeSetShadeSmooth"); - shade_smooth_edge->custom1 = ATTR_DOMAIN_EDGE; - shade_smooth_edge->locx = 120.0f; - shade_smooth_edge->locy = -100.0f; - bNode *shade_smooth_face = nodeAddNode(nullptr, group, "GeometryNodeSetShadeSmooth"); - shade_smooth_face->custom1 = ATTR_DOMAIN_FACE; - shade_smooth_face->locx = 300.0f; - shade_smooth_face->locy = -100.0f; - bNode *edge_angle = nodeAddNode(nullptr, group, "GeometryNodeInputMeshEdgeAngle"); - edge_angle->locx = -420.0f; - edge_angle->locy = -220.0f; - bNode *edge_smooth = nodeAddNode(nullptr, group, "GeometryNodeInputEdgeSmooth"); - edge_smooth->locx = -60.0f; - edge_smooth->locy = -160.0f; - bNode *face_smooth = nodeAddNode(nullptr, group, "GeometryNodeInputShadeSmooth"); - face_smooth->locx = -240.0f; - face_smooth->locy = -340.0f; - bNode *boolean_and = nodeAddNode(nullptr, group, "FunctionNodeBooleanMath"); - boolean_and->custom1 = NODE_BOOLEAN_MATH_AND; - boolean_and->locx = -60.0f; - boolean_and->locy = -220.0f; - bNode *less_than_or_equal = nodeAddNode(nullptr, group, "FunctionNodeCompare"); - static_cast(less_than_or_equal->storage)->operation = - NODE_COMPARE_LESS_EQUAL; - less_than_or_equal->locx = -240.0f; - less_than_or_equal->locy = -180.0f; - - nodeAddLink(group, - edge_angle, - nodeFindSocket(edge_angle, SOCK_OUT, "Unsigned Angle"), - less_than_or_equal, - nodeFindSocket(less_than_or_equal, SOCK_IN, "A")); - nodeAddLink(group, - shade_smooth_face, - nodeFindSocket(shade_smooth_face, SOCK_OUT, "Geometry"), - group_output, - nodeFindSocket(group_output, SOCK_IN, "Socket_0")); - nodeAddLink(group, - group_input_angle, - nodeFindSocket(group_input_angle, SOCK_OUT, "Socket_1"), - less_than_or_equal, - nodeFindSocket(less_than_or_equal, SOCK_IN, "B")); - nodeAddLink(group, - less_than_or_equal, - nodeFindSocket(less_than_or_equal, SOCK_OUT, "Result"), - boolean_and, - nodeFindSocket(boolean_and, SOCK_IN, "Boolean")); - nodeAddLink(group, - face_smooth, - nodeFindSocket(face_smooth, SOCK_OUT, "Smooth"), - boolean_and, - nodeFindSocket(boolean_and, SOCK_IN, "Boolean_001")); - nodeAddLink(group, - group_input_mesh, - nodeFindSocket(group_input_mesh, SOCK_OUT, "Socket_0"), - shade_smooth_edge, - nodeFindSocket(shade_smooth_edge, SOCK_IN, "Geometry")); - nodeAddLink(group, - edge_smooth, - nodeFindSocket(edge_smooth, SOCK_OUT, "Smooth"), - shade_smooth_edge, - nodeFindSocket(shade_smooth_edge, SOCK_IN, "Selection")); - nodeAddLink(group, - shade_smooth_edge, - nodeFindSocket(shade_smooth_edge, SOCK_OUT, "Geometry"), - shade_smooth_face, - nodeFindSocket(shade_smooth_face, SOCK_IN, "Geometry")); - nodeAddLink(group, - boolean_and, - nodeFindSocket(boolean_and, SOCK_OUT, "Boolean"), - shade_smooth_edge, - nodeFindSocket(shade_smooth_edge, SOCK_IN, "Shade Smooth")); - - LISTBASE_FOREACH (bNode *, node, &group->nodes) { - nodeSetSelected(node, false); - } - - return group; -} - -void BKE_main_mesh_legacy_convert_auto_smooth(Main &bmain) -{ - using namespace blender; - bNodeTree *auto_smooth_node_tree = nullptr; - LISTBASE_FOREACH (Object *, object, &bmain.objects) { - if (object->type != OB_MESH) { - continue; - } - Mesh *mesh = static_cast(object->data); - if (!(mesh->flag & ME_AUTOSMOOTH_LEGACY)) { - continue; - } - if (CustomData_has_layer(&mesh->loop_data, CD_CUSTOMLOOPNORMAL)) { - continue; - } - if (!auto_smooth_node_tree) { - auto_smooth_node_tree = add_auto_smooth_node_tree(bmain); - BKE_ntree_update_main_tree(&bmain, auto_smooth_node_tree, nullptr); - } - auto *md = reinterpret_cast(BKE_modifier_new(eModifierType_Nodes)); - STRNCPY(md->modifier.name, DATA_("Auto Smooth")); - BKE_modifier_unique_name(&object->modifiers, &md->modifier); - md->node_group = auto_smooth_node_tree; - if (!BLI_listbase_is_empty(&object->modifiers) && - static_cast(object->modifiers.last)->type == eModifierType_Subsurf) - { - /* Add the auto smooth node group before the last subdivision surface modifier if possible. - * Subdivision surface modifiers have special handling for interpolating face corner normals, - * and recalculating them afterwards isn't usually helpful and can be much slower. */ - BLI_insertlinkbefore(&object->modifiers, object->modifiers.last, md); - } - else { - BLI_addtail(&object->modifiers, md); - } - - md->settings.properties = bke::idprop::create_group("Nodes Modifier Settings").release(); - IDProperty *angle_prop = - bke::idprop::create(DATA_("Socket_1"), mesh->smoothresh_legacy).release(); - auto *ui_data = reinterpret_cast(IDP_ui_data_ensure(angle_prop)); - ui_data->base.rna_subtype = PROP_ANGLE; - ui_data->soft_min = 0.0f; - ui_data->soft_max = DEG2RADF(180.0f); - IDP_AddToGroup(md->settings.properties, angle_prop); - IDP_AddToGroup(md->settings.properties, - bke::idprop::create(DATA_("Input_1_use_attribute"), 0).release()); - IDP_AddToGroup(md->settings.properties, - bke::idprop::create(DATA_("Input_1_attribute_name"), "").release()); - } -} - -/** \} */ diff --git a/source/blender/blenkernel/intern/mesh_mirror.cc b/source/blender/blenkernel/intern/mesh_mirror.cc index 9a415c16163..61e66515697 100644 --- a/source/blender/blenkernel/intern/mesh_mirror.cc +++ b/source/blender/blenkernel/intern/mesh_mirror.cc @@ -15,7 +15,6 @@ #include "DNA_meshdata_types.h" #include "DNA_object_types.h" -#include "BKE_attribute.hh" #include "BKE_deform.h" #include "BKE_lib_id.h" #include "BKE_lib_query.h" @@ -390,8 +389,8 @@ Mesh *BKE_mesh_mirror_apply_mirror_on_axis_for_modifier(MirrorModifierData *mmd, } /* handle custom split normals */ - if (ob->type == OB_MESH && CustomData_has_layer(&result->loop_data, CD_CUSTOMLOOPNORMAL) && - result->faces_num > 0) + if (ob->type == OB_MESH && (((Mesh *)ob->data)->flag & ME_AUTOSMOOTH) && + CustomData_has_layer(&result->loop_data, CD_CUSTOMLOOPNORMAL) && result->faces_num > 0) { blender::Array loop_normals(result_corner_verts.size()); blender::short2 *clnors = static_cast( @@ -421,6 +420,8 @@ Mesh *BKE_mesh_mirror_apply_mirror_on_axis_for_modifier(MirrorModifierData *mmd, sharp_edges, sharp_faces, clnors, + true, + result->smoothresh, &lnors_spacearr, loop_normals); diff --git a/source/blender/blenkernel/intern/mesh_normals.cc b/source/blender/blenkernel/intern/mesh_normals.cc index 640075689f3..599802bba33 100644 --- a/source/blender/blenkernel/intern/mesh_normals.cc +++ b/source/blender/blenkernel/intern/mesh_normals.cc @@ -299,42 +299,6 @@ static void normals_calc_faces_and_verts(const Span positions, /** \name Mesh Normal Calculation * \{ */ -blender::bke::MeshNormalDomain Mesh::normals_domain() const -{ - using namespace blender; - using namespace blender::bke; - if (this->faces_num == 0) { - return MeshNormalDomain::Point; - } - - if (CustomData_has_layer(&this->loop_data, CD_CUSTOMLOOPNORMAL)) { - return MeshNormalDomain::Corner; - } - - const AttributeAccessor attributes = this->attributes(); - const VArray sharp_faces = *attributes.lookup_or_default( - "sharp_face", ATTR_DOMAIN_FACE, false); - - const array_utils::BooleanMix face_mix = array_utils::booleans_mix_calc(sharp_faces); - if (face_mix == array_utils::BooleanMix::AllTrue) { - return MeshNormalDomain::Face; - } - - const VArray sharp_edges = *attributes.lookup_or_default( - "sharp_edge", ATTR_DOMAIN_EDGE, false); - const array_utils::BooleanMix edge_mix = array_utils::booleans_mix_calc(sharp_edges); - if (edge_mix == array_utils::BooleanMix::AllTrue) { - return MeshNormalDomain::Face; - } - - if (edge_mix == array_utils::BooleanMix::AllFalse && - face_mix == array_utils::BooleanMix::AllFalse) { - return MeshNormalDomain::Point; - } - - return MeshNormalDomain::Corner; -} - blender::Span Mesh::vert_normals() const { using namespace blender; @@ -384,52 +348,25 @@ blender::Span Mesh::face_normals() const return this->runtime->face_normals_cache.data(); } -blender::Span Mesh::corner_normals() const +void BKE_mesh_ensure_normals_for_display(Mesh *mesh) { - using namespace blender; - using namespace blender::bke; - this->runtime->corner_normals_cache.ensure([&](Vector &r_data) { - r_data.reinitialize(this->totloop); - const OffsetIndices faces = this->faces(); - switch (this->normals_domain()) { - case MeshNormalDomain::Point: { - array_utils::gather(this->vert_normals(), this->corner_verts(), r_data.as_mutable_span()); - break; - } - case MeshNormalDomain::Face: { - const Span face_normals = this->face_normals(); - threading::parallel_for(faces.index_range(), 1024, [&](const IndexRange range) { - for (const int i : range) { - r_data.as_mutable_span().slice(faces[i]).fill(face_normals[i]); - } - }); - break; - } - case MeshNormalDomain::Corner: { - const bool *sharp_edges = static_cast( - CustomData_get_layer_named(&this->edge_data, CD_PROP_BOOL, "sharp_edge")); - const bool *sharp_faces = static_cast( - CustomData_get_layer_named(&this->face_data, CD_PROP_BOOL, "sharp_face")); - const short2 *custom_normals = static_cast( - CustomData_get_layer(&this->loop_data, CD_CUSTOMLOOPNORMAL)); - mesh::normals_calc_loop(this->vert_positions(), - this->edges(), - this->faces(), - this->corner_verts(), - this->corner_edges(), - this->corner_to_face_map(), - this->vert_normals(), - this->face_normals(), - sharp_edges, - sharp_faces, - custom_normals, - nullptr, - r_data); - break; + switch (mesh->runtime->wrapper_type) { + case ME_WRAPPER_TYPE_SUBD: + case ME_WRAPPER_TYPE_MDATA: + mesh->vert_normals(); + mesh->face_normals(); + break; + case ME_WRAPPER_TYPE_BMESH: { + BMEditMesh *em = mesh->edit_mesh; + if (blender::bke::EditMeshData *emd = mesh->runtime->edit_data) { + if (!emd->vertexCos.is_empty()) { + BKE_editmesh_cache_ensure_vert_normals(em, emd); + BKE_editmesh_cache_ensure_face_normals(em, emd); + } } + return; } - }); - return this->runtime->corner_normals_cache.data(); + } } void BKE_lnor_spacearr_init(MLoopNorSpaceArray *lnors_spacearr, @@ -827,7 +764,7 @@ static void mesh_edges_sharp_tag(const OffsetIndices faces, /* We want to avoid tagging edges as sharp when it is already defined as such by * other causes than angle threshold. */ - if (is_angle_sharp) { + if (!r_sharp_edges.is_empty() && is_angle_sharp) { r_sharp_edges[edge_i] = true; } } @@ -841,60 +778,9 @@ static void mesh_edges_sharp_tag(const OffsetIndices faces, /* We want to avoid tagging edges as sharp when it is already defined as such by * other causes than angle threshold. */ - r_sharp_edges[edge_i] = false; - } - /* Else, edge is already 'disqualified' (i.e. sharp)! */ - } - } -} - -/** - * Builds a simplified map from edges to face corners, marking special values when - * it encounters sharp edges or borders between faces with flipped winding orders. - */ -static void build_edge_to_loop_map_with_flip_and_sharp(const OffsetIndices faces, - const Span corner_verts, - const Span corner_edges, - const Span sharp_faces, - const Span sharp_edges, - MutableSpan edge_to_loops) -{ - auto face_is_smooth = [&](const int face_i) { - return sharp_faces.is_empty() || !sharp_faces[face_i]; - }; - - for (const int face_i : faces.index_range()) { - for (const int loop_index : faces[face_i]) { - const int vert_i = corner_verts[loop_index]; - const int edge_i = corner_edges[loop_index]; - - int2 &e2l = edge_to_loops[edge_i]; - - /* Check whether current edge might be smooth or sharp */ - if ((e2l[0] | e2l[1]) == 0) { - /* 'Empty' edge until now, set e2l[0] (and e2l[1] to INDEX_UNSET to tag it as unset). */ - e2l[0] = loop_index; - /* We have to check this here too, else we might miss some flat faces!!! */ - e2l[1] = !face_is_smooth(face_i) ? INDEX_INVALID : INDEX_UNSET; - } - else if (e2l[1] == INDEX_UNSET) { - /* Second loop using this edge, time to test its sharpness. - * An edge is sharp if it is tagged as such, or its face is not smooth, - * or both face have opposed (flipped) normals, i.e. both loops on the same edge share the - * same vertex. */ - if (!face_is_smooth(face_i) || (!sharp_edges.is_empty() && sharp_edges[edge_i]) || - vert_i == corner_verts[e2l[0]]) - { - /* NOTE: we are sure that loop != 0 here ;). */ - e2l[1] = INDEX_INVALID; + if (!r_sharp_edges.is_empty()) { + r_sharp_edges[edge_i] = false; } - else { - e2l[1] = loop_index; - } - } - else if (!IS_EDGE_SHARP(e2l)) { - /* More than two loops using this edge, tag as sharp if not yet done. */ - e2l[1] = INDEX_INVALID; } /* Else, edge is already 'disqualified' (i.e. sharp)! */ } @@ -1323,9 +1209,36 @@ void normals_calc_loop(const Span vert_positions, const bool *sharp_edges, const bool *sharp_faces, const short2 *clnors_data, + bool use_split_normals, + float split_angle, CornerNormalSpaceArray *r_lnors_spacearr, MutableSpan r_loop_normals) { + /* For now this is not supported. + * If we do not use split normals, we do not generate anything fancy! */ + BLI_assert(use_split_normals || !(r_lnors_spacearr)); + + if (!use_split_normals) { + /* In this case, simply fill `r_loop_normals` with `vert_normals` + * (or `face_normals` for flat faces), quite simple! + * Note this is done here to keep some logic and consistency in this quite complex code, + * since we may want to use loop_normals even when mesh's 'autosmooth' is disabled + * (see e.g. mesh mapping code). As usual, we could handle that on case-by-case basis, + * but simpler to keep it well confined here. */ + for (const int face_index : faces.index_range()) { + const bool is_face_flat = sharp_faces && sharp_faces[face_index]; + for (const int corner : faces[face_index]) { + if (is_face_flat) { + copy_v3_v3(r_loop_normals[corner], face_normals[face_index]); + } + else { + copy_v3_v3(r_loop_normals[corner], vert_normals[corner_verts[corner]]); + } + } + } + return; + } + /** * Mapping edge -> loops. * If that edge is used by more than two loops (faces), @@ -1342,6 +1255,9 @@ void normals_calc_loop(const Span vert_positions, * Note also that loose edges always have both values set to 0! */ Array edge_to_loops(edges.size(), int2(0)); + /* When using custom loop normals, disable the angle feature! */ + const bool check_angle = (split_angle < float(M_PI)) && (clnors_data == nullptr); + CornerNormalSpaceArray _lnors_spacearr; #ifdef DEBUG_TIME @@ -1373,13 +1289,17 @@ void normals_calc_loop(const Span vert_positions, array_utils::gather(vert_normals, corner_verts, r_loop_normals, 1024); /* This first loop check which edges are actually smooth, and compute edge vectors. */ - build_edge_to_loop_map_with_flip_and_sharp( - faces, - corner_verts, - corner_edges, - Span(sharp_faces, sharp_faces ? faces.size() : 0), - Span(sharp_edges, sharp_edges ? edges.size() : 0), - edge_to_loops); + mesh_edges_sharp_tag(faces, + corner_verts, + corner_edges, + loop_to_face_map, + face_normals, + Span(sharp_faces, sharp_faces ? faces.size() : 0), + Span(sharp_edges, sharp_edges ? edges.size() : 0), + check_angle, + split_angle, + edge_to_loops, + {}); Vector single_corners; Vector fan_corners; @@ -1447,6 +1367,10 @@ static void mesh_normals_loop_custom_set(Span positions, BitVector<> done_loops(corner_verts.size(), false); Array loop_normals(corner_verts.size()); const Array loop_to_face = build_loop_to_face_map(faces); + /* In this case we always consider split nors as ON, + * and do not want to use angle to define smooth fans! */ + const bool use_split_normals = true; + const float split_angle = float(M_PI); /* Compute current lnor spacearr. */ normals_calc_loop(positions, @@ -1460,6 +1384,8 @@ static void mesh_normals_loop_custom_set(Span positions, sharp_edges.data(), sharp_faces, r_clnors_data.data(), + use_split_normals, + split_angle, &lnors_spacearr, loop_normals); @@ -1581,6 +1507,8 @@ static void mesh_normals_loop_custom_set(Span positions, sharp_edges.data(), sharp_faces, r_clnors_data.data(), + use_split_normals, + split_angle, &lnors_spacearr, loop_normals); } diff --git a/source/blender/blenkernel/intern/mesh_remap.cc b/source/blender/blenkernel/intern/mesh_remap.cc index 1c98b9e3870..8ddf18ba34d 100644 --- a/source/blender/blenkernel/intern/mesh_remap.cc +++ b/source/blender/blenkernel/intern/mesh_remap.cc @@ -303,6 +303,20 @@ void BKE_mesh_remap_find_best_match_from_mesh(const float (*vert_positions_dst)[ /** \name Mesh to Mesh Mapping * \{ */ +void BKE_mesh_remap_calc_source_cddata_masks_from_map_modes(const int /*vert_mode*/, + const int /*edge_mode*/, + const int loop_mode, + const int /*face_mode*/, + CustomData_MeshMasks *r_cddata_mask) +{ + /* vert, edge and face mapping modes never need extra cddata from source object. */ + const bool need_lnors_src = (loop_mode & MREMAP_USE_LOOP) && (loop_mode & MREMAP_USE_NORMAL); + + if (need_lnors_src) { + r_cddata_mask->lmask |= CD_MASK_NORMAL; + } +} + void BKE_mesh_remap_init(MeshPairRemap *map, const int items_num) { MemArena *mem = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, __func__); @@ -455,6 +469,7 @@ void BKE_mesh_remap_calc_verts_from_mesh(const int mode, const float ray_radius, const float (*vert_positions_dst)[3], const int numverts_dst, + const bool /*dirty_nors_dst*/, const Mesh *me_src, Mesh *me_dst, MeshPairRemap *r_map) @@ -680,6 +695,7 @@ void BKE_mesh_remap_calc_edges_from_mesh(const int mode, const int numverts_dst, const blender::int2 *edges_dst, const int numedges_dst, + const bool /*dirty_nors_dst*/, const Mesh *me_src, Mesh *me_dst, MeshPairRemap *r_map) @@ -1212,12 +1228,19 @@ void BKE_mesh_remap_calc_loops_from_mesh(const int mode, const SpaceTransform *space_transform, const float max_dist, const float ray_radius, - const Mesh *mesh_dst, + Mesh *mesh_dst, const float (*vert_positions_dst)[3], const int numverts_dst, + const blender::int2 *edges_dst, + const int numedges_dst, const int *corner_verts_dst, + const int *corner_edges_dst, const int numloops_dst, const blender::OffsetIndices faces_dst, + CustomData *ldata_dst, + const bool use_split_nors_dst, + const float split_angle_dst, + const bool dirty_nors_dst, const Mesh *me_src, MeshRemapIslandsCalc gen_islands_src, const float islands_precision_src, @@ -1263,7 +1286,7 @@ void BKE_mesh_remap_calc_loops_from_mesh(const int mode, blender::Span loop_normals_src; blender::Span face_normals_dst; - blender::Span loop_normals_dst; + blender::float3 *loop_normals_dst; blender::Array face_cents_src; @@ -1319,13 +1342,52 @@ void BKE_mesh_remap_calc_loops_from_mesh(const int mode, face_normals_dst = mesh_dst->face_normals(); } if (need_lnors_dst) { - loop_normals_dst = mesh_dst->corner_normals(); + const blender::short2 *custom_nors_dst = static_cast( + CustomData_get_layer(ldata_dst, CD_CUSTOMLOOPNORMAL)); + + /* Cache loop normals into a temporary custom data layer. */ + loop_normals_dst = static_cast( + CustomData_get_layer_for_write(ldata_dst, CD_NORMAL, numloops_dst)); + + const bool do_loop_normals_dst = (loop_normals_dst == nullptr); + if (!loop_normals_dst) { + loop_normals_dst = static_cast( + CustomData_add_layer(ldata_dst, CD_NORMAL, CD_SET_DEFAULT, numloops_dst)); + CustomData_set_layer_flag(ldata_dst, CD_NORMAL, CD_FLAG_TEMPORARY); + } + if (dirty_nors_dst || do_loop_normals_dst) { + const bool *sharp_edges = static_cast( + CustomData_get_layer_named(&mesh_dst->edge_data, CD_PROP_BOOL, "sharp_edge")); + const bool *sharp_faces = static_cast( + CustomData_get_layer_named(&mesh_dst->face_data, CD_PROP_BOOL, "sharp_face")); + blender::bke::mesh::normals_calc_loop( + {reinterpret_cast(vert_positions_dst), numverts_dst}, + {edges_dst, numedges_dst}, + faces_dst, + {corner_verts_dst, numloops_dst}, + {corner_edges_dst, numloops_dst}, + mesh_dst->corner_to_face_map(), + mesh_dst->vert_normals(), + mesh_dst->face_normals(), + sharp_edges, + sharp_faces, + custom_nors_dst, + use_split_nors_dst, + split_angle_dst, + nullptr, + {loop_normals_dst, numloops_dst}); + } } - if (need_pnors_src) { - face_normals_src = me_src->face_normals(); - } - if (need_lnors_src) { - loop_normals_src = me_src->corner_normals(); + if (need_pnors_src || need_lnors_src) { + if (need_pnors_src) { + face_normals_src = me_src->face_normals(); + } + if (need_lnors_src) { + loop_normals_src = {static_cast( + CustomData_get_layer(&me_src->loop_data, CD_NORMAL)), + me_src->totloop}; + BLI_assert(loop_normals_src.data() != nullptr); + } } } diff --git a/source/blender/blenkernel/intern/mesh_runtime.cc b/source/blender/blenkernel/intern/mesh_runtime.cc index d93336fca62..b8ef5358c36 100644 --- a/source/blender/blenkernel/intern/mesh_runtime.cc +++ b/source/blender/blenkernel/intern/mesh_runtime.cc @@ -355,16 +355,10 @@ void BKE_mesh_tag_edges_split(Mesh *mesh) } } -void BKE_mesh_tag_sharpness_changed(Mesh *mesh) -{ - mesh->runtime->corner_normals_cache.tag_dirty(); -} - void BKE_mesh_tag_face_winding_changed(Mesh *mesh) { mesh->runtime->vert_normals_cache.tag_dirty(); mesh->runtime->face_normals_cache.tag_dirty(); - mesh->runtime->corner_normals_cache.tag_dirty(); mesh->runtime->vert_to_corner_map_cache.tag_dirty(); } @@ -372,7 +366,6 @@ void BKE_mesh_tag_positions_changed(Mesh *mesh) { mesh->runtime->vert_normals_cache.tag_dirty(); mesh->runtime->face_normals_cache.tag_dirty(); - mesh->runtime->corner_normals_cache.tag_dirty(); BKE_mesh_tag_positions_changed_no_normals(mesh); } diff --git a/source/blender/blenkernel/intern/mesh_tangent.cc b/source/blender/blenkernel/intern/mesh_tangent.cc index 07bfbaf0bf5..4863201c78d 100644 --- a/source/blender/blenkernel/intern/mesh_tangent.cc +++ b/source/blender/blenkernel/intern/mesh_tangent.cc @@ -138,12 +138,20 @@ void BKE_mesh_calc_loop_tangent_single(Mesh *mesh, return; } + const float(*loop_normals)[3] = static_cast( + CustomData_get_layer(&mesh->loop_data, CD_NORMAL)); + if (!loop_normals) { + BKE_report( + reports, RPT_ERROR, "Tangent space computation needs loop normals, none found, aborting"); + return; + } + BKE_mesh_calc_loop_tangent_single_ex( reinterpret_cast(mesh->vert_positions().data()), mesh->totvert, mesh->corner_verts().data(), r_looptangents, - reinterpret_cast(mesh->corner_normals().data()), + loop_normals, reinterpret_cast(uv_map.data()), mesh->totloop, mesh->faces(), @@ -598,7 +606,7 @@ void BKE_mesh_calc_loop_tangents(Mesh *me_eval, tangent_names_len, reinterpret_cast(me_eval->vert_normals().data()), reinterpret_cast(me_eval->face_normals().data()), - reinterpret_cast(me_eval->corner_normals().data()), + static_cast(CustomData_get_layer(&me_eval->loop_data, CD_NORMAL)), /* may be nullptr */ static_cast(CustomData_get_layer(&me_eval->vert_data, CD_ORCO)), /* result */ diff --git a/source/blender/blenkernel/intern/mesh_wrapper.cc b/source/blender/blenkernel/intern/mesh_wrapper.cc index e2283ed5cf9..10256a69a2c 100644 --- a/source/blender/blenkernel/intern/mesh_wrapper.cc +++ b/source/blender/blenkernel/intern/mesh_wrapper.cc @@ -130,17 +130,16 @@ void BKE_mesh_wrapper_ensure_mdata(Mesh *me) me->vert_positions_for_write().copy_from(edit_data->vertexCos); me->runtime->is_original_bmesh = false; } - - if (me->runtime->wrapper_type_finalize) { - BKE_mesh_wrapper_deferred_finalize_mdata(me); - } - MEM_delete(me->runtime->edit_data); me->runtime->edit_data = nullptr; break; } } + if (me->runtime->wrapper_type_finalize) { + BKE_mesh_wrapper_deferred_finalize_mdata(me, &me->runtime->cd_mask_extra); + } + /* Keep type assignment last, so that read-only access only uses the mdata code paths after all * the underlying data has been initialized. */ me->runtime->wrapper_type = ME_WRAPPER_TYPE_MDATA; @@ -385,17 +384,22 @@ static Mesh *mesh_wrapper_ensure_subdivision(Mesh *me) if (use_clnors) { /* If custom normals are present and the option is turned on calculate the split * normals and clear flag so the normals get interpolated to the result mesh. */ - void *data = CustomData_add_layer(&me->loop_data, CD_NORMAL, CD_CONSTRUCT, me->totloop); - memcpy(data, me->corner_normals().data(), me->corner_normals().size_in_bytes()); + BKE_mesh_calc_normals_split(me); + CustomData_clear_layer_flag(&me->loop_data, CD_NORMAL, CD_FLAG_TEMPORARY); } Mesh *subdiv_mesh = BKE_subdiv_to_mesh(subdiv, &mesh_settings, me); if (use_clnors) { - BKE_mesh_set_custom_normals(subdiv_mesh, - static_cast(CustomData_get_layer_for_write( - &subdiv_mesh->loop_data, CD_NORMAL, me->totloop))); - CustomData_free_layers(&subdiv_mesh->loop_data, CD_NORMAL, me->totloop); + float(*lnors)[3] = static_cast( + CustomData_get_layer_for_write(&subdiv_mesh->loop_data, CD_NORMAL, subdiv_mesh->totloop)); + BLI_assert(lnors != nullptr); + BKE_mesh_set_custom_normals(subdiv_mesh, lnors); + CustomData_set_layer_flag(&me->loop_data, CD_NORMAL, CD_FLAG_TEMPORARY); + CustomData_set_layer_flag(&subdiv_mesh->loop_data, CD_NORMAL, CD_FLAG_TEMPORARY); + } + else if (runtime_data->calc_loop_normals) { + BKE_mesh_calc_normals_split(subdiv_mesh); } if (!ELEM(subdiv, runtime_data->subdiv_cpu, runtime_data->subdiv_gpu)) { diff --git a/source/blender/blenkernel/intern/modifier.cc b/source/blender/blenkernel/intern/modifier.cc index 7ddbc0cefc7..936723b3563 100644 --- a/source/blender/blenkernel/intern/modifier.cc +++ b/source/blender/blenkernel/intern/modifier.cc @@ -39,7 +39,7 @@ #include "BLI_session_uuid.h" #include "BLI_string.h" #include "BLI_string_utf8.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_utildefines.h" #include "BLT_translation.h" @@ -225,14 +225,15 @@ void BKE_modifier_session_uuid_generate(ModifierData *md) md->session_uuid = BLI_session_uuid_generate(); } -void BKE_modifier_unique_name(ListBase *modifiers, ModifierData *md) +bool BKE_modifier_unique_name(ListBase *modifiers, ModifierData *md) { if (modifiers && md) { const ModifierTypeInfo *mti = BKE_modifier_get_info(ModifierType(md->type)); - BLI_uniquename( + return BLI_uniquename( modifiers, md, DATA_(mti->name), '.', offsetof(ModifierData, name), sizeof(md->name)); } + return false; } bool BKE_modifier_depends_ontime(Scene *scene, ModifierData *md) diff --git a/source/blender/blenkernel/intern/movieclip.cc b/source/blender/blenkernel/intern/movieclip.cc index a54d9de5260..18844f0038c 100644 --- a/source/blender/blenkernel/intern/movieclip.cc +++ b/source/blender/blenkernel/intern/movieclip.cc @@ -66,8 +66,6 @@ #include "DEG_depsgraph.hh" #include "DEG_depsgraph_query.hh" -#include "DRW_engine.h" - #include "GPU_texture.h" #include "BLO_read_write.hh" @@ -101,8 +99,6 @@ static void movie_clip_copy_data(Main * /*bmain*/, ID *id_dst, const ID *id_src, BKE_tracking_copy(&movie_clip_dst->tracking, &movie_clip_src->tracking, flag_subdata); movie_clip_dst->tracking_context = nullptr; - BLI_listbase_clear((ListBase *)&movie_clip_dst->drawdata); - BKE_color_managed_colorspace_settings_copy(&movie_clip_dst->colorspace_settings, &movie_clip_src->colorspace_settings); } @@ -115,7 +111,6 @@ static void movie_clip_free_data(ID *id) free_buffers(movie_clip); BKE_tracking_free(&movie_clip->tracking); - DRW_drawdata_free(id); } static void movie_clip_foreach_id(ID *id, LibraryForeachIDData *data) diff --git a/source/blender/blenkernel/intern/nla.cc b/source/blender/blenkernel/intern/nla.cc index 16f255cb8f3..e68d02244ee 100644 --- a/source/blender/blenkernel/intern/nla.cc +++ b/source/blender/blenkernel/intern/nla.cc @@ -20,7 +20,7 @@ #include "BLI_ghash.h" #include "BLI_listbase.h" #include "BLI_string.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_utildefines.h" #include "BLT_translation.h" diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index 825a4092908..1ce3458771d 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -41,7 +41,7 @@ #include "BLI_stack.hh" #include "BLI_string.h" #include "BLI_string_utf8.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_threads.h" #include "BLI_utildefines.h" #include "BLI_vector_set.hh" @@ -79,7 +79,7 @@ #include "RNA_prototypes.h" #include "NOD_common.h" -#include "NOD_composite.hh" +#include "NOD_composite.h" #include "NOD_geometry.hh" #include "NOD_geometry_nodes_lazy_function.hh" #include "NOD_node_declaration.hh" @@ -87,7 +87,6 @@ #include "NOD_shader.h" #include "NOD_socket.hh" #include "NOD_texture.h" -#include "NOD_zone_socket_items.hh" #include "DEG_depsgraph.hh" #include "DEG_depsgraph_build.hh" @@ -783,10 +782,20 @@ void ntreeBlendWrite(BlendWriter *writer, bNodeTree *ntree) } } if (node->type == GEO_NODE_SIMULATION_OUTPUT) { - blender::nodes::SimulationItemsAccessor::blend_write(writer, *node); + const NodeGeometrySimulationOutput &storage = + *static_cast(node->storage); + BLO_write_struct_array(writer, NodeSimulationItem, storage.items_num, storage.items); + for (const NodeSimulationItem &item : Span(storage.items, storage.items_num)) { + BLO_write_string(writer, item.name); + } } if (node->type == GEO_NODE_REPEAT_OUTPUT) { - blender::nodes::RepeatItemsAccessor::blend_write(writer, *node); + const NodeGeometryRepeatOutput &storage = *static_cast( + node->storage); + BLO_write_struct_array(writer, NodeRepeatItem, storage.items_num, storage.items); + for (const NodeRepeatItem &item : Span(storage.items, storage.items_num)) { + BLO_write_string(writer, item.name); + } } } @@ -978,11 +987,21 @@ void ntreeBlendReadData(BlendDataReader *reader, ID *owner_id, bNodeTree *ntree) break; } case GEO_NODE_SIMULATION_OUTPUT: { - blender::nodes::SimulationItemsAccessor::blend_read_data(reader, *node); + NodeGeometrySimulationOutput &storage = *static_cast( + node->storage); + BLO_read_data_address(reader, &storage.items); + for (const NodeSimulationItem &item : Span(storage.items, storage.items_num)) { + BLO_read_data_address(reader, &item.name); + } break; } case GEO_NODE_REPEAT_OUTPUT: { - blender::nodes::RepeatItemsAccessor::blend_read_data(reader, *node); + NodeGeometryRepeatOutput &storage = *static_cast( + node->storage); + BLO_read_data_address(reader, &storage.items); + for (const NodeRepeatItem &item : Span(storage.items, storage.items_num)) { + BLO_read_data_address(reader, &item.name); + } break; } @@ -1153,7 +1172,7 @@ namespace blender::bke { static void node_add_sockets_from_type(bNodeTree *ntree, bNode *node, bNodeType *ntype) { - if (ntype->declare) { + if (ntype->declare || ntype->declare_dynamic) { node_verify_sockets(ntree, node, true); return; } @@ -1455,8 +1474,8 @@ static void node_free_type(void *nodetype_v) * or we'd want to update *all* active Mains, which we cannot do anyway currently. */ blender::bke::update_typeinfo(G_MAIN, nullptr, nullptr, nodetype, nullptr, true); - delete nodetype->static_declaration; - nodetype->static_declaration = nullptr; + delete nodetype->fixed_declaration; + nodetype->fixed_declaration = nullptr; /* Can be null when the type is not dynamically allocated. */ if (nodetype->free_self) { @@ -1470,9 +1489,11 @@ void nodeRegisterType(bNodeType *nt) BLI_assert(nt->idname[0] != '\0'); BLI_assert(nt->poll != nullptr); - if (nt->declare) { - nt->static_declaration = new blender::nodes::NodeDeclaration(); - blender::nodes::build_node_declaration(*nt, *nt->static_declaration, nullptr, nullptr); + if (nt->declare && !nt->declare_dynamic) { + if (nt->fixed_declaration == nullptr) { + nt->fixed_declaration = new blender::nodes::NodeDeclaration(); + blender::nodes::build_node_declaration(*nt, *nt->fixed_declaration); + } } BLI_ghash_insert(blender::bke::nodetypes_hash, nt->idname, nt); @@ -3266,12 +3287,8 @@ void node_free_node(bNodeTree *ntree, bNode *node) MEM_freeN(node->prop); } - if (node->runtime->declaration) { - /* Only free if this declaration is not shared with the node type, which can happen if it does - * not depend on any context. */ - if (node->runtime->declaration != node->typeinfo->static_declaration) { - delete node->runtime->declaration; - } + if (node->typeinfo->declare_dynamic) { + delete node->runtime->declaration; } MEM_delete(node->runtime); @@ -3736,20 +3753,18 @@ bool nodeDeclarationEnsureOnOutdatedNode(bNodeTree *ntree, bNode *node) if (node->runtime->declaration != nullptr) { return false; } - if (node->typeinfo->declare) { - if (node->typeinfo->static_declaration) { - if (!node->typeinfo->static_declaration->is_context_dependent) { - node->runtime->declaration = node->typeinfo->static_declaration; - return true; - } - } - } - if (node->typeinfo->declare) { + if (node->typeinfo->declare_dynamic) { BLI_assert(ntree != nullptr); BLI_assert(node != nullptr); blender::nodes::update_node_declaration_and_sockets(*ntree, *node); return true; } + if (node->typeinfo->declare) { + /* Declaration should have been created in #nodeRegisterType. */ + BLI_assert(node->typeinfo->fixed_declaration != nullptr); + node->runtime->declaration = node->typeinfo->fixed_declaration; + return true; + } return false; } @@ -4104,52 +4119,6 @@ void node_type_base_custom(bNodeType *ntype, namespace blender::bke { -std::optional socket_type_to_custom_data_type(eNodeSocketDatatype type) -{ - switch (type) { - case SOCK_FLOAT: - return CD_PROP_FLOAT; - case SOCK_VECTOR: - return CD_PROP_FLOAT3; - case SOCK_RGBA: - return CD_PROP_COLOR; - case SOCK_BOOLEAN: - return CD_PROP_BOOL; - case SOCK_ROTATION: - return CD_PROP_QUATERNION; - case SOCK_INT: - return CD_PROP_INT32; - case SOCK_STRING: - return CD_PROP_STRING; - default: - return std::nullopt; - } -} - -std::optional custom_data_type_to_socket_type(eCustomDataType type) -{ - switch (type) { - case CD_PROP_FLOAT: - return SOCK_FLOAT; - case CD_PROP_INT32: - return SOCK_INT; - case CD_PROP_FLOAT3: - return SOCK_VECTOR; - case CD_PROP_FLOAT2: - return SOCK_VECTOR; - case CD_PROP_BOOL: - return SOCK_BOOLEAN; - case CD_PROP_COLOR: - return SOCK_RGBA; - case CD_PROP_BYTE_COLOR: - return SOCK_RGBA; - case CD_PROP_QUATERNION: - return SOCK_ROTATION; - default: - return std::nullopt; - } -} - struct SocketTemplateIdentifierCallbackData { bNodeSocketTemplate *list; bNodeSocketTemplate *ntemp; diff --git a/source/blender/blenkernel/intern/node_tree_anonymous_attributes.cc b/source/blender/blenkernel/intern/node_tree_anonymous_attributes.cc index 9ef4a0a9a9b..a4541dc4e7b 100644 --- a/source/blender/blenkernel/intern/node_tree_anonymous_attributes.cc +++ b/source/blender/blenkernel/intern/node_tree_anonymous_attributes.cc @@ -3,7 +3,6 @@ * SPDX-License-Identifier: GPL-2.0-or-later */ #include "NOD_node_declaration.hh" -#include "NOD_socket.hh" #include "BKE_node_runtime.hh" #include "BKE_node_tree_anonymous_attributes.hh" @@ -22,6 +21,11 @@ namespace blender::bke::anonymous_attribute_inferencing { namespace aal = nodes::aal; using nodes::NodeDeclaration; +bool is_possible_field_socket(const eNodeSocketDatatype type) +{ + return ELEM(type, SOCK_FLOAT, SOCK_VECTOR, SOCK_RGBA, SOCK_BOOLEAN, SOCK_INT, SOCK_ROTATION); +} + static bool socket_is_field(const bNodeSocket &socket) { return socket.display_shape == SOCK_DISPLAY_SHAPE_DIAMOND; @@ -185,7 +189,7 @@ class bNodeTreeToDotOptionsForAnonymousAttributeInferencing : public bNodeTreeTo ss << "]"; return ss.str(); } - else if (nodes::socket_type_supports_fields(eNodeSocketDatatype(socket.type))) { + else if (is_possible_field_socket(eNodeSocketDatatype(socket.type))) { std::stringstream ss; ss << socket.identifier << " ["; bits::foreach_1_index(result_.propagated_fields_by_socket[socket.index_in_tree()], @@ -268,7 +272,7 @@ static AnonymousAttributeInferencingResult analyse_anonymous_attribute_usages( if (type == SOCK_GEOMETRY) { all_geometry_sources.append_and_get_index({InputGeometrySource{i}}); } - else if (nodes::socket_type_supports_fields(type)) { + else if (is_possible_field_socket(type)) { all_field_sources.append_and_get_index({InputFieldSource{i}}); } } @@ -484,7 +488,7 @@ static AnonymousAttributeInferencingResult analyse_anonymous_attribute_usages( for (const int field_source_index : geometry_source.field_sources) { for (const bNodeSocket *other_socket : group_output_node->input_sockets().drop_back(1)) { - if (!nodes::socket_type_supports_fields(eNodeSocketDatatype(other_socket->type))) { + if (!is_possible_field_socket(eNodeSocketDatatype(other_socket->type))) { continue; } if (propagated_fields_by_socket[other_socket->index_in_tree()][field_source_index] @@ -499,7 +503,7 @@ static AnonymousAttributeInferencingResult analyse_anonymous_attribute_usages( } }); } - else if (nodes::socket_type_supports_fields(eNodeSocketDatatype(socket->type))) { + else if (is_possible_field_socket(eNodeSocketDatatype(socket->type))) { const BoundedBitSpan propagated_fields = propagated_fields_by_socket[socket->index_in_tree()]; bits::foreach_1_index(propagated_fields, [&](const int field_source_index) { diff --git a/source/blender/blenkernel/intern/node_tree_field_inferencing.cc b/source/blender/blenkernel/intern/node_tree_field_inferencing.cc index d584e42d749..ee475246fcd 100644 --- a/source/blender/blenkernel/intern/node_tree_field_inferencing.cc +++ b/source/blender/blenkernel/intern/node_tree_field_inferencing.cc @@ -5,9 +5,7 @@ #include "BKE_node.hh" #include "BKE_node_runtime.hh" -#include "NOD_geometry.hh" #include "NOD_node_declaration.hh" -#include "NOD_socket.hh" #include "BLI_resource_scope.hh" #include "BLI_set.hh" @@ -22,9 +20,14 @@ using nodes::OutputFieldDependency; using nodes::OutputSocketFieldType; using nodes::SocketDeclaration; +static bool is_field_socket_type(eNodeSocketDatatype type) +{ + return ELEM(type, SOCK_FLOAT, SOCK_INT, SOCK_BOOLEAN, SOCK_VECTOR, SOCK_RGBA, SOCK_ROTATION); +} + static bool is_field_socket_type(const bNodeSocket &socket) { - return nodes::socket_type_supports_fields((eNodeSocketDatatype)socket.typeinfo->type); + return is_field_socket_type((eNodeSocketDatatype)socket.typeinfo->type); } static InputSocketFieldType get_interface_input_field_type(const bNode &node, @@ -502,15 +505,9 @@ static void determine_group_input_states( const bNodeSocketType *typeinfo = group_input->socket_typeinfo(); const eNodeSocketDatatype type = typeinfo ? eNodeSocketDatatype(typeinfo->type) : SOCK_CUSTOM; - if (!nodes::socket_type_supports_fields(type)) { + if (!is_field_socket_type(type)) { new_inferencing_interface.inputs[index] = InputSocketFieldType::None; } - else if (group_input->default_input != NODE_INPUT_DEFAULT_VALUE) { - new_inferencing_interface.inputs[index] = InputSocketFieldType::Implicit; - } - else if (is_layer_selection_field(*group_input)) { - new_inferencing_interface.inputs[index] = InputSocketFieldType::Implicit; - } else if (group_input->flag & NODE_INTERFACE_SOCKET_SINGLE_VALUE_ONLY) { new_inferencing_interface.inputs[index] = InputSocketFieldType::None; } @@ -521,13 +518,8 @@ static void determine_group_input_states( for (const bNode *node : tree.group_input_nodes()) { for (const bNodeSocket *output_socket : node->output_sockets().drop_back(1)) { SocketFieldState &state = field_state_by_socket_id[output_socket->index_in_tree()]; - const int output_index = output_socket->index(); if (state.requires_single) { - if (new_inferencing_interface.inputs[output_index] == InputSocketFieldType::Implicit) { - /* Don't override hard-coded implicit fields. */ - continue; - } - new_inferencing_interface.inputs[output_index] = InputSocketFieldType::None; + new_inferencing_interface.inputs[output_socket->index()] = InputSocketFieldType::None; } } } diff --git a/source/blender/blenkernel/intern/node_tree_update.cc b/source/blender/blenkernel/intern/node_tree_update.cc index b7b5f208e61..0af746ce420 100644 --- a/source/blender/blenkernel/intern/node_tree_update.cc +++ b/source/blender/blenkernel/intern/node_tree_update.cc @@ -563,12 +563,8 @@ class NodeTreeMainUpdater { if (ntype.updatefunc) { ntype.updatefunc(&ntree, node); } - if (ntype.declare) { - /* Should have been created when the node was registered. */ - BLI_assert(ntype.static_declaration != nullptr); - if (ntype.static_declaration->is_context_dependent) { - nodes::update_node_declaration_and_sockets(ntree, *node); - } + if (ntype.declare_dynamic) { + nodes::update_node_declaration_and_sockets(ntree, *node); } } } @@ -1291,6 +1287,15 @@ void BKE_ntree_update_tag_node_removed(bNodeTree *ntree) add_tree_tag(ntree, NTREE_CHANGED_REMOVED_NODE); } +void BKE_ntree_update_tag_node_reordered(bNodeTree *ntree) +{ + /* Don't add a tree update tag to avoid reevaluations for trivial operations like selection or + * parenting that typically influence the node order. This means the node order can be different + * for original and evaluated trees. A different solution might avoid sorting nodes based on UI + * states like selection, which would require not tying the node order to the drawing order. */ + ntree->runtime->topology_cache_mutex.tag_dirty(); +} + void BKE_ntree_update_tag_node_mute(bNodeTree *ntree, bNode *node) { add_node_tag(ntree, node, NTREE_CHANGED_NODE_PROPERTY); diff --git a/source/blender/blenkernel/intern/object.cc b/source/blender/blenkernel/intern/object.cc index ac914a994bb..da4ebefc357 100644 --- a/source/blender/blenkernel/intern/object.cc +++ b/source/blender/blenkernel/intern/object.cc @@ -2637,7 +2637,6 @@ Object *BKE_object_duplicate(Main *bmain, } break; case OB_GPENCIL_LEGACY: - case OB_GREASE_PENCIL: if (dupflag & USER_DUP_GPENCIL) { id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags); } @@ -3559,36 +3558,46 @@ void BKE_boundbox_minmax(const BoundBox *bb, } } -std::optional BKE_object_boundbox_get(Object *ob) +const BoundBox *BKE_object_boundbox_get(Object *ob) { + BoundBox *bb = nullptr; + switch (ob->type) { case OB_MESH: - return BKE_mesh_boundbox_get(ob); + bb = BKE_mesh_boundbox_get(ob); + break; case OB_CURVES_LEGACY: case OB_SURF: case OB_FONT: - return *BKE_curve_boundbox_get(ob); + bb = BKE_curve_boundbox_get(ob); + break; case OB_MBALL: - if (const BoundBox *bb = BKE_mball_boundbox_get(ob)) { - return *bb; - } - return std::nullopt; + bb = BKE_mball_boundbox_get(ob); + break; case OB_LATTICE: - return *BKE_lattice_boundbox_get(ob); + bb = BKE_lattice_boundbox_get(ob); + break; case OB_ARMATURE: - return *BKE_armature_boundbox_get(ob); + bb = BKE_armature_boundbox_get(ob); + break; case OB_GPENCIL_LEGACY: - return *BKE_gpencil_boundbox_get(ob); + bb = BKE_gpencil_boundbox_get(ob); + break; case OB_CURVES: - return BKE_curves_boundbox_get(ob); + bb = BKE_curves_boundbox_get(ob); + break; case OB_POINTCLOUD: - return BKE_pointcloud_boundbox_get(ob); + bb = BKE_pointcloud_boundbox_get(ob); + break; case OB_VOLUME: - return *BKE_volume_boundbox_get(ob); + bb = BKE_volume_boundbox_get(ob); + break; case OB_GREASE_PENCIL: - return BKE_grease_pencil_boundbox_get(ob); + bb = BKE_grease_pencil_boundbox_get(ob); + default: + break; } - return std::nullopt; + return bb; } void BKE_object_boundbox_calc_from_mesh(Object *ob, const Mesh *me_eval) @@ -3654,7 +3663,8 @@ bool BKE_object_boundbox_calc_from_evaluated_geometry(Object *ob) void BKE_object_dimensions_get(Object *ob, float r_vec[3]) { - if (const std::optional bb = BKE_object_boundbox_get(ob)) { + const BoundBox *bb = BKE_object_boundbox_get(ob); + if (bb) { float3 scale; mat4_to_size(scale, ob->object_to_world); @@ -3673,7 +3683,8 @@ void BKE_object_dimensions_set_ex(Object *ob, const float ob_scale_orig[3], const float ob_obmat_orig[4][4]) { - if (const std::optional bb = BKE_object_boundbox_get(ob)) { + const BoundBox *bb = BKE_object_boundbox_get(ob); + if (bb) { float3 len; len.x = bb->vec[4][0] - bb->vec[0][0]; len.y = bb->vec[2][1] - bb->vec[0][1]; @@ -3717,7 +3728,7 @@ void BKE_object_minmax(Object *ob, float r_min[3], float r_max[3], const bool us break; } case OB_MESH: { - const BoundBox bb = BKE_mesh_boundbox_get(ob); + const BoundBox bb = *BKE_mesh_boundbox_get(ob); BKE_boundbox_minmax(&bb, ob->object_to_world, r_min, r_max); changed = true; break; @@ -3761,13 +3772,13 @@ void BKE_object_minmax(Object *ob, float r_min[3], float r_max[3], const bool us break; } case OB_CURVES: { - const BoundBox bb = BKE_curves_boundbox_get(ob); + const BoundBox bb = *BKE_curves_boundbox_get(ob); BKE_boundbox_minmax(&bb, ob->object_to_world, r_min, r_max); changed = true; break; } case OB_POINTCLOUD: { - const BoundBox bb = BKE_pointcloud_boundbox_get(ob); + const BoundBox bb = *BKE_pointcloud_boundbox_get(ob); BKE_boundbox_minmax(&bb, ob->object_to_world, r_min, r_max); changed = true; break; @@ -3779,7 +3790,7 @@ void BKE_object_minmax(Object *ob, float r_min[3], float r_max[3], const bool us break; } case OB_GREASE_PENCIL: { - const BoundBox bb = BKE_grease_pencil_boundbox_get(ob); + const BoundBox bb = *BKE_grease_pencil_boundbox_get(ob); BKE_boundbox_minmax(&bb, ob->object_to_world, r_min, r_max); changed = true; break; @@ -3967,7 +3978,9 @@ bool BKE_object_minmax_dupli(Depsgraph *depsgraph, /* Do not modify the original bounding-box. */ temp_ob.runtime.bb = nullptr; BKE_object_replace_data_on_shallow_copy(&temp_ob, dob->ob_data); - if (const std::optional bb = BKE_object_boundbox_get(&temp_ob)) { + const BoundBox *bb = BKE_object_boundbox_get(&temp_ob); + + if (bb) { int i; for (i = 0; i < 8; i++) { float3 vec; diff --git a/source/blender/blenkernel/intern/object_deform.cc b/source/blender/blenkernel/intern/object_deform.cc index a4634ce2c13..0ead3fb6cc7 100644 --- a/source/blender/blenkernel/intern/object_deform.cc +++ b/source/blender/blenkernel/intern/object_deform.cc @@ -15,7 +15,7 @@ #include "BLI_ghash.h" #include "BLI_listbase.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_utildefines.h" #include "DNA_armature_types.h" diff --git a/source/blender/blenkernel/intern/object_dupli.cc b/source/blender/blenkernel/intern/object_dupli.cc index 0f2d8228ea1..2eb4ad70181 100644 --- a/source/blender/blenkernel/intern/object_dupli.cc +++ b/source/blender/blenkernel/intern/object_dupli.cc @@ -30,7 +30,6 @@ #include "DNA_anim_types.h" #include "DNA_collection_types.h" #include "DNA_curves_types.h" -#include "DNA_grease_pencil_types.h" #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" #include "DNA_modifier_types.h" @@ -928,11 +927,6 @@ static void make_duplis_geometry_set_impl(const DupliContext *ctx, make_dupli(ctx, ctx->object, &pointcloud->id, parent_transform, component_index++); } } - if (ctx->object->type != OB_GREASE_PENCIL || geometry_set_is_instance) { - if (const GreasePencil *grease_pencil = geometry_set.get_grease_pencil()) { - make_dupli(ctx, ctx->object, &grease_pencil->id, parent_transform, component_index++); - } - } const bool creates_duplis_for_components = component_index >= 1; const Instances *instances = geometry_set.get_instances(); diff --git a/source/blender/blenkernel/intern/object_update.cc b/source/blender/blenkernel/intern/object_update.cc index 81f5790e160..7340d3ddfa1 100644 --- a/source/blender/blenkernel/intern/object_update.cc +++ b/source/blender/blenkernel/intern/object_update.cc @@ -243,11 +243,13 @@ void BKE_object_handle_data_update(Depsgraph *depsgraph, Scene *scene, Object *o /** Bounding box from evaluated geometry. */ static void object_sync_boundbox_to_original(Object *object_orig, Object *object_eval) { - if (!object_eval->runtime.bb || (object_eval->runtime.bb->flag & BOUNDBOX_DIRTY)) { + const BoundBox *bb = object_eval->runtime.bb; + if (!bb || (bb->flag & BOUNDBOX_DIRTY)) { BKE_object_boundbox_calc_from_evaluated_geometry(object_eval); } - if (const std::optional bb = BKE_object_boundbox_get(object_eval)) { + bb = BKE_object_boundbox_get(object_eval); + if (bb != nullptr) { if (object_orig->runtime.bb == nullptr) { object_orig->runtime.bb = MEM_new(__func__); } diff --git a/source/blender/blenkernel/intern/paint.cc b/source/blender/blenkernel/intern/paint.cc index 7868288b442..6ae62a5b131 100644 --- a/source/blender/blenkernel/intern/paint.cc +++ b/source/blender/blenkernel/intern/paint.cc @@ -1085,8 +1085,6 @@ eObjectMode BKE_paint_object_mode_from_paintmode(ePaintMode mode) return OB_MODE_EDIT; case PAINT_MODE_SCULPT_CURVES: return OB_MODE_SCULPT_CURVES; - case PAINT_MODE_GPENCIL: - return OB_MODE_PAINT_GREASE_PENCIL; case PAINT_MODE_INVALID: default: return OB_MODE_OBJECT; diff --git a/source/blender/blenkernel/intern/particle_system.cc b/source/blender/blenkernel/intern/particle_system.cc index 5b323abddec..ca26b6a1cbd 100644 --- a/source/blender/blenkernel/intern/particle_system.cc +++ b/source/blender/blenkernel/intern/particle_system.cc @@ -37,7 +37,7 @@ #include "BLI_math_rotation.h" #include "BLI_math_vector.h" #include "BLI_rand.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_task.h" #include "BLI_threads.h" #include "BLI_utildefines.h" @@ -1573,7 +1573,8 @@ static void integrate_particle( * Authors: Simon Clavet, Philippe Beaudoin and Pierre Poulin * Website: http://www.iro.umontreal.ca/labs/infographie/papers/Clavet-2005-PVFS/ * - * Presented at SIGGRAPH, (2005) + * Presented at Siggraph, (2005) + * * \{ */ #define PSYS_FLUID_SPRINGS_INITIAL_SIZE 256 diff --git a/source/blender/blenkernel/intern/pbvh.cc b/source/blender/blenkernel/intern/pbvh.cc index e7d5e772078..25d83ae57c1 100644 --- a/source/blender/blenkernel/intern/pbvh.cc +++ b/source/blender/blenkernel/intern/pbvh.cc @@ -10,7 +10,6 @@ #include -#include "BLI_array_utils.hh" #include "BLI_bitmap.h" #include "BLI_math_geom.h" #include "BLI_math_matrix.h" @@ -648,7 +647,7 @@ static void pbvh_build(PBVH *pbvh, pbvh->nodes.clear_and_shrink(); pbvh->prim_indices.reinitialize(totprim); - blender::array_utils::fill_index_range(pbvh->prim_indices); + std::iota(pbvh->prim_indices.begin(), pbvh->prim_indices.end(), 0); } pbvh->nodes.resize(1); @@ -656,7 +655,7 @@ static void pbvh_build(PBVH *pbvh, build_sub(pbvh, material_indices, sharp_faces, 0, cb, prim_bbc, 0, totprim, nullptr, 0); } -static void pbvh_draw_args_init(const Mesh &mesh, PBVH *pbvh, PBVH_GPU_Args *args, PBVHNode *node) +static void pbvh_draw_args_init(PBVH *pbvh, PBVH_GPU_Args *args, PBVHNode *node) { memset((void *)args, 0, sizeof(*args)); @@ -681,9 +680,6 @@ static void pbvh_draw_args_init(const Mesh &mesh, PBVH *pbvh, PBVH_GPU_Args *arg nullptr; } - args->active_color = mesh.active_color_attribute; - args->render_color = mesh.default_color_attribute; - switch (pbvh->header.type) { case PBVH_FACES: args->vert_data = pbvh->vert_data; @@ -694,6 +690,9 @@ static void pbvh_draw_args_init(const Mesh &mesh, PBVH *pbvh, PBVH_GPU_Args *arg args->vert_normals = pbvh->vert_normals; args->face_normals = pbvh->face_normals; + args->active_color = pbvh->mesh->active_color_attribute; + args->render_color = pbvh->mesh->default_color_attribute; + args->prim_indices = node->prim_indices; args->face_sets = pbvh->face_sets; args->looptri_faces = pbvh->looptri_faces; @@ -709,6 +708,9 @@ static void pbvh_draw_args_init(const Mesh &mesh, PBVH *pbvh, PBVH_GPU_Args *arg args->face_sets = pbvh->face_sets; args->faces = pbvh->faces; + args->active_color = pbvh->mesh->active_color_attribute; + args->render_color = pbvh->mesh->default_color_attribute; + args->mesh_grids_num = pbvh->totgrid; args->grids = pbvh->grids; args->grid_flag_mats = pbvh->grid_flag_mats; @@ -722,7 +724,9 @@ static void pbvh_draw_args_init(const Mesh &mesh, PBVH *pbvh, PBVH_GPU_Args *arg args->vert_data = &args->bm->vdata; args->loop_data = &args->bm->ldata; args->face_data = &args->bm->pdata; - args->bm_faces = &node->bm_faces; + args->bm_faces = node->bm_faces; + args->bm_other_verts = node->bm_other_verts; + args->bm_unique_vert = node->bm_unique_verts; args->cd_mask_layer = CustomData_get_offset(&pbvh->header.bm->vdata, CD_PAINT_MASK); break; @@ -1015,6 +1019,16 @@ void BKE_pbvh_free(PBVH *pbvh) if (node.draw_batches) { DRW_pbvh_node_free(node.draw_batches); } + + if (node.bm_faces) { + BLI_gset_free(node.bm_faces, nullptr); + } + if (node.bm_unique_verts) { + BLI_gset_free(node.bm_unique_verts, nullptr); + } + if (node.bm_other_verts) { + BLI_gset_free(node.bm_other_verts, nullptr); + } } if (node.flag & (PBVH_Leaf | PBVH_TexLeaf)) { @@ -1384,10 +1398,10 @@ static void node_update_mask_redraw(PBVH &pbvh, PBVHNode &node) PBVHVertexIter vd; BKE_pbvh_vertex_iter_begin (&pbvh, &node, vd, PBVH_ITER_ALL) { - if (vd.mask < 1.0f) { + if (vd.mask && *vd.mask < 1.0f) { has_unmasked = true; } - if (vd.mask > 0.0f) { + if (vd.mask && *vd.mask > 0.0f) { has_masked = false; } } @@ -1436,14 +1450,14 @@ bool BKE_pbvh_get_color_layer(Mesh *me, CustomDataLayer **r_layer, eAttrDomain * return *r_layer != nullptr; } -static void node_update_draw_buffers(const Mesh &mesh, PBVH &pbvh, PBVHNode &node) +static void node_update_draw_buffers(PBVH &pbvh, PBVHNode &node) { /* Create and update draw buffers. The functions called here must not * do any OpenGL calls. Flags are not cleared immediately, that happens * after GPU_pbvh_buffer_flush() which does the final OpenGL calls. */ if (node.flag & PBVH_RebuildDrawBuffers) { PBVH_GPU_Args args; - pbvh_draw_args_init(mesh, &pbvh, &args, &node); + pbvh_draw_args_init(&pbvh, &args, &node); node.draw_batches = DRW_pbvh_node_create(args); } @@ -1452,7 +1466,7 @@ static void node_update_draw_buffers(const Mesh &mesh, PBVH &pbvh, PBVHNode &nod if (node.draw_batches) { PBVH_GPU_Args args; - pbvh_draw_args_init(mesh, &pbvh, &args, &node); + pbvh_draw_args_init(&pbvh, &args, &node); DRW_pbvh_node_update(node.draw_batches, args); } } @@ -1466,10 +1480,7 @@ void pbvh_free_draw_buffers(PBVH * /*pbvh*/, PBVHNode *node) } } -static void pbvh_update_draw_buffers(const Mesh &mesh, - PBVH *pbvh, - Span nodes, - int update_flag) +static void pbvh_update_draw_buffers(PBVH *pbvh, Span nodes, int update_flag) { using namespace blender; if (pbvh->header.type == PBVH_BMESH && !pbvh->header.bm) { @@ -1486,7 +1497,7 @@ static void pbvh_update_draw_buffers(const Mesh &mesh, else if ((node->flag & PBVH_UpdateDrawBuffers) && node->draw_batches) { PBVH_GPU_Args args; - pbvh_draw_args_init(mesh, pbvh, &args, node); + pbvh_draw_args_init(pbvh, &args, node); DRW_pbvh_update_pre(node->draw_batches, args); } } @@ -1496,7 +1507,7 @@ static void pbvh_update_draw_buffers(const Mesh &mesh, threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) { for (PBVHNode *node : nodes.slice(range)) { - node_update_draw_buffers(mesh, *pbvh, *node); + node_update_draw_buffers(*pbvh, *node); } }); @@ -1639,14 +1650,23 @@ static void pbvh_grids_node_visibility_update(PBVH *pbvh, PBVHNode *node) static void pbvh_bmesh_node_visibility_update(PBVHNode *node) { - for (BMVert *v : node->bm_unique_verts) { + GSet *unique, *other; + + unique = BKE_pbvh_bmesh_node_unique_verts(node); + other = BKE_pbvh_bmesh_node_other_verts(node); + + GSetIterator gs_iter; + + GSET_ITER (gs_iter, unique) { + BMVert *v = static_cast(BLI_gsetIterator_getKey(&gs_iter)); if (!BM_elem_flag_test(v, BM_ELEM_HIDDEN)) { BKE_pbvh_node_fully_hidden_set(node, false); return; } } - for (BMVert *v : node->bm_other_verts) { + GSET_ITER (gs_iter, other) { + BMVert *v = static_cast(BLI_gsetIterator_getKey(&gs_iter)); if (!BM_elem_flag_test(v, BM_ELEM_HIDDEN)) { BKE_pbvh_node_fully_hidden_set(node, false); return; @@ -1965,20 +1985,12 @@ int BKE_pbvh_num_faces(const PBVH *pbvh) return 0; } -blender::Span BKE_pbvh_node_get_vert_indices(const PBVHNode *node) +const int *BKE_pbvh_node_get_vert_indices(PBVHNode *node) { - return node->vert_indices; + return node->vert_indices.data(); } -blender::Span BKE_pbvh_node_get_unique_vert_indices(const PBVHNode *node) -{ - return node->vert_indices.as_span().take_front(node->uniq_verts); -} - -void BKE_pbvh_node_num_verts(const PBVH *pbvh, - const PBVHNode *node, - int *r_uniquevert, - int *r_totvert) +void BKE_pbvh_node_num_verts(PBVH *pbvh, PBVHNode *node, int *r_uniquevert, int *r_totvert) { int tot; @@ -2001,9 +2013,9 @@ void BKE_pbvh_node_num_verts(const PBVH *pbvh, } break; case PBVH_BMESH: - tot = node->bm_unique_verts.size(); + tot = BLI_gset_len(node->bm_unique_verts); if (r_totvert) { - *r_totvert = tot + node->bm_other_verts.size(); + *r_totvert = tot + BLI_gset_len(node->bm_other_verts); } if (r_uniquevert) { *r_uniquevert = tot; @@ -2879,8 +2891,7 @@ static bool pbvh_draw_search(PBVHNode *node, PBVHDrawSearchData *data) return true; } -void BKE_pbvh_draw_cb(const Mesh &mesh, - PBVH *pbvh, +void BKE_pbvh_draw_cb(PBVH *pbvh, bool update_only_visible, PBVHFrustumPlanes *update_frustum, PBVHFrustumPlanes *draw_frustum, @@ -2919,7 +2930,7 @@ void BKE_pbvh_draw_cb(const Mesh &mesh, /* Update draw buffers. */ if (!nodes.is_empty() && (update_flag & (PBVH_RebuildDrawBuffers | PBVH_UpdateDrawBuffers))) { - pbvh_update_draw_buffers(mesh, pbvh, nodes, update_flag); + pbvh_update_draw_buffers(pbvh, nodes, update_flag); } /* Draw visible nodes. */ @@ -2933,7 +2944,7 @@ void BKE_pbvh_draw_cb(const Mesh &mesh, for (PBVHNode *node : nodes) { if (!(node->flag & PBVH_FullyHidden)) { - pbvh_draw_args_init(mesh, pbvh, &args, node); + pbvh_draw_args_init(pbvh, &args, node); draw_fn(user_data, node->draw_batches, args); } } @@ -3127,10 +3138,8 @@ void pbvh_vertex_iter_init(PBVH *pbvh, PBVHNode *node, PBVHVertexIter *vi, int m vi->is_mesh = !pbvh->vert_positions.is_empty(); if (pbvh->header.type == PBVH_BMESH) { - vi->bm_unique_verts = node->bm_unique_verts.begin(); - vi->bm_unique_verts_end = node->bm_unique_verts.end(); - vi->bm_other_verts = node->bm_other_verts.begin(); - vi->bm_other_verts_end = node->bm_other_verts.end(); + BLI_gsetIterator_init(&vi->bm_unique_verts, node->bm_unique_verts); + BLI_gsetIterator_init(&vi->bm_other_verts, node->bm_other_verts); vi->bm_vdata = &pbvh->header.bm->vdata; vi->cd_vert_mask_offset = CustomData_get_offset(vi->bm_vdata, CD_PAINT_MASK); } @@ -3140,7 +3149,7 @@ void pbvh_vertex_iter_init(PBVH *pbvh, PBVHNode *node, PBVHVertexIter *vi, int m vi->grid_hidden = pbvh->grid_hidden; } - vi->mask = 0.0f; + vi->mask = nullptr; if (pbvh->header.type == PBVH_FACES) { vi->vert_normals = pbvh->vert_normals; vi->hide_vert = pbvh->hide_vert; @@ -3389,13 +3398,13 @@ static void pbvh_face_iter_step(PBVHFaceIter *fd, bool do_step) switch (fd->pbvh_type_) { case PBVH_BMESH: { if (do_step) { - (*fd->bm_faces_iter_)++; - if (*fd->bm_faces_iter_ == fd->node_->bm_faces.end()) { + BLI_gsetIterator_step(&fd->bm_faces_iter_); + if (BLI_gsetIterator_done(&fd->bm_faces_iter_)) { return; } } - BMFace *f = **fd->bm_faces_iter_; + BMFace *f = (BMFace *)BLI_gsetIterator_getKey(&fd->bm_faces_iter_); fd->face.i = intptr_t(f); fd->index = f->head.index; @@ -3509,7 +3518,7 @@ void BKE_pbvh_face_iter_init(PBVH *pbvh, PBVHNode *node, PBVHFaceIter *fd) fd->cd_hide_poly_ = CustomData_get_offset_named( &pbvh->header.bm->pdata, CD_PROP_INT32, ".hide_poly"); - fd->bm_faces_iter_ = node->bm_faces.begin(); + BLI_gsetIterator_init(&fd->bm_faces_iter_, node->bm_faces); break; } @@ -3532,7 +3541,7 @@ bool BKE_pbvh_face_iter_done(PBVHFaceIter *fd) case PBVH_GRIDS: return fd->prim_index_ >= fd->node_->prim_indices.size(); case PBVH_BMESH: - return *fd->bm_faces_iter_ == fd->node_->bm_faces.end(); + return BLI_gsetIterator_done(&fd->bm_faces_iter_); default: BLI_assert_unreachable(); return true; diff --git a/source/blender/blenkernel/intern/pbvh_bmesh.cc b/source/blender/blenkernel/intern/pbvh_bmesh.cc index 655ad1896e5..eaf8ecb9b78 100644 --- a/source/blender/blenkernel/intern/pbvh_bmesh.cc +++ b/source/blender/blenkernel/intern/pbvh_bmesh.cc @@ -8,6 +8,7 @@ #include "MEM_guardedalloc.h" +#include "BLI_buffer.h" #include "BLI_ghash.h" #include "BLI_heap_simple.h" #include "BLI_math_geom.h" @@ -25,16 +26,7 @@ #include "bmesh.h" #include "pbvh_intern.hh" -#include "PIL_time.h" - -#include "CLG_log.h" - -static CLG_LogRef LOG = {"pbvh.bmesh"}; - -using blender::Array; -using blender::IndexRange; using blender::Span; -using blender::Vector; /* Avoid skinny faces */ #define USE_EDGEQUEUE_EVEN_SUBDIV @@ -42,10 +34,10 @@ using blender::Vector; # include "BKE_global.h" #endif -/* Support for only operating on front-faces. */ +/* Support for only operating on front-faces */ #define USE_EDGEQUEUE_FRONTFACE -/* Don't add edges into the queue multiple times. */ +/* don't add edges into the queue multiple times */ #define USE_EDGEQUEUE_TAG /** * Ensure we don't have dirty tags for the edge queue, and that they are left cleared. @@ -118,13 +110,11 @@ static void pbvh_bmesh_verify(PBVH *pbvh); } \ ((void)0) -static std::array bm_edges_from_tri(BMesh *bm, const blender::Span v_tri) +static void bm_edges_from_tri(BMesh *bm, BMVert *v_tri[3], BMEdge *e_tri[3]) { - return { - BM_edge_create(bm, v_tri[0], v_tri[1], nullptr, BM_CREATE_NO_DOUBLE), - BM_edge_create(bm, v_tri[1], v_tri[2], nullptr, BM_CREATE_NO_DOUBLE), - BM_edge_create(bm, v_tri[2], v_tri[0], nullptr, BM_CREATE_NO_DOUBLE), - }; + e_tri[0] = BM_edge_create(bm, v_tri[0], v_tri[1], nullptr, BM_CREATE_NO_DOUBLE); + e_tri[1] = BM_edge_create(bm, v_tri[1], v_tri[2], nullptr, BM_CREATE_NO_DOUBLE); + e_tri[2] = BM_edge_create(bm, v_tri[2], v_tri[0], nullptr, BM_CREATE_NO_DOUBLE); } BLI_INLINE void bm_face_as_array_index_tri(BMFace *f, int r_index[3]) @@ -186,11 +176,11 @@ static BMVert *bm_vert_hash_lookup_chain(GHash *deleted_verts, BMVert *v) return v; } if (*v_next_p == nullptr) { - /* Removed and not remapped. */ + /* removed and not remapped */ return nullptr; } - /* Remapped. */ + /* remapped */ v = *v_next_p; } } @@ -199,37 +189,44 @@ static BMVert *bm_vert_hash_lookup_chain(GHash *deleted_verts, BMVert *v) /****************************** Building ******************************/ -/* Update node data after splitting. */ +/* Update node data after splitting */ static void pbvh_bmesh_node_finalize(PBVH *pbvh, const int node_index, const int cd_vert_node_offset, const int cd_face_node_offset) { + GSetIterator gs_iter; PBVHNode *n = &pbvh->nodes[node_index]; bool has_visible = false; + /* Create vert hash sets */ + n->bm_unique_verts = BLI_gset_ptr_new("bm_unique_verts"); + n->bm_other_verts = BLI_gset_ptr_new("bm_other_verts"); + BB_reset(&n->vb); - for (BMFace *f : n->bm_faces) { - /* Update ownership of faces. */ + GSET_ITER (gs_iter, n->bm_faces) { + BMFace *f = static_cast(BLI_gsetIterator_getKey(&gs_iter)); + + /* Update ownership of faces */ BM_ELEM_CD_SET_INT(f, cd_face_node_offset, node_index); - /* Update vertices. */ + /* Update vertices */ BMLoop *l_first = BM_FACE_FIRST_LOOP(f); BMLoop *l_iter = l_first; do { BMVert *v = l_iter->v; - if (!n->bm_unique_verts.contains(v)) { + if (!BLI_gset_haskey(n->bm_unique_verts, v)) { if (BM_ELEM_CD_GET_INT(v, cd_vert_node_offset) != DYNTOPO_NODE_NONE) { - n->bm_other_verts.add(v); + BLI_gset_add(n->bm_other_verts, v); } else { - n->bm_unique_verts.add(v); + BLI_gset_insert(n->bm_unique_verts, v); BM_ELEM_CD_SET_INT(v, cd_vert_node_offset, node_index); } } - /* Update node bounding box. */ + /* Update node bounding box */ BB_expand(&n->vb, v->co); } while ((l_iter = l_iter->next) != l_first); @@ -243,102 +240,117 @@ static void pbvh_bmesh_node_finalize(PBVH *pbvh, n->orig_vb = n->vb; - /* Build GPU buffers for new node and update vertex normals. */ + /* Build GPU buffers for new node and update vertex normals */ BKE_pbvh_node_mark_rebuild_draw(n); BKE_pbvh_node_fully_hidden_set(n, !has_visible); n->flag |= PBVH_UpdateNormals; } -/* Recursively split the node if it exceeds the leaf_limit. */ -static void pbvh_bmesh_node_split(PBVH *pbvh, const Span bbc_array, int node_index) +/* Recursively split the node if it exceeds the leaf_limit */ +static void pbvh_bmesh_node_split(PBVH *pbvh, const BBC *bbc_array, int node_index) { const int cd_vert_node_offset = pbvh->cd_vert_node_offset; const int cd_face_node_offset = pbvh->cd_face_node_offset; PBVHNode *n = &pbvh->nodes[node_index]; - if (n->bm_faces.size() <= pbvh->leaf_limit) { - /* Node limit not exceeded. */ + if (BLI_gset_len(n->bm_faces) <= pbvh->leaf_limit) { + /* Node limit not exceeded */ pbvh_bmesh_node_finalize(pbvh, node_index, cd_vert_node_offset, cd_face_node_offset); return; } - /* Calculate bounding box around primitive centroids. */ + /* Calculate bounding box around primitive centroids */ BB cb; BB_reset(&cb); - for (BMFace *f : n->bm_faces) { + GSetIterator gs_iter; + GSET_ITER (gs_iter, n->bm_faces) { + const BMFace *f = static_cast(BLI_gsetIterator_getKey(&gs_iter)); const BBC *bbc = &bbc_array[BM_elem_index_get(f)]; BB_expand(&cb, bbc->bcentroid); } - /* Find widest axis and its midpoint. */ + /* Find widest axis and its midpoint */ const int axis = BB_widest_axis(&cb); const float mid = (cb.bmax[axis] + cb.bmin[axis]) * 0.5f; - /* Add two new child nodes. */ + /* Add two new child nodes */ const int children = pbvh->nodes.size(); n->children_offset = children; pbvh_grow_nodes(pbvh, pbvh->nodes.size() + 2); - /* Array reallocated, update current node pointer. */ + /* Array reallocated, update current node pointer */ n = &pbvh->nodes[node_index]; /* Initialize children */ PBVHNode *c1 = &pbvh->nodes[children], *c2 = &pbvh->nodes[children + 1]; c1->flag |= PBVH_Leaf; c2->flag |= PBVH_Leaf; - c1->bm_faces.reserve(n->bm_faces.size() / 2); - c2->bm_faces.reserve(n->bm_faces.size() / 2); + c1->bm_faces = BLI_gset_ptr_new_ex("bm_faces", BLI_gset_len(n->bm_faces) / 2); + c2->bm_faces = BLI_gset_ptr_new_ex("bm_faces", BLI_gset_len(n->bm_faces) / 2); - /* Partition the parent node's faces between the two children. */ - for (BMFace *f : n->bm_faces) { + /* Partition the parent node's faces between the two children */ + GSET_ITER (gs_iter, n->bm_faces) { + BMFace *f = static_cast(BLI_gsetIterator_getKey(&gs_iter)); const BBC *bbc = &bbc_array[BM_elem_index_get(f)]; if (bbc->bcentroid[axis] < mid) { - c1->bm_faces.add(f); + BLI_gset_insert(c1->bm_faces, f); } else { - c2->bm_faces.add(f); + BLI_gset_insert(c2->bm_faces, f); } } /* Enforce at least one primitive in each node */ - blender::Set *empty = nullptr; - blender::Set *other; - if (c1->bm_faces.is_empty()) { - empty = &c1->bm_faces; - other = &c2->bm_faces; + GSet *empty = nullptr, *other; + if (BLI_gset_len(c1->bm_faces) == 0) { + empty = c1->bm_faces; + other = c2->bm_faces; } - else if (c2->bm_faces.is_empty()) { - empty = &c2->bm_faces; - other = &c1->bm_faces; + else if (BLI_gset_len(c2->bm_faces) == 0) { + empty = c2->bm_faces; + other = c1->bm_faces; } if (empty) { - for (BMFace *f : *other) { - empty->add(f); - other->remove(f); + GSET_ITER (gs_iter, other) { + void *key = BLI_gsetIterator_getKey(&gs_iter); + BLI_gset_insert(empty, key); + BLI_gset_remove(other, key, nullptr); break; } } /* Clear this node */ - /* Mark this node's unique verts as unclaimed. */ - for (BMVert *v : n->bm_unique_verts) { - BM_ELEM_CD_SET_INT(v, cd_vert_node_offset, DYNTOPO_NODE_NONE); + /* Mark this node's unique verts as unclaimed */ + if (n->bm_unique_verts) { + GSET_ITER (gs_iter, n->bm_unique_verts) { + BMVert *v = static_cast(BLI_gsetIterator_getKey(&gs_iter)); + BM_ELEM_CD_SET_INT(v, cd_vert_node_offset, DYNTOPO_NODE_NONE); + } + BLI_gset_free(n->bm_unique_verts, nullptr); } - /* Unclaim faces. */ - for (BMFace *f : n->bm_faces) { + /* Unclaim faces */ + GSET_ITER (gs_iter, n->bm_faces) { + BMFace *f = static_cast(BLI_gsetIterator_getKey(&gs_iter)); BM_ELEM_CD_SET_INT(f, cd_face_node_offset, DYNTOPO_NODE_NONE); } - n->bm_faces.clear_and_shrink(); + BLI_gset_free(n->bm_faces, nullptr); + + if (n->bm_other_verts) { + BLI_gset_free(n->bm_other_verts, nullptr); + } if (n->layer_disp) { MEM_freeN(n->layer_disp); } + n->bm_faces = nullptr; + n->bm_unique_verts = nullptr; + n->bm_other_verts = nullptr; n->layer_disp = nullptr; if (n->draw_batches) { @@ -346,26 +358,26 @@ static void pbvh_bmesh_node_split(PBVH *pbvh, const Span bbc_array, int nod } n->flag &= ~PBVH_Leaf; - /* Recurse. */ + /* Recurse */ pbvh_bmesh_node_split(pbvh, bbc_array, children); pbvh_bmesh_node_split(pbvh, bbc_array, children + 1); /* Array maybe reallocated, update current node pointer */ n = &pbvh->nodes[node_index]; - /* Update bounding box. */ + /* Update bounding box */ BB_reset(&n->vb); BB_expand_with_bb(&n->vb, &pbvh->nodes[n->children_offset].vb); BB_expand_with_bb(&n->vb, &pbvh->nodes[n->children_offset + 1].vb); n->orig_vb = n->vb; } -/* Recursively split the node if it exceeds the leaf_limit. */ +/* Recursively split the node if it exceeds the leaf_limit */ static bool pbvh_bmesh_node_limit_ensure(PBVH *pbvh, int node_index) { - PBVHNode &node = pbvh->nodes[node_index]; - const int faces_num = node.bm_faces.size(); - if (faces_num <= pbvh->leaf_limit) { + GSet *bm_faces = pbvh->nodes[node_index].bm_faces; + const int bm_faces_size = BLI_gset_len(bm_faces); + if (bm_faces_size <= pbvh->leaf_limit) { /* Node limit not exceeded */ return false; } @@ -373,11 +385,13 @@ static bool pbvh_bmesh_node_limit_ensure(PBVH *pbvh, int node_index) /* Trigger draw manager cache invalidation. */ pbvh->draw_cache_invalid = true; - /* For each BMFace, store the AABB and AABB centroid. */ - Array bbc_array(faces_num); + /* For each BMFace, store the AABB and AABB centroid */ + BBC *bbc_array = static_cast(MEM_mallocN(sizeof(BBC) * bm_faces_size, "BBC")); - int i = 0; - for (BMFace *f : node.bm_faces) { + GSetIterator gs_iter; + int i; + GSET_ITER_INDEX (gs_iter, bm_faces, i) { + BMFace *f = static_cast(BLI_gsetIterator_getKey(&gs_iter)); BBC *bbc = &bbc_array[i]; BB_reset((BB *)bbc); @@ -388,21 +402,57 @@ static bool pbvh_bmesh_node_limit_ensure(PBVH *pbvh, int node_index) } while ((l_iter = l_iter->next) != l_first); BBC_update_centroid(bbc); - /* So we can do direct lookups on 'bbc_array'. */ + /* so we can do direct lookups on 'bbc_array' */ BM_elem_index_set(f, i); /* set_dirty! */ - i++; } - /* Likely this is already dirty. */ pbvh->header.bm->elem_index_dirty |= BM_FACE; pbvh_bmesh_node_split(pbvh, bbc_array, node_index); + MEM_freeN(bbc_array); + return true; } /**********************************************************************/ +#if 0 +static int pbvh_bmesh_node_offset_from_elem(PBVH *pbvh, BMElem *ele) +{ + switch (ele->head.htype) { + case BM_VERT: + return pbvh->cd_vert_node_offset; + default: + BLI_assert(ele->head.htype == BM_FACE); + return pbvh->cd_face_node_offset; + } +} + +static int pbvh_bmesh_node_index_from_elem(PBVH *pbvh, void *key) +{ + const int cd_node_offset = pbvh_bmesh_node_offset_from_elem(pbvh, key); + const int node_index = BM_ELEM_CD_GET_INT((BMElem *)key, cd_node_offset); + + BLI_assert(node_index != DYNTOPO_NODE_NONE); + BLI_assert(node_index < pbvh->totnode); + (void)pbvh; + + return node_index; +} + +static PBVHNode *pbvh_bmesh_node_from_elem(PBVH *pbvh, void *key) +{ + return &pbvh->nodes[pbvh_bmesh_node_index_from_elem(pbvh, key)]; +} + +/* typecheck */ +# define pbvh_bmesh_node_index_from_elem(pbvh, key) \ + (CHECK_TYPE_ANY(key, BMFace *, BMVert *), pbvh_bmesh_node_index_from_elem(pbvh, key)) +# define pbvh_bmesh_node_from_elem(pbvh, key) \ + (CHECK_TYPE_ANY(key, BMFace *, BMVert *), pbvh_bmesh_node_from_elem(pbvh, key)) +#endif + BLI_INLINE int pbvh_bmesh_node_index_from_vert(PBVH *pbvh, const BMVert *key) { const int node_index = BM_ELEM_CD_GET_INT((const BMElem *)key, pbvh->cd_vert_node_offset); @@ -430,9 +480,7 @@ BLI_INLINE PBVHNode *pbvh_bmesh_node_from_face(PBVH *pbvh, const BMFace *key) } static BMVert *pbvh_bmesh_vert_create(PBVH *pbvh, - const BMVert *v1, - const BMVert *v2, - const int node_index, + int node_index, const float co[3], const float no[3], const int cd_vert_mask_offset) @@ -441,20 +489,19 @@ static BMVert *pbvh_bmesh_vert_create(PBVH *pbvh, BLI_assert((pbvh->nodes.size() == 1 || node_index) && node_index <= pbvh->nodes.size()); - /* Avoid initializing customdata because its quite involved. */ - BMVert *v = BM_vert_create(pbvh->header.bm, co, nullptr, BM_CREATE_NOP); + /* avoid initializing customdata because its quite involved */ + BMVert *v = BM_vert_create(pbvh->header.bm, co, nullptr, BM_CREATE_SKIP_CD); + CustomData_bmesh_set_default(&pbvh->header.bm->vdata, &v->head.data); - BM_data_interp_from_verts(pbvh->header.bm, v1, v2, v, 0.5f); - - /* This value is logged below. */ + /* This value is logged below */ copy_v3_v3(v->no, no); - node->bm_unique_verts.add(v); + BLI_gset_insert(node->bm_unique_verts, v); BM_ELEM_CD_SET_INT(v, pbvh->cd_vert_node_offset, node_index); node->flag |= PBVH_UpdateDrawBuffers | PBVH_UpdateBB | PBVH_TopologyUpdated; - /* Log the new vertex. */ + /* Log the new vertex */ BM_log_vert_added(pbvh->bm_log, v, cd_vert_mask_offset); return v; @@ -463,33 +510,49 @@ static BMVert *pbvh_bmesh_vert_create(PBVH *pbvh, /** * \note Callers are responsible for checking if the face exists before adding. */ -static BMFace *pbvh_bmesh_face_create(PBVH *pbvh, - int node_index, - const blender::Span v_tri, - const blender::Span e_tri, - const BMFace *f_example) +static BMFace *pbvh_bmesh_face_create( + PBVH *pbvh, int node_index, BMVert *v_tri[3], BMEdge *e_tri[3], const BMFace *f_example) { PBVHNode *node = &pbvh->nodes[node_index]; - /* Ensure we never add existing face. */ - BLI_assert(!BM_face_exists(v_tri.data(), 3)); + /* ensure we never add existing face */ + BLI_assert(!BM_face_exists(v_tri, 3)); - BMFace *f = BM_face_create( - pbvh->header.bm, v_tri.data(), e_tri.data(), 3, f_example, BM_CREATE_NOP); + BMFace *f = BM_face_create(pbvh->header.bm, v_tri, e_tri, 3, f_example, BM_CREATE_NOP); f->head.hflag = f_example->head.hflag; - node->bm_faces.add(f); + BLI_gset_insert(node->bm_faces, f); BM_ELEM_CD_SET_INT(f, pbvh->cd_face_node_offset, node_index); + /* mark node for update */ node->flag |= PBVH_UpdateDrawBuffers | PBVH_UpdateNormals | PBVH_TopologyUpdated; node->flag &= ~PBVH_FullyHidden; - /* Log the new face. */ + /* Log the new face */ BM_log_face_added(pbvh->bm_log, f); return f; } +/* Return the number of faces in 'node' that use vertex 'v' */ +#if 0 +static int pbvh_bmesh_node_vert_use_count(PBVH *pbvh, PBVHNode *node, BMVert *v) +{ + BMFace *f; + int count = 0; + + BM_FACES_OF_VERT_ITER_BEGIN (f, v) { + PBVHNode *f_node = pbvh_bmesh_node_from_face(pbvh, f); + if (f_node == node) { + count++; + } + } + BM_FACES_OF_VERT_ITER_END; + + return count; +} +#endif + #define pbvh_bmesh_node_vert_use_count_is_equal(pbvh, node, v, n) \ (pbvh_bmesh_node_vert_use_count_at_most(pbvh, node, v, (n) + 1) == n) @@ -515,7 +578,7 @@ static int pbvh_bmesh_node_vert_use_count_at_most(PBVH *pbvh, return count; } -/* Return a node that uses vertex 'v' other than its current owner. */ +/* Return a node that uses vertex 'v' other than its current owner */ static PBVHNode *pbvh_bmesh_vert_other_node_find(PBVH *pbvh, BMVert *v) { PBVHNode *current_node = pbvh_bmesh_node_from_vert(pbvh, v); @@ -536,48 +599,51 @@ static PBVHNode *pbvh_bmesh_vert_other_node_find(PBVH *pbvh, BMVert *v) static void pbvh_bmesh_vert_ownership_transfer(PBVH *pbvh, PBVHNode *new_owner, BMVert *v) { PBVHNode *current_owner = pbvh_bmesh_node_from_vert(pbvh, v); + /* mark node for update */ current_owner->flag |= PBVH_UpdateDrawBuffers | PBVH_UpdateBB | PBVH_TopologyUpdated; BLI_assert(current_owner != new_owner); - /* Remove current ownership. */ - current_owner->bm_unique_verts.remove(v); + /* Remove current ownership */ + BLI_gset_remove(current_owner->bm_unique_verts, v, nullptr); /* Set new ownership */ BM_ELEM_CD_SET_INT(v, pbvh->cd_vert_node_offset, new_owner - pbvh->nodes.data()); - new_owner->bm_unique_verts.add(v); - new_owner->bm_other_verts.remove(v); - BLI_assert(!new_owner->bm_other_verts.contains(v)); + BLI_gset_insert(new_owner->bm_unique_verts, v); + BLI_gset_remove(new_owner->bm_other_verts, v, nullptr); + BLI_assert(!BLI_gset_haskey(new_owner->bm_other_verts, v)); + /* mark node for update */ new_owner->flag |= PBVH_UpdateDrawBuffers | PBVH_UpdateBB | PBVH_TopologyUpdated; } static void pbvh_bmesh_vert_remove(PBVH *pbvh, BMVert *v) { - /* Never match for first time. */ + /* never match for first time */ int f_node_index_prev = DYNTOPO_NODE_NONE; PBVHNode *v_node = pbvh_bmesh_node_from_vert(pbvh, v); - v_node->bm_unique_verts.remove(v); + BLI_gset_remove(v_node->bm_unique_verts, v, nullptr); BM_ELEM_CD_SET_INT(v, pbvh->cd_vert_node_offset, DYNTOPO_NODE_NONE); - /* Have to check each neighboring face's node. */ + /* Have to check each neighboring face's node */ BMFace *f; BM_FACES_OF_VERT_ITER_BEGIN (f, v) { const int f_node_index = pbvh_bmesh_node_index_from_face(pbvh, f); - /* Faces often share the same node, quick check to avoid redundant #BLI_gset_remove calls. */ + /* faces often share the same node, + * quick check to avoid redundant #BLI_gset_remove calls */ if (f_node_index_prev != f_node_index) { f_node_index_prev = f_node_index; PBVHNode *f_node = &pbvh->nodes[f_node_index]; f_node->flag |= PBVH_UpdateDrawBuffers | PBVH_UpdateBB | PBVH_TopologyUpdated; - /* Remove current ownership. */ - f_node->bm_other_verts.remove(v); + /* Remove current ownership */ + BLI_gset_remove(f_node->bm_other_verts, v, nullptr); - BLI_assert(!f_node->bm_unique_verts.contains(v)); - BLI_assert(!f_node->bm_other_verts.contains(v)); + BLI_assert(!BLI_gset_haskey(f_node->bm_unique_verts, v)); + BLI_assert(!BLI_gset_haskey(f_node->bm_other_verts, v)); } } BM_FACES_OF_VERT_ITER_END; @@ -587,14 +653,15 @@ static void pbvh_bmesh_face_remove(PBVH *pbvh, BMFace *f) { PBVHNode *f_node = pbvh_bmesh_node_from_face(pbvh, f); - /* Check if any of this face's vertices need to be removed from the node. */ + /* Check if any of this face's vertices need to be removed + * from the node */ BMLoop *l_first = BM_FACE_FIRST_LOOP(f); BMLoop *l_iter = l_first; do { BMVert *v = l_iter->v; if (pbvh_bmesh_node_vert_use_count_is_equal(pbvh, f_node, v, 1)) { - if (f_node->bm_unique_verts.contains(v)) { - /* Find a different node that uses 'v'. */ + if (BLI_gset_haskey(f_node->bm_unique_verts, v)) { + /* Find a different node that uses 'v' */ PBVHNode *new_node; new_node = pbvh_bmesh_vert_other_node_find(pbvh, v); @@ -605,34 +672,37 @@ static void pbvh_bmesh_face_remove(PBVH *pbvh, BMFace *f) } } else { - /* Remove from other verts. */ - f_node->bm_other_verts.remove(v); + /* Remove from other verts */ + BLI_gset_remove(f_node->bm_other_verts, v, nullptr); } } } while ((l_iter = l_iter->next) != l_first); - /* Remove face from node and top level. */ - f_node->bm_faces.remove(f); + /* Remove face from node and top level */ + BLI_gset_remove(f_node->bm_faces, f, nullptr); BM_ELEM_CD_SET_INT(f, pbvh->cd_face_node_offset, DYNTOPO_NODE_NONE); - /* Log removed face. */ + /* Log removed face */ BM_log_face_removed(pbvh->bm_log, f); - /* Mark node for update. */ + /* mark node for update */ f_node->flag |= PBVH_UpdateDrawBuffers | PBVH_UpdateNormals | PBVH_TopologyUpdated; } -static Array pbvh_bmesh_edge_loops(BMEdge *e) +static void pbvh_bmesh_edge_loops(BLI_Buffer *buf, BMEdge *e) { - /* Fast-path for most common case where an edge has 2 faces no need to iterate twice. */ - std::array manifold_loops; - if (LIKELY(BM_edge_loop_pair(e, &manifold_loops[0], &manifold_loops[1]))) { - return Array(Span(manifold_loops)); + /* fast-path for most common case where an edge has 2 faces, + * no need to iterate twice. + * This assumes that the buffer */ + BMLoop **data = static_cast(buf->data); + BLI_assert(buf->alloc_count >= 2); + if (LIKELY(BM_edge_loop_pair(e, &data[0], &data[1]))) { + buf->count = 2; + } + else { + BLI_buffer_reinit(buf, BM_edge_face_count(e)); + BM_iter_as_array(nullptr, BM_LOOPS_OF_EDGE, e, static_cast(buf->data), buf->count); } - Array loops(BM_edge_face_count(e)); - BM_iter_as_array( - nullptr, BM_LOOPS_OF_EDGE, e, reinterpret_cast(loops.data()), loops.size()); - return loops; } static void pbvh_bmesh_node_drop_orig(PBVHNode *node) @@ -648,7 +718,7 @@ static void pbvh_bmesh_node_drop_orig(PBVHNode *node) struct EdgeQueue { HeapSimple *heap; const float *center; - float center_proj[3]; /* For when we use projected coords. */ + float center_proj[3]; /* for when we use projected coords. */ float radius_squared; float limit_len_squared; #ifdef USE_EDGEQUEUE_EVEN_SUBDIV @@ -716,12 +786,12 @@ static bool edge_queue_tri_in_sphere(const EdgeQueue *q, BMFace *f) BMVert *v_tri[3]; float c[3]; - /* Get closest point in triangle to sphere center. */ + /* Get closest point in triangle to sphere center */ BM_face_as_array_vert_tri(f, v_tri); closest_on_tri_to_point_v3(c, q->center, v_tri[0]->co, v_tri[1]->co, v_tri[2]->co); - /* Check if triangle intersects the sphere. */ + /* Check if triangle intersects the sphere */ return len_squared_v3v3(q->center, c) <= q->radius_squared; } @@ -731,7 +801,7 @@ static bool edge_queue_tri_in_circle(const EdgeQueue *q, BMFace *f) float c[3]; float tri_proj[3][3]; - /* Get closest point in triangle to sphere center. */ + /* Get closest point in triangle to sphere center */ BM_face_as_array_vert_tri(f, v_tri); project_plane_normalized_v3_v3v3(tri_proj[0], v_tri[0]->co, q->view_normal); @@ -740,11 +810,11 @@ static bool edge_queue_tri_in_circle(const EdgeQueue *q, BMFace *f) closest_on_tri_to_point_v3(c, q->center_proj, tri_proj[0], tri_proj[1], tri_proj[2]); - /* Check if triangle intersects the sphere. */ + /* Check if triangle intersects the sphere */ return len_squared_v3v3(q->center_proj, c) <= q->radius_squared; } -/* Return true if the vertex mask is less than 1.0, false otherwise. */ +/* Return true if the vertex mask is less than 1.0, false otherwise */ static bool check_mask(EdgeQueueContext *eq_ctx, BMVert *v) { return BM_ELEM_CD_GET_FLOAT(v, eq_ctx->cd_vert_mask_offset) < 1.0f; @@ -815,7 +885,7 @@ static void long_edge_queue_edge_add_recursive( if (l_edge->radial_next != l_edge) { /* How much longer we need to be to consider for subdividing - * (avoids subdividing faces which are only *slightly* skinny). */ + * (avoids subdividing faces which are only *slightly* skinny) */ # define EVEN_EDGELEN_THRESHOLD 1.2f /* How much the limit increases per recursion * (avoids performing subdivisions too far away). */ @@ -869,7 +939,7 @@ static void long_edge_queue_face_add(EdgeQueueContext *eq_ctx, BMFace *f) #endif if (eq_ctx->q->edge_queue_tri_in_range(eq_ctx->q, f)) { - /* Check each edge of the face. */ + /* Check each edge of the face */ BMLoop *l_first = BM_FACE_FIRST_LOOP(f); BMLoop *l_iter = l_first; do { @@ -900,7 +970,7 @@ static void short_edge_queue_face_add(EdgeQueueContext *eq_ctx, BMFace *f) BMLoop *l_iter; BMLoop *l_first; - /* Check each edge of the face. */ + /* Check each edge of the face */ l_iter = l_first = BM_FACE_FIRST_LOOP(f); do { short_edge_queue_edge_add(eq_ctx, l_iter->e); @@ -908,8 +978,7 @@ static void short_edge_queue_face_add(EdgeQueueContext *eq_ctx, BMFace *f) } } -/** - * Create a priority queue containing vertex pairs connected by a long +/* Create a priority queue containing vertex pairs connected by a long * edge as defined by PBVH.bm_max_edge_len. * * Only nodes marked for topology update are checked, and in those @@ -955,11 +1024,16 @@ static void long_edge_queue_create(EdgeQueueContext *eq_ctx, #endif for (PBVHNode &node : pbvh->nodes) { - /* Check leaf nodes marked for topology update. */ + /* Check leaf nodes marked for topology update */ if ((node.flag & PBVH_Leaf) && (node.flag & PBVH_UpdateTopology) && !(node.flag & PBVH_FullyHidden)) { - for (BMFace *f : node.bm_faces) { + GSetIterator gs_iter; + + /* Check each face */ + GSET_ITER (gs_iter, node.bm_faces) { + BMFace *f = static_cast(BLI_gsetIterator_getKey(&gs_iter)); + long_edge_queue_face_add(eq_ctx, f); } } @@ -1012,7 +1086,12 @@ static void short_edge_queue_create(EdgeQueueContext *eq_ctx, if ((node.flag & PBVH_Leaf) && (node.flag & PBVH_UpdateTopology) && !(node.flag & PBVH_FullyHidden)) { - for (BMFace *f : node.bm_faces) { + GSetIterator gs_iter; + + /* Check each face */ + GSET_ITER (gs_iter, node.bm_faces) { + BMFace *f = static_cast(BLI_gsetIterator_getKey(&gs_iter)); + short_edge_queue_face_add(eq_ctx, f); } } @@ -1021,51 +1100,54 @@ static void short_edge_queue_create(EdgeQueueContext *eq_ctx, /*************************** Topology update **************************/ -/** - * Copy custom data from src to dst edge. - * - * \note The BM_ELEM_TAG is used to tell whether an edge is in the queue for collapse/split, - * so we do not copy this flag as we do not want the new edge to appear in the queue. - */ -static void copy_edge_data(BMesh &bm, BMEdge &dst, /*const*/ BMEdge &src) +static void pbvh_bmesh_split_edge(EdgeQueueContext *eq_ctx, + PBVH *pbvh, + BMEdge *e, + BLI_Buffer *edge_loops) { - dst.head.hflag = src.head.hflag & ~BM_ELEM_TAG; - CustomData_bmesh_copy_data(&bm.edata, &bm.edata, src.head.data, &dst.head.data); -} - -static void pbvh_bmesh_split_edge(EdgeQueueContext *eq_ctx, PBVH *pbvh, BMEdge *e) -{ - BMesh *bm = pbvh->header.bm; - float co_mid[3], no_mid[3]; - /* Get all faces adjacent to the edge. */ - Array edge_loops = pbvh_bmesh_edge_loops(e); + /* Get all faces adjacent to the edge */ + pbvh_bmesh_edge_loops(edge_loops, e); - /* Create a new vertex in current node at the edge's midpoint. */ + /* Create a new vertex in current node at the edge's midpoint */ mid_v3_v3v3(co_mid, e->v1->co, e->v2->co); mid_v3_v3v3(no_mid, e->v1->no, e->v2->no); normalize_v3(no_mid); int node_index = BM_ELEM_CD_GET_INT(e->v1, eq_ctx->cd_vert_node_offset); BMVert *v_new = pbvh_bmesh_vert_create( - pbvh, e->v1, e->v2, node_index, co_mid, no_mid, eq_ctx->cd_vert_mask_offset); + pbvh, node_index, co_mid, no_mid, eq_ctx->cd_vert_mask_offset); - /* For each face, add two new triangles and delete the original. */ - for (const int i : edge_loops.index_range()) { - BMLoop *l_adj = edge_loops[i]; + /* update paint mask */ + if (eq_ctx->cd_vert_mask_offset != -1) { + float mask_v1 = BM_ELEM_CD_GET_FLOAT(e->v1, eq_ctx->cd_vert_mask_offset); + float mask_v2 = BM_ELEM_CD_GET_FLOAT(e->v2, eq_ctx->cd_vert_mask_offset); + float mask_v_new = 0.5f * (mask_v1 + mask_v2); + + BM_ELEM_CD_SET_FLOAT(v_new, eq_ctx->cd_vert_mask_offset, mask_v_new); + } + + /* For each face, add two new triangles and delete the original */ + for (int i = 0; i < edge_loops->count; i++) { + BMLoop *l_adj = BLI_buffer_at(edge_loops, BMLoop *, i); BMFace *f_adj = l_adj->f; + BMFace *f_new; + BMVert *v_opp, *v1, *v2; + BMVert *v_tri[3]; + BMEdge *e_tri[3]; BLI_assert(f_adj->len == 3); int ni = BM_ELEM_CD_GET_INT(f_adj, eq_ctx->cd_face_node_offset); - /* Find the vertex not in the edge. */ - BMVert *v_opp = l_adj->prev->v; + /* Find the vertex not in the edge */ + v_opp = l_adj->prev->v; - /* Get e->v1 and e->v2 in the order they appear in the existing face so that the new faces' - * winding orders match. */ - BMVert *v1 = l_adj->v; - BMVert *v2 = l_adj->next->v; + /* Get e->v1 and e->v2 in the order they appear in the + * existing face so that the new faces' winding orders + * match */ + v1 = l_adj->v; + v2 = l_adj->next->v; if (ni != node_index && i == 0) { pbvh_bmesh_vert_ownership_transfer(pbvh, &pbvh->nodes[ni], v_new); @@ -1080,7 +1162,7 @@ static void pbvh_bmesh_split_edge(EdgeQueueContext *eq_ctx, PBVH *pbvh, BMEdge * * - original face is `(v1, v2, v3)` * *
    -     *         + v_opp
    +     *         + v3(v_opp)
          *        /|\
          *       / | \
          *      /  |  \
    @@ -1089,71 +1171,68 @@ static void pbvh_bmesh_split_edge(EdgeQueueContext *eq_ctx, PBVH *pbvh, BMEdge *
          *   /     |     \
          *  /  e1  |  e2  \
          * +-------+-------+
    -     * v1      v_new     v2
    +     * v1      v4(v_new) v2
          *  (first) (second)
          * 
    * - * - f_new (first): `v_tri=(v1, v_new, v_opp), e_tri=(e1, e5, e4)` - * - f_new (second): `v_tri=(v_new, v2, v_opp), e_tri=(e2, e3, e5)` + * - f_new (first): `v_tri=(v1, v4, v3), e_tri=(e1, e5, e4)` + * - f_new (second): `v_tri=(v4, v2, v3), e_tri=(e2, e3, e5)` */ - /* Create first face (v1, v_new, v_opp). */ - const std::array first_tri({v1, v_new, v_opp}); - const std::array first_edges = bm_edges_from_tri(bm, first_tri); - copy_edge_data(*bm, *first_edges[0], *e); + /* Create two new faces */ + v_tri[0] = v1; + v_tri[1] = v_new; + v_tri[2] = v_opp; + bm_edges_from_tri(pbvh->header.bm, v_tri, e_tri); + f_new = pbvh_bmesh_face_create(pbvh, ni, v_tri, e_tri, f_adj); + long_edge_queue_face_add(eq_ctx, f_new); - BMFace *f_new_first = pbvh_bmesh_face_create(pbvh, ni, first_tri, first_edges, f_adj); - long_edge_queue_face_add(eq_ctx, f_new_first); - - /* Create second face (v_new, v2, v_opp). */ - const std::array second_tri({v_new, v2, v_opp}); - const std::array second_edges{ - BM_edge_create(bm, second_tri[0], second_tri[1], nullptr, BM_CREATE_NO_DOUBLE), - BM_edge_create(bm, second_tri[1], second_tri[2], nullptr, BM_CREATE_NO_DOUBLE), - first_edges[1], - }; - copy_edge_data(*bm, *second_edges[0], *e); - - BMFace *f_new_second = pbvh_bmesh_face_create(pbvh, ni, second_tri, second_edges, f_adj); - long_edge_queue_face_add(eq_ctx, f_new_second); + v_tri[0] = v_new; + v_tri[1] = v2; + // v_tri[2] = v_opp; /* Unchanged. */ + e_tri[0] = BM_edge_create(pbvh->header.bm, v_tri[0], v_tri[1], nullptr, BM_CREATE_NO_DOUBLE); + e_tri[2] = e_tri[1]; /* switched */ + e_tri[1] = BM_edge_create(pbvh->header.bm, v_tri[1], v_tri[2], nullptr, BM_CREATE_NO_DOUBLE); + f_new = pbvh_bmesh_face_create(pbvh, ni, v_tri, e_tri, f_adj); + long_edge_queue_face_add(eq_ctx, f_new); /* Delete original */ pbvh_bmesh_face_remove(pbvh, f_adj); - BM_face_kill(bm, f_adj); + BM_face_kill(pbvh->header.bm, f_adj); /* Ensure new vertex is in the node */ - if (!pbvh->nodes[ni].bm_unique_verts.contains(v_new)) { - pbvh->nodes[ni].bm_other_verts.add(v_new); + if (!BLI_gset_haskey(pbvh->nodes[ni].bm_unique_verts, v_new)) { + BLI_gset_add(pbvh->nodes[ni].bm_other_verts, v_new); } if (BM_vert_edge_count_is_over(v_opp, 8)) { BMIter bm_iter; BMEdge *e2; + BM_ITER_ELEM (e2, &bm_iter, v_opp, BM_EDGES_OF_VERT) { long_edge_queue_edge_add(eq_ctx, e2); } } } - BM_edge_kill(bm, e); + BM_edge_kill(pbvh->header.bm, e); } -static bool pbvh_bmesh_subdivide_long_edges(EdgeQueueContext *eq_ctx, PBVH *pbvh) +static bool pbvh_bmesh_subdivide_long_edges(EdgeQueueContext *eq_ctx, + PBVH *pbvh, + BLI_Buffer *edge_loops) { - const double start_time = PIL_check_seconds_timer(); - bool any_subdivided = false; while (!BLI_heapsimple_is_empty(eq_ctx->q->heap)) { BMVert **pair = static_cast(BLI_heapsimple_pop_min(eq_ctx->q->heap)); - BMVert *v1 = pair[0]; - BMVert *v2 = pair[1]; + BMVert *v1 = pair[0], *v2 = pair[1]; + BMEdge *e; BLI_mempool_free(eq_ctx->pool, pair); pair = nullptr; /* Check that the edge still exists */ - BMEdge *e; if (!(e = BM_edge_exists(v1, v2))) { continue; } @@ -1161,7 +1240,15 @@ static bool pbvh_bmesh_subdivide_long_edges(EdgeQueueContext *eq_ctx, PBVH *pbvh EDGE_QUEUE_DISABLE(e); #endif + /* At the moment edges never get shorter (subdivision will make new edges) + * unlike collapse where edges can become longer. */ +#if 0 + if (len_squared_v3v3(v1->co, v2->co) <= eq_ctx->q->limit_len_squared) { + continue; + } +#else BLI_assert(len_squared_v3v3(v1->co, v2->co) > eq_ctx->q->limit_len_squared); +#endif /* Check that the edge's vertices are still in the PBVH. It's * possible that an edge collapse has deleted adjacent faces @@ -1175,26 +1262,27 @@ static bool pbvh_bmesh_subdivide_long_edges(EdgeQueueContext *eq_ctx, PBVH *pbvh any_subdivided = true; - pbvh_bmesh_split_edge(eq_ctx, pbvh, e); + pbvh_bmesh_split_edge(eq_ctx, pbvh, e, edge_loops); } #ifdef USE_EDGEQUEUE_TAG_VERIFY pbvh_bmesh_edge_tag_verify(pbvh); #endif - CLOG_INFO( - &LOG, 2, "Long edge subdivision took %f seconds.", PIL_check_seconds_timer() - start_time); - return any_subdivided; } -static void pbvh_bmesh_collapse_edge( - PBVH *pbvh, BMEdge *e, BMVert *v1, BMVert *v2, GHash *deleted_verts, EdgeQueueContext *eq_ctx) +static void pbvh_bmesh_collapse_edge(PBVH *pbvh, + BMEdge *e, + BMVert *v1, + BMVert *v2, + GHash *deleted_verts, + BLI_Buffer *deleted_faces, + EdgeQueueContext *eq_ctx) { - BMesh &bm = *pbvh->header.bm; - /* Prefer deleting the vertex that is less masked. */ - BMVert *v_del; - BMVert *v_conn; + BMVert *v_del, *v_conn; + + /* one of the two vertices may be masked, select the correct one for deletion */ if (BM_ELEM_CD_GET_FLOAT(v1, eq_ctx->cd_vert_mask_offset) < BM_ELEM_CD_GET_FLOAT(v2, eq_ctx->cd_vert_mask_offset)) { @@ -1206,82 +1294,112 @@ static void pbvh_bmesh_collapse_edge( v_conn = v1; } - /* Remove the merge vertex from the PBVH. */ + /* Remove the merge vertex from the PBVH */ pbvh_bmesh_vert_remove(pbvh, v_del); - /* Remove all faces adjacent to the edge. */ + /* Remove all faces adjacent to the edge */ BMLoop *l_adj; while ((l_adj = e->l)) { BMFace *f_adj = l_adj->f; pbvh_bmesh_face_remove(pbvh, f_adj); - BM_face_kill(&bm, f_adj); + BM_face_kill(pbvh->header.bm, f_adj); } - /* Kill the edge. */ + /* Kill the edge */ BLI_assert(BM_edge_is_wire(e)); - BM_edge_kill(&bm, e); + BM_edge_kill(pbvh->header.bm, e); /* For all remaining faces of v_del, create a new face that is the * same except it uses v_conn instead of v_del */ /* NOTE: this could be done with BM_vert_splice(), but that * requires handling other issues like duplicate edges, so doesn't * really buy anything. */ - Vector deleted_faces; + BLI_buffer_clear(deleted_faces); BMLoop *l; - BM_LOOPS_OF_VERT_ITER_BEGIN (l, v_del) { - /* Get vertices, replace use of v_del with v_conn */ - BMFace *f = l->f; - /* Check if a face using these vertices already exists. If so, skip adding this face and mark - * the existing one for deletion as well. Prevents extraneous "flaps" from being created. - * Check is similar to #BM_face_exists. */ - if (BMFace *existing_face = bm_face_exists_tri_from_loop_vert(l->next, v_conn)) { - deleted_faces.append(existing_face); + BM_LOOPS_OF_VERT_ITER_BEGIN (l, v_del) { + BMFace *existing_face; + + /* Get vertices, replace use of v_del with v_conn */ + // BM_iter_as_array(nullptr, BM_VERTS_OF_FACE, f, (void **)v_tri, 3); + BMFace *f = l->f; +#if 0 + BMVert *v_tri[3]; + BM_face_as_array_vert_tri(f, v_tri); + for (int i = 0; i < 3; i++) { + if (v_tri[i] == v_del) { + v_tri[i] = v_conn; + } + } +#endif + + /* Check if a face using these vertices already exists. If so, + * skip adding this face and mark the existing one for + * deletion as well. Prevents extraneous "flaps" from being + * created. */ +#if 0 + if (UNLIKELY(existing_face = BM_face_exists(v_tri, 3))) +#else + if (UNLIKELY(existing_face = bm_face_exists_tri_from_loop_vert(l->next, v_conn))) +#endif + { + BLI_buffer_append(deleted_faces, BMFace *, existing_face); } else { - const std::array v_tri{v_conn, l->next->v, l->prev->v}; + BMVert *v_tri[3] = {v_conn, l->next->v, l->prev->v}; - BLI_assert(!BM_face_exists(v_tri.data(), 3)); + BLI_assert(!BM_face_exists(v_tri, 3)); + BMEdge *e_tri[3]; PBVHNode *n = pbvh_bmesh_node_from_face(pbvh, f); int ni = n - pbvh->nodes.data(); - const std::array e_tri = bm_edges_from_tri(&bm, v_tri); + bm_edges_from_tri(pbvh->header.bm, v_tri, e_tri); pbvh_bmesh_face_create(pbvh, ni, v_tri, e_tri, f); /* Ensure that v_conn is in the new face's node */ - if (!n->bm_unique_verts.contains(v_conn)) { - n->bm_other_verts.add(v_conn); + if (!BLI_gset_haskey(n->bm_unique_verts, v_conn)) { + BLI_gset_add(n->bm_other_verts, v_conn); } } - deleted_faces.append(f); + BLI_buffer_append(deleted_faces, BMFace *, f); } BM_LOOPS_OF_VERT_ITER_END; - /* Delete the tagged faces. */ - for (BMFace *f_del : deleted_faces) { - /* Get vertices and edges of face. */ + /* Delete the tagged faces */ + for (int i = 0; i < deleted_faces->count; i++) { + BMFace *f_del = BLI_buffer_at(deleted_faces, BMFace *, i); + + /* Get vertices and edges of face */ BLI_assert(f_del->len == 3); BMLoop *l_iter = BM_FACE_FIRST_LOOP(f_del); - const std::array v_tri{l_iter->v, l_iter->next->v, l_iter->next->next->v}; - const std::array e_tri{l_iter->e, l_iter->next->e, l_iter->next->next->e}; + BMVert *v_tri[3]; + BMEdge *e_tri[3]; + v_tri[0] = l_iter->v; + e_tri[0] = l_iter->e; + l_iter = l_iter->next; + v_tri[1] = l_iter->v; + e_tri[1] = l_iter->e; + l_iter = l_iter->next; + v_tri[2] = l_iter->v; + e_tri[2] = l_iter->e; /* Remove the face */ pbvh_bmesh_face_remove(pbvh, f_del); - BM_face_kill(&bm, f_del); + BM_face_kill(pbvh->header.bm, f_del); /* Check if any of the face's edges are now unused by any * face, if so delete them */ - for (const int j : IndexRange(3)) { + for (int j = 0; j < 3; j++) { if (BM_edge_is_wire(e_tri[j])) { - BM_edge_kill(&bm, e_tri[j]); + BM_edge_kill(pbvh->header.bm, e_tri[j]); } } /* Check if any of the face's vertices are now unused, if so * remove them from the PBVH */ - for (const int j : IndexRange(3)) { + for (int j = 0; j < 3; j++) { if ((v_tri[j] != v_del) && (v_tri[j]->e == nullptr)) { pbvh_bmesh_vert_remove(pbvh, v_tri[j]); @@ -1291,13 +1409,13 @@ static void pbvh_bmesh_collapse_edge( v_conn = nullptr; } BLI_ghash_insert(deleted_verts, v_tri[j], nullptr); - BM_vert_kill(&bm, v_tri[j]); + BM_vert_kill(pbvh->header.bm, v_tri[j]); } } } /* Move v_conn to the midpoint of v_conn and v_del (if v_conn still exists, it - * may have been deleted above). */ + * may have been deleted above) */ if (v_conn != nullptr) { BM_log_vert_before_modified(pbvh->bm_log, v_conn, eq_ctx->cd_vert_mask_offset); mid_v3_v3v3(v_conn->co, v_conn->co, v_del->co); @@ -1318,33 +1436,32 @@ static void pbvh_bmesh_collapse_edge( BM_log_vert_removed(pbvh->bm_log, v_del, eq_ctx->cd_vert_mask_offset); /* v_conn == nullptr is OK */ BLI_ghash_insert(deleted_verts, v_del, v_conn); - BM_vert_kill(&bm, v_del); + BM_vert_kill(pbvh->header.bm, v_del); } -static bool pbvh_bmesh_collapse_short_edges(EdgeQueueContext *eq_ctx, PBVH *pbvh) +static bool pbvh_bmesh_collapse_short_edges(EdgeQueueContext *eq_ctx, + PBVH *pbvh, + BLI_Buffer *deleted_faces) { - const double start_time = PIL_check_seconds_timer(); - const float min_len_squared = pbvh->bm_min_edge_len * pbvh->bm_min_edge_len; bool any_collapsed = false; - /* Deleted verts point to vertices they were merged into, or nullptr when removed. */ + /* deleted verts point to vertices they were merged into, or nullptr when removed. */ GHash *deleted_verts = BLI_ghash_ptr_new("deleted_verts"); while (!BLI_heapsimple_is_empty(eq_ctx->q->heap)) { BMVert **pair = static_cast(BLI_heapsimple_pop_min(eq_ctx->q->heap)); - BMVert *v1 = pair[0]; - BMVert *v2 = pair[1]; + BMVert *v1 = pair[0], *v2 = pair[1]; BLI_mempool_free(eq_ctx->pool, pair); pair = nullptr; - /* Check the verts still exists. */ + /* Check the verts still exist */ if (!(v1 = bm_vert_hash_lookup_chain(deleted_verts, v1)) || !(v2 = bm_vert_hash_lookup_chain(deleted_verts, v2)) || (v1 == v2)) { continue; } - /* Check that the edge still exists. */ + /* Check that the edge still exists */ BMEdge *e; if (!(e = BM_edge_exists(v1, v2))) { continue; @@ -1357,8 +1474,9 @@ static bool pbvh_bmesh_collapse_short_edges(EdgeQueueContext *eq_ctx, PBVH *pbvh continue; } - /* Check that the edge's vertices are still in the PBVH. It's possible that an edge collapse - * has deleted adjacent faces and the node has been split, thus leaving wire edges and + /* Check that the edge's vertices are still in the PBVH. It's + * possible that an edge collapse has deleted adjacent faces + * and the node has been split, thus leaving wire edges and * associated vertices. */ if ((BM_ELEM_CD_GET_INT(e->v1, eq_ctx->cd_vert_node_offset) == DYNTOPO_NODE_NONE) || (BM_ELEM_CD_GET_INT(e->v2, eq_ctx->cd_vert_node_offset) == DYNTOPO_NODE_NONE)) @@ -1368,14 +1486,11 @@ static bool pbvh_bmesh_collapse_short_edges(EdgeQueueContext *eq_ctx, PBVH *pbvh any_collapsed = true; - pbvh_bmesh_collapse_edge(pbvh, e, v1, v2, deleted_verts, eq_ctx); + pbvh_bmesh_collapse_edge(pbvh, e, v1, v2, deleted_verts, deleted_faces, eq_ctx); } BLI_ghash_free(deleted_verts, nullptr, nullptr); - CLOG_INFO( - &LOG, 2, "Short edge collapse took %f seconds.", PIL_check_seconds_timer() - start_time); - return any_collapsed; } @@ -1393,8 +1508,12 @@ bool pbvh_bmesh_node_raycast(PBVHNode *node, bool hit = false; float nearest_vertex_co[3] = {0.0f}; + BLI_assert(!use_original || (BLI_gset_len(node->bm_faces) > 0 && node->bm_tot_ortri)); + use_original = use_original && node->bm_tot_ortri; + GSetIterator gs_iter; + if (use_original && node->bm_tot_ortri) { for (int i = 0; i < node->bm_tot_ortri; i++) { float *cos[3]; @@ -1413,7 +1532,7 @@ bool pbvh_bmesh_node_raycast(PBVHNode *node, if (r_active_vertex) { float location[3] = {0.0f}; madd_v3_v3v3fl(location, ray_start, ray_normal, *depth); - for (const int j : IndexRange(3)) { + for (int j = 0; j < 3; j++) { if (j == 0 || len_squared_v3v3(location, cos[j]) < len_squared_v3v3(location, nearest_vertex_co)) { copy_v3_v3(nearest_vertex_co, cos[j]); @@ -1425,7 +1544,9 @@ bool pbvh_bmesh_node_raycast(PBVHNode *node, } } else { - for (BMFace *f : node->bm_faces) { + GSET_ITER (gs_iter, node->bm_faces) { + BMFace *f = static_cast(BLI_gsetIterator_getKey(&gs_iter)); + BLI_assert(f->len == 3); if (!BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { @@ -1444,7 +1565,7 @@ bool pbvh_bmesh_node_raycast(PBVHNode *node, if (r_active_vertex) { float location[3] = {0.0f}; madd_v3_v3v3fl(location, ray_start, ray_normal, *depth); - for (const int j : IndexRange(3)) { + for (int j = 0; j < 3; j++) { if (j == 0 || len_squared_v3v3(location, v_tri[j]->co) < len_squared_v3v3(location, nearest_vertex_co)) { @@ -1471,10 +1592,13 @@ bool BKE_pbvh_bmesh_node_raycast_detail(PBVHNode *node, return false; } + GSetIterator gs_iter; bool hit = false; BMFace *f_hit = nullptr; - for (BMFace *f : node->bm_faces) { + GSET_ITER (gs_iter, node->bm_faces) { + BMFace *f = static_cast(BLI_gsetIterator_getKey(&gs_iter)); + BLI_assert(f->len == 3); if (!BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { BMVert *v_tri[3]; @@ -1497,7 +1621,7 @@ bool BKE_pbvh_bmesh_node_raycast_detail(PBVHNode *node, float len2 = len_squared_v3v3(v_tri[1]->co, v_tri[2]->co); float len3 = len_squared_v3v3(v_tri[2]->co, v_tri[0]->co); - /* Detail returned will be set to the maximum allowed size, so take max here. */ + /* detail returned will be set to the maximum allowed size, so take max here */ *r_edge_length = sqrtf(max_fff(len1, len2, len3)); } @@ -1526,7 +1650,11 @@ bool pbvh_bmesh_node_nearest_to_ray(PBVHNode *node, } } else { - for (BMFace *f : node->bm_faces) { + GSetIterator gs_iter; + + GSET_ITER (gs_iter, node->bm_faces) { + BMFace *f = static_cast(BLI_gsetIterator_getKey(&gs_iter)); + BLI_assert(f->len == 3); if (!BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { BMVert *v_tri[3]; @@ -1545,14 +1673,17 @@ void pbvh_bmesh_normals_update(Span nodes) { for (PBVHNode *node : nodes) { if (node->flag & PBVH_UpdateNormals) { - for (BMFace *face : node->bm_faces) { - BM_face_normal_update(face); + GSetIterator gs_iter; + + GSET_ITER (gs_iter, node->bm_faces) { + BM_face_normal_update(static_cast(BLI_gsetIterator_getKey(&gs_iter))); } - for (BMVert *vert : node->bm_unique_verts) { - BM_vert_normal_update(vert); + GSET_ITER (gs_iter, node->bm_unique_verts) { + BM_vert_normal_update(static_cast(BLI_gsetIterator_getKey(&gs_iter))); } - for (BMVert *vert : node->bm_other_verts) { - BM_vert_normal_update(vert); + /* This should be unneeded normally */ + GSET_ITER (gs_iter, node->bm_other_verts) { + BM_vert_normal_update(static_cast(BLI_gsetIterator_getKey(&gs_iter))); } node->flag &= ~PBVH_UpdateNormals; } @@ -1560,8 +1691,8 @@ void pbvh_bmesh_normals_update(Span nodes) } struct FastNodeBuildInfo { - int totface; /* Number of faces. */ - int start; /* Start of faces in array. */ + int totface; /* number of faces */ + int start; /* start of faces in array */ FastNodeBuildInfo *child1; FastNodeBuildInfo *child2; }; @@ -1580,7 +1711,7 @@ static void pbvh_bmesh_node_limit_ensure_fast( return; } - /* Calculate bounding box around primitive centroids. */ + /* Calculate bounding box around primitive centroids */ BB cb; BB_reset(&cb); for (int i = 0; i < node->totface; i++) { @@ -1590,15 +1721,15 @@ static void pbvh_bmesh_node_limit_ensure_fast( BB_expand(&cb, bbc->bcentroid); } - /* Initialize the children. */ + /* initialize the children */ - /* Find widest axis and its midpoint. */ + /* Find widest axis and its midpoint */ const int axis = BB_widest_axis(&cb); const float mid = (cb.bmax[axis] + cb.bmin[axis]) * 0.5f; int num_child1 = 0, num_child2 = 0; - /* Split vertices along the middle line. */ + /* split vertices along the middle line */ const int end = node->start + node->totface; for (int i = node->start; i < end - num_child2; i++) { BMFace *f = nodeinfo[i]; @@ -1607,7 +1738,7 @@ static void pbvh_bmesh_node_limit_ensure_fast( if (bbc->bcentroid[axis] > mid) { int i_iter = end - num_child2 - 1; int candidate = -1; - /* Found a face that should be part of another node, look for a face to substitute with. */ + /* found a face that should be part of another node, look for a face to substitute with */ for (; i_iter > i; i_iter--) { BMFace *f_iter = nodeinfo[i_iter]; @@ -1624,13 +1755,13 @@ static void pbvh_bmesh_node_limit_ensure_fast( BMFace *tmp = nodeinfo[i]; nodeinfo[i] = nodeinfo[candidate]; nodeinfo[candidate] = tmp; - /* Increase both counts. */ + /* increase both counts */ num_child1++; num_child2++; } else { - /* Not finding candidate means second half of array part is full of - * second node parts, just increase the number of child nodes for it. */ + /* not finding candidate means second half of array part is full of + * second node parts, just increase the number of child nodes for it */ num_child2++; } } @@ -1639,7 +1770,7 @@ static void pbvh_bmesh_node_limit_ensure_fast( } } - /* Ensure at least one child in each node. */ + /* ensure at least one child in each node */ if (num_child2 == 0) { num_child2++; num_child1--; @@ -1649,8 +1780,8 @@ static void pbvh_bmesh_node_limit_ensure_fast( num_child2--; } - /* At this point, faces should have been split along the array range sequentially, - * each sequential part belonging to one node only. */ + /* at this point, faces should have been split along the array range sequentially, + * each sequential part belonging to one node only */ BLI_assert((num_child1 + num_child2) == node->totface); node->child1 = child1 = static_cast( @@ -1672,7 +1803,7 @@ static void pbvh_bmesh_create_nodes_fast_recursive( PBVH *pbvh, BMFace **nodeinfo, BBC *bbc_array, FastNodeBuildInfo *node, int node_index) { PBVHNode *n = &pbvh->nodes[node_index]; - /* Two cases, node does not have children or does have children. */ + /* two cases, node does not have children or does have children */ if (node->child1) { int children_offset = pbvh->nodes.size(); @@ -1685,22 +1816,26 @@ static void pbvh_bmesh_create_nodes_fast_recursive( n = &pbvh->nodes[node_index]; - /* Update bounding box. */ + /* Update bounding box */ BB_reset(&n->vb); BB_expand_with_bb(&n->vb, &pbvh->nodes[n->children_offset].vb); BB_expand_with_bb(&n->vb, &pbvh->nodes[n->children_offset + 1].vb); n->orig_vb = n->vb; } else { - /* Node does not have children so it's a leaf node, populate with faces and tag accordingly - * this is an expensive part but it's not so easily thread-able due to vertex node indices. */ + /* node does not have children so it's a leaf node, populate with faces and tag accordingly + * this is an expensive part but it's not so easily thread-able due to vertex node indices */ const int cd_vert_node_offset = pbvh->cd_vert_node_offset; const int cd_face_node_offset = pbvh->cd_face_node_offset; bool has_visible = false; n->flag = PBVH_Leaf; - n->bm_faces.reserve(node->totface); + n->bm_faces = BLI_gset_ptr_new_ex("bm_faces", node->totface); + + /* Create vert hash sets */ + n->bm_unique_verts = BLI_gset_ptr_new("bm_unique_verts"); + n->bm_other_verts = BLI_gset_ptr_new("bm_other_verts"); BB_reset(&n->vb); @@ -1710,25 +1845,25 @@ static void pbvh_bmesh_create_nodes_fast_recursive( BMFace *f = nodeinfo[i]; BBC *bbc = &bbc_array[BM_elem_index_get(f)]; - /* Update ownership of faces. */ - n->bm_faces.add_new(f); + /* Update ownership of faces */ + BLI_gset_insert(n->bm_faces, f); BM_ELEM_CD_SET_INT(f, cd_face_node_offset, node_index); - /* Update vertices. */ + /* Update vertices */ BMLoop *l_first = BM_FACE_FIRST_LOOP(f); BMLoop *l_iter = l_first; do { BMVert *v = l_iter->v; - if (!n->bm_unique_verts.contains(v)) { + if (!BLI_gset_haskey(n->bm_unique_verts, v)) { if (BM_ELEM_CD_GET_INT(v, cd_vert_node_offset) != DYNTOPO_NODE_NONE) { - n->bm_other_verts.add(v); + BLI_gset_add(n->bm_other_verts, v); } else { - n->bm_unique_verts.add(v); + BLI_gset_insert(n->bm_unique_verts, v); BM_ELEM_CD_SET_INT(v, cd_vert_node_offset, node_index); } } - /* Update node bounding box. */ + /* Update node bounding box */ } while ((l_iter = l_iter->next) != l_first); if (!BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { @@ -1743,7 +1878,7 @@ static void pbvh_bmesh_create_nodes_fast_recursive( n->orig_vb = n->vb; - /* Build GPU buffers for new node and update vertex normals. */ + /* Build GPU buffers for new node and update vertex normals */ BKE_pbvh_node_mark_rebuild_draw(n); BKE_pbvh_node_fully_hidden_set(n, !has_visible); @@ -1772,12 +1907,12 @@ void BKE_pbvh_build_bmesh(PBVH *pbvh, pbvh->header.type = PBVH_BMESH; pbvh->bm_log = log; - /* TODO: choose leaf limit better. */ + /* TODO: choose leaf limit better */ pbvh->leaf_limit = 400; BKE_pbvh_update_bmesh_offsets(pbvh, cd_vert_node_offset, cd_face_node_offset); - /* bounding box array of all faces, no need to recalculate every time. */ + /* bounding box array of all faces, no need to recalculate every time */ BBC *bbc_array = static_cast(MEM_mallocN(sizeof(BBC) * bm->totface, "BBC")); BMFace **nodeinfo = static_cast( MEM_mallocN(sizeof(*nodeinfo) * bm->totface, "nodeinfo")); @@ -1810,20 +1945,20 @@ void BKE_pbvh_build_bmesh(PBVH *pbvh, BM_ELEM_CD_SET_INT(v, cd_vert_node_offset, DYNTOPO_NODE_NONE); } - /* Set up root node. */ + /* setup root node */ FastNodeBuildInfo rootnode = {0}; rootnode.totface = bm->totface; - /* Start recursion, assign faces to nodes accordingly. */ + /* start recursion, assign faces to nodes accordingly */ pbvh_bmesh_node_limit_ensure_fast(pbvh, nodeinfo, bbc_array, &rootnode, arena); /* We now have all faces assigned to a node, * next we need to assign those to the gsets of the nodes. */ - /* Start with all faces in the root node. */ + /* Start with all faces in the root node */ pbvh->nodes.append({}); - /* Take root node and visit and populate children recursively. */ + /* take root node and visit and populate children recursively */ pbvh_bmesh_create_nodes_fast_recursive(pbvh, nodeinfo, bbc_array, &rootnode, 0); BLI_memarena_free(arena); @@ -1839,6 +1974,9 @@ bool BKE_pbvh_bmesh_update_topology(PBVH *pbvh, const bool use_frontface, const bool use_projected) { + /* 2 is enough for edge faces - manifold edge */ + BLI_buffer_declare_static(BMLoop *, edge_loops, BLI_BUFFER_NOP, 2); + BLI_buffer_declare_static(BMFace *, deleted_faces, BLI_BUFFER_NOP, 32); const int cd_vert_mask_offset = CustomData_get_offset(&pbvh->header.bm->vdata, CD_PAINT_MASK); const int cd_vert_node_offset = pbvh->cd_vert_node_offset; const int cd_face_node_offset = pbvh->cd_face_node_offset; @@ -1863,7 +2001,7 @@ bool BKE_pbvh_bmesh_update_topology(PBVH *pbvh, short_edge_queue_create( &eq_ctx, pbvh, center, view_normal, radius, use_frontface, use_projected); - modified |= pbvh_bmesh_collapse_short_edges(&eq_ctx, pbvh); + modified |= pbvh_bmesh_collapse_short_edges(&eq_ctx, pbvh, &deleted_faces); BLI_heapsimple_free(q.heap, nullptr); BLI_mempool_destroy(queue_pool); } @@ -1882,7 +2020,7 @@ bool BKE_pbvh_bmesh_update_topology(PBVH *pbvh, long_edge_queue_create( &eq_ctx, pbvh, center, view_normal, radius, use_frontface, use_projected); - modified |= pbvh_bmesh_subdivide_long_edges(&eq_ctx, pbvh); + modified |= pbvh_bmesh_subdivide_long_edges(&eq_ctx, pbvh, &edge_loops); BLI_heapsimple_free(q.heap, nullptr); BLI_mempool_destroy(queue_pool); } @@ -1893,6 +2031,8 @@ bool BKE_pbvh_bmesh_update_topology(PBVH *pbvh, node.flag &= ~PBVH_UpdateTopology; } } + BLI_buffer_free(&edge_loops); + BLI_buffer_free(&deleted_faces); /* Go over all changed nodes and check if anything needs to be updated. */ for (PBVHNode &node : pbvh->nodes) { @@ -1916,14 +2056,14 @@ bool BKE_pbvh_bmesh_update_topology(PBVH *pbvh, void BKE_pbvh_bmesh_node_save_orig(BMesh *bm, BMLog *log, PBVHNode *node, bool use_original) { - /* Skip if original coords/triangles are already saved. */ + /* Skip if original coords/triangles are already saved */ if (node->bm_orco) { return; } - const int totvert = node->bm_unique_verts.size() + node->bm_other_verts.size(); + const int totvert = BLI_gset_len(node->bm_unique_verts) + BLI_gset_len(node->bm_other_verts); - const int tottri = node->bm_faces.size(); + const int tottri = BLI_gset_len(node->bm_faces); node->bm_orco = static_cast( MEM_mallocN(sizeof(*node->bm_orco) * totvert, __func__)); @@ -1931,9 +2071,11 @@ void BKE_pbvh_bmesh_node_save_orig(BMesh *bm, BMLog *log, PBVHNode *node, bool u node->bm_orvert = static_cast( MEM_mallocN(sizeof(*node->bm_orvert) * totvert, __func__)); - /* Copy out the vertices and assign a temporary index. */ + /* Copy out the vertices and assign a temporary index */ int i = 0; - for (BMVert *v : node->bm_unique_verts) { + GSetIterator gs_iter; + GSET_ITER (gs_iter, node->bm_unique_verts) { + BMVert *v = static_cast(BLI_gsetIterator_getKey(&gs_iter)); const float *origco = BM_log_original_vert_co(log, v); if (use_original && origco) { @@ -1947,7 +2089,8 @@ void BKE_pbvh_bmesh_node_save_orig(BMesh *bm, BMLog *log, PBVHNode *node, bool u BM_elem_index_set(v, i); /* set_dirty! */ i++; } - for (BMVert *v : node->bm_other_verts) { + GSET_ITER (gs_iter, node->bm_other_verts) { + BMVert *v = static_cast(BLI_gsetIterator_getKey(&gs_iter)); const float *origco = BM_log_original_vert_co(log, v); if (use_original && origco) { @@ -1966,11 +2109,24 @@ void BKE_pbvh_bmesh_node_save_orig(BMesh *bm, BMLog *log, PBVHNode *node, bool u /* Copy the triangles */ i = 0; - for (BMFace *f : node->bm_faces) { + GSET_ITER (gs_iter, node->bm_faces) { + BMFace *f = static_cast(BLI_gsetIterator_getKey(&gs_iter)); + if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { continue; } + +#if 0 + BMIter bm_iter; + BMVert *v; + int j = 0; + BM_ITER_ELEM (v, &bm_iter, f, BM_VERTS_OF_FACE) { + node->bm_ortri[i][j] = BM_elem_index_get(v); + j++; + } +#else bm_face_as_array_index_tri(f, node->bm_ortri[i]); +#endif i++; } node->bm_tot_ortri = i; @@ -1982,10 +2138,11 @@ void BKE_pbvh_bmesh_after_stroke(PBVH *pbvh) for (int i = 0; i < totnode; i++) { PBVHNode *n = &pbvh->nodes[i]; if (n->flag & PBVH_Leaf) { - /* Free orco/ortri data. */ + /* Free orco/ortri data */ pbvh_bmesh_node_drop_orig(n); - /* Recursively split nodes that have gotten too many elements. */ + /* Recursively split nodes that have gotten too many + * elements */ pbvh_bmesh_node_limit_ensure(pbvh, i); } } @@ -2002,17 +2159,17 @@ void BKE_pbvh_node_mark_topology_update(PBVHNode *node) node->flag |= PBVH_UpdateTopology; } -const blender::Set &BKE_pbvh_bmesh_node_unique_verts(PBVHNode *node) +GSet *BKE_pbvh_bmesh_node_unique_verts(PBVHNode *node) { return node->bm_unique_verts; } -const blender::Set &BKE_pbvh_bmesh_node_other_verts(PBVHNode *node) +GSet *BKE_pbvh_bmesh_node_other_verts(PBVHNode *node) { return node->bm_other_verts; } -const blender::Set &BKE_pbvh_bmesh_node_faces(PBVHNode *node) +GSet *BKE_pbvh_bmesh_node_faces(PBVHNode *node) { return node->bm_faces; } @@ -2074,7 +2231,7 @@ static void print_flag_factors(int flag) static void pbvh_bmesh_verify(PBVH *pbvh) { - /* Build list of faces & verts to lookup. */ + /* build list of faces & verts to lookup */ GSet *faces_all = BLI_gset_ptr_new_ex(__func__, pbvh->header.bm->totface); BMIter iter; @@ -2096,13 +2253,13 @@ static void pbvh_bmesh_verify(PBVH *pbvh) } } - /* Check vert/face counts. */ + /* Check vert/face counts */ { int totface = 0, totvert = 0; for (int i = 0; i < pbvh->totnode; i++) { PBVHNode *n = &pbvh->nodes[i]; - totface += n->bm_faces.is_empty() ? n->bm_faces.size() : 0; - totvert += n->bm_unique_verts ? n->bm_unique_verts.size() : 0; + totface += n->bm_faces ? BLI_gset_len(n->bm_faces) : 0; + totvert += n->bm_unique_verts ? BLI_gset_len(n->bm_unique_verts) : 0; } BLI_assert(totface == BLI_gset_len(faces_all)); @@ -2116,26 +2273,26 @@ static void pbvh_bmesh_verify(PBVH *pbvh) BMVert *v; PBVHNode *n = pbvh_bmesh_node_lookup(pbvh, f); - /* Check that the face's node is a leaf. */ + /* Check that the face's node is a leaf */ BLI_assert(n->flag & PBVH_Leaf); - /* Check that the face's node knows it owns the face. */ - BLI_assert(n->bm_faces.contains(f)); + /* Check that the face's node knows it owns the face */ + BLI_assert(BLI_gset_haskey(n->bm_faces, f)); /* Check the face's vertices... */ BM_ITER_ELEM (v, &bm_iter, f, BM_VERTS_OF_FACE) { PBVHNode *nv; - /* Check that the vertex is in the node. */ + /* Check that the vertex is in the node */ BLI_assert(BLI_gset_haskey(n->bm_unique_verts, v) ^ BLI_gset_haskey(n->bm_other_verts, v)); - /* Check that the vertex has a node owner. */ + /* Check that the vertex has a node owner */ nv = pbvh_bmesh_node_lookup(pbvh, v); - /* Check that the vertex's node knows it owns the vert. */ + /* Check that the vertex's node knows it owns the vert */ BLI_assert(BLI_gset_haskey(nv->bm_unique_verts, v)); - /* Check that the vertex isn't duplicated as an 'other' vert. */ + /* Check that the vertex isn't duplicated as an 'other' vert */ BLI_assert(!BLI_gset_haskey(nv->bm_other_verts, v)); } } @@ -2145,23 +2302,24 @@ static void pbvh_bmesh_verify(PBVH *pbvh) { BMVert *v; BM_ITER_MESH (v, &iter, pbvh->header.bm, BM_VERTS_OF_MESH) { - /* Vertex isn't tracked. */ + /* vertex isn't tracked */ if (BM_ELEM_CD_GET_INT(v, pbvh->cd_vert_node_offset) == DYNTOPO_NODE_NONE) { continue; } PBVHNode *n = pbvh_bmesh_node_lookup(pbvh, v); - /* Check that the vert's node is a leaf. */ + /* Check that the vert's node is a leaf */ BLI_assert(n->flag & PBVH_Leaf); - /* Check that the vert's node knows it owns the vert. */ + /* Check that the vert's node knows it owns the vert */ BLI_assert(BLI_gset_haskey(n->bm_unique_verts, v)); - /* Check that the vertex isn't duplicated as an 'other' vert. */ + /* Check that the vertex isn't duplicated as an 'other' vert */ BLI_assert(!BLI_gset_haskey(n->bm_other_verts, v)); - /* Check that the vert's node also contains one of the vert's adjacent faces. */ + /* Check that the vert's node also contains one of the vert's + * adjacent faces */ bool found = false; BMIter bm_iter; BMFace *f = nullptr; @@ -2211,7 +2369,8 @@ static void pbvh_bmesh_verify(PBVH *pbvh) if (n->flag & PBVH_Leaf) { GSetIterator gs_iter; - for (BMFace *f : n->bm_faces) { + GSET_ITER (gs_iter, n->bm_faces) { + BMFace *f = BLI_gsetIterator_getKey(&gs_iter); PBVHNode *n_other = pbvh_bmesh_node_lookup(pbvh, f); BLI_assert(n == n_other); BLI_assert(BLI_gset_haskey(faces_all, f)); diff --git a/source/blender/blenkernel/intern/pbvh_colors.cc b/source/blender/blenkernel/intern/pbvh_colors.cc index 3e4803abae4..907f62fedb1 100644 --- a/source/blender/blenkernel/intern/pbvh_colors.cc +++ b/source/blender/blenkernel/intern/pbvh_colors.cc @@ -161,13 +161,14 @@ void BKE_pbvh_vertex_color_set(PBVH *pbvh, PBVHVertRef vertex, const float color } void BKE_pbvh_swap_colors(PBVH *pbvh, - const blender::Span indices, - blender::MutableSpan r_colors) + const int *indices, + const int indices_num, + float (*r_colors)[4]) { blender::bke::to_static_color_type(eCustomDataType(pbvh->color_layer->type), [&](auto dummy) { using T = decltype(dummy); T *pbvh_colors = static_cast(pbvh->color_layer->data); - for (const int i : indices.index_range()) { + for (const int i : IndexRange(indices_num)) { T temp = pbvh_colors[indices[i]]; blender::bke::from_float(r_colors[i], pbvh_colors[indices[i]]); blender::bke::to_float(temp, r_colors[i]); @@ -176,29 +177,31 @@ void BKE_pbvh_swap_colors(PBVH *pbvh, } void BKE_pbvh_store_colors(PBVH *pbvh, - const blender::Span indices, - blender::MutableSpan r_colors) + const int *indices, + const int indices_num, + float (*r_colors)[4]) { blender::bke::to_static_color_type(eCustomDataType(pbvh->color_layer->type), [&](auto dummy) { using T = decltype(dummy); T *pbvh_colors = static_cast(pbvh->color_layer->data); - for (const int i : indices.index_range()) { + for (const int i : IndexRange(indices_num)) { blender::bke::to_float(pbvh_colors[indices[i]], r_colors[i]); } }); } void BKE_pbvh_store_colors_vertex(PBVH *pbvh, - const blender::Span indices, - blender::MutableSpan r_colors) + const int *indices, + const int indices_num, + float (*r_colors)[4]) { if (pbvh->color_domain == ATTR_DOMAIN_POINT) { - BKE_pbvh_store_colors(pbvh, indices, r_colors); + BKE_pbvh_store_colors(pbvh, indices, indices_num, r_colors); } else { blender::bke::to_static_color_type(eCustomDataType(pbvh->color_layer->type), [&](auto dummy) { using T = decltype(dummy); - for (const int i : indices.index_range()) { + for (const int i : IndexRange(indices_num)) { blender::bke::pbvh_vertex_color_get(*pbvh, BKE_pbvh_make_vref(indices[i]), r_colors[i]); } }); diff --git a/source/blender/blenkernel/intern/pbvh_intern.hh b/source/blender/blenkernel/intern/pbvh_intern.hh index 3c213b8c202..2e261826780 100644 --- a/source/blender/blenkernel/intern/pbvh_intern.hh +++ b/source/blender/blenkernel/intern/pbvh_intern.hh @@ -6,7 +6,6 @@ #include "BLI_array.hh" #include "BLI_math_vector_types.hh" -#include "BLI_set.hh" #include "BLI_span.hh" #include "BLI_vector.hh" @@ -18,8 +17,6 @@ struct PBVHGPUFormat; struct MLoopTri; -struct BMVert; -struct BMFace; /* Axis-aligned bounding box */ struct BB { @@ -110,13 +107,13 @@ struct PBVHNode { /* Dyntopo */ - /* Set of pointers to the BMFaces used by this node. + /* GSet of pointers to the BMFaces used by this node. * NOTE: PBVH_BMESH only. Faces are always triangles * (dynamic topology forcibly triangulates the mesh). */ - blender::Set bm_faces; - blender::Set bm_unique_verts; - blender::Set bm_other_verts; + GSet *bm_faces = nullptr; + GSet *bm_unique_verts = nullptr; + GSet *bm_other_verts = nullptr; /* Deprecated. Stores original coordinates of triangles. */ float (*bm_orco)[3] = nullptr; diff --git a/source/blender/blenkernel/intern/pointcloud.cc b/source/blender/blenkernel/intern/pointcloud.cc index d27eed16a32..678c20aafc9 100644 --- a/source/blender/blenkernel/intern/pointcloud.cc +++ b/source/blender/blenkernel/intern/pointcloud.cc @@ -259,10 +259,16 @@ std::optional> PointCloud::bounds_min_max() con return this->runtime->bounds_cache.data(); } -BoundBox BKE_pointcloud_boundbox_get(Object *ob) +BoundBox *BKE_pointcloud_boundbox_get(Object *ob) { using namespace blender; BLI_assert(ob->type == OB_POINTCLOUD); + if (ob->runtime.bb != nullptr && (ob->runtime.bb->flag & BOUNDBOX_DIRTY) == 0) { + return ob->runtime.bb; + } + if (ob->runtime.bb == nullptr) { + ob->runtime.bb = MEM_cnew(__func__); + } std::optional> bounds; if (ob->runtime.geometry_set_eval) { @@ -273,15 +279,14 @@ BoundBox BKE_pointcloud_boundbox_get(Object *ob) bounds = pointcloud->bounds_min_max(); } - BoundBox bb; if (bounds) { - BKE_boundbox_init_from_minmax(&bb, bounds->min, bounds->max); + BKE_boundbox_init_from_minmax(ob->runtime.bb, bounds->min, bounds->max); } else { - BKE_boundbox_init_from_minmax(&bb, float3(-1), float3(1)); + BKE_boundbox_init_from_minmax(ob->runtime.bb, float3(-1), float3(1)); } - return bb; + return ob->runtime.bb; } bool BKE_pointcloud_attribute_required(const PointCloud * /*pointcloud*/, const char *name) diff --git a/source/blender/blenkernel/intern/preferences.cc b/source/blender/blenkernel/intern/preferences.cc index 57b1c1c7c36..34fc0a342e6 100644 --- a/source/blender/blenkernel/intern/preferences.cc +++ b/source/blender/blenkernel/intern/preferences.cc @@ -19,7 +19,7 @@ #include "BLI_path_util.h" #include "BLI_string.h" #include "BLI_string_utf8.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BKE_appdir.h" #include "BKE_preferences.h" diff --git a/source/blender/blenkernel/intern/report.cc b/source/blender/blenkernel/intern/report.cc index a47aafff9d0..2cc13192a01 100644 --- a/source/blender/blenkernel/intern/report.cc +++ b/source/blender/blenkernel/intern/report.cc @@ -10,14 +10,12 @@ #include #include #include -#include #include "MEM_guardedalloc.h" #include "BLI_blenlib.h" #include "BLI_dynstr.h" -#include "BLI_listbase.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_utildefines.h" #include "BLT_translation.h" @@ -62,20 +60,6 @@ void BKE_reports_init(ReportList *reports, int flag) reports->storelevel = RPT_INFO; reports->printlevel = RPT_ERROR; reports->flag = flag; - - reports->lock = MEM_new(__func__); -} - -void BKE_reports_free(ReportList *reports) -{ - if (!reports) { - return; - } - - BKE_reports_clear(reports); - - MEM_delete(reports->lock); - reports->lock = nullptr; } void BKE_reports_clear(ReportList *reports) @@ -86,8 +70,6 @@ void BKE_reports_clear(ReportList *reports) return; } - std::scoped_lock lock(*reports->lock); - report = static_cast(reports->list.first); while (report) { @@ -100,28 +82,6 @@ void BKE_reports_clear(ReportList *reports) BLI_listbase_clear(&reports->list); } -void BKE_reports_lock(ReportList *reports) -{ - reports->lock->lock(); -} - -void BKE_reports_unlock(ReportList *reports) -{ - reports->lock->unlock(); -} - -void BKE_reports_move_to_reports(ReportList *reports_dst, ReportList *reports_src) -{ - BLI_assert(reports_dst); - if (!reports_src) { - return; - } - - std::scoped_lock lock(*reports_src->lock, *reports_dst->lock); - - BLI_movelisttolist(&reports_dst->list, &reports_src->list); -} - void BKE_report(ReportList *reports, eReportType type, const char *_message) { Report *report; @@ -134,8 +94,6 @@ void BKE_report(ReportList *reports, eReportType type, const char *_message) } if (reports && (reports->flag & RPT_STORE) && (type >= reports->storelevel)) { - std::scoped_lock lock(*reports->lock); - char *message_alloc; report = static_cast(MEM_callocN(sizeof(Report), "Report")); report->type = type; @@ -166,8 +124,6 @@ void BKE_reportf(ReportList *reports, eReportType type, const char *_format, ... } if (reports && (reports->flag & RPT_STORE) && (type >= reports->storelevel)) { - std::scoped_lock lock(*reports->lock); - report = static_cast(MEM_callocN(sizeof(Report), "Report")); va_start(args, _format); @@ -189,9 +145,6 @@ static void reports_prepend_impl(ReportList *reports, const char *prepend) { /* Caller must ensure. */ BLI_assert(reports && reports->list.first); - - std::scoped_lock lock(*reports->lock); - const size_t prefix_len = strlen(prepend); LISTBASE_FOREACH (Report *, report, &reports->list) { char *message = BLI_string_joinN(prepend, report->message); @@ -240,8 +193,6 @@ void BKE_report_print_level_set(ReportList *reports, eReportType level) return; } - std::scoped_lock lock(*reports->lock); - reports->printlevel = level; } @@ -260,8 +211,6 @@ void BKE_report_store_level_set(ReportList *reports, eReportType level) return; } - std::scoped_lock lock(*reports->lock); - reports->storelevel = level; } @@ -274,8 +223,6 @@ char *BKE_reports_string(ReportList *reports, eReportType level) return nullptr; } - std::scoped_lock lock(*reports->lock); - ds = BLI_dynstr_new(); LISTBASE_FOREACH (Report *, report, &reports->list) { if (report->type >= level) { @@ -327,8 +274,6 @@ void BKE_reports_print(ReportList *reports, eReportType level) Report *BKE_reports_last_displayable(ReportList *reports) { - std::scoped_lock lock(*reports->lock); - LISTBASE_FOREACH_BACKWARD (Report *, report, &reports->list) { if (ELEM(report->type, RPT_ERROR, RPT_WARNING, RPT_INFO)) { return report; @@ -341,8 +286,6 @@ Report *BKE_reports_last_displayable(ReportList *reports) bool BKE_reports_contain(ReportList *reports, eReportType level) { if (reports != nullptr) { - std::scoped_lock lock(*reports->lock); - LISTBASE_FOREACH (Report *, report, &reports->list) { if (report->type >= level) { return true; @@ -358,8 +301,6 @@ bool BKE_report_write_file_fp(FILE *fp, ReportList *reports, const char *header) fputs(header, fp); } - std::scoped_lock lock(*reports->lock); - LISTBASE_FOREACH (Report *, report, &reports->list) { fprintf((FILE *)fp, "%s # %s\n", report->message, report->typestr); } diff --git a/source/blender/blenkernel/intern/rigidbody.cc b/source/blender/blenkernel/intern/rigidbody.cc index 0aa861b5790..87a0c29a8d5 100644 --- a/source/blender/blenkernel/intern/rigidbody.cc +++ b/source/blender/blenkernel/intern/rigidbody.cc @@ -492,7 +492,8 @@ static rbCollisionShape *rigidbody_validate_sim_shape_helper(RigidBodyWorld *rbw */ /* XXX: all dimensions are auto-determined now... later can add stored settings for this */ /* get object dimensions without scaling */ - if (const std::optional bb = BKE_object_boundbox_get(ob)) { + const BoundBox *bb = BKE_object_boundbox_get(ob); + if (bb) { size[0] = (bb->vec[4][0] - bb->vec[0][0]); size[1] = (bb->vec[2][1] - bb->vec[0][1]); size[2] = (bb->vec[1][2] - bb->vec[0][2]); @@ -1768,7 +1769,7 @@ static void rigidbody_update_sim_ob(Depsgraph *depsgraph, Object *ob, RigidBodyO float(*positions)[3] = reinterpret_cast( mesh->vert_positions_for_write().data()); int totvert = mesh->totvert; - const std::optional bb = BKE_object_boundbox_get(ob); + const BoundBox *bb = BKE_object_boundbox_get(ob); RB_shape_trimesh_update(static_cast(rbo->shared->physics_shape), (float *)positions, diff --git a/source/blender/blenkernel/intern/scene.cc b/source/blender/blenkernel/intern/scene.cc index 1260b51afde..6cd64c91b1a 100644 --- a/source/blender/blenkernel/intern/scene.cc +++ b/source/blender/blenkernel/intern/scene.cc @@ -43,7 +43,7 @@ #include "BLI_blenlib.h" #include "BLI_math_rotation.h" #include "BLI_string.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_task.h" #include "BLI_threads.h" #include "BLI_utildefines.h" diff --git a/source/blender/blenkernel/intern/screen.cc b/source/blender/blenkernel/intern/screen.cc index 789e09ed42a..0fb7d7281f2 100644 --- a/source/blender/blenkernel/intern/screen.cc +++ b/source/blender/blenkernel/intern/screen.cc @@ -36,7 +36,6 @@ #include "BLI_math_vector.h" #include "BLI_mempool.h" #include "BLI_rect.h" -#include "BLI_string.h" #include "BLI_utildefines.h" #include "BLT_translation.h" @@ -315,16 +314,16 @@ void BKE_spacedata_freelist(ListBase *lb) static void panel_list_copy(ListBase *newlb, const ListBase *lb) { BLI_listbase_clear(newlb); + BLI_duplicatelist(newlb, lb); - LISTBASE_FOREACH (const Panel *, old_panel, lb) { - Panel *new_panel = BKE_panel_new(old_panel->type); - Panel_Runtime *new_runtime = new_panel->runtime; - *new_panel = *old_panel; - new_panel->runtime = new_runtime; + /* copy panel pointers */ + Panel *new_panel = static_cast(newlb->first); + Panel *panel = static_cast(lb->first); + for (; new_panel; new_panel = new_panel->next, panel = panel->next) { new_panel->activedata = nullptr; new_panel->drawname = nullptr; - BLI_addtail(newlb, new_panel); - panel_list_copy(&new_panel->children, &old_panel->children); + memset(&new_panel->runtime, 0x0, sizeof(new_panel->runtime)); + panel_list_copy(&new_panel->children, &panel->children); } } @@ -487,38 +486,23 @@ void BKE_region_callback_free_gizmomap_set(void (*callback)(wmGizmoMap *)) region_free_gizmomap_callback = callback; } -Panel *BKE_panel_new(PanelType *panel_type) -{ - Panel *panel = MEM_cnew(__func__); - panel->runtime = MEM_new(__func__); - panel->type = panel_type; - if (panel_type) { - STRNCPY(panel->panelname, panel_type->idname); - } - return panel; -} - -void BKE_panel_free(Panel *panel) +static void area_region_panels_free_recursive(Panel *panel) { MEM_SAFE_FREE(panel->activedata); MEM_SAFE_FREE(panel->drawname); - MEM_delete(panel->runtime); - MEM_freeN(panel); -} -static void area_region_panels_free_recursive(Panel *panel) -{ LISTBASE_FOREACH_MUTABLE (Panel *, child_panel, &panel->children) { area_region_panels_free_recursive(child_panel); } - BKE_panel_free(panel); + + MEM_freeN(panel); } void BKE_area_region_panels_free(ListBase *panels) { LISTBASE_FOREACH_MUTABLE (Panel *, panel, panels) { /* Free custom data just for parent panels to avoid a double free. */ - MEM_SAFE_FREE(panel->runtime->custom_data_ptr); + MEM_SAFE_FREE(panel->runtime.custom_data_ptr); area_region_panels_free_recursive(panel); } BLI_listbase_clear(panels); @@ -1111,11 +1095,11 @@ static void direct_link_panel_list(BlendDataReader *reader, ListBase *lb) BLO_read_list(reader, lb); LISTBASE_FOREACH (Panel *, panel, lb) { - panel->runtime = MEM_new(__func__); panel->runtime_flag = 0; panel->activedata = nullptr; panel->type = nullptr; panel->drawname = nullptr; + panel->runtime.custom_data_ptr = nullptr; direct_link_panel_list(reader, &panel->children); } } diff --git a/source/blender/blenkernel/intern/shader_fx.cc b/source/blender/blenkernel/intern/shader_fx.cc index 9b059456514..ad26c89e920 100644 --- a/source/blender/blenkernel/intern/shader_fx.cc +++ b/source/blender/blenkernel/intern/shader_fx.cc @@ -12,7 +12,7 @@ #include "BLI_blenlib.h" #include "BLI_math_vector.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_utildefines.h" #include "BLT_translation.h" @@ -121,13 +121,14 @@ void BKE_shaderfx_free(ShaderFxData *fx) BKE_shaderfx_free_ex(fx, 0); } -void BKE_shaderfx_unique_name(ListBase *shaders, ShaderFxData *fx) +bool BKE_shaderfx_unique_name(ListBase *shaders, ShaderFxData *fx) { if (shaders && fx) { const ShaderFxTypeInfo *fxi = BKE_shaderfx_get_info(ShaderFxType(fx->type)); - BLI_uniquename( + return BLI_uniquename( shaders, fx, DATA_(fxi->name), '.', offsetof(ShaderFxData, name), sizeof(fx->name)); } + return false; } bool BKE_shaderfx_depends_ontime(ShaderFxData *fx) diff --git a/source/blender/blenkernel/intern/shrinkwrap.cc b/source/blender/blenkernel/intern/shrinkwrap.cc index 42f429cb26c..059874b10a4 100644 --- a/source/blender/blenkernel/intern/shrinkwrap.cc +++ b/source/blender/blenkernel/intern/shrinkwrap.cc @@ -27,7 +27,6 @@ #include "BLI_utildefines.h" #include "BKE_DerivedMesh.h" -#include "BKE_attribute.h" #include "BKE_cdderivedmesh.h" #include "BKE_context.h" #include "BKE_lattice.h" @@ -142,8 +141,9 @@ bool BKE_shrinkwrap_init_tree( if (force_normals || BKE_shrinkwrap_needs_normals(shrinkType, shrinkMode)) { data->face_normals = reinterpret_cast(mesh->face_normals().data()); - if (mesh->normals_domain() == blender::bke::MeshNormalDomain::Corner) { - data->clnors = reinterpret_cast(mesh->corner_normals().data()); + if ((mesh->flag & ME_AUTOSMOOTH) != 0) { + data->clnors = static_cast( + CustomData_get_layer(&mesh->loop_data, CD_NORMAL)); } } diff --git a/source/blender/blenkernel/intern/studiolight.cc b/source/blender/blenkernel/intern/studiolight.cc index 09d72ce9328..dfbd80c45c9 100644 --- a/source/blender/blenkernel/intern/studiolight.cc +++ b/source/blender/blenkernel/intern/studiolight.cc @@ -21,7 +21,7 @@ #include "BLI_math_vector.h" #include "BLI_path_util.h" #include "BLI_string.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "DNA_listBase.h" diff --git a/source/blender/blenkernel/intern/subdiv_modifier.cc b/source/blender/blenkernel/intern/subdiv_modifier.cc index 795dbe6bb80..9261088b9df 100644 --- a/source/blender/blenkernel/intern/subdiv_modifier.cc +++ b/source/blender/blenkernel/intern/subdiv_modifier.cc @@ -2,7 +2,6 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ -#include "BKE_attribute.h" #include "BKE_subdiv_modifier.hh" #include "MEM_guardedalloc.h" @@ -86,8 +85,14 @@ static ModifierData *modifier_get_last_enabled_for_mode(const Scene *scene, bool BKE_subsurf_modifier_use_custom_loop_normals(const SubsurfModifierData *smd, const Mesh *mesh) { - return smd->flags & eSubsurfModifierFlag_UseCustomNormals && - mesh->normals_domain() == blender::bke::MeshNormalDomain::Corner; + return (smd->flags & eSubsurfModifierFlag_UseCustomNormals) && (mesh->flag & ME_AUTOSMOOTH) && + CustomData_has_layer(&mesh->loop_data, CD_CUSTOMLOOPNORMAL); +} + +static bool subsurf_modifier_use_autosmooth_or_split_normals(const SubsurfModifierData *smd, + const Mesh *mesh) +{ + return (mesh->flag & ME_AUTOSMOOTH) || BKE_subsurf_modifier_use_custom_loop_normals(smd, mesh); } static bool is_subdivision_evaluation_possible_on_gpu() @@ -121,7 +126,7 @@ bool BKE_subsurf_modifier_force_disable_gpu_evaluation_for_mesh(const SubsurfMod return false; } - return BKE_subsurf_modifier_use_custom_loop_normals(smd, mesh); + return subsurf_modifier_use_autosmooth_or_split_normals(smd, mesh); } bool BKE_subsurf_modifier_can_do_gpu_subdiv(const Scene *scene, @@ -134,9 +139,9 @@ bool BKE_subsurf_modifier_can_do_gpu_subdiv(const Scene *scene, return false; } - /* Deactivate GPU subdivision if auto-smooth or custom split normals are used as those are + /* Deactivate GPU subdivision if autosmooth or custom split normals are used as those are * complicated to support on GPU, and should really be separate workflows. */ - if (BKE_subsurf_modifier_use_custom_loop_normals(smd, mesh)) { + if (subsurf_modifier_use_autosmooth_or_split_normals(smd, mesh)) { return false; } diff --git a/source/blender/blenkernel/intern/tracking.cc b/source/blender/blenkernel/intern/tracking.cc index b6c9801344a..343607dfa11 100644 --- a/source/blender/blenkernel/intern/tracking.cc +++ b/source/blender/blenkernel/intern/tracking.cc @@ -32,7 +32,7 @@ #include "BLI_math_vector.h" #include "BLI_math_vector_types.hh" #include "BLI_string.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_threads.h" #include "BLI_utildefines.h" diff --git a/source/blender/blenkernel/intern/tracking_util.cc b/source/blender/blenkernel/intern/tracking_util.cc index ca4198de290..ff0ee2fa5ad 100644 --- a/source/blender/blenkernel/intern/tracking_util.cc +++ b/source/blender/blenkernel/intern/tracking_util.cc @@ -20,7 +20,7 @@ #include "BLI_math_color.h" #include "BLI_math_vector.h" #include "BLI_string.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_threads.h" #include "BLI_utildefines.h" diff --git a/source/blender/blenkernel/intern/unit.cc b/source/blender/blenkernel/intern/unit.cc index d69e0c83d78..c2e9f2d5f56 100644 --- a/source/blender/blenkernel/intern/unit.cc +++ b/source/blender/blenkernel/intern/unit.cc @@ -127,1329 +127,221 @@ struct bUnitCollection { int length; }; -#define UNIT_COLLECTION_LENGTH(def) (ARRAY_SIZE(def) - 1) - -/* Clang-format wraps this define badly. */ +/* Keep table Alignment. */ /* clang-format off */ -#define NULL_UNIT { \ - /*name*/ nullptr, \ - /*name_plural*/ nullptr, \ - /*name_short*/ nullptr, \ - /*name_alt*/ nullptr, \ - /*name_display*/ nullptr, \ - /*identifier*/ nullptr, \ - /*scalar*/ 0.0, \ - /*bias*/ 0.0, \ - /*flag*/ 0, \ - } -/* clang-format on */ + + +#define UNIT_COLLECTION_LENGTH(def) (ARRAY_SIZE(def) - 1) +#define NULL_UNIT {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, 0.0, 0.0} + /* Dummy */ -static bUnitDef buDummyDef[] = { - { - /*name*/ "", - /*name_plural*/ nullptr, - /*name_short*/ "", - /*name_alt*/ nullptr, - /*name_display*/ nullptr, - /*identifier*/ nullptr, - /*scalar*/ 1.0, - /*bias*/ 0.0, - /*flag*/ 0, - }, - NULL_UNIT, -}; +static bUnitDef buDummyDef[] = { {"", nullptr, "", nullptr, nullptr, nullptr, 1.0, 0.0}, NULL_UNIT}; +static bUnitCollection buDummyCollection = {buDummyDef, 0, 0, UNIT_COLLECTION_LENGTH(buDummyDef)}; -static bUnitCollection buDummyCollection = { - /*units*/ buDummyDef, - /*base_unit*/ 0, - /*flag*/ 0, - /*length*/ UNIT_COLLECTION_LENGTH(buDummyDef), -}; /* Lengths. */ -static bUnitDef buMetricLenDef[] = { - { - /*name*/ "kilometer", - /*name_plural*/ "kilometers", - /*name_short*/ "km", - /*name_alt*/ nullptr, - /*name_display*/ "Kilometers", - /*identifier*/ "KILOMETERS", - /*scalar*/ UN_SC_KM, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, - { - /*name*/ "hectometer", - /*name_plural*/ "hectometers", - /*name_short*/ "hm", - /*name_alt*/ nullptr, - /*name_display*/ "100 Meters", - /*identifier*/ "HECTOMETERS", - /*scalar*/ UN_SC_HM, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_SUPPRESS, - }, - { - /*name*/ "dekameter", - /*name_plural*/ "dekameters", - /*name_short*/ "dam", - /*name_alt*/ nullptr, - /*name_display*/ "10 Meters", - /*identifier*/ "DEKAMETERS", - /*scalar*/ UN_SC_DAM, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_SUPPRESS, - }, - /* Base unit. */ - { - /*name*/ "meter", - /*name_plural*/ "meters", - /*name_short*/ "m", - /*name_alt*/ nullptr, - /*name_display*/ "Meters", - /*identifier*/ "METERS", - /*scalar*/ UN_SC_M, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, - { - /*name*/ "decimeter", - /*name_plural*/ "decimeters", - /*name_short*/ "dm", - /*name_alt*/ nullptr, - /*name_display*/ "10 Centimeters", - /*identifier*/ "DECIMETERS", - /*scalar*/ UN_SC_DM, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_SUPPRESS, - }, - { - /*name*/ "centimeter", - /*name_plural*/ "centimeters", - /*name_short*/ "cm", - /*name_alt*/ nullptr, - /*name_display*/ "Centimeters", - /*identifier*/ "CENTIMETERS", - /*scalar*/ UN_SC_CM, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, - { - /*name*/ "millimeter", - /*name_plural*/ "millimeters", - /*name_short*/ "mm", - /*name_alt*/ nullptr, - /*name_display*/ "Millimeters", - /*identifier*/ "MILLIMETERS", - /*scalar*/ UN_SC_MM, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE | B_UNIT_DEF_TENTH, - }, - { - /*name*/ "micrometer", - /*name_plural*/ "micrometers", - /*name_short*/ "µm", - /*name_alt*/ "um", - /*name_display*/ "Micrometers", - /*identifier*/ "MICROMETERS", - /*scalar*/ UN_SC_UM, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, +static bUnitDef buMetricLenDef[] = { +{"kilometer", "kilometers", "km", nullptr, "Kilometers", "KILOMETERS", UN_SC_KM, 0.0, B_UNIT_DEF_NONE}, {"hectometer", "hectometers", "hm", nullptr, "100 Meters", "HECTOMETERS", UN_SC_HM, 0.0, B_UNIT_DEF_SUPPRESS}, {"dekameter", "dekameters", "dam", nullptr, "10 Meters", "DEKAMETERS", UN_SC_DAM, 0.0, B_UNIT_DEF_SUPPRESS}, {"meter", "meters", "m", nullptr, "Meters", "METERS", UN_SC_M, 0.0, B_UNIT_DEF_NONE}, /* Base unit. */ +{"decimeter", "decimeters", "dm", nullptr, "10 Centimeters", "DECIMETERS", UN_SC_DM, 0.0, B_UNIT_DEF_SUPPRESS}, {"centimeter", "centimeters", "cm", nullptr, "Centimeters", "CENTIMETERS", UN_SC_CM, 0.0, B_UNIT_DEF_NONE}, {"millimeter", "millimeters", "mm", nullptr, "Millimeters", "MILLIMETERS", UN_SC_MM, 0.0, B_UNIT_DEF_NONE | B_UNIT_DEF_TENTH}, {"micrometer", "micrometers", "µm", "um", "Micrometers", "MICROMETERS", UN_SC_UM, 0.0, B_UNIT_DEF_NONE}, /* These get displayed because of float precision problems in the transform header, * could work around, but for now probably people won't use these. */ #if 0 - { - /*name*/ "nanometer", - /*name_plural*/ "nanometers", - /*name_short*/ "nm", - /*name_alt*/ nullptr, - /*name_display*/ "Nanometers", - /*identifier*/ "NANOMETERS", - /*scalar*/ 0.000000001, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, - { - /*name*/ "picometer", - /*name_plural*/ "picometers", - /*name_short*/ "pm", - /*name_alt*/ nullptr, - /*name_display*/ "Picometers", - /*identifier*/ "PICOMETERS", - /*scalar*/ 0.000000000001, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, +{"nanometer", "Nanometers", "nm", nullptr, 0.000000001, 0.0, B_UNIT_DEF_NONE}, {"picometer", "Picometers", "pm", nullptr, 0.000000000001, 0.0, B_UNIT_DEF_NONE}, #endif - NULL_UNIT, -}; -static const bUnitCollection buMetricLenCollection = { - /*units*/ buMetricLenDef, - /*base_unit*/ 3, - /*flag*/ 0, - /*length*/ UNIT_COLLECTION_LENGTH(buMetricLenDef), -}; +NULL_UNIT, }; +static const bUnitCollection buMetricLenCollection = {buMetricLenDef, 3, 0, UNIT_COLLECTION_LENGTH(buMetricLenDef)}; + + +static bUnitDef buImperialLenDef[] = { +{"mile", "miles", "mi", nullptr, "Miles", "MILES", UN_SC_MI, 0.0, B_UNIT_DEF_NONE}, {"furlong", "furlongs", "fur", nullptr, "Furlongs", "FURLONGS", UN_SC_FUR, 0.0, B_UNIT_DEF_SUPPRESS}, {"chain", "chains", "ch", nullptr, "Chains", "CHAINS", UN_SC_CH, 0.0, B_UNIT_DEF_SUPPRESS}, {"yard", "yards", "yd", nullptr, "Yards", "YARDS", UN_SC_YD, 0.0, B_UNIT_DEF_SUPPRESS}, {"foot", "feet", "'", "ft", "Feet", "FEET", UN_SC_FT, 0.0, B_UNIT_DEF_NONE | B_UNIT_DEF_NO_SPACE}, /* Base unit. */ +{"inch", "inches", "\"", "in", "Inches", "INCHES", UN_SC_IN, 0.0, B_UNIT_DEF_NONE | B_UNIT_DEF_NO_SPACE}, {"thou", "thou", "thou", "mil", "Thou", "THOU", UN_SC_MIL, 0.0, B_UNIT_DEF_NONE}, /* Plural for "thou" has no 's'. */ +NULL_UNIT, }; +static bUnitCollection buImperialLenCollection = {buImperialLenDef, 4, 0, UNIT_COLLECTION_LENGTH(buImperialLenDef)}; -static bUnitDef buImperialLenDef[] = { - { - /*name*/ "mile", - /*name_plural*/ "miles", - /*name_short*/ "mi", - /*name_alt*/ nullptr, - /*name_display*/ "Miles", - /*identifier*/ "MILES", - /*scalar*/ UN_SC_MI, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, - { - /*name*/ "furlong", - /*name_plural*/ "furlongs", - /*name_short*/ "fur", - /*name_alt*/ nullptr, - /*name_display*/ "Furlongs", - /*identifier*/ "FURLONGS", - /*scalar*/ UN_SC_FUR, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_SUPPRESS, - }, - { - /*name*/ "chain", - /*name_plural*/ "chains", - /*name_short*/ "ch", - /*name_alt*/ nullptr, - /*name_display*/ "Chains", - /*identifier*/ "CHAINS", - /*scalar*/ UN_SC_CH, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_SUPPRESS, - }, - { - /*name*/ "yard", - /*name_plural*/ "yards", - /*name_short*/ "yd", - /*name_alt*/ nullptr, - /*name_display*/ "Yards", - /*identifier*/ "YARDS", - /*scalar*/ UN_SC_YD, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_SUPPRESS, - }, - /* Base unit. */ - { - /*name*/ "foot", - /*name_plural*/ "feet", - /*name_short*/ "'", - /*name_alt*/ "ft", - /*name_display*/ "Feet", - /*identifier*/ "FEET", - /*scalar*/ UN_SC_FT, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE | B_UNIT_DEF_NO_SPACE, - }, - { - /*name*/ "inch", - /*name_plural*/ "inches", - /*name_short*/ "\"", - /*name_alt*/ "in", - /*name_display*/ "Inches", - /*identifier*/ "INCHES", - /*scalar*/ UN_SC_IN, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE | B_UNIT_DEF_NO_SPACE, - }, - /* NOTE: Plural for "thou" has no 's'. */ - { - /*name*/ "thou", - /*name_plural*/ "thou", - /*name_short*/ "thou", - /*name_alt*/ "mil", - /*name_display*/ "Thou", - /*identifier*/ "THOU", - /*scalar*/ UN_SC_MIL, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, - NULL_UNIT, -}; -static bUnitCollection buImperialLenCollection = { - /*units*/ buImperialLenDef, - /*base_unit*/ 4, - /*flag*/ 0, - /*length*/ UNIT_COLLECTION_LENGTH(buImperialLenDef), -}; /* Areas. */ -static bUnitDef buMetricAreaDef[] = { - { - /*name*/ "square kilometer", - /*name_plural*/ "square kilometers", - /*name_short*/ "km" BLI_STR_UTF8_SUPERSCRIPT_2, - /*name_alt*/ "km2", - /*name_display*/ "Square Kilometers", - /*identifier*/ nullptr, - /*scalar*/ UN_SC_KM *UN_SC_KM, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, - { - /*name*/ "square hectometer", - /*name_plural*/ "square hectometers", - /*name_short*/ "hm" BLI_STR_UTF8_SUPERSCRIPT_2, - /*name_alt*/ "hm2", - /*name_display*/ "Square Hectometers", - /*identifier*/ nullptr, - /*scalar*/ UN_SC_HM *UN_SC_HM, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_SUPPRESS, - }, /* Hectare. */ - { - /*name*/ "square dekameter", - /*name_plural*/ "square dekameters", - /*name_short*/ "dam" BLI_STR_UTF8_SUPERSCRIPT_2, - /*name_alt*/ "dam2", - /*name_display*/ "Square Dekameters", - /*identifier*/ nullptr, - /*scalar*/ UN_SC_DAM *UN_SC_DAM, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_SUPPRESS, - }, - /* Base unit. */ - { - /*name*/ "square meter", - /*name_plural*/ "square meters", - /*name_short*/ "m" BLI_STR_UTF8_SUPERSCRIPT_2, - /*name_alt*/ "m2", - /*name_display*/ "Square Meters", - /*identifier*/ nullptr, - /*scalar*/ UN_SC_M *UN_SC_M, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, - { - /*name*/ "square decimeter", - /*name_plural*/ "square decimetees", - /*name_short*/ "dm" BLI_STR_UTF8_SUPERSCRIPT_2, - /*name_alt*/ "dm2", - /*name_display*/ "Square Decimeters", - /*identifier*/ nullptr, - /*scalar*/ UN_SC_DM *UN_SC_DM, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_SUPPRESS, - }, - { - /*name*/ "square centimeter", - /*name_plural*/ "square centimeters", - /*name_short*/ "cm" BLI_STR_UTF8_SUPERSCRIPT_2, - /*name_alt*/ "cm2", - /*name_display*/ "Square Centimeters", - /*identifier*/ nullptr, - /*scalar*/ UN_SC_CM *UN_SC_CM, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, - { - /*name*/ "square millimeter", - /*name_plural*/ "square millimeters", - /*name_short*/ "mm" BLI_STR_UTF8_SUPERSCRIPT_2, - /*name_alt*/ "mm2", - /*name_display*/ "Square Millimeters", - /*identifier*/ nullptr, - /*scalar*/ UN_SC_MM *UN_SC_MM, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE | B_UNIT_DEF_TENTH, - }, - { - /*name*/ "square micrometer", - /*name_plural*/ "square micrometers", - /*name_short*/ "µm" BLI_STR_UTF8_SUPERSCRIPT_2, - /*name_alt*/ "um2", - /*name_display*/ "Square Micrometers", - /*identifier*/ nullptr, - /*scalar*/ UN_SC_UM *UN_SC_UM, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, - NULL_UNIT, -}; -static bUnitCollection buMetricAreaCollection = { - /*units*/ buMetricAreaDef, - /*base_unit*/ 3, - /*flag*/ 0, - /*length*/ UNIT_COLLECTION_LENGTH(buMetricAreaDef), -}; +static bUnitDef buMetricAreaDef[] = { +{"square kilometer", "square kilometers", "km²", "km2", "Square Kilometers", nullptr, UN_SC_KM * UN_SC_KM, 0.0, B_UNIT_DEF_NONE}, {"square hectometer", "square hectometers", "hm²", "hm2", "Square Hectometers", nullptr, UN_SC_HM * UN_SC_HM, 0.0, B_UNIT_DEF_SUPPRESS}, /* Hectare. */ +{"square dekameter", "square dekameters", "dam²", "dam2", "Square Dekameters", nullptr, UN_SC_DAM * UN_SC_DAM, 0.0, B_UNIT_DEF_SUPPRESS}, /* are */ +{"square meter", "square meters", "m²", "m2", "Square Meters", nullptr, UN_SC_M * UN_SC_M, 0.0, B_UNIT_DEF_NONE}, /* Base unit. */ +{"square decimeter", "square decimetees", "dm²", "dm2", "Square Decimeters", nullptr, UN_SC_DM * UN_SC_DM, 0.0, B_UNIT_DEF_SUPPRESS}, {"square centimeter", "square centimeters", "cm²", "cm2", "Square Centimeters", nullptr, UN_SC_CM * UN_SC_CM, 0.0, B_UNIT_DEF_NONE}, {"square millimeter", "square millimeters", "mm²", "mm2", "Square Millimeters", nullptr, UN_SC_MM * UN_SC_MM, 0.0, B_UNIT_DEF_NONE | B_UNIT_DEF_TENTH}, {"square micrometer", "square micrometers", "µm²", "um2", "Square Micrometers", nullptr, UN_SC_UM * UN_SC_UM, 0.0, B_UNIT_DEF_NONE}, NULL_UNIT, }; +static bUnitCollection buMetricAreaCollection = {buMetricAreaDef, 3, 0, UNIT_COLLECTION_LENGTH(buMetricAreaDef)}; + + +static bUnitDef buImperialAreaDef[] = { +{"square mile", "square miles", "sq mi", "sq m", "Square Miles", nullptr, UN_SC_MI * UN_SC_MI, 0.0, B_UNIT_DEF_NONE}, {"square furlong", "square furlongs", "sq fur", nullptr, "Square Furlongs", nullptr, UN_SC_FUR * UN_SC_FUR, 0.0, B_UNIT_DEF_SUPPRESS}, {"square chain", "square chains", "sq ch", nullptr, "Square Chains", nullptr, UN_SC_CH * UN_SC_CH, 0.0, B_UNIT_DEF_SUPPRESS}, {"square yard", "square yards", "sq yd", nullptr, "Square Yards", nullptr, UN_SC_YD * UN_SC_YD, 0.0, B_UNIT_DEF_NONE}, {"square foot", "square feet", "sq ft", nullptr, "Square Feet", nullptr, UN_SC_FT * UN_SC_FT, 0.0, B_UNIT_DEF_NONE}, /* Base unit. */ +{"square inch", "square inches", "sq in", nullptr, "Square Inches", nullptr, UN_SC_IN * UN_SC_IN, 0.0, B_UNIT_DEF_NONE}, {"square thou", "square thou", "sq mil", nullptr, "Square Thou", nullptr, UN_SC_MIL * UN_SC_MIL, 0.0, B_UNIT_DEF_NONE}, NULL_UNIT, }; +static bUnitCollection buImperialAreaCollection = {buImperialAreaDef, 4, 0, UNIT_COLLECTION_LENGTH(buImperialAreaDef)}; -static bUnitDef buImperialAreaDef[] = { - { - /*name*/ "square mile", - /*name_plural*/ "square miles", - /*name_short*/ "sq mi", - /*name_alt*/ "sq m", - /*name_display*/ "Square Miles", - /*identifier*/ nullptr, - /*scalar*/ UN_SC_MI *UN_SC_MI, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, - { - /*name*/ "square furlong", - /*name_plural*/ "square furlongs", - /*name_short*/ "sq fur", - /*name_alt*/ nullptr, - /*name_display*/ "Square Furlongs", - /*identifier*/ nullptr, - /*scalar*/ UN_SC_FUR *UN_SC_FUR, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_SUPPRESS, - }, - { - /*name*/ "square chain", - /*name_plural*/ "square chains", - /*name_short*/ "sq ch", - /*name_alt*/ nullptr, - /*name_display*/ "Square Chains", - /*identifier*/ nullptr, - /*scalar*/ UN_SC_CH *UN_SC_CH, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_SUPPRESS, - }, - { - /*name*/ "square yard", - /*name_plural*/ "square yards", - /*name_short*/ "sq yd", - /*name_alt*/ nullptr, - /*name_display*/ "Square Yards", - /*identifier*/ nullptr, - /*scalar*/ UN_SC_YD *UN_SC_YD, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, - /* Base unit. */ - { - /*name*/ "square foot", - /*name_plural*/ "square feet", - /*name_short*/ "sq ft", - /*name_alt*/ nullptr, - /*name_display*/ "Square Feet", - /*identifier*/ nullptr, - /*scalar*/ UN_SC_FT *UN_SC_FT, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, - { - /*name*/ "square inch", - /*name_plural*/ "square inches", - /*name_short*/ "sq in", - /*name_alt*/ nullptr, - /*name_display*/ "Square Inches", - /*identifier*/ nullptr, - /*scalar*/ UN_SC_IN *UN_SC_IN, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, - { - /*name*/ "square thou", - /*name_plural*/ "square thou", - /*name_short*/ "sq mil", - /*name_alt*/ nullptr, - /*name_display*/ "Square Thou", - /*identifier*/ nullptr, - /*scalar*/ UN_SC_MIL *UN_SC_MIL, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, - NULL_UNIT, -}; -static bUnitCollection buImperialAreaCollection = { - /*units*/ buImperialAreaDef, - /*base_unit*/ 4, - /*flag*/ 0, - /*length*/ UNIT_COLLECTION_LENGTH(buImperialAreaDef), -}; /* Volumes. */ -static bUnitDef buMetricVolDef[] = { - { - /*name*/ "cubic kilometer", - /*name_plural*/ "cubic kilometers", - /*name_short*/ "km" BLI_STR_UTF8_SUPERSCRIPT_3, - /*name_alt*/ "km3", - /*name_display*/ "Cubic Kilometers", - /*identifier*/ nullptr, - /*scalar*/ UN_SC_KM *UN_SC_KM *UN_SC_KM, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, - { - /*name*/ "cubic hectometer", - /*name_plural*/ "cubic hectometers", - /*name_short*/ "hm" BLI_STR_UTF8_SUPERSCRIPT_3, - /*name_alt*/ "hm3", - /*name_display*/ "Cubic Hectometers", - /*identifier*/ nullptr, - /*scalar*/ UN_SC_HM *UN_SC_HM *UN_SC_HM, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_SUPPRESS, - }, - { - /*name*/ "cubic dekameter", - /*name_plural*/ "cubic dekameters", - /*name_short*/ "dam" BLI_STR_UTF8_SUPERSCRIPT_3, - /*name_alt*/ "dam3", - /*name_display*/ "Cubic Dekameters", - /*identifier*/ nullptr, - /*scalar*/ UN_SC_DAM *UN_SC_DAM *UN_SC_DAM, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_SUPPRESS, - }, - /* Base unit. */ - { - /*name*/ "cubic meter", - /*name_plural*/ "cubic meters", - /*name_short*/ "m" BLI_STR_UTF8_SUPERSCRIPT_3, - /*name_alt*/ "m3", - /*name_display*/ "Cubic Meters", - /*identifier*/ nullptr, - /*scalar*/ UN_SC_M *UN_SC_M *UN_SC_M, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, - { - /*name*/ "cubic decimeter", - /*name_plural*/ "cubic decimeters", - /*name_short*/ "dm" BLI_STR_UTF8_SUPERSCRIPT_3, - /*name_alt*/ "dm3", - /*name_display*/ "Cubic Decimeters", - /*identifier*/ nullptr, - /*scalar*/ UN_SC_DM *UN_SC_DM *UN_SC_DM, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_SUPPRESS, - }, - { - /*name*/ "cubic centimeter", - /*name_plural*/ "cubic centimeters", - /*name_short*/ "cm" BLI_STR_UTF8_SUPERSCRIPT_3, - /*name_alt*/ "cm3", - /*name_display*/ "Cubic Centimeters", - /*identifier*/ nullptr, - /*scalar*/ UN_SC_CM *UN_SC_CM *UN_SC_CM, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, - { - /*name*/ "cubic millimeter", - /*name_plural*/ "cubic millimeters", - /*name_short*/ "mm" BLI_STR_UTF8_SUPERSCRIPT_3, - /*name_alt*/ "mm3", - /*name_display*/ "Cubic Millimeters", - /*identifier*/ nullptr, - /*scalar*/ UN_SC_MM *UN_SC_MM *UN_SC_MM, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE | B_UNIT_DEF_TENTH, - }, - { - /*name*/ "cubic micrometer", - /*name_plural*/ "cubic micrometers", - /*name_short*/ "µm" BLI_STR_UTF8_SUPERSCRIPT_3, - /*name_alt*/ "um3", - /*name_display*/ "Cubic Micrometers", - /*identifier*/ nullptr, - /*scalar*/ UN_SC_UM *UN_SC_UM *UN_SC_UM, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, - NULL_UNIT, -}; -static bUnitCollection buMetricVolCollection = { - /*units*/ buMetricVolDef, - /*base_unit*/ 3, - /*flag*/ 0, - /*length*/ UNIT_COLLECTION_LENGTH(buMetricVolDef), -}; +static bUnitDef buMetricVolDef[] = { +{"cubic kilometer", "cubic kilometers", "km³", "km3", "Cubic Kilometers", nullptr, UN_SC_KM * UN_SC_KM * UN_SC_KM, 0.0, B_UNIT_DEF_NONE}, {"cubic hectometer", "cubic hectometers", "hm³", "hm3", "Cubic Hectometers", nullptr, UN_SC_HM * UN_SC_HM * UN_SC_HM, 0.0, B_UNIT_DEF_SUPPRESS}, {"cubic dekameter", "cubic dekameters", "dam³", "dam3", "Cubic Dekameters", nullptr, UN_SC_DAM * UN_SC_DAM * UN_SC_DAM, 0.0, B_UNIT_DEF_SUPPRESS}, {"cubic meter", "cubic meters", "m³", "m3", "Cubic Meters", nullptr, UN_SC_M * UN_SC_M * UN_SC_M, 0.0, B_UNIT_DEF_NONE}, /* Base unit. */ +{"cubic decimeter", "cubic decimeters", "dm³", "dm3", "Cubic Decimeters", nullptr, UN_SC_DM * UN_SC_DM * UN_SC_DM, 0.0, B_UNIT_DEF_SUPPRESS}, {"cubic centimeter", "cubic centimeters", "cm³", "cm3", "Cubic Centimeters", nullptr, UN_SC_CM * UN_SC_CM * UN_SC_CM, 0.0, B_UNIT_DEF_NONE}, {"cubic millimeter", "cubic millimeters", "mm³", "mm3", "Cubic Millimeters", nullptr, UN_SC_MM * UN_SC_MM * UN_SC_MM, 0.0, B_UNIT_DEF_NONE | B_UNIT_DEF_TENTH}, {"cubic micrometer", "cubic micrometers", "µm³", "um3", "Cubic Micrometers", nullptr, UN_SC_UM * UN_SC_UM * UN_SC_UM, 0.0, B_UNIT_DEF_NONE}, NULL_UNIT, }; +static bUnitCollection buMetricVolCollection = {buMetricVolDef, 3, 0, UNIT_COLLECTION_LENGTH(buMetricVolDef)}; + + +static bUnitDef buImperialVolDef[] = { +{"cubic mile", "cubic miles", "cu mi", "cu m", "Cubic Miles", nullptr, UN_SC_MI * UN_SC_MI * UN_SC_MI, 0.0, B_UNIT_DEF_NONE}, {"cubic furlong", "cubic furlongs", "cu fur", nullptr, "Cubic Furlongs", nullptr, UN_SC_FUR * UN_SC_FUR * UN_SC_FUR, 0.0, B_UNIT_DEF_SUPPRESS}, {"cubic chain", "cubic chains", "cu ch", nullptr, "Cubic Chains", nullptr, UN_SC_CH * UN_SC_CH * UN_SC_CH, 0.0, B_UNIT_DEF_SUPPRESS}, {"cubic yard", "cubic yards", "cu yd", nullptr, "Cubic Yards", nullptr, UN_SC_YD * UN_SC_YD * UN_SC_YD, 0.0, B_UNIT_DEF_NONE}, {"cubic foot", "cubic feet", "cu ft", nullptr, "Cubic Feet", nullptr, UN_SC_FT * UN_SC_FT * UN_SC_FT, 0.0, B_UNIT_DEF_NONE}, /* Base unit. */ +{"cubic inch", "cubic inches", "cu in", nullptr, "Cubic Inches", nullptr, UN_SC_IN * UN_SC_IN * UN_SC_IN, 0.0, B_UNIT_DEF_NONE}, {"cubic thou", "cubic thou", "cu mil", nullptr, "Cubic Thou", nullptr, UN_SC_MIL * UN_SC_MIL * UN_SC_MIL, 0.0, B_UNIT_DEF_NONE}, NULL_UNIT, }; +static bUnitCollection buImperialVolCollection = {buImperialVolDef, 4, 0, UNIT_COLLECTION_LENGTH(buImperialVolDef)}; -static bUnitDef buImperialVolDef[] = { - { - /*name*/ "cubic mile", - /*name_plural*/ "cubic miles", - /*name_short*/ "cu mi", - /*name_alt*/ "cu m", - /*name_display*/ "Cubic Miles", - /*identifier*/ nullptr, - /*scalar*/ UN_SC_MI *UN_SC_MI *UN_SC_MI, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, - { - /*name*/ "cubic furlong", - /*name_plural*/ "cubic furlongs", - /*name_short*/ "cu fur", - /*name_alt*/ nullptr, - /*name_display*/ "Cubic Furlongs", - /*identifier*/ nullptr, - /*scalar*/ UN_SC_FUR *UN_SC_FUR *UN_SC_FUR, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_SUPPRESS, - }, - { - /*name*/ "cubic chain", - /*name_plural*/ "cubic chains", - /*name_short*/ "cu ch", - /*name_alt*/ nullptr, - /*name_display*/ "Cubic Chains", - /*identifier*/ nullptr, - /*scalar*/ UN_SC_CH *UN_SC_CH *UN_SC_CH, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_SUPPRESS, - }, - { - /*name*/ "cubic yard", - /*name_plural*/ "cubic yards", - /*name_short*/ "cu yd", - /*name_alt*/ nullptr, - /*name_display*/ "Cubic Yards", - /*identifier*/ nullptr, - /*scalar*/ UN_SC_YD *UN_SC_YD *UN_SC_YD, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, - /* Base unit. */ - { - /*name*/ "cubic foot", - /*name_plural*/ "cubic feet", - /*name_short*/ "cu ft", - /*name_alt*/ nullptr, - /*name_display*/ "Cubic Feet", - /*identifier*/ nullptr, - /*scalar*/ UN_SC_FT *UN_SC_FT *UN_SC_FT, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, - { - /*name*/ "cubic inch", - /*name_plural*/ "cubic inches", - /*name_short*/ "cu in", - /*name_alt*/ nullptr, - /*name_display*/ "Cubic Inches", - /*identifier*/ nullptr, - /*scalar*/ UN_SC_IN *UN_SC_IN *UN_SC_IN, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, - { - /*name*/ "cubic thou", - /*name_plural*/ "cubic thou", - /*name_short*/ "cu mil", - /*name_alt*/ nullptr, - /*name_display*/ "Cubic Thou", - /*identifier*/ nullptr, - /*scalar*/ UN_SC_MIL *UN_SC_MIL *UN_SC_MIL, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, - NULL_UNIT, -}; -static bUnitCollection buImperialVolCollection = { - /*units*/ buImperialVolDef, - /*base_unit*/ 4, - /*flag*/ 0, - /*length*/ UNIT_COLLECTION_LENGTH(buImperialVolDef), -}; /* Mass. */ -static bUnitDef buMetricMassDef[] = { - { - /*name*/ "ton", - /*name_plural*/ "tonnes", - /*name_short*/ "ton", - /*name_alt*/ "t", - /*name_display*/ "Tonnes", - /*identifier*/ "TONNES", - /*scalar*/ UN_SC_MTON, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, - { - /*name*/ "quintal", - /*name_plural*/ "quintals", - /*name_short*/ "ql", - /*name_alt*/ "q", - /*name_display*/ "100 Kilograms", - /*identifier*/ "QUINTALS", - /*scalar*/ UN_SC_QL, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_SUPPRESS, - }, - /* Base unit. */ - { - /*name*/ "kilogram", - /*name_plural*/ "kilograms", - /*name_short*/ "kg", - /*name_alt*/ nullptr, - /*name_display*/ "Kilograms", - /*identifier*/ "KILOGRAMS", - /*scalar*/ UN_SC_KG, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, - { - /*name*/ "hectogram", - /*name_plural*/ "hectograms", - /*name_short*/ "hg", - /*name_alt*/ nullptr, - /*name_display*/ "Hectograms", - /*identifier*/ "HECTOGRAMS", - /*scalar*/ UN_SC_HG, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_SUPPRESS, - }, - { - /*name*/ "dekagram", - /*name_plural*/ "dekagrams", - /*name_short*/ "dag", - /*name_alt*/ nullptr, - /*name_display*/ "10 Grams", - /*identifier*/ "DEKAGRAMS", - /*scalar*/ UN_SC_DAG, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_SUPPRESS, - }, - { - /*name*/ "gram", - /*name_plural*/ "grams", - /*name_short*/ "g", - /*name_alt*/ nullptr, - /*name_display*/ "Grams", - /*identifier*/ "GRAMS", - /*scalar*/ UN_SC_G, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, - { - /*name*/ "milligram", - /*name_plural*/ "milligrams", - /*name_short*/ "mg", - /*name_alt*/ nullptr, - /*name_display*/ "Milligrams", - /*identifier*/ "MILLIGRAMS", - /*scalar*/ UN_SC_MG, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, - NULL_UNIT, -}; -static bUnitCollection buMetricMassCollection = { - /*units*/ buMetricMassDef, - /*base_unit*/ 2, - /*flag*/ 0, - /*length*/ UNIT_COLLECTION_LENGTH(buMetricMassDef), -}; +static bUnitDef buMetricMassDef[] = { +{"ton", "tonnes", "ton", "t", "Tonnes", "TONNES", UN_SC_MTON, 0.0, B_UNIT_DEF_NONE}, {"quintal", "quintals", "ql", "q", "100 Kilograms", "QUINTALS", UN_SC_QL, 0.0, B_UNIT_DEF_SUPPRESS}, {"kilogram", "kilograms", "kg", nullptr, "Kilograms", "KILOGRAMS", UN_SC_KG, 0.0, B_UNIT_DEF_NONE}, /* Base unit. */ +{"hectogram", "hectograms", "hg", nullptr, "Hectograms", "HECTOGRAMS", UN_SC_HG, 0.0, B_UNIT_DEF_SUPPRESS}, {"dekagram", "dekagrams", "dag", nullptr, "10 Grams", "DEKAGRAMS", UN_SC_DAG, 0.0, B_UNIT_DEF_SUPPRESS}, {"gram", "grams", "g", nullptr, "Grams", "GRAMS", UN_SC_G, 0.0, B_UNIT_DEF_NONE}, {"milligram", "milligrams", "mg", nullptr, "Milligrams", "MILLIGRAMS", UN_SC_MG, 0.0, B_UNIT_DEF_NONE}, NULL_UNIT, }; +static bUnitCollection buMetricMassCollection = {buMetricMassDef, 2, 0, UNIT_COLLECTION_LENGTH(buMetricMassDef)}; -static bUnitDef buImperialMassDef[] = { - { - /*name*/ "ton", - /*name_plural*/ "tonnes", - /*name_short*/ "ton", - /*name_alt*/ "t", - /*name_display*/ "Tonnes", - /*identifier*/ "TONNES", - /*scalar*/ UN_SC_ITON, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, - { - /*name*/ "centum weight", - /*name_plural*/ "centum weights", - /*name_short*/ "cwt", - /*name_alt*/ nullptr, - /*name_display*/ "Centum weights", - /*identifier*/ "CENTUM_WEIGHTS", - /*scalar*/ UN_SC_CWT, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, - { - /*name*/ "stone", - /*name_plural*/ "stones", - /*name_short*/ "st", - /*name_alt*/ nullptr, - /*name_display*/ "Stones", - /*identifier*/ "STONES", - /*scalar*/ UN_SC_ST, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, - /* Base unit. */ - { - /*name*/ "pound", - /*name_plural*/ "pounds", - /*name_short*/ "lb", - /*name_alt*/ nullptr, - /*name_display*/ "Pounds", - /*identifier*/ "POUNDS", - /*scalar*/ UN_SC_LB, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, - { - /*name*/ "ounce", - /*name_plural*/ "ounces", - /*name_short*/ "oz", - /*name_alt*/ nullptr, - /*name_display*/ "Ounces", - /*identifier*/ "OUNCES", - /*scalar*/ UN_SC_OZ, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, - NULL_UNIT, -}; -static bUnitCollection buImperialMassCollection = { - /*units*/ buImperialMassDef, - /*base_unit*/ 3, - /*flag*/ 0, - /*length*/ UNIT_COLLECTION_LENGTH(buImperialMassDef), -}; + +static bUnitDef buImperialMassDef[] = { +{"ton", "tonnes", "ton", "t", "Tonnes", "TONNES", UN_SC_ITON, 0.0, B_UNIT_DEF_NONE}, {"centum weight", "centum weights", "cwt", nullptr, "Centum weights", "CENTUM_WEIGHTS", UN_SC_CWT, 0.0, B_UNIT_DEF_NONE}, {"stone", "stones", "st", nullptr, "Stones", "STONES", UN_SC_ST, 0.0, B_UNIT_DEF_NONE}, {"pound", "pounds", "lb", nullptr, "Pounds", "POUNDS", UN_SC_LB, 0.0, B_UNIT_DEF_NONE}, /* Base unit. */ +{"ounce", "ounces", "oz", nullptr, "Ounces", "OUNCES", UN_SC_OZ, 0.0, B_UNIT_DEF_NONE}, NULL_UNIT, }; +static bUnitCollection buImperialMassCollection = {buImperialMassDef, 3, 0, UNIT_COLLECTION_LENGTH(buImperialMassDef)}; /* Even if user scales the system to a point where km^3 is used, velocity and * acceleration aren't scaled: that's why we have so few units for them. */ /* Velocity. */ -static bUnitDef buMetricVelDef[] = { - /* Base unit. */ - { - /*name*/ "meter per second", - /*name_plural*/ "meters per second", - /*name_short*/ "m/s", - /*name_alt*/ nullptr, - /*name_display*/ "Meters per second", - /*identifier*/ nullptr, - /*scalar*/ UN_SC_M, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, - { - /*name*/ "kilometer per hour", - /*name_plural*/ "kilometers per hour", - /*name_short*/ "km/h", - /*name_alt*/ nullptr, - /*name_display*/ "Kilometers per hour", - /*identifier*/ nullptr, - /*scalar*/ UN_SC_KM / 3600.0f, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_SUPPRESS, - }, - NULL_UNIT, -}; -static bUnitCollection buMetricVelCollection = { - /*units*/ buMetricVelDef, - /*base_unit*/ 0, - /*flag*/ 0, - /*length*/ UNIT_COLLECTION_LENGTH(buMetricVelDef), -}; +static bUnitDef buMetricVelDef[] = { +{"meter per second", "meters per second", "m/s", nullptr, "Meters per second", nullptr, UN_SC_M, 0.0, B_UNIT_DEF_NONE}, /* Base unit. */ +{"kilometer per hour", "kilometers per hour", "km/h", nullptr, "Kilometers per hour", nullptr, UN_SC_KM / 3600.0f, 0.0, B_UNIT_DEF_SUPPRESS}, NULL_UNIT, }; +static bUnitCollection buMetricVelCollection = {buMetricVelDef, 0, 0, UNIT_COLLECTION_LENGTH(buMetricVelDef)}; + + +static bUnitDef buImperialVelDef[] = { +{"foot per second", "feet per second", "ft/s", "fps", "Feet per second", nullptr, UN_SC_FT, 0.0, B_UNIT_DEF_NONE}, /* Base unit. */ +{"mile per hour", "miles per hour", "mph", nullptr, "Miles per hour", nullptr, UN_SC_MI / 3600.0f, 0.0, B_UNIT_DEF_SUPPRESS}, NULL_UNIT, }; +static bUnitCollection buImperialVelCollection = {buImperialVelDef, 0, 0, UNIT_COLLECTION_LENGTH(buImperialVelDef)}; -static bUnitDef buImperialVelDef[] = { - /* Base unit. */ - { - /*name*/ "foot per second", - /*name_plural*/ "feet per second", - /*name_short*/ "ft/s", - /*name_alt*/ "fps", - /*name_display*/ "Feet per second", - /*identifier*/ nullptr, - /*scalar*/ UN_SC_FT, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, - { - /*name*/ "mile per hour", - /*name_plural*/ "miles per hour", - /*name_short*/ "mph", - /*name_alt*/ nullptr, - /*name_display*/ "Miles per hour", - /*identifier*/ nullptr, - /*scalar*/ UN_SC_MI / 3600.0f, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_SUPPRESS, - }, - NULL_UNIT, -}; -static bUnitCollection buImperialVelCollection = { - /*units*/ buImperialVelDef, - /*base_unit*/ 0, - /*flag*/ 0, - /*length*/ UNIT_COLLECTION_LENGTH(buImperialVelDef), -}; /* Acceleration. */ -static bUnitDef buMetricAclDef[] = { - /* Base unit. */ - { - /*name*/ "meter per second squared", - /*name_plural*/ "meters per second squared", - /*name_short*/ "m/s" BLI_STR_UTF8_SUPERSCRIPT_2, - /*name_alt*/ "m/s2", - /*name_display*/ "Meters per second squared", - /*identifier*/ nullptr, - /*scalar*/ UN_SC_M, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, - NULL_UNIT, -}; -static bUnitCollection buMetricAclCollection = { - /*units*/ buMetricAclDef, - /*base_unit*/ 0, - /*flag*/ 0, - /*length*/ UNIT_COLLECTION_LENGTH(buMetricAclDef), -}; +static bUnitDef buMetricAclDef[] = { +{"meter per second squared", "meters per second squared", "m/s²", "m/s2", "Meters per second squared", nullptr, UN_SC_M, 0.0, B_UNIT_DEF_NONE}, /* Base unit. */ +NULL_UNIT, }; +static bUnitCollection buMetricAclCollection = {buMetricAclDef, 0, 0, UNIT_COLLECTION_LENGTH(buMetricAclDef)}; + + +static bUnitDef buImperialAclDef[] = { +{"foot per second squared", "feet per second squared", "ft/s²", "ft/s2", "Feet per second squared", nullptr, UN_SC_FT, 0.0, B_UNIT_DEF_NONE}, /* Base unit. */ +NULL_UNIT, }; +static bUnitCollection buImperialAclCollection = {buImperialAclDef, 0, 0, UNIT_COLLECTION_LENGTH(buImperialAclDef)}; -static bUnitDef buImperialAclDef[] = { - /* Base unit. */ - { - /*name*/ "foot per second squared", - /*name_plural*/ "feet per second squared", - /*name_short*/ "ft/s" BLI_STR_UTF8_SUPERSCRIPT_2, - /*name_alt*/ "ft/s2", - /*name_display*/ "Feet per second squared", - /*identifier*/ nullptr, - /*scalar*/ UN_SC_FT, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, - NULL_UNIT, -}; -static bUnitCollection buImperialAclCollection = { - /*units*/ buImperialAclDef, - /*base_unit*/ 0, - /*flag*/ 0, - /*length*/ UNIT_COLLECTION_LENGTH(buImperialAclDef), -}; /* Time. */ -static bUnitDef buNaturalTimeDef[] = { - /* Weeks? - probably not needed for Blender. */ - { - /*name*/ "day", - /*name_plural*/ "days", - /*name_short*/ "d", - /*name_alt*/ nullptr, - /*name_display*/ "Days", - /*identifier*/ "DAYS", - /*scalar*/ 86400.0, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, - { - /*name*/ "hour", - /*name_plural*/ "hours", - /*name_short*/ "hr", - /*name_alt*/ "h", - /*name_display*/ "Hours", - /*identifier*/ "HOURS", - /*scalar*/ 3600.0, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, - { - /*name*/ "minute", - /*name_plural*/ "minutes", - /*name_short*/ "min", - /*name_alt*/ "m", - /*name_display*/ "Minutes", - /*identifier*/ "MINUTES", - /*scalar*/ 60.0, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, - /* Base unit. */ - { - /*name*/ "second", - /*name_plural*/ "seconds", - /*name_short*/ "sec", - /*name_alt*/ "s", - /*name_display*/ "Seconds", - /*identifier*/ "SECONDS", - /*scalar*/ 1.0, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, - { - /*name*/ "millisecond", - /*name_plural*/ "milliseconds", - /*name_short*/ "ms", - /*name_alt*/ nullptr, - /*name_display*/ "Milliseconds", - /*identifier*/ "MILLISECONDS", - /*scalar*/ 0.001, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, - { - /*name*/ "microsecond", - /*name_plural*/ "microseconds", - /*name_short*/ "µs", - /*name_alt*/ "us", - /*name_display*/ "Microseconds", - /*identifier*/ "MICROSECONDS", - /*scalar*/ 0.000001, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, - NULL_UNIT, -}; -static bUnitCollection buNaturalTimeCollection = { - /*units*/ buNaturalTimeDef, - /*base_unit*/ 3, - /*flag*/ 0, - /*length*/ UNIT_COLLECTION_LENGTH(buNaturalTimeDef), -}; +static bUnitDef buNaturalTimeDef[] = { +/* Weeks? - probably not needed for Blender. */ +{"day", "days", "d", nullptr, "Days", "DAYS", 86400.0, 0.0, B_UNIT_DEF_NONE}, {"hour", "hours", "hr", "h", "Hours", "HOURS", 3600.0, 0.0, B_UNIT_DEF_NONE}, {"minute", "minutes", "min", "m", "Minutes", "MINUTES", 60.0, 0.0, B_UNIT_DEF_NONE}, {"second", "seconds", "sec", "s", "Seconds", "SECONDS", 1.0, 0.0, B_UNIT_DEF_NONE}, /* Base unit. */ +{"millisecond", "milliseconds", "ms", nullptr, "Milliseconds", "MILLISECONDS", 0.001, 0.0, B_UNIT_DEF_NONE}, {"microsecond", "microseconds", "µs", "us", "Microseconds", "MICROSECONDS", 0.000001, 0.0, B_UNIT_DEF_NONE}, NULL_UNIT, }; +static bUnitCollection buNaturalTimeCollection = {buNaturalTimeDef, 3, 0, UNIT_COLLECTION_LENGTH(buNaturalTimeDef)}; -static bUnitDef buNaturalRotDef[] = { - { - /*name*/ "degree", - /*name_plural*/ "degrees", - /*name_short*/ BLI_STR_UTF8_DEGREE_SIGN, - /*name_alt*/ "d", - /*name_display*/ "Degrees", - /*identifier*/ "DEGREES", - /*scalar*/ M_PI / 180.0, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE | B_UNIT_DEF_NO_SPACE, - }, - /* `arcminutes` / `arcseconds` are used in Astronomy/Navigation areas. */ - { - /*name*/ "arcminute", - /*name_plural*/ "arcminutes", - /*name_short*/ "'", - /*name_alt*/ nullptr, - /*name_display*/ "Arcminutes", - /*identifier*/ "ARCMINUTES", - /*scalar*/ (M_PI / 180.0) / 60.0, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_SUPPRESS | B_UNIT_DEF_NO_SPACE, - }, - { - /*name*/ "arcsecond", - /*name_plural*/ "arcseconds", - /*name_short*/ "\"", - /*name_alt*/ nullptr, - /*name_display*/ "Arcseconds", - /*identifier*/ "ARCSECONDS", - /*scalar*/ (M_PI / 180.0) / 3600.0, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_SUPPRESS | B_UNIT_DEF_NO_SPACE, - }, - { - /*name*/ "radian", - /*name_plural*/ "radians", - /*name_short*/ "r", - /*name_alt*/ nullptr, - /*name_display*/ "Radians", - /*identifier*/ "RADIANS", - /*scalar*/ 1.0, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, + + + +static bUnitDef buNaturalRotDef[] = { + {"degree", "degrees", BLI_STR_UTF8_DEGREE_SIGN, "d", "Degrees", "DEGREES", M_PI / 180.0, 0.0, B_UNIT_DEF_NONE | B_UNIT_DEF_NO_SPACE}, + /* arcminutes/arcseconds are used in Astronomy/Navigation areas... */ + {"arcminute", "arcminutes", "'", nullptr, "Arcminutes", "ARCMINUTES", (M_PI / 180.0) / 60.0, 0.0, B_UNIT_DEF_SUPPRESS | B_UNIT_DEF_NO_SPACE}, + {"arcsecond", "arcseconds", "\"", nullptr, "Arcseconds", "ARCSECONDS", (M_PI / 180.0) / 3600.0, 0.0, B_UNIT_DEF_SUPPRESS | B_UNIT_DEF_NO_SPACE}, + {"radian", "radians", "r", nullptr, "Radians", "RADIANS", 1.0, 0.0, B_UNIT_DEF_NONE}, #if 0 - { - /*name*/ "turn", - /*name_plural*/ "turns", - /*name_short*/ "t", - /*name_alt*/ nullptr, - /*name_display*/ "Turns", - /*identifier*/ nullptr, - /*scalar*/ 1.0 / (M_PI * 2.0), - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, + {"turn", "turns", "t", nullptr, "Turns", nullptr, 1.0 / (M_PI * 2.0), 0.0, B_UNIT_DEF_NONE}, #endif - NULL_UNIT, -}; -static bUnitCollection buNaturalRotCollection = { - /*units*/ buNaturalRotDef, - /*base_unit*/ 0, - /*flag*/ 0, - /*length*/ UNIT_COLLECTION_LENGTH(buNaturalRotDef), -}; +NULL_UNIT, }; +static bUnitCollection buNaturalRotCollection = {buNaturalRotDef, 0, 0, UNIT_COLLECTION_LENGTH(buNaturalRotDef)}; + /* Camera Lengths. */ -static bUnitDef buCameraLenDef[] = { - /* Base unit. */ - { - /*name*/ "meter", - /*name_plural*/ "meters", - /*name_short*/ "m", - /*name_alt*/ nullptr, - /*name_display*/ "Meters", - /*identifier*/ nullptr, - /*scalar*/ UN_SC_KM, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, - { - /*name*/ "decimeter", - /*name_plural*/ "decimeters", - /*name_short*/ "dm", - /*name_alt*/ nullptr, - /*name_display*/ "10 Centimeters", - /*identifier*/ nullptr, - /*scalar*/ UN_SC_HM, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_SUPPRESS, - }, - { - /*name*/ "centimeter", - /*name_plural*/ "centimeters", - /*name_short*/ "cm", - /*name_alt*/ nullptr, - /*name_display*/ "Centimeters", - /*identifier*/ nullptr, - /*scalar*/ UN_SC_DAM, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_SUPPRESS, - }, - { - /*name*/ "millimeter", - /*name_plural*/ "millimeters", - /*name_short*/ "mm", - /*name_alt*/ nullptr, - /*name_display*/ "Millimeters", - /*identifier*/ nullptr, - /*scalar*/ UN_SC_M, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, - { - /*name*/ "micrometer", - /*name_plural*/ "micrometers", - /*name_short*/ "µm", - /*name_alt*/ "um", - /*name_display*/ "Micrometers", - /*identifier*/ nullptr, - /*scalar*/ UN_SC_MM, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_SUPPRESS, - }, - NULL_UNIT, -}; -static bUnitCollection buCameraLenCollection = { - /*units*/ buCameraLenDef, - /*base_unit*/ 3, - /*flag*/ 0, - /*length*/ UNIT_COLLECTION_LENGTH(buCameraLenDef), -}; +static bUnitDef buCameraLenDef[] = { +{"meter", "meters", "m", nullptr, "Meters", nullptr, UN_SC_KM, 0.0, B_UNIT_DEF_NONE}, /* Base unit. */ +{"decimeter", "decimeters", "dm", nullptr, "10 Centimeters", nullptr, UN_SC_HM, 0.0, B_UNIT_DEF_SUPPRESS}, {"centimeter", "centimeters", "cm", nullptr, "Centimeters", nullptr, UN_SC_DAM, 0.0, B_UNIT_DEF_SUPPRESS}, {"millimeter", "millimeters", "mm", nullptr, "Millimeters", nullptr, UN_SC_M, 0.0, B_UNIT_DEF_NONE}, {"micrometer", "micrometers", "µm", "um", "Micrometers", nullptr, UN_SC_MM, 0.0, B_UNIT_DEF_SUPPRESS}, NULL_UNIT, }; +static bUnitCollection buCameraLenCollection = {buCameraLenDef, 3, 0, UNIT_COLLECTION_LENGTH(buCameraLenDef)}; + /* (Light) Power. */ -static bUnitDef buPowerDef[] = { - { - /*name*/ "gigawatt", - /*name_plural*/ "gigawatts", - /*name_short*/ "GW", - /*name_alt*/ nullptr, - /*name_display*/ "Gigawatts", - /*identifier*/ nullptr, - /*scalar*/ 1e9f, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, - { - /*name*/ "megawatt", - /*name_plural*/ "megawatts", - /*name_short*/ "MW", - /*name_alt*/ nullptr, - /*name_display*/ "Megawatts", - /*identifier*/ nullptr, - /*scalar*/ 1e6f, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_CASE_SENSITIVE, - }, - { - /*name*/ "kilowatt", - /*name_plural*/ "kilowatts", - /*name_short*/ "kW", - /*name_alt*/ nullptr, - /*name_display*/ "Kilowatts", - /*identifier*/ nullptr, - /*scalar*/ 1e3f, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_SUPPRESS, - }, - /* Base unit. */ - { - /*name*/ "watt", - /*name_plural*/ "watts", - /*name_short*/ "W", - /*name_alt*/ nullptr, - /*name_display*/ "Watts", - /*identifier*/ nullptr, - /*scalar*/ 1.0f, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, - { - /*name*/ "milliwatt", - /*name_plural*/ "milliwatts", - /*name_short*/ "mW", - /*name_alt*/ nullptr, - /*name_display*/ "Milliwatts", - /*identifier*/ nullptr, - /*scalar*/ 1e-3f, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_CASE_SENSITIVE, - }, - { - /*name*/ "microwatt", - /*name_plural*/ "microwatts", - /*name_short*/ "µW", - /*name_alt*/ "uW", - /*name_display*/ "Microwatts", - /*identifier*/ nullptr, - /*scalar*/ 1e-6f, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, - { - /*name*/ "nanowatt", - /*name_plural*/ "nanowatts", - /*name_short*/ "nW", - /*name_alt*/ nullptr, - /*name_display*/ "Nanowatts", - /*identifier*/ nullptr, - /*scalar*/ 1e-9f, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, - NULL_UNIT, -}; -static bUnitCollection buPowerCollection = { - /*units*/ buPowerDef, - /*base_unit*/ 3, - /*flag*/ 0, - /*length*/ UNIT_COLLECTION_LENGTH(buPowerDef), -}; +static bUnitDef buPowerDef[] = { +{"gigawatt", "gigawatts", "GW", nullptr, "Gigawatts", nullptr, 1e9f, 0.0, B_UNIT_DEF_NONE}, {"megawatt", "megawatts", "MW", nullptr, "Megawatts", nullptr, 1e6f, 0.0, B_UNIT_DEF_CASE_SENSITIVE}, {"kilowatt", "kilowatts", "kW", nullptr, "Kilowatts", nullptr, 1e3f, 0.0, B_UNIT_DEF_SUPPRESS}, {"watt", "watts", "W", nullptr, "Watts", nullptr, 1.0f, 0.0, B_UNIT_DEF_NONE}, /* Base unit. */ +{"milliwatt", "milliwatts", "mW", nullptr, "Milliwatts", nullptr, 1e-3f, 0.0, B_UNIT_DEF_CASE_SENSITIVE}, {"microwatt", "microwatts", "µW", "uW", "Microwatts", nullptr, 1e-6f, 0.0, B_UNIT_DEF_NONE}, {"nanowatt", "nanowatts", "nW", nullptr, "Nanowatts", nullptr, 1e-9f, 0.0, B_UNIT_DEF_NONE}, NULL_UNIT, }; +static bUnitCollection buPowerCollection = {buPowerDef, 3, 0, UNIT_COLLECTION_LENGTH(buPowerDef)}; + /* Temperature */ -static bUnitDef buMetricTempDef[] = { - { - /*name*/ "kelvin", - /*name_plural*/ "kelvin", - /*name_short*/ "K", - /*name_alt*/ nullptr, - /*name_display*/ "Kelvin", - /*identifier*/ "KELVIN", - /*scalar*/ 1.0f, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, - /* Base unit. */ - { - /*name*/ "celsius", - /*name_plural*/ "celsius", - /*name_short*/ BLI_STR_UTF8_DEGREE_SIGN "C", - /*name_alt*/ "C", - /*name_display*/ "Celsius", - /*identifier*/ "CELSIUS", - /*scalar*/ 1.0f, - /*bias*/ 273.15, - /*flag*/ B_UNIT_DEF_NONE, - }, - NULL_UNIT, -}; -static bUnitCollection buMetricTempCollection = { - /*units*/ buMetricTempDef, - /*base_unit*/ 0, - /*flag*/ 0, - /*length*/ UNIT_COLLECTION_LENGTH(buMetricTempDef), +static bUnitDef buMetricTempDef[] = { + {"kelvin", "kelvin", "K", nullptr, "Kelvin", "KELVIN", 1.0f, 0.0, B_UNIT_DEF_NONE}, /* Base unit. */ + {"celsius", "celsius", BLI_STR_UTF8_DEGREE_SIGN "C", "C", "Celsius", "CELSIUS", 1.0f, 273.15, B_UNIT_DEF_NONE}, + NULL_UNIT, }; +static bUnitCollection buMetricTempCollection = {buMetricTempDef, 0, 0, UNIT_COLLECTION_LENGTH(buMetricTempDef)}; -static bUnitDef buImperialTempDef[] = { - { - /*name*/ "kelvin", - /*name_plural*/ "kelvin", - /*name_short*/ "K", - /*name_alt*/ nullptr, - /*name_display*/ "Kelvin", - /*identifier*/ "KELVIN", - /*scalar*/ 1.0f, - /*bias*/ 0.0, - /*flag*/ B_UNIT_DEF_NONE, - }, - /* Base unit. */ - { - /*name*/ "fahrenheit", - /*name_plural*/ "fahrenheit", - /*name_short*/ BLI_STR_UTF8_DEGREE_SIGN "F", - /*name_alt*/ "F", - /*name_display*/ "Fahrenheit", - /*identifier*/ "FAHRENHEIT", - /*scalar*/ UN_SC_FAH, - /*bias*/ 459.67, - /*flag*/ B_UNIT_DEF_NONE, - }, - NULL_UNIT, -}; -static bUnitCollection buImperialTempCollection = { - /*units*/ buImperialTempDef, - /*base_unit*/ 1, - /*flag*/ 0, - /*length*/ UNIT_COLLECTION_LENGTH(buImperialTempDef), + +static bUnitDef buImperialTempDef[] = { + {"kelvin", "kelvin", "K", nullptr, "Kelvin", "KELVIN", 1.0f, 0.0, B_UNIT_DEF_NONE}, /* Base unit. */ + {"fahrenheit", "fahrenheit", BLI_STR_UTF8_DEGREE_SIGN "F", "F", "Fahrenheit", "FAHRENHEIT", UN_SC_FAH, 459.67, B_UNIT_DEF_NONE}, + NULL_UNIT, }; +static bUnitCollection buImperialTempCollection = { +buImperialTempDef, 1, 0, UNIT_COLLECTION_LENGTH(buImperialTempDef)}; + +/* clang-format on */ #define UNIT_SYSTEM_TOT (((sizeof(bUnitSystems) / B_UNIT_TYPE_TOT) / sizeof(void *)) - 1) static const bUnitCollection *bUnitSystems[][B_UNIT_TYPE_TOT] = { /* Natural. */ - { - /*B_UNIT_NONE*/ nullptr, - /*B_UNIT_LENGTH*/ nullptr, - /*B_UNIT_AREA*/ nullptr, - /*B_UNIT_VOLUME*/ nullptr, - /*B_UNIT_MASS*/ nullptr, - /*B_UNIT_ROTATION*/ &buNaturalRotCollection, - /*B_UNIT_TIME*/ &buNaturalTimeCollection, - /*B_UNIT_TIME_ABSOLUTE*/ &buNaturalTimeCollection, - /*B_UNIT_VELOCITY*/ nullptr, - /*B_UNIT_ACCELERATION*/ nullptr, - /*B_UNIT_CAMERA*/ nullptr, - /*B_UNIT_POWER*/ nullptr, - /*B_UNIT_TEMPERATURE*/ nullptr, - }, + {nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + &buNaturalRotCollection, + &buNaturalTimeCollection, + &buNaturalTimeCollection, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr}, /* Metric. */ - { - /*B_UNIT_NONE*/ nullptr, - /*B_UNIT_LENGTH*/ &buMetricLenCollection, - /*B_UNIT_AREA*/ &buMetricAreaCollection, - /*B_UNIT_VOLUME*/ &buMetricVolCollection, - /*B_UNIT_MASS*/ &buMetricMassCollection, - /*B_UNIT_ROTATION*/ &buNaturalRotCollection, - /*B_UNIT_TIME*/ &buNaturalTimeCollection, - /*B_UNIT_TIME_ABSOLUTE*/ &buNaturalTimeCollection, - /*B_UNIT_VELOCITY*/ &buMetricVelCollection, - /*B_UNIT_ACCELERATION*/ &buMetricAclCollection, - /*B_UNIT_CAMERA*/ &buCameraLenCollection, - /*B_UNIT_POWER*/ &buPowerCollection, - /*B_UNIT_TEMPERATURE*/ &buMetricTempCollection, - }, + {nullptr, + &buMetricLenCollection, + &buMetricAreaCollection, + &buMetricVolCollection, + &buMetricMassCollection, + &buNaturalRotCollection, + &buNaturalTimeCollection, + &buNaturalTimeCollection, + &buMetricVelCollection, + &buMetricAclCollection, + &buCameraLenCollection, + &buPowerCollection, + &buMetricTempCollection}, /* Imperial. */ - { - /*B_UNIT_NONE*/ nullptr, - /*B_UNIT_LENGTH*/ &buImperialLenCollection, - /*B_UNIT_AREA*/ &buImperialAreaCollection, - /*B_UNIT_VOLUME*/ &buImperialVolCollection, - /*B_UNIT_MASS*/ &buImperialMassCollection, - /*B_UNIT_ROTATION*/ &buNaturalRotCollection, - /*B_UNIT_TIME*/ &buNaturalTimeCollection, - /*B_UNIT_TIME_ABSOLUTE*/ &buNaturalTimeCollection, - /*B_UNIT_VELOCITY*/ &buImperialVelCollection, - /*B_UNIT_ACCELERATION*/ &buImperialAclCollection, - /*B_UNIT_CAMERA*/ &buCameraLenCollection, - /*B_UNIT_POWER*/ &buPowerCollection, - /*B_UNIT_TEMPERATURE*/ &buImperialTempCollection, - }, - {nullptr}, + {nullptr, + &buImperialLenCollection, + &buImperialAreaCollection, + &buImperialVolCollection, + &buImperialMassCollection, + &buNaturalRotCollection, + &buNaturalTimeCollection, + &buNaturalTimeCollection, + &buImperialVelCollection, + &buImperialAclCollection, + &buCameraLenCollection, + &buPowerCollection, + &buImperialTempCollection}, + {nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr}, }; static const bUnitCollection *unit_get_system(int system, int type) diff --git a/source/blender/blenkernel/intern/volume_render.cc b/source/blender/blenkernel/intern/volume_render.cc index 209f5881539..ce8f9c1584c 100644 --- a/source/blender/blenkernel/intern/volume_render.cc +++ b/source/blender/blenkernel/intern/volume_render.cc @@ -385,20 +385,14 @@ static void grow_triangles(blender::MutableSpan verts, /* Compute the offset for every vertex based on the connected edges. * This formula simply tries increases the length of all edges. */ blender::Array offsets(verts.size(), {0, 0, 0}); - blender::Array weights(verts.size(), 0.0f); for (const std::array &tri : tris) { offsets[tri[0]] += factor * (2 * verts[tri[0]] - verts[tri[1]] - verts[tri[2]]); offsets[tri[1]] += factor * (2 * verts[tri[1]] - verts[tri[0]] - verts[tri[2]]); offsets[tri[2]] += factor * (2 * verts[tri[2]] - verts[tri[0]] - verts[tri[1]]); - weights[tri[0]] += 1.0; - weights[tri[1]] += 1.0; - weights[tri[2]] += 1.0; } /* Apply the computed offsets. */ for (const int i : verts.index_range()) { - if (weights[i] > 0.0f) { - verts[i] += offsets[i] / weights[i]; - } + verts[i] += offsets[i]; } } #endif /* WITH_OPENVDB */ diff --git a/source/blender/blenkernel/intern/workspace.cc b/source/blender/blenkernel/intern/workspace.cc index d030d08656e..cc7dc186689 100644 --- a/source/blender/blenkernel/intern/workspace.cc +++ b/source/blender/blenkernel/intern/workspace.cc @@ -12,7 +12,7 @@ #include "BLI_listbase.h" #include "BLI_string.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_utildefines.h" #include "BLT_translation.h" diff --git a/source/blender/blenlib/BLI_array_utils.hh b/source/blender/blenlib/BLI_array_utils.hh index fda1c32de35..f45e8dfbe82 100644 --- a/source/blender/blenlib/BLI_array_utils.hh +++ b/source/blender/blenlib/BLI_array_utils.hh @@ -4,12 +4,9 @@ #pragma once -#include - #include "BLI_generic_span.hh" #include "BLI_generic_virtual_array.hh" #include "BLI_index_mask.hh" -#include "BLI_offset_indices.hh" #include "BLI_task.hh" #include "BLI_virtual_array.hh" @@ -148,26 +145,6 @@ inline void gather(const VArray &src, }); } -/** - * Copy the \a src data from the groups defined by \a src_offsets to the groups in \a dst defined - * by \a dst_offsets. Groups to use are masked by \a selection, and it is assumed that the - * corresponding groups have the same size. - */ -void copy_group_to_group(OffsetIndices src_offsets, - OffsetIndices dst_offsets, - const IndexMask &selection, - GSpan src, - GMutableSpan dst); -template -void copy_group_to_group(OffsetIndices src_offsets, - OffsetIndices dst_offsets, - const IndexMask &selection, - Span src, - MutableSpan dst) -{ - copy_group_to_group(src_offsets, dst_offsets, selection, GSpan(src), GMutableSpan(dst)); -} - /** * Count the number of occurrences of each index. * \param indices: The indices to count. @@ -179,9 +156,6 @@ void copy_group_to_group(OffsetIndices src_offsets, void count_indices(Span indices, MutableSpan counts); void invert_booleans(MutableSpan span); -void invert_booleans(MutableSpan span, const IndexMask &mask); - -int64_t count_booleans(const VArray &varray); enum class BooleanMix { None, @@ -220,13 +194,4 @@ template inline Vector find_all_ranges(const Span spa return ranges; } -/** - * Fill the span with increasing indices: 0, 1, 2, ... - * Optionally, the start value can be provided. - */ -template inline void fill_index_range(MutableSpan span, const T start = 0) -{ - std::iota(span.begin(), span.end(), start); -} - } // namespace blender::array_utils diff --git a/source/blender/blenlib/BLI_compiler_compat.h b/source/blender/blenlib/BLI_compiler_compat.h index b7a6ce548d9..8945c15a4c4 100644 --- a/source/blender/blenlib/BLI_compiler_compat.h +++ b/source/blender/blenlib/BLI_compiler_compat.h @@ -40,8 +40,6 @@ template static inline T decltype_helper(T x) #if defined(__GNUC__) # define BLI_NOINLINE __attribute__((noinline)) -#elif defined(_MSC_VER) -# define BLI_NOINLINE __declspec(noinline) #else # define BLI_NOINLINE #endif diff --git a/source/blender/blenlib/BLI_listbase.h b/source/blender/blenlib/BLI_listbase.h index 5cb1e5f3cab..394f5b23f5d 100644 --- a/source/blender/blenlib/BLI_listbase.h +++ b/source/blender/blenlib/BLI_listbase.h @@ -45,10 +45,9 @@ void *BLI_findlink(const struct ListBase *listbase, int number) ATTR_WARN_UNUSED ATTR_NONNULL(1); /** - * Returns the element before/after \a link that is \a step links away, numbering from 0. \a step - * is allowed to be negative. Returns NULL when the link is out-of-bounds. + * Returns the nth element after \a link, numbering from 0. */ -void *BLI_findlinkfrom(struct Link *start, int step) ATTR_WARN_UNUSED_RESULT; +void *BLI_findlinkfrom(struct Link *start, int number) ATTR_WARN_UNUSED_RESULT; /** * Finds the first element of \a listbase which contains the null-terminated diff --git a/source/blender/blenlib/BLI_math_matrix.hh b/source/blender/blenlib/BLI_math_matrix.hh index 78fb886c3e6..49cb9246f00 100644 --- a/source/blender/blenlib/BLI_math_matrix.hh +++ b/source/blender/blenlib/BLI_math_matrix.hh @@ -230,24 +230,12 @@ template const VectorT forward, const VectorT up); -/** - * Create a rotation matrix from only one \a up axis. - * The other axes are chosen to always be orthogonal. The resulting matrix is a basis matrix. - * \note `up` must be normalized. - * \note This can be used to create a tangent basis from a normal vector. - * \note The output of this function is not given to be same across blender version. Prefer using - * `from_orthonormal_axes` for more stable output. - */ -template [[nodiscard]] MatT from_up_axis(const VectorT up); - /** * This returns a version of \a mat with orthonormal basis axes. * This leaves the given \a axis untouched. * * In other words this removes the shear of the matrix. However this doesn't properly account for * volume preservation, and so, the axes keep their respective length. - * - * \note Prefer using `from_up_axis` to create a orthogonal basis around a vector. */ template [[nodiscard]] MatT orthogonalize(const MatT &mat, const Axis axis); @@ -405,15 +393,6 @@ template [[nodiscard]] MatBase orthographic( T left, T right, T bottom, T top, T near_clip, T far_clip); -/** - * \brief Create an orthographic projection matrix using OpenGL coordinate convention: - * Maps each axis range to [-1..1] range for all axes except Z. - * The Z axis is collapsed to 0 which eliminates the depth component. So it cannot be used with - * depth testing. - * The resulting matrix can be used with either #project_point or #transform_point. - */ -template MatBase orthographic_infinite(T left, T right, T bottom, T top); - /** * \brief Create a perspective projection matrix using OpenGL coordinate convention: * Maps each axis range to [-1..1] range for all axes. @@ -434,16 +413,6 @@ template [[nodiscard]] MatBase perspective_fov( T angle_left, T angle_right, T angle_bottom, T angle_top, T near_clip, T far_clip); -/** - * \brief Create a perspective projection matrix using OpenGL coordinate convention: - * Maps each axis range to [-1..1] range for all axes except for the Z where [near_clip..inf] is - * mapped to [-1..1]. - * `left`, `right`, `bottom`, `top` are frustum side distances at `z=near_clip`. - * The resulting matrix can be used with #project_point. - */ -template -[[nodiscard]] MatBase perspective_infinite(T left, T right, T bottom, T top, T near_clip); - } // namespace projection /** \} */ @@ -1395,23 +1364,6 @@ template return matrix; } -template [[nodiscard]] MatT from_up_axis(const VectorT up) -{ - BLI_assert(is_unit_scale(up)); - using T = typename MatT::base_type; - using Vec3T = VecBase; - /* Duff, Tom, et al. "Building an orthonormal basis, revisited." JCGT 6.1 (2017). */ - T sign = math::sign(up.z); - T a = T(-1) / (sign + up.z); - T b = up.x * up.y * a; - - MatBase basis; - basis.x_axis() = Vec3T(1.0f + sign * square(up.x) * a, sign * b, -sign * up.x); - basis.y_axis() = Vec3T(b, sign + square(up.y) * a, -up.y); - basis.z_axis() = up; - return MatT(basis); -} - template [[nodiscard]] MatT orthogonalize(const MatT &mat, const Axis axis) { using T = typename MatT::base_type; @@ -1573,23 +1525,6 @@ MatBase orthographic(T left, T right, T bottom, T top, T near_clip, T f return mat; } -template MatBase orthographic_infinite(T left, T right, T bottom, T top) -{ - const T x_delta = right - left; - const T y_delta = top - bottom; - - MatBase mat = MatBase::identity(); - if (x_delta != 0 && y_delta != 0) { - mat[0][0] = T(2.0) / x_delta; - mat[3][0] = -(right + left) / x_delta; - mat[1][1] = T(2.0) / y_delta; - mat[3][1] = -(top + bottom) / y_delta; - mat[2][2] = 0.0f; - mat[3][2] = 0.0f; - } - return mat; -} - template MatBase perspective(T left, T right, T bottom, T top, T near_clip, T far_clip) { @@ -1611,29 +1546,6 @@ MatBase perspective(T left, T right, T bottom, T top, T near_clip, T fa return mat; } -template -MatBase perspective_infinite(T left, T right, T bottom, T top, T near_clip) -{ - const T x_delta = right - left; - const T y_delta = top - bottom; - - /* From "Projection Matrix Tricks" by Eric Lengyel GDC 2007. */ - MatBase mat = MatBase::identity(); - if (x_delta != 0 && y_delta != 0) { - mat[0][0] = near_clip * T(2.0) / x_delta; - mat[1][1] = near_clip * T(2.0) / y_delta; - mat[2][0] = (right + left) / x_delta; /* NOTE: negate Z. */ - mat[2][1] = (top + bottom) / y_delta; - /* Page 17. Choosing an epsilon for 32 bit floating-point precision. */ - constexpr float eps = 2.4e-7f; - mat[2][2] = -1.0f; - mat[2][3] = (eps - 1.0f); - mat[3][2] = (eps - 2.0f) * near_clip; - mat[3][3] = 0.0f; - } - return mat; -} - template [[nodiscard]] MatBase perspective_fov( T angle_left, T angle_right, T angle_bottom, T angle_top, T near_clip, T far_clip) diff --git a/source/blender/blenlib/BLI_math_vector.hh b/source/blender/blenlib/BLI_math_vector.hh index 0f501ebbf0b..f59848fc3f4 100644 --- a/source/blender/blenlib/BLI_math_vector.hh +++ b/source/blender/blenlib/BLI_math_vector.hh @@ -632,54 +632,6 @@ template [[nodiscard]] inline int dominant_axis(const VecBase return ((b.x > b.y) ? ((b.x > b.z) ? 0 : 2) : ((b.y > b.z) ? 1 : 2)); } -/** - * \return the maximum component of a vector. - */ -template [[nodiscard]] inline T reduce_max(const VecBase &a) -{ - T result = a[0]; - for (int i = 1; i < Size; i++) { - if (a[i] > result) { - result = a[i]; - } - } - return result; -} - -/** - * \return the minimum component of a vector. - */ -template [[nodiscard]] inline T reduce_min(const VecBase &a) -{ - T result = a[0]; - for (int i = 1; i < Size; i++) { - if (a[i] < result) { - result = a[i]; - } - } - return result; -} - -/** - * \return the sum of the components of a vector. - */ -template [[nodiscard]] inline T reduce_add(const VecBase &a) -{ - T result = a[0]; - for (int i = 1; i < Size; i++) { - result += a[i]; - } - return result; -} - -/** - * \return the average of the components of a vector. - */ -template [[nodiscard]] inline T average(const VecBase &a) -{ - return reduce_add(a) * (T(1) / T(Size)); -} - /** * Calculates a perpendicular vector to \a v. * \note Returned vector can be in any perpendicular direction. diff --git a/source/blender/blenlib/BLI_mmap.h b/source/blender/blenlib/BLI_mmap.h index 6560e452444..11684e59525 100644 --- a/source/blender/blenlib/BLI_mmap.h +++ b/source/blender/blenlib/BLI_mmap.h @@ -33,7 +33,6 @@ bool BLI_mmap_read(BLI_mmap_file *file, void *dest, size_t offset, size_t length ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1); void *BLI_mmap_get_pointer(BLI_mmap_file *file) ATTR_WARN_UNUSED_RESULT; -size_t BLI_mmap_get_length(const BLI_mmap_file *file) ATTR_WARN_UNUSED_RESULT; void BLI_mmap_free(BLI_mmap_file *file) ATTR_NONNULL(1); diff --git a/source/blender/blenlib/BLI_string_utf8_symbols.h b/source/blender/blenlib/BLI_string_utf8_symbols.h index 5b4f41bdc21..7a8b547597a 100644 --- a/source/blender/blenlib/BLI_string_utf8_symbols.h +++ b/source/blender/blenlib/BLI_string_utf8_symbols.h @@ -23,10 +23,6 @@ extern "C" { /** u00B0: `°`. */ #define BLI_STR_UTF8_DEGREE_SIGN "\xc2\xb0" -/** u00B2: `²` */ -#define BLI_STR_UTF8_SUPERSCRIPT_2 "\xc2\xb2" -/** u00B3: `³` */ -#define BLI_STR_UTF8_SUPERSCRIPT_3 "\xc2\xb3" /** u00D7: `×`. */ #define BLI_STR_UTF8_MULTIPLICATION_SIGN "\xc3\x97" /** u2014: `—` */ diff --git a/source/blender/blenlib/BLI_string_utils.hh b/source/blender/blenlib/BLI_string_utils.h similarity index 96% rename from source/blender/blenlib/BLI_string_utils.hh rename to source/blender/blenlib/BLI_string_utils.h index 1e540be67fb..dfe0f1ed396 100644 --- a/source/blender/blenlib/BLI_string_utils.hh +++ b/source/blender/blenlib/BLI_string_utils.h @@ -9,13 +9,14 @@ */ #include -#include #include "BLI_compiler_attrs.h" -#include "BLI_function_ref.hh" -#include "BLI_string_ref.hh" #include "BLI_utildefines.h" +#ifdef __cplusplus +extern "C" { +#endif + struct ListBase; typedef bool (*UniquenameCheckCallback)(void *arg, const char *name); @@ -141,7 +142,7 @@ size_t BLI_string_flip_side_name(char *name_dst, /** * Ensures name is unique (according to criteria specified by caller in unique_check callback), - * incrementing its numeric suffix as necessary. + * incrementing its numeric suffix as necessary. Returns true if name had to be adjusted. * * \param unique_check: Return true if name is not unique * \param arg: Additional arg to unique_check--meaning is up to caller @@ -149,28 +150,17 @@ size_t BLI_string_flip_side_name(char *name_dst, * \param delim: Delimits numeric suffix in name * \param name: Name to be ensured unique * \param name_maxncpy: Maximum length of name area + * \return true if there if the name was changed */ -void BLI_uniquename_cb(UniquenameCheckCallback unique_check, +bool BLI_uniquename_cb(UniquenameCheckCallback unique_check, void *arg, const char *defname, char delim, char *name, size_t name_maxncpy) ATTR_NONNULL(1, 3, 5); - -/** - * Ensures name is unique (according to criteria specified by caller in unique_check callback), - * incrementing its numeric suffix as necessary. - * - * \param unique_check: Return true if name is not unique - * \param delim: Delimits numeric suffix in name - * \param name: Name to be ensured unique - */ -std::string BLI_uniquename_cb(blender::FunctionRef unique_check, - char delim, - blender::StringRef name); /** * Ensures that the specified block has a unique name within the containing list, - * incrementing its numeric suffix as necessary. + * incrementing its numeric suffix as necessary. Returns true if name had to be adjusted. * * \param list: List containing the block * \param vlink: The block to check the name for @@ -179,7 +169,7 @@ std::string BLI_uniquename_cb(blender::FunctionRef uni * \param name_offset: Offset of name within block structure * \param name_maxncpy: Maximum length of name area */ -void BLI_uniquename(struct ListBase *list, +bool BLI_uniquename(struct ListBase *list, void *vlink, const char *defname, char delim, @@ -542,3 +532,7 @@ BLI_INLINE char *_BLI_string_join_by_sep_charN_11(_BLI_STRING_ARGS_10) /** \} */ #undef _BLI_STRING_ARGS_0 + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/blenlib/BLI_vector.hh b/source/blender/blenlib/BLI_vector.hh index 79e4b111dca..01f454f8a38 100644 --- a/source/blender/blenlib/BLI_vector.hh +++ b/source/blender/blenlib/BLI_vector.hh @@ -41,7 +41,7 @@ namespace blender { namespace internal { void vector_print_stats(const char *name, - const void *address, + void *address, int64_t size, int64_t capacity, int64_t inlineCapacity, diff --git a/source/blender/blenlib/BLI_virtual_array.hh b/source/blender/blenlib/BLI_virtual_array.hh index 3b7548f814d..59ca7cd4301 100644 --- a/source/blender/blenlib/BLI_virtual_array.hh +++ b/source/blender/blenlib/BLI_virtual_array.hh @@ -647,20 +647,6 @@ template class VArrayCommon { } return impl_->size(); } - /** - * Get the first element. - */ - T first() const - { - return (*this)[0]; - } - /** - * Get the nth last element. - */ - T last(const int64_t n = 0) const - { - return (*this)[this->size() - 1 - n]; - } /** True when the size is zero or when there is no virtual array. */ bool is_empty() const diff --git a/source/blender/blenlib/CMakeLists.txt b/source/blender/blenlib/CMakeLists.txt index ee4e569f94d..9a246fdb9b5 100644 --- a/source/blender/blenlib/CMakeLists.txt +++ b/source/blender/blenlib/CMakeLists.txt @@ -26,7 +26,7 @@ set(SRC intern/BLI_color.cc intern/BLI_dial_2d.c intern/BLI_dynstr.c - intern/BLI_filelist.cc + intern/BLI_filelist.c intern/BLI_ghash.c intern/BLI_ghash_utils.c intern/BLI_heap.c @@ -61,12 +61,12 @@ set(SRC intern/cpp_types.cc intern/delaunay_2d.cc intern/dot_export.cc - intern/dynlib.cc + intern/dynlib.c intern/easing.c intern/endian_switch.c intern/expr_pylike_eval.c + intern/fileops.c intern/fileops.cc - intern/fileops_c.cc intern/filereader_file.c intern/filereader_gzip.c intern/filereader_memory.c @@ -120,7 +120,7 @@ set(SRC intern/noise.c intern/noise.cc intern/offset_indices.cc - intern/path_util.cc + intern/path_util.c intern/polyfill_2d.c intern/polyfill_2d_beautify.c intern/quadric.c @@ -136,13 +136,13 @@ set(SRC intern/sort.c intern/sort_utils.c intern/stack.c - intern/storage.cc + intern/storage.c intern/string.c intern/string_cursor_utf8.c intern/string_ref.cc intern/string_search.cc - intern/string_utf8.cc - intern/string_utils.cc + intern/string_utf8.c + intern/string_utils.c intern/system.c intern/task_graph.cc intern/task_iterator.c @@ -160,8 +160,8 @@ set(SRC intern/virtual_array.cc intern/voronoi_2d.c intern/voxel.c - intern/winstuff.cc - intern/winstuff_dir.cc + intern/winstuff.c + intern/winstuff_dir.c # Private headers. intern/BLI_mempool_private.h @@ -354,7 +354,7 @@ set(SRC BLI_string_search.hh BLI_string_utf8.h BLI_string_utf8_symbols.h - BLI_string_utils.hh + BLI_string_utils.h BLI_sub_frame.hh BLI_sys_types.h BLI_system.h @@ -445,6 +445,7 @@ if(WIN32) ) list(APPEND LIB bf_intern_utfconv + dxguid dxgi ) list(APPEND SRC @@ -550,7 +551,6 @@ if(WITH_GTESTS) tests/BLI_string_search_test.cc tests/BLI_string_test.cc tests/BLI_string_utf8_test.cc - tests/BLI_string_utils_test.cc tests/BLI_task_graph_test.cc tests/BLI_task_test.cc tests/BLI_tempfile_test.cc diff --git a/source/blender/blenlib/intern/BLI_filelist.cc b/source/blender/blenlib/intern/BLI_filelist.c similarity index 89% rename from source/blender/blenlib/intern/BLI_filelist.cc rename to source/blender/blenlib/intern/BLI_filelist.c index 407223311a2..4f6b57907fd 100644 --- a/source/blender/blenlib/intern/BLI_filelist.cc +++ b/source/blender/blenlib/intern/BLI_filelist.c @@ -21,7 +21,7 @@ #ifdef WIN32 # include "BLI_winstuff.h" -# include "utfconv.hh" +# include "utfconv.h" # include # include #else @@ -40,7 +40,7 @@ #include "BLI_listbase.h" #include "BLI_path_util.h" #include "BLI_string.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "../imbuf/IMB_imbuf.h" @@ -111,7 +111,7 @@ struct BuildDirCtx { static void bli_builddir(struct BuildDirCtx *dir_ctx, const char *dirname) { DIR *dir = opendir(dirname); - if (UNLIKELY(dir == nullptr)) { + if (UNLIKELY(dir == NULL)) { fprintf(stderr, "Failed to open dir (%s): %s\n", errno ? strerror(errno) : "unknown error", @@ -119,7 +119,7 @@ static void bli_builddir(struct BuildDirCtx *dir_ctx, const char *dirname) return; } - ListBase dirbase = {nullptr, nullptr}; + ListBase dirbase = {NULL, NULL}; int newnum = 0; const struct dirent *fname; bool has_current = false, has_parent = false; @@ -135,9 +135,9 @@ static void bli_builddir(struct BuildDirCtx *dir_ctx, const char *dirname) dirname_with_slash[dirname_with_slash_len] = '\0'; } - while ((fname = readdir(dir)) != nullptr) { - struct dirlink *const dlink = (struct dirlink *)malloc(sizeof(dirlink)); - if (dlink != nullptr) { + while ((fname = readdir(dir)) != NULL) { + struct dirlink *const dlink = (struct dirlink *)malloc(sizeof(struct dirlink)); + if (dlink != NULL) { dlink->name = BLI_strdup(fname->d_name); if (FILENAME_IS_PARENT(dlink->name)) { has_parent = true; @@ -155,8 +155,8 @@ static void bli_builddir(struct BuildDirCtx *dir_ctx, const char *dirname) STRNCPY(pardir, dirname); if (BLI_path_parent_dir(pardir) && (BLI_access(pardir, R_OK) == 0)) { - struct dirlink *const dlink = (struct dirlink *)malloc(sizeof(dirlink)); - if (dlink != nullptr) { + struct dirlink *const dlink = (struct dirlink *)malloc(sizeof(struct dirlink)); + if (dlink != NULL) { dlink->name = BLI_strdup(FILENAME_PARENT); BLI_addhead(&dirbase, dlink); newnum++; @@ -164,8 +164,8 @@ static void bli_builddir(struct BuildDirCtx *dir_ctx, const char *dirname) } } if (!has_current) { - struct dirlink *const dlink = (struct dirlink *)malloc(sizeof(dirlink)); - if (dlink != nullptr) { + struct dirlink *const dlink = (struct dirlink *)malloc(sizeof(struct dirlink)); + if (dlink != NULL) { dlink->name = BLI_strdup(FILENAME_CURRENT); BLI_addhead(&dirbase, dlink); newnum++; @@ -175,30 +175,30 @@ static void bli_builddir(struct BuildDirCtx *dir_ctx, const char *dirname) if (newnum) { if (dir_ctx->files) { void *const tmp = MEM_reallocN(dir_ctx->files, - (dir_ctx->files_num + newnum) * sizeof(direntry)); + (dir_ctx->files_num + newnum) * sizeof(struct direntry)); if (tmp) { - dir_ctx->files = (direntry *)tmp; + dir_ctx->files = (struct direntry *)tmp; } else { /* Reallocation may fail. */ MEM_freeN(dir_ctx->files); - dir_ctx->files = nullptr; + dir_ctx->files = NULL; } } - if (dir_ctx->files == nullptr) { - dir_ctx->files = (struct direntry *)MEM_mallocN(newnum * sizeof(direntry), __func__); + if (dir_ctx->files == NULL) { + dir_ctx->files = (struct direntry *)MEM_mallocN(newnum * sizeof(struct direntry), __func__); } - if (UNLIKELY(dir_ctx->files == nullptr)) { + if (UNLIKELY(dir_ctx->files == NULL)) { fprintf(stderr, "Couldn't get memory for dir: %s\n", dirname); dir_ctx->files_num = 0; } else { - struct dirlink *dlink = (dirlink *)dirbase.first; + struct dirlink *dlink = (struct dirlink *)dirbase.first; struct direntry *file = &dir_ctx->files[dir_ctx->files_num]; while (dlink) { - memset(file, 0, sizeof(direntry)); + memset(file, 0, sizeof(struct direntry)); file->relname = dlink->name; file->path = BLI_string_joinN(dirname_with_slash, dlink->name); if (BLI_stat(file->path, &file->s) != -1) { @@ -216,7 +216,7 @@ static void bli_builddir(struct BuildDirCtx *dir_ctx, const char *dirname) qsort(dir_ctx->files, dir_ctx->files_num, - sizeof(direntry), + sizeof(struct direntry), (int (*)(const void *, const void *))direntry_cmp); } @@ -231,7 +231,7 @@ uint BLI_filelist_dir_contents(const char *dirname, struct direntry **r_filelist struct BuildDirCtx dir_ctx; dir_ctx.files_num = 0; - dir_ctx.files = nullptr; + dir_ctx.files = NULL; bli_builddir(&dir_ctx, dirname); @@ -241,7 +241,7 @@ uint BLI_filelist_dir_contents(const char *dirname, struct direntry **r_filelist else { /* Keep Blender happy. Blender stores this in a variable * where 0 has special meaning..... */ - *r_filelist = static_cast(MEM_mallocN(sizeof(**r_filelist), __func__)); + *r_filelist = MEM_mallocN(sizeof(**r_filelist), __func__); } return dir_ctx.files_num; @@ -250,7 +250,7 @@ uint BLI_filelist_dir_contents(const char *dirname, struct direntry **r_filelist void BLI_filelist_entry_size_to_string(const struct stat *st, const uint64_t st_size_fallback, /* Used to change MB -> M, etc. - is that really useful? */ - const bool /*compact*/, + const bool UNUSED(compact), char r_size[FILELIST_DIRENTRY_SIZE_LEN]) { /* @@ -258,7 +258,7 @@ void BLI_filelist_entry_size_to_string(const struct stat *st, * will buy us some time until files get bigger than 4GB or until * everyone starts using __USE_FILE_OFFSET64 or equivalent. */ - double size = double(st ? st->st_size : st_size_fallback); + double size = (double)(st ? st->st_size : st_size_fallback); #ifdef WIN32 BLI_str_format_byte_unit(r_size, size, false); #else @@ -267,7 +267,7 @@ void BLI_filelist_entry_size_to_string(const struct stat *st, } void BLI_filelist_entry_mode_to_string(const struct stat *st, - const bool /*compact*/, + const bool UNUSED(compact), char r_mode1[FILELIST_DIRENTRY_MODE_LEN], char r_mode2[FILELIST_DIRENTRY_MODE_LEN], char r_mode3[FILELIST_DIRENTRY_MODE_LEN]) @@ -315,7 +315,7 @@ void BLI_filelist_entry_mode_to_string(const struct stat *st, } void BLI_filelist_entry_owner_to_string(const struct stat *st, - const bool /*compact*/, + const bool UNUSED(compact), char r_owner[FILELIST_DIRENTRY_OWNER_LEN]) { #ifdef WIN32 @@ -372,7 +372,7 @@ void BLI_filelist_entry_datetime_to_string(const struct stat *st, const time_t zero = 0; /* Prevent impossible dates in windows. */ - if (tm == nullptr) { + if (tm == NULL) { tm = localtime(&zero); } @@ -399,10 +399,10 @@ void BLI_filelist_entry_duplicate(struct direntry *dst, const struct direntry *s { *dst = *src; if (dst->relname) { - dst->relname = static_cast(MEM_dupallocN(src->relname)); + dst->relname = MEM_dupallocN(src->relname); } if (dst->path) { - dst->path = static_cast(MEM_dupallocN(src->path)); + dst->path = MEM_dupallocN(src->path); } } @@ -412,8 +412,7 @@ void BLI_filelist_duplicate(struct direntry **dest_filelist, { uint i; - *dest_filelist = static_cast( - MEM_mallocN(sizeof(**dest_filelist) * size_t(nrentries), __func__)); + *dest_filelist = MEM_mallocN(sizeof(**dest_filelist) * (size_t)(nrentries), __func__); for (i = 0; i < nrentries; i++) { struct direntry *const src = &src_filelist[i]; struct direntry *dst = &(*dest_filelist)[i]; @@ -438,7 +437,7 @@ void BLI_filelist_free(struct direntry *filelist, const uint nrentries) BLI_filelist_entry_free(&filelist[i]); } - if (filelist != nullptr) { + if (filelist != NULL) { MEM_freeN(filelist); } } diff --git a/source/blender/blenlib/intern/BLI_mmap.c b/source/blender/blenlib/intern/BLI_mmap.c index bc19876a84f..fef40f9ee90 100644 --- a/source/blender/blenlib/intern/BLI_mmap.c +++ b/source/blender/blenlib/intern/BLI_mmap.c @@ -213,11 +213,6 @@ void *BLI_mmap_get_pointer(BLI_mmap_file *file) return file->memory; } -size_t BLI_mmap_get_length(const BLI_mmap_file *file) -{ - return file->length; -} - void BLI_mmap_free(BLI_mmap_file *file) { #ifndef WIN32 diff --git a/source/blender/blenlib/intern/array_utils.cc b/source/blender/blenlib/intern/array_utils.cc index 1de31e2d4fd..88a78e51e68 100644 --- a/source/blender/blenlib/intern/array_utils.cc +++ b/source/blender/blenlib/intern/array_utils.cc @@ -48,18 +48,6 @@ void gather(const GSpan src, const IndexMask &indices, GMutableSpan dst, const i gather(GVArray::ForSpan(src), indices, dst, grain_size); } -void copy_group_to_group(const OffsetIndices src_offsets, - const OffsetIndices dst_offsets, - const IndexMask &selection, - const GSpan src, - GMutableSpan dst) -{ - /* Each group might be large, so a threaded copy might make sense here too. */ - selection.foreach_index(GrainSize(512), [&](const int i) { - dst.slice(dst_offsets[i]).copy_from(src.slice(src_offsets[i])); - }); -} - void count_indices(const Span indices, MutableSpan counts) { if (indices.size() < 8192 || BLI_system_thread_count() < 4) { @@ -85,11 +73,6 @@ void invert_booleans(MutableSpan span) }); } -void invert_booleans(MutableSpan span, const IndexMask &mask) -{ - mask.foreach_index_optimized([&](const int64_t i) { span[i] = !span[i]; }); -} - BooleanMix booleans_mix_calc(const VArray &varray, const IndexRange range_to_check) { if (varray.is_empty()) { @@ -141,40 +124,4 @@ BooleanMix booleans_mix_calc(const VArray &varray, const IndexRange range_ [&](BooleanMix a, BooleanMix b) { return (a == b) ? a : BooleanMix::Mixed; }); } -int64_t count_booleans(const VArray &varray) -{ - if (varray.is_empty()) { - return 0; - } - const CommonVArrayInfo info = varray.common_info(); - if (info.type == CommonVArrayInfo::Type::Single) { - return *static_cast(info.data) ? varray.size() : 0; - } - if (info.type == CommonVArrayInfo::Type::Span) { - const Span span(static_cast(info.data), varray.size()); - return threading::parallel_reduce( - varray.index_range(), - 4096, - 0, - [&](const IndexRange range, const int64_t init) { - const Span slice = span.slice(range); - return init + std::count(slice.begin(), slice.end(), true); - }, - std::plus()); - } - return threading::parallel_reduce( - varray.index_range(), - 2048, - 0, - [&](const IndexRange range, const int64_t init) { - int64_t value = init; - /* Alternatively, this could use #materialize to retrieve many values at once. */ - for (const int64_t i : range) { - value += int64_t(varray[i]); - } - return value; - }, - std::plus()); -} - } // namespace blender::array_utils diff --git a/source/blender/blenlib/intern/dynlib.cc b/source/blender/blenlib/intern/dynlib.c similarity index 87% rename from source/blender/blenlib/intern/dynlib.cc rename to source/blender/blenlib/intern/dynlib.c index a64d1aaad09..6cf18708890 100644 --- a/source/blender/blenlib/intern/dynlib.cc +++ b/source/blender/blenlib/intern/dynlib.c @@ -20,8 +20,8 @@ struct DynamicLibrary { #ifdef WIN32 # define WIN32_LEAN_AND_MEAN -# include "utf_winfunc.hh" -# include "utfconv.hh" +# include "utf_winfunc.h" +# include "utfconv.h" # include DynamicLibrary *BLI_dynlib_open(const char *name) @@ -37,7 +37,7 @@ DynamicLibrary *BLI_dynlib_open(const char *name) return NULL; } - lib = MEM_cnew("Dynamic Library"); + lib = MEM_callocN(sizeof(*lib), "Dynamic Library"); lib->handle = handle; return lib; @@ -45,7 +45,7 @@ DynamicLibrary *BLI_dynlib_open(const char *name) void *BLI_dynlib_find_symbol(DynamicLibrary *lib, const char *symname) { - return GetProcAddress(HMODULE(lib->handle), symname); + return GetProcAddress(lib->handle, symname); } char *BLI_dynlib_get_error_as_string(DynamicLibrary *lib) @@ -78,7 +78,7 @@ char *BLI_dynlib_get_error_as_string(DynamicLibrary *lib) void BLI_dynlib_close(DynamicLibrary *lib) { - FreeLibrary(HMODULE(lib->handle)); + FreeLibrary(lib->handle); MEM_freeN(lib); } @@ -92,10 +92,10 @@ DynamicLibrary *BLI_dynlib_open(const char *name) void *handle = dlopen(name, RTLD_LAZY); if (!handle) { - return nullptr; + return NULL; } - lib = MEM_cnew("Dynamic Library"); + lib = MEM_callocN(sizeof(*lib), "Dynamic Library"); lib->handle = handle; return lib; diff --git a/source/blender/blenlib/intern/fileops_c.cc b/source/blender/blenlib/intern/fileops.c similarity index 93% rename from source/blender/blenlib/intern/fileops_c.cc rename to source/blender/blenlib/intern/fileops.c index d0cda1617ca..b633ed6435a 100644 --- a/source/blender/blenlib/intern/fileops_c.cc +++ b/source/blender/blenlib/intern/fileops.c @@ -21,8 +21,8 @@ #ifdef WIN32 # include "BLI_fileops_types.h" # include "BLI_winstuff.h" -# include "utf_winfunc.hh" -# include "utfconv.hh" +# include "utf_winfunc.h" +# include "utfconv.h" # include # include # include @@ -44,7 +44,7 @@ #include "BLI_fileops.h" #include "BLI_path_util.h" #include "BLI_string.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_sys_types.h" /* for intptr_t support */ #include "BLI_utildefines.h" @@ -53,7 +53,7 @@ #ifdef WIN32 /* Text string used as the "verb" for Windows shell operations. */ -static const char *windows_operation_string(FileExternalOperation operation) +static char *windows_operation_string(FileExternalOperation operation) { switch (operation) { case FILE_EXTERNAL_OPERATION_OPEN: @@ -145,7 +145,7 @@ int64_t BLI_read(int fd, void *buf, size_t nbytes) bool BLI_file_external_operation_supported(const char *filepath, FileExternalOperation operation) { #ifdef WIN32 - const char *opstring = windows_operation_string(operation); + char *opstring = windows_operation_string(operation); return BLI_windows_external_operation_supported(filepath, opstring); #else UNUSED_VARS(filepath, operation); @@ -156,7 +156,7 @@ bool BLI_file_external_operation_supported(const char *filepath, FileExternalOpe bool BLI_file_external_operation_execute(const char *filepath, FileExternalOperation operation) { #ifdef WIN32 - const char *opstring = windows_operation_string(operation); + char *opstring = windows_operation_string(operation); if (BLI_windows_external_operation_supported(filepath, opstring) && BLI_windows_external_operation_execute(filepath, opstring)) { @@ -262,12 +262,12 @@ bool BLI_file_magic_is_gzip(const char header[4]) bool BLI_file_magic_is_zstd(const char header[4]) { - /* ZSTD files consist of concatenated frames, each either a ZSTD frame or a skippable frame. - * Both types of frames start with a magic number: `0xFD2FB528` for ZSTD frames and `0x184D2A5` + /* ZSTD files consist of concatenated frames, each either a Zstd frame or a skippable frame. + * Both types of frames start with a magic number: 0xFD2FB528 for Zstd frames and 0x184D2A5* * for skippable frames, with the * being anything from 0 to F. * - * To check whether a file is ZSTD-compressed, we just check whether the first frame matches - * either. Seeking through the file until a ZSTD frame is found would make things more + * To check whether a file is Zstd-compressed, we just check whether the first frame matches + * either. Seeking through the file until a Zstd frame is found would make things more * complicated and the probability of a false positive is rather low anyways. * * Note that LZ4 uses a compatible format, so even though its compressed frames have a @@ -316,7 +316,7 @@ bool BLI_file_touch(const char *filepath) { FILE *f = BLI_fopen(filepath, "r+b"); - if (f != nullptr) { + if (f != NULL) { int c = getc(f); if (c == EOF) { @@ -402,7 +402,7 @@ bool BLI_dir_create_recursive(const char *dirname) size_t len = strlen(dirname); if (len >= sizeof(dirname_static_buf)) { - dirname_mut = MEM_cnew_array(len + 1, __func__); + dirname_mut = MEM_mallocN(len + 1, __func__); } memcpy(dirname_mut, dirname, len + 1); @@ -564,23 +564,23 @@ static bool delete_soft(const wchar_t *path_16, const char **error_message) * S_FALSE, which is not an error. Both HRESULT values S_OK and S_FALSE indicate success. */ hr = CoCreateInstance( - CLSID_FileOperation, NULL, CLSCTX_ALL, IID_IFileOperation, (void **)&pfo); + &CLSID_FileOperation, NULL, CLSCTX_ALL, &IID_IFileOperation, (void **)&pfo); if (SUCCEEDED(hr)) { /* Flags for deletion: * FOF_ALLOWUNDO: Enables moving file to recycling bin. * FOF_SILENT: Don't show progress dialog box. * FOF_WANTNUKEWARNING: Show dialog box if file can't be moved to recycling bin. */ - hr = pfo->SetOperationFlags(FOF_ALLOWUNDO | FOF_SILENT | FOF_WANTNUKEWARNING); + hr = pfo->lpVtbl->SetOperationFlags(pfo, FOF_ALLOWUNDO | FOF_SILENT | FOF_WANTNUKEWARNING); if (SUCCEEDED(hr)) { - hr = SHCreateItemFromParsingName(path_16, NULL, IID_IShellItem, (void **)&psi); + hr = SHCreateItemFromParsingName(path_16, NULL, &IID_IShellItem, (void **)&psi); if (SUCCEEDED(hr)) { - hr = pfo->DeleteItem(psi, NULL); + hr = pfo->lpVtbl->DeleteItem(pfo, psi, NULL); if (SUCCEEDED(hr)) { - hr = pfo->PerformOperations(); + hr = pfo->lpVtbl->PerformOperations(pfo); if (FAILED(hr)) { *error_message = "Failed to prepare delete operation"; @@ -589,7 +589,7 @@ static bool delete_soft(const wchar_t *path_16, const char **error_message) else { *error_message = "Failed to prepare delete operation"; } - psi->Release(); + psi->lpVtbl->Release(psi); } else { *error_message = "Failed to parse path"; @@ -598,7 +598,7 @@ static bool delete_soft(const wchar_t *path_16, const char **error_message) else { *error_message = "Failed to set operation flags"; } - pfo->Release(); + pfo->lpVtbl->Release(pfo); } else { *error_message = "Failed to create FileOperation instance"; @@ -731,7 +731,7 @@ static const char *path_destination_ensure_filename(const char *path_src, size_t buf_size_needed = path_dst_len + strlen(filename_src) + 1; char *path_dst_with_filename = (buf_size_needed <= buf_size) ? buf : - MEM_cnew_array(buf_size_needed, __func__); + MEM_mallocN(buf_size_needed, __func__); BLI_string_join(path_dst_with_filename, buf_size_needed, path_dst, filename_src); return path_dst_with_filename; } @@ -822,11 +822,11 @@ static void join_dirfile_alloc(char **dst, size_t *alloc_len, const char *dir, c { size_t len = strlen(dir) + strlen(file) + 1; - if (*dst == nullptr) { - *dst = MEM_cnew_array(len + 1, "join_dirfile_alloc path"); + if (*dst == NULL) { + *dst = MEM_mallocN(len + 1, "join_dirfile_alloc path"); } else if (*alloc_len < len) { - *dst = static_cast(MEM_reallocN(*dst, len + 1)); + *dst = MEM_reallocN(*dst, len + 1); } *alloc_len = len; @@ -862,9 +862,9 @@ static int recursive_operation(const char *startfrom, RecursiveOp_Callback callback_dir_post) { struct stat st; - char *from = NULL, *to = nullptr; - char *from_path = NULL, *to_path = nullptr; - struct dirent **dirlist = nullptr; + char *from = NULL, *to = NULL; + char *from_path = NULL, *to_path = NULL; + struct dirent **dirlist = NULL; size_t from_alloc_len = -1, to_alloc_len = -1; int i, n = 0, ret = 0; @@ -884,7 +884,7 @@ static int recursive_operation(const char *startfrom, if (!S_ISDIR(st.st_mode)) { /* source isn't a directory, can't do recursive walking for it, * so just call file callback and leave */ - if (callback_file != nullptr) { + if (callback_file != NULL) { ret = callback_file(from, to); if (ret != RecursiveOp_Callback_OK) { ret = -1; @@ -893,7 +893,7 @@ static int recursive_operation(const char *startfrom, break; } - n = scandir(startfrom, &dirlist, nullptr, alphasort); + n = scandir(startfrom, &dirlist, NULL, alphasort); if (n < 0) { /* error opening directory for listing */ perror("scandir"); @@ -901,7 +901,7 @@ static int recursive_operation(const char *startfrom, break; } - if (callback_dir_pre != nullptr) { + if (callback_dir_pre != NULL) { ret = callback_dir_pre(from, to); if (ret != RecursiveOp_Callback_OK) { if (ret == RecursiveOp_Callback_StopRecurs) { @@ -946,7 +946,7 @@ static int recursive_operation(const char *startfrom, ret = recursive_operation( from_path, to_path, callback_dir_pre, callback_file, callback_dir_post); } - else if (callback_file != nullptr) { + else if (callback_file != NULL) { ret = callback_file(from_path, to_path); if (ret != RecursiveOp_Callback_OK) { ret = -1; @@ -961,7 +961,7 @@ static int recursive_operation(const char *startfrom, break; } - if (callback_dir_post != nullptr) { + if (callback_dir_post != NULL) { ret = callback_dir_post(from, to); if (ret != RecursiveOp_Callback_OK) { ret = -1; @@ -969,29 +969,29 @@ static int recursive_operation(const char *startfrom, } } while (false); - if (dirlist != nullptr) { + if (dirlist != NULL) { for (i = 0; i < n; i++) { free(dirlist[i]); } free(dirlist); } - if (from_path != nullptr) { + if (from_path != NULL) { MEM_freeN(from_path); } - if (to_path != nullptr) { + if (to_path != NULL) { MEM_freeN(to_path); } - if (from != nullptr) { + if (from != NULL) { MEM_freeN(from); } - if (to != nullptr) { + if (to != NULL) { MEM_freeN(to); } return ret; } -static int delete_callback_post(const char *from, const char * /*to*/) +static int delete_callback_post(const char *from, const char *UNUSED(to)) { if (rmdir(from)) { perror("rmdir"); @@ -1002,7 +1002,7 @@ static int delete_callback_post(const char *from, const char * /*to*/) return RecursiveOp_Callback_OK; } -static int delete_single_file(const char *from, const char * /*to*/) +static int delete_single_file(const char *from, const char *UNUSED(to)) { if (unlink(from)) { perror("unlink"); @@ -1069,14 +1069,14 @@ static int delete_soft(const char *file, const char **error_message) args[1] = "move"; args[2] = file; args[3] = "trash:/"; - args[4] = nullptr; + args[4] = NULL; process_failed = "kioclient5 reported failure"; } else { args[0] = "gio"; args[1] = "trash"; args[2] = file; - args[3] = nullptr; + args[3] = NULL; process_failed = "gio reported failure"; } @@ -1141,7 +1141,7 @@ int BLI_delete(const char *path, bool dir, bool recursive) BLI_assert(!BLI_path_is_rel(path)); if (recursive) { - return recursive_operation(path, nullptr, nullptr, delete_single_file, delete_callback_post); + return recursive_operation(path, NULL, NULL, delete_single_file, delete_callback_post); } if (dir) { return rmdir(path); @@ -1252,7 +1252,7 @@ static int copy_single_file(const char *from, const char *to) need_free = 0; } else { - link_buffer = MEM_cnew_array(st.st_size + 2, "copy_single_file link_buffer"); + link_buffer = MEM_callocN(st.st_size + 2, "copy_single_file link_buffer"); need_free = 1; } @@ -1352,11 +1352,10 @@ static int move_single_file(const char *from, const char *to) int BLI_path_move(const char *path_src, const char *path_dst) { - int ret = recursive_operation(path_src, path_dst, move_callback_pre, move_single_file, nullptr); + int ret = recursive_operation(path_src, path_dst, move_callback_pre, move_single_file, NULL); if (ret && ret != -1) { - return recursive_operation( - path_src, nullptr, nullptr, delete_single_file, delete_callback_post); + return recursive_operation(path_src, NULL, NULL, delete_single_file, delete_callback_post); } return ret; @@ -1374,7 +1373,7 @@ static const char *path_destination_ensure_filename(const char *path_src, const size_t buf_size_needed = strlen(path_dst) + 1 + strlen(filename_src) + 1; char *path_dst_with_filename = (buf_size_needed <= buf_size) ? buf : - MEM_cnew_array(buf_size_needed, __func__); + MEM_mallocN(buf_size_needed, __func__); BLI_path_join(path_dst_with_filename, buf_size_needed, path_dst, filename_src); path_dst = path_dst_with_filename; } @@ -1391,7 +1390,7 @@ int BLI_copy(const char *path_src, const char *path_dst) int ret; ret = recursive_operation( - path_src, path_dst_with_filename, copy_callback_pre, copy_single_file, nullptr); + path_src, path_dst_with_filename, copy_callback_pre, copy_single_file, NULL); if (!ELEM(path_dst_with_filename, path_dst_buf, path_dst)) { MEM_freeN((void *)path_dst_with_filename); diff --git a/source/blender/blenlib/intern/fileops.cc b/source/blender/blenlib/intern/fileops.cc index 32a5d60a400..c402721b891 100644 --- a/source/blender/blenlib/intern/fileops.cc +++ b/source/blender/blenlib/intern/fileops.cc @@ -9,7 +9,7 @@ #include "BLI_fileops.hh" #ifdef WIN32 -# include "utfconv.hh" +# include "utfconv.h" #endif namespace blender { diff --git a/source/blender/blenlib/intern/filereader_memory.c b/source/blender/blenlib/intern/filereader_memory.c index b849f930b4b..fbb29a00c47 100644 --- a/source/blender/blenlib/intern/filereader_memory.c +++ b/source/blender/blenlib/intern/filereader_memory.c @@ -120,7 +120,7 @@ FileReader *BLI_filereader_new_mmap(int filedes) MemoryReader *mem = MEM_callocN(sizeof(MemoryReader), __func__); mem->mmap = mmap; - mem->length = BLI_mmap_get_length(mmap); + mem->length = BLI_lseek(filedes, 0, SEEK_END); mem->reader.read = memory_read_mmap; mem->reader.seek = memory_seek; diff --git a/source/blender/blenlib/intern/listbase.cc b/source/blender/blenlib/intern/listbase.cc index 8e0ddc6ebe5..9af2634c5d3 100644 --- a/source/blender/blenlib/intern/listbase.cc +++ b/source/blender/blenlib/intern/listbase.cc @@ -560,24 +560,17 @@ void *BLI_rfindlink(const ListBase *listbase, int number) return link; } -void *BLI_findlinkfrom(Link *start, int steps) +void *BLI_findlinkfrom(Link *start, int number) { Link *link = nullptr; - if (steps >= 0) { + if (number >= 0) { link = start; - while (link != nullptr && steps != 0) { - steps--; + while (link != nullptr && number != 0) { + number--; link = link->next; } } - else { - link = start; - while (link != nullptr && steps != 0) { - steps++; - link = link->prev; - } - } return link; } diff --git a/source/blender/blenlib/intern/math_matrix.cc b/source/blender/blenlib/intern/math_matrix.cc index 9bba5257aeb..a52219b98b4 100644 --- a/source/blender/blenlib/intern/math_matrix.cc +++ b/source/blender/blenlib/intern/math_matrix.cc @@ -505,8 +505,6 @@ template float4x4 orthographic( float left, float right, float bottom, float top, float near_clip, float far_clip); template float4x4 perspective( float left, float right, float bottom, float top, float near_clip, float far_clip); -template float4x4 perspective_infinite( - float left, float right, float bottom, float top, float near_clip); } // namespace projection diff --git a/source/blender/blenlib/intern/noise.cc b/source/blender/blenlib/intern/noise.cc index 3ec0dc2858d..ab5b8f388f9 100644 --- a/source/blender/blenlib/intern/noise.cc +++ b/source/blender/blenlib/intern/noise.cc @@ -381,9 +381,8 @@ BLI_INLINE float noise_grad(uint32_t hash, float x, float y, float z, float w) BLI_INLINE float floor_fraction(float x, int &i) { - float x_floor = math::floor(x); - i = int(x_floor); - return x - x_floor; + i = int(x) - ((x < 0) ? 1 : 0); + return x - i; } BLI_INLINE float perlin_noise(float position) diff --git a/source/blender/blenlib/intern/path_util.cc b/source/blender/blenlib/intern/path_util.c similarity index 98% rename from source/blender/blenlib/intern/path_util.cc rename to source/blender/blenlib/intern/path_util.c index bb6e0deee23..92c994022af 100644 --- a/source/blender/blenlib/intern/path_util.cc +++ b/source/blender/blenlib/intern/path_util.c @@ -18,12 +18,12 @@ #include "BLI_path_util.h" #include "BLI_string.h" #include "BLI_string_utf8.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_utildefines.h" #ifdef WIN32 -# include "utf_winfunc.hh" -# include "utfconv.hh" +# include "utf_winfunc.h" +# include "utfconv.h" # include # ifdef _WIN32_IE # undef _WIN32_IE @@ -75,10 +75,10 @@ int BLI_path_sequence_decode(const char *path, bool found_digit = false; const char *const lslash = BLI_path_slash_rfind(path); const char *const extension = BLI_path_extension_or_end(lslash ? lslash : path); - const uint lslash_len = lslash != nullptr ? int(lslash - path) : 0; + const uint lslash_len = lslash != NULL ? (int)(lslash - path) : 0; const uint name_end = (uint)(extension - path); - for (i = name_end - 1; i >= int(lslash_len); i--) { + for (i = name_end - 1; i >= (int)lslash_len; i--) { if (isdigit(path[i])) { if (found_digit) { nums = i; @@ -97,7 +97,7 @@ int BLI_path_sequence_decode(const char *path, } if (found_digit) { - const long long int ret = strtoll(&(path[nums]), nullptr, 10); + const long long int ret = strtoll(&(path[nums]), NULL, 10); if (ret >= INT_MIN && ret <= INT_MAX) { if (tail) { BLI_strncpy(tail, &path[nume + 1], tail_maxncpy); @@ -277,7 +277,7 @@ static int path_normalize_impl(char *path, bool check_blend_relative_prefix) (start_temp = ((start <= &path[path_len - 3]) && STREQ(&path[path_len - 3], SEP_STR "..")) ? &path[path_len - 3] : - nullptr)) + NULL)) { start = start_temp + 1; /* Skip the `/`. */ BLI_assert(start_base != start); @@ -852,7 +852,7 @@ const char *BLI_path_parent_dir_end(const char *path, size_t path_len) if ((p > path) && (p != path_end)) { return p; } - return nullptr; + return NULL; } bool BLI_path_parent_dir(char *path) @@ -941,7 +941,7 @@ static bool path_frame_chars_find_range(const char *path, int *char_start, int * static void ensure_digits(char *path, int digits) { char *file = (char *)BLI_path_basename(path); - if (strrchr(file, '#') == nullptr) { + if (strrchr(file, '#') == NULL) { int len = strlen(file); while (digits--) { @@ -1253,7 +1253,7 @@ bool BLI_path_program_extensions_add_win32(char *program_name, const size_t prog const char *ext = BLI_getenv("PATHEXT"); if (ext) { const int program_name_len = strlen(program_name); - char *filename = static_cast(alloca(program_name_len + ext_max)); + char *filename = alloca(program_name_len + ext_max); char *filename_ext; const char *ext_next; @@ -1360,7 +1360,7 @@ void BLI_setenv(const char *env, const char *val) void BLI_setenv_if_new(const char *env, const char *val) { - if (BLI_getenv(env) == nullptr) { + if (BLI_getenv(env) == NULL) { BLI_setenv(env, val); } } @@ -1517,7 +1517,7 @@ bool BLI_path_extension_replace(char *path, size_t path_maxncpy, const char *ext bool BLI_path_extension_strip(char *path) { char *path_ext = (char *)BLI_path_extension(path); - if (path_ext == nullptr) { + if (path_ext == NULL) { return false; } *path_ext = '\0'; @@ -1616,7 +1616,7 @@ const char *BLI_path_extension_or_end(const char *filepath) * Only using trailing extension characters has the advantage that stripping the extension * never leads to a blank string (which can't be used as a file path). * Matches Python's `os.path.splitext`. */ - const char *ext = nullptr; + const char *ext = NULL; bool has_non_ext = false; const char *c = filepath; for (; *c; c++) { @@ -1629,7 +1629,7 @@ const char *BLI_path_extension_or_end(const char *filepath) } case SEP: case ALTSEP: { - ext = nullptr; + ext = NULL; has_non_ext = false; break; } @@ -1649,7 +1649,7 @@ const char *BLI_path_extension_or_end(const char *filepath) const char *BLI_path_extension(const char *filepath) { const char *ext = BLI_path_extension_or_end(filepath); - return *ext ? ext : nullptr; + return *ext ? ext : NULL; } size_t BLI_path_append(char *__restrict dst, const size_t dst_maxncpy, const char *__restrict file) @@ -1987,10 +1987,8 @@ int BLI_path_cmp_normalized(const char *p1, const char *p2) const size_t p1_size = strlen(p1) + 1; const size_t p2_size = strlen(p2) + 1; - char *norm_p1 = (p1_size <= sizeof(norm_p1_buf)) ? norm_p1_buf : - MEM_cnew_array(p1_size, __func__); - char *norm_p2 = (p2_size <= sizeof(norm_p2_buf)) ? norm_p2_buf : - MEM_cnew_array(p2_size, __func__); + char *norm_p1 = (p1_size <= sizeof(norm_p1_buf)) ? norm_p1_buf : MEM_mallocN(p1_size, __func__); + char *norm_p2 = (p2_size <= sizeof(norm_p2_buf)) ? norm_p2_buf : MEM_mallocN(p2_size, __func__); memcpy(norm_p1, p1, p1_size); memcpy(norm_p2, p2, p2_size); diff --git a/source/blender/blenlib/intern/storage.cc b/source/blender/blenlib/intern/storage.c similarity index 90% rename from source/blender/blenlib/intern/storage.cc rename to source/blender/blenlib/intern/storage.c index 2c9a3aabd7c..0215b8695c6 100644 --- a/source/blender/blenlib/intern/storage.cc +++ b/source/blender/blenlib/intern/storage.c @@ -37,7 +37,7 @@ #ifdef WIN32 # include "BLI_string_utf8.h" # include "BLI_winstuff.h" -# include "utfconv.hh" +# include "utfconv.h" # include # include # include @@ -100,7 +100,7 @@ char *BLI_current_working_dir(char *dir, const size_t maxncpy) memcpy(dir, pwd, srclen + 1); return dir; } - return nullptr; + return NULL; } return getcwd(dir, maxncpy); # endif @@ -172,7 +172,7 @@ double BLI_dir_free_space(const char *dir) } # endif - return ((double(disk.f_bsize)) * (double(disk.f_bfree))); + return (((double)disk.f_bsize) * ((double)disk.f_bfree)); #endif } @@ -235,14 +235,14 @@ eFileAttributes BLI_file_attributes(const char *path) WCHAR wline[FILE_MAXDIR]; if (conv_utf_8_to_16(path, wline, ARRAY_SIZE(wline)) != 0) { - return eFileAttributes(ret); + return ret; } DWORD attr = GetFileAttributesW(wline); if (attr == INVALID_FILE_ATTRIBUTES) { BLI_assert_msg(GetLastError() != ERROR_FILE_NOT_FOUND, "BLI_file_attributes should only be called on existing files."); - return eFileAttributes(ret); + return ret; } if (attr & FILE_ATTRIBUTE_READONLY) { @@ -287,7 +287,7 @@ eFileAttributes BLI_file_attributes(const char *path) * If Archived set FILE_ATTR_ARCHIVE */ # endif - return eFileAttributes(ret); + return ret; } #endif @@ -311,30 +311,30 @@ bool BLI_file_alias_target(const char *filepath, IShellLinkW *Shortcut = NULL; hr = CoCreateInstance( - CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLinkW, (LPVOID *)&Shortcut); + &CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, &IID_IShellLinkW, (LPVOID *)&Shortcut); bool success = false; if (SUCCEEDED(hr)) { IPersistFile *PersistFile; - hr = Shortcut->QueryInterface(IID_IPersistFile, (LPVOID *)&PersistFile); + hr = Shortcut->lpVtbl->QueryInterface(Shortcut, &IID_IPersistFile, (LPVOID *)&PersistFile); if (SUCCEEDED(hr)) { WCHAR path_utf16[FILE_MAXDIR] = {0}; if (conv_utf_8_to_16(filepath, path_utf16, ARRAY_SIZE(path_utf16)) == 0) { - hr = PersistFile->Load(path_utf16, STGM_READ); + hr = PersistFile->lpVtbl->Load(PersistFile, path_utf16, STGM_READ); if (SUCCEEDED(hr)) { - hr = Shortcut->Resolve(0, SLR_NO_UI | SLR_UPDATE); + hr = Shortcut->lpVtbl->Resolve(Shortcut, 0, SLR_NO_UI | SLR_UPDATE); if (SUCCEEDED(hr)) { wchar_t target_utf16[FILE_MAXDIR] = {0}; - hr = Shortcut->GetPath(target_utf16, FILE_MAXDIR, NULL, 0); + hr = Shortcut->lpVtbl->GetPath(Shortcut, target_utf16, FILE_MAXDIR, NULL, 0); if (SUCCEEDED(hr)) { success = (conv_utf_16_to_8(target_utf16, r_targetpath, FILE_MAXDIR) == 0); } } - PersistFile->Release(); + PersistFile->lpVtbl->Release(PersistFile); } } } - Shortcut->Release(); + Shortcut->lpVtbl->Release(Shortcut); } CoUninitialize(); @@ -451,45 +451,45 @@ static void *file_read_data_as_mem_impl(FILE *fp, { BLI_stat_t st; if (BLI_fstat(fileno(fp), &st) == -1) { - return nullptr; + return NULL; } if (S_ISDIR(st.st_mode)) { - return nullptr; + return NULL; } if (BLI_fseek(fp, 0L, SEEK_END) == -1) { - return nullptr; + return NULL; } /* Don't use the 'st_size' because it may be the symlink. */ const long int filelen = BLI_ftell(fp); if (filelen == -1) { - return nullptr; + return NULL; } if (BLI_fseek(fp, 0L, SEEK_SET) == -1) { - return nullptr; + return NULL; } void *mem = MEM_mallocN(filelen + pad_bytes, __func__); - if (mem == nullptr) { - return nullptr; + if (mem == NULL) { + return NULL; } const long int filelen_read = fread(mem, 1, filelen, fp); if ((filelen_read < 0) || ferror(fp)) { MEM_freeN(mem); - return nullptr; + return NULL; } if (read_size_exact) { if (filelen_read != filelen) { MEM_freeN(mem); - return nullptr; + return NULL; } } else { if (filelen_read < filelen) { mem = MEM_reallocN(mem, filelen_read + pad_bytes); - if (mem == nullptr) { - return nullptr; + if (mem == NULL) { + return NULL; } } } @@ -502,7 +502,7 @@ static void *file_read_data_as_mem_impl(FILE *fp, void *BLI_file_read_text_as_mem(const char *filepath, size_t pad_bytes, size_t *r_size) { FILE *fp = BLI_fopen(filepath, "r"); - void *mem = nullptr; + void *mem = NULL; if (fp) { mem = file_read_data_as_mem_impl(fp, false, pad_bytes, r_size); fclose(fp); @@ -513,7 +513,7 @@ void *BLI_file_read_text_as_mem(const char *filepath, size_t pad_bytes, size_t * void *BLI_file_read_binary_as_mem(const char *filepath, size_t pad_bytes, size_t *r_size) { FILE *fp = BLI_fopen(filepath, "rb"); - void *mem = nullptr; + void *mem = NULL; if (fp) { mem = file_read_data_as_mem_impl(fp, true, pad_bytes, r_size); fclose(fp); @@ -526,15 +526,15 @@ void *BLI_file_read_text_as_mem_with_newline_as_nil(const char *filepath, size_t pad_bytes, size_t *r_size) { - char *mem = static_cast(BLI_file_read_text_as_mem(filepath, pad_bytes, r_size)); - if (mem != nullptr) { + char *mem = BLI_file_read_text_as_mem(filepath, pad_bytes, r_size); + if (mem != NULL) { char *mem_end = mem + *r_size; if (pad_bytes != 0) { *mem_end = '\0'; } for (char *p = mem, *p_next; p != mem_end; p = p_next) { - p_next = static_cast(memchr(p, '\n', mem_end - p)); - if (p_next != nullptr) { + p_next = memchr(p, '\n', mem_end - p); + if (p_next != NULL) { if (trim_trailing_space) { for (char *p_trim = p_next - 1; p_trim > p && ELEM(*p_trim, ' ', '\t'); p_trim--) { *p_trim = '\0'; @@ -554,24 +554,24 @@ void *BLI_file_read_text_as_mem_with_newline_as_nil(const char *filepath, LinkNode *BLI_file_read_as_lines(const char *filepath) { FILE *fp = BLI_fopen(filepath, "r"); - LinkNodePair lines = {nullptr, nullptr}; + LinkNodePair lines = {NULL, NULL}; char *buf; size_t size; if (!fp) { - return nullptr; + return NULL; } BLI_fseek(fp, 0, SEEK_END); size = (size_t)BLI_ftell(fp); BLI_fseek(fp, 0, SEEK_SET); - if (UNLIKELY(size == size_t(-1))) { + if (UNLIKELY(size == (size_t)-1)) { fclose(fp); - return nullptr; + return NULL; } - buf = MEM_cnew_array(size, "file_as_lines"); + buf = MEM_mallocN(size, "file_as_lines"); if (buf) { size_t i, last = 0; diff --git a/source/blender/blenlib/intern/string_utf8.cc b/source/blender/blenlib/intern/string_utf8.c similarity index 96% rename from source/blender/blenlib/intern/string_utf8.cc rename to source/blender/blenlib/intern/string_utf8.c index 67dcab4f90f..50ce09ea1d7 100644 --- a/source/blender/blenlib/intern/string_utf8.cc +++ b/source/blender/blenlib/intern/string_utf8.c @@ -22,7 +22,7 @@ #include "BLI_string.h" /* #BLI_string_debug_size. */ #include "BLI_string_utf8.h" /* own include */ #ifdef WIN32 -# include "utfconv.hh" +# include "utfconv.h" #endif #ifdef __GNUC__ # pragma GCC diagnostic error "-Wsign-conversion" @@ -180,7 +180,7 @@ ptrdiff_t BLI_str_utf8_invalid_byte(const char *str, size_t length) * we only add/subtract extra utf8 bytes in code below * (ab number, aka number of bytes remaining in the utf8 sequence after the initial one). */ ab = utf8_char_compute_skip(c) - 1; - if (length <= size_t(ab)) { + if (length <= (size_t)ab) { goto utf8_error; } @@ -194,14 +194,14 @@ ptrdiff_t BLI_str_utf8_invalid_byte(const char *str, size_t length) /* Check for overlong sequences for each different length */ switch (ab) { case 1: - /* Check for: XX00 000X. */ + /* Check for xx00 000x */ if ((c & 0x3e) == 0) { goto utf8_error; } continue; /* We know there aren't any more bytes to check */ case 2: - /* Check for: 1110 0000, XX0X XXXX. */ + /* Check for 1110 0000, xx0x xxxx */ if (c == 0xe0 && (*p & 0x20) == 0) { goto utf8_error; } @@ -243,28 +243,28 @@ ptrdiff_t BLI_str_utf8_invalid_byte(const char *str, size_t length) break; case 3: - /* Check for: 1111 0000, XX00 XXXX. */ + /* Check for 1111 0000, xx00 xxxx */ if (c == 0xf0 && (*p & 0x30) == 0) { goto utf8_error; } break; case 4: - /* Check for 1111 1000, XX00 0XXX. */ + /* Check for 1111 1000, xx00 0xxx */ if (c == 0xf8 && (*p & 0x38) == 0) { goto utf8_error; } break; case 5: - /* Check for: 1111 1100, XX00 00XX. */ + /* Check for 1111 1100, xx00 00xx */ if (c == 0xfc && (*p & 0x3c) == 0) { goto utf8_error; } break; } - /* Check for valid bytes after the 2nd, if any; all must start 10. */ + /* Check for valid bytes after the 2nd, if any; all must start 10 */ while (--ab > 0) { p++; length--; @@ -290,7 +290,7 @@ int BLI_str_utf8_invalid_strip(char *str, size_t length) while ((bad_char = BLI_str_utf8_invalid_byte(str, length)) != -1) { str += bad_char; - length -= size_t(bad_char + 1); + length -= (size_t)(bad_char + 1); if (length == 0) { /* last character bad, strip it */ @@ -483,7 +483,7 @@ int BLI_str_utf8_char_width_or_error(const char *p) return -1; } - return BLI_wcwidth_or_error(char32_t(unicode)); + return BLI_wcwidth_or_error((char32_t)unicode); } int BLI_str_utf8_char_width_safe(const char *p) @@ -493,7 +493,7 @@ int BLI_str_utf8_char_width_safe(const char *p) return 1; } - return BLI_wcwidth_safe(char32_t(unicode)); + return BLI_wcwidth_safe((char32_t)unicode); } /* -------------------------------------------------------------------- */ @@ -598,10 +598,10 @@ char32_t BLI_str_utf32_char_to_upper(const char32_t wc) if (wc >= from[0] && wc <= from[ARRAY_SIZE(from) - 2]) { /* Binary search since these are sorted. */ - size_t min = 0; - size_t max = ARRAY_SIZE(from) - 2; + int min = 0; + int max = ARRAY_SIZE(from) - 2; while (max >= min) { - const size_t mid = (min + max) / 2; + const int mid = (min + max) / 2; if (wc > from[mid]) { min = mid + 1; } @@ -704,10 +704,10 @@ char32_t BLI_str_utf32_char_to_lower(const char32_t wc) if (wc >= from[0] && wc <= from[ARRAY_SIZE(from) - 2]) { /* Binary search since these are sorted. */ - size_t min = 0; - size_t max = ARRAY_SIZE(from) - 2; + int min = 0; + int max = ARRAY_SIZE(from) - 2; while (max >= min) { - const size_t mid = (min + max) / 2; + const int mid = (min + max) / 2; if (wc > from[mid]) { min = mid + 1; } @@ -739,7 +739,7 @@ uint BLI_str_utf8_as_unicode_or_error(const char *p) { /* Originally `g_utf8_get_char` in GLIB. */ - const uchar c = uchar(*p); + const uchar c = (uchar)*p; char mask = 0; const int len = utf8_char_compute_skip_or_error_with_mask(c, &mask); @@ -769,7 +769,7 @@ uint BLI_str_utf8_as_unicode_step_or_error(const char *__restrict p, char mask = 0; const int len = utf8_char_compute_skip_or_error_with_mask(c, &mask); - if (UNLIKELY(len == -1) || (*index + size_t(len) > p_len)) { + if (UNLIKELY(len == -1) || (*index + (size_t)len > p_len)) { return BLI_UTF8_ERR; } @@ -777,7 +777,7 @@ uint BLI_str_utf8_as_unicode_step_or_error(const char *__restrict p, if (UNLIKELY(result == BLI_UTF8_ERR)) { return BLI_UTF8_ERR; } - *index += size_t(len); + *index += (size_t)len; BLI_assert(*index <= p_len); return result; } @@ -969,7 +969,7 @@ size_t BLI_str_partition_utf8(const char *str, const char **r_sep, const char **r_suf) { - return BLI_str_partition_ex_utf8(str, nullptr, delim, r_sep, r_suf, false); + return BLI_str_partition_ex_utf8(str, NULL, delim, r_sep, r_suf, false); } size_t BLI_str_rpartition_utf8(const char *str, @@ -977,7 +977,7 @@ size_t BLI_str_rpartition_utf8(const char *str, const char **r_sep, const char **r_suf) { - return BLI_str_partition_ex_utf8(str, nullptr, delim, r_sep, r_suf, true); + return BLI_str_partition_ex_utf8(str, NULL, delim, r_sep, r_suf, true); } size_t BLI_str_partition_ex_utf8(const char *str, @@ -988,7 +988,7 @@ size_t BLI_str_partition_ex_utf8(const char *str, const bool from_right) { const size_t str_len = end ? (size_t)(end - str) : strlen(str); - if (end == nullptr) { + if (end == NULL) { end = str + str_len; } @@ -999,7 +999,7 @@ size_t BLI_str_partition_ex_utf8(const char *str, size_t index = 0; for (char *sep = (char *)(from_right ? BLI_str_find_prev_char_utf8(end, str) : str); from_right ? (sep > str) : ((sep < end) && (*sep != '\0')); - sep = (char *)(from_right ? (str != sep ? BLI_str_find_prev_char_utf8(sep, str) : nullptr) : + sep = (char *)(from_right ? (str != sep ? BLI_str_find_prev_char_utf8(sep, str) : NULL) : str + index)) { size_t index_ofs = 0; @@ -1021,7 +1021,7 @@ size_t BLI_str_partition_ex_utf8(const char *str, suf = sep; /* Useful in 'from_right' case! */ } - *r_suf = *r_sep = nullptr; + *r_suf = *r_sep = NULL; return str_len; } @@ -1038,7 +1038,7 @@ size_t BLI_str_partition_ex_utf8(const char *str, int BLI_str_utf8_offset_to_index(const char *str, const size_t str_len, const int offset_target) { BLI_assert(offset_target >= 0); - const size_t offset_target_as_size = size_t(offset_target); + const size_t offset_target_as_size = (size_t)offset_target; size_t offset = 0; int index = 0; /* Note that `offset != offset_target_as_size` works for valid utf8 strings. */ @@ -1069,7 +1069,7 @@ int BLI_str_utf8_offset_from_index(const char *str, const size_t str_len, const int BLI_str_utf8_offset_to_column(const char *str, const size_t str_len, const int offset_target) { BLI_assert(offset_target >= 0); - const size_t offset_target_clamp = MIN2(size_t(offset_target), str_len); + const size_t offset_target_clamp = MIN2((size_t)offset_target, str_len); size_t offset = 0; int column = 0; while (offset < offset_target_clamp) { @@ -1101,7 +1101,7 @@ int BLI_str_utf8_offset_to_column_with_tabs(const char *str, const int tab_width) { BLI_assert(offset_target >= 0); - const size_t offset_target_clamp = MIN2(size_t(offset_target), str_len); + const size_t offset_target_clamp = MIN2((size_t)offset_target, str_len); size_t offset = 0; int column = 0; while (offset < offset_target_clamp) { diff --git a/source/blender/blenlib/intern/string_utils.cc b/source/blender/blenlib/intern/string_utils.c similarity index 85% rename from source/blender/blenlib/intern/string_utils.cc rename to source/blender/blenlib/intern/string_utils.c index 9dee1a128d9..1e3ce101f36 100644 --- a/source/blender/blenlib/intern/string_utils.cc +++ b/source/blender/blenlib/intern/string_utils.c @@ -10,14 +10,11 @@ #include #include -#include - #include "MEM_guardedalloc.h" -#include "BLI_array.hh" #include "BLI_string.h" #include "BLI_string_utf8.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_utildefines.h" #include "BLI_dynstr.h" @@ -34,7 +31,7 @@ char *BLI_string_replaceN(const char *__restrict str, const char *__restrict substr_old, const char *__restrict substr_new) { - DynStr *ds = nullptr; + DynStr *ds = NULL; size_t len_old = strlen(substr_old); const char *match; @@ -44,7 +41,7 @@ char *BLI_string_replaceN(const char *__restrict str, * keep dicing and replacing. */ while ((match = strstr(str, substr_old))) { /* the assembly buffer only gets created when we actually need to rebuild the string */ - if (ds == nullptr) { + if (ds == NULL) { ds = BLI_dynstr_new(); } @@ -53,7 +50,7 @@ char *BLI_string_replaceN(const char *__restrict str, if (str != match) { /* Add the segment of the string from `str` to match to the buffer, * then restore the value at match. */ - BLI_dynstr_nappend(ds, str, int(match - str)); + BLI_dynstr_nappend(ds, str, (int)(match - str)); /* now our current position should be set on the start of the match */ str = match; @@ -124,17 +121,17 @@ size_t BLI_string_replace_range( /* Grow, first handle special cases. */ /* Special case, the src_end is entirely clipped. */ - if (UNLIKELY(int(string_maxncpy) <= src_beg + dst_len)) { + if (UNLIKELY((int)string_maxncpy <= src_beg + dst_len)) { /* There is only room for the destination. */ - dst_len = (int(string_maxncpy) - src_beg) - 1; + dst_len = ((int)string_maxncpy - src_beg) - 1; string_len = src_end; string[string_len] = '\0'; } const int ofs = dst_len - src_len; /* Clip the string when inserting the destination string exceeds `string_maxncpy`. */ - if (string_len + ofs >= int(string_maxncpy)) { - string_len = (int(string_maxncpy) - ofs) - 1; + if (string_len + ofs >= (int)string_maxncpy) { + string_len = ((int)string_maxncpy - ofs) - 1; string[string_len] = '\0'; BLI_assert(src_end <= string_len); } @@ -154,7 +151,7 @@ size_t BLI_string_replace_range( } if (dst_len > 0) { - memcpy(string + src_beg, dst, size_t(dst_len)); + memcpy(string + src_beg, dst, (size_t)dst_len); } BLI_assert(string[string_len] == '\0'); return (size_t)string_len; @@ -272,11 +269,11 @@ size_t BLI_string_flip_side_name(char *name_dst, BLI_string_debug_size(name_dst, name_dst_maxncpy); size_t len; - char *prefix = static_cast(alloca(name_dst_maxncpy)); /* The part before the facing */ - char *suffix = static_cast(alloca(name_dst_maxncpy)); /* The part after the facing */ - char *number = static_cast(alloca(name_dst_maxncpy)); /* The number extension string */ - const char *replace = nullptr; - char *index = nullptr; + char *prefix = alloca(name_dst_maxncpy); /* The part before the facing */ + char *suffix = alloca(name_dst_maxncpy); /* The part after the facing */ + char *number = alloca(name_dst_maxncpy); /* The number extension string */ + const char *replace = NULL; + char *index = NULL; bool is_set = false; *prefix = *suffix = *number = '\0'; @@ -357,7 +354,7 @@ size_t BLI_string_flip_side_name(char *name_dst, } if (!is_set && len > 5) { - /* Test for a separator to apply the rule: ultimate left or right. */ + /* hrms, why test for a separator? lets do the rule 'ultimate left or right' */ if (((index = BLI_strcasestr(prefix, "right")) == prefix) || (index == prefix + len - 5)) { is_set = true; if (index[0] == 'r') { @@ -388,7 +385,7 @@ size_t BLI_string_flip_side_name(char *name_dst, /* Unique name utils. */ -void BLI_uniquename_cb(UniquenameCheckCallback unique_check, +bool BLI_uniquename_cb(UniquenameCheckCallback unique_check, void *arg, const char *defname, char delim, @@ -403,8 +400,8 @@ void BLI_uniquename_cb(UniquenameCheckCallback unique_check, if (unique_check(arg, name)) { char numstr[16]; - char *tempname = static_cast(alloca(name_maxncpy)); - char *left = static_cast(alloca(name_maxncpy)); + char *tempname = alloca(name_maxncpy); + char *left = alloca(name_maxncpy); int number; size_t len = BLI_string_split_name_number(name, delim, left, &number); do { @@ -424,39 +421,11 @@ void BLI_uniquename_cb(UniquenameCheckCallback unique_check, } while (unique_check(arg, tempname)); BLI_strncpy(name, tempname, name_maxncpy); - } -} -std::string BLI_uniquename_cb(blender::FunctionRef unique_check, - const char delim, - const blender::StringRef name) -{ - std::string new_name = name; - - if (!unique_check(new_name)) { - return new_name; + return true; } - int number; - blender::Array left_buffer(int64_t(new_name.size()) + 1); - const size_t len = BLI_string_split_name_number( - new_name.c_str(), delim, left_buffer.data(), &number); - - const std::string left = left_buffer.data(); - - do { - std::array num_str; - BLI_snprintf(num_str.data(), num_str.size(), "%c%03d", delim, ++number); - - if (len == 0) { - new_name = num_str.data(); - } - else { - new_name = left + num_str.data(); - } - } while (unique_check(new_name)); - - return new_name; + return false; } /** @@ -470,10 +439,11 @@ std::string BLI_uniquename_cb(blender::FunctionRef uni */ static bool uniquename_find_dupe(ListBase *list, void *vlink, const char *name, int name_offset) { - for (Link *link = static_cast(list->first); link; link = link->next) { + Link *link; + + for (link = list->first; link; link = link->next) { if (link != vlink) { - if (STREQ(static_cast(POINTER_OFFSET((const char *)link, name_offset)), name)) - { + if (STREQ(POINTER_OFFSET((const char *)link, name_offset), name)) { return true; } } @@ -482,27 +452,28 @@ static bool uniquename_find_dupe(ListBase *list, void *vlink, const char *name, return false; } -struct UniqueNameCheckData { - ListBase *lb; - void *vlink; - int name_offset; -}; - static bool uniquename_unique_check(void *arg, const char *name) { - UniqueNameCheckData *data = static_cast(arg); - + struct { + ListBase *lb; + void *vlink; + int name_offset; + } *data = arg; return uniquename_find_dupe(data->lb, data->vlink, name, data->name_offset); } -void BLI_uniquename(ListBase *list, +bool BLI_uniquename(ListBase *list, void *vlink, const char *defname, char delim, int name_offset, size_t name_maxncpy) { - UniqueNameCheckData data{}; + struct { + ListBase *lb; + void *vlink; + int name_offset; + } data; data.lb = list; data.vlink = vlink; data.name_offset = name_offset; @@ -510,16 +481,16 @@ void BLI_uniquename(ListBase *list, BLI_assert(name_maxncpy > 1); /* See if we are given an empty string */ - if (ELEM(nullptr, vlink)) { - return; + if (ELEM(NULL, vlink)) { + return false; } - BLI_uniquename_cb(uniquename_unique_check, - &data, - defname, - delim, - static_cast(POINTER_OFFSET(vlink, name_offset)), - name_maxncpy); + return BLI_uniquename_cb(uniquename_unique_check, + &data, + defname, + delim, + POINTER_OFFSET(vlink, name_offset), + name_maxncpy); } size_t BLI_string_len_array(const char *strings[], uint strings_num) @@ -595,7 +566,7 @@ size_t BLI_string_join_array_by_sep_char( char *BLI_string_join_arrayN(const char *strings[], uint strings_num) { const size_t result_size = BLI_string_len_array(strings, strings_num) + 1; - char *result = MEM_cnew_array(result_size, __func__); + char *result = MEM_mallocN(sizeof(char) * result_size, __func__); char *c = result; for (uint i = 0; i < strings_num; i++) { const size_t string_len = strlen(strings[i]); @@ -612,7 +583,7 @@ char *BLI_string_join_array_by_sep_charN(char sep, const char *strings[], uint s { const size_t result_size = BLI_string_len_array(strings, strings_num) + (strings_num ? strings_num - 1 : 0) + 1; - char *result = MEM_cnew_array(result_size, __func__); + char *result = MEM_mallocN(sizeof(char) * result_size, __func__); char *c = result; if (strings_num != 0) { for (uint i = 0; i < strings_num; i++) { @@ -642,7 +613,7 @@ char *BLI_string_join_array_by_sep_char_with_tableN(char sep, result_size = 1; } - char *result = MEM_cnew_array(result_size, __func__); + char *result = MEM_mallocN(sizeof(char) * result_size, __func__); char *c = result; if (strings_num != 0) { for (uint i = 0; i < strings_num; i++) { diff --git a/source/blender/blenlib/intern/vector.cc b/source/blender/blenlib/intern/vector.cc index e17dcb5c77a..b3f594721cb 100644 --- a/source/blender/blenlib/intern/vector.cc +++ b/source/blender/blenlib/intern/vector.cc @@ -8,7 +8,7 @@ #include void blender::internal::vector_print_stats(const char *name, - const void *address, + void *address, int64_t size, int64_t capacity, int64_t inlineCapacity, diff --git a/source/blender/blenlib/intern/winstuff.cc b/source/blender/blenlib/intern/winstuff.c similarity index 96% rename from source/blender/blenlib/intern/winstuff.cc rename to source/blender/blenlib/intern/winstuff.c index 777799d148d..c08721e633a 100644 --- a/source/blender/blenlib/intern/winstuff.cc +++ b/source/blender/blenlib/intern/winstuff.c @@ -25,8 +25,8 @@ # include "BLI_utildefines.h" # include "BLI_winstuff.h" -# include "utf_winfunc.hh" -# include "utfconv.hh" +# include "utf_winfunc.h" +# include "utfconv.h" /* FILE_MAXDIR + FILE_MAXFILE */ @@ -458,25 +458,27 @@ bool BLI_windows_get_directx_driver_version(const wchar_t *deviceSubString, { IDXGIFactory *pFactory = NULL; IDXGIAdapter *pAdapter = NULL; - if (CreateDXGIFactory(__uuidof(IDXGIFactory), (void **)&pFactory) == S_OK) { - for (UINT i = 0; pFactory->EnumAdapters(i, &pAdapter) != DXGI_ERROR_NOT_FOUND; ++i) { + if (CreateDXGIFactory(&IID_IDXGIFactory, (void **)&pFactory) == S_OK) { + for (UINT i = 0; IDXGIFactory_EnumAdapters(pFactory, i, &pAdapter) != DXGI_ERROR_NOT_FOUND; + ++i) { LARGE_INTEGER version; - if (pAdapter->CheckInterfaceSupport(__uuidof(IDXGIDevice), &version) == S_OK) { + if (IDXGIAdapter_CheckInterfaceSupport(pAdapter, &IID_IDXGIDevice, &version) == S_OK) { DXGI_ADAPTER_DESC desc; - if (pAdapter->GetDesc(&desc) == S_OK) { + if (IDXGIAdapter_GetDesc(pAdapter, &desc) == S_OK) { if (wcsstr(desc.Description, deviceSubString)) { *r_driverVersion = version.QuadPart; - pAdapter->Release(); - pFactory->Release(); + IDXGIAdapter_Release(pAdapter); + IDXGIFactory_Release(pFactory); return true; } } } - pAdapter->Release(); + IDXGIAdapter_Release(pAdapter); } - pFactory->Release(); + + IDXGIFactory_Release(pFactory); } return false; diff --git a/source/blender/blenlib/intern/winstuff_dir.cc b/source/blender/blenlib/intern/winstuff_dir.c similarity index 97% rename from source/blender/blenlib/intern/winstuff_dir.cc rename to source/blender/blenlib/intern/winstuff_dir.c index f655fff58a6..b687fb544da 100644 --- a/source/blender/blenlib/intern/winstuff_dir.cc +++ b/source/blender/blenlib/intern/winstuff_dir.c @@ -23,7 +23,7 @@ # define WIN32_SKIP_HKEY_PROTECTION /* Need to use `HKEY`. */ # include "BLI_utildefines.h" # include "BLI_winstuff.h" -# include "utfconv.hh" +# include "utfconv.h" # define PATH_SUFFIX "\\*" # define PATH_SUFFIX_LEN 2 @@ -58,7 +58,7 @@ DIR *opendir(const char *path) if ((GetFileAttributesW(path_16) & FILE_ATTRIBUTE_DIRECTORY) && ((path_len = strlen(path)) < (sizeof(newd->path) - PATH_SUFFIX_LEN))) { - newd = static_cast(MEM_mallocN(sizeof(DIR), "opendir")); + newd = MEM_mallocN(sizeof(DIR), "opendir"); newd->handle = INVALID_HANDLE_VALUE; memcpy(newd->path, path, path_len); memcpy(newd->path + path_len, PATH_SUFFIX, PATH_SUFFIX_LEN + 1); diff --git a/source/blender/blenlib/tests/BLI_math_matrix_test.cc b/source/blender/blenlib/tests/BLI_math_matrix_test.cc index 11eb04cb582..75169ecd80d 100644 --- a/source/blender/blenlib/tests/BLI_math_matrix_test.cc +++ b/source/blender/blenlib/tests/BLI_math_matrix_test.cc @@ -274,15 +274,6 @@ TEST(math_matrix, MatrixInit) {-1.10289, 2.70714, -0.674535, 0}, {1, 2, 3, 1}); EXPECT_TRUE(is_equal(m, expect, 0.00001f)); - - float3 up = normalize(float3(1, 2, 3)); - m = from_up_axis(up); - /* Output is not expected to be stable. Just test if they satisfy the expectations. */ - EXPECT_EQ(m.z_axis(), up); - EXPECT_LE(abs(dot(m.z_axis(), m.x_axis())), 0.000001f); - EXPECT_LE(abs(dot(m.y_axis(), m.x_axis())), 0.000001f); - EXPECT_LE(abs(dot(m.z_axis(), m.y_axis())), 0.000001f); - EXPECT_NEAR(1.0f, determinant(m), 1e-6); } TEST(math_matrix, MatrixModify) diff --git a/source/blender/blenlib/tests/BLI_memiter_test.cc b/source/blender/blenlib/tests/BLI_memiter_test.cc index a06bfbfa503..1fe3986a3cc 100644 --- a/source/blender/blenlib/tests/BLI_memiter_test.cc +++ b/source/blender/blenlib/tests/BLI_memiter_test.cc @@ -8,7 +8,7 @@ #include "BLI_array_utils.h" #include "BLI_memiter.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_ressource_strings.h" #include "BLI_string.h" diff --git a/source/blender/blenlib/tests/BLI_path_util_test.cc b/source/blender/blenlib/tests/BLI_path_util_test.cc index ed1ca02ed69..d81a369f4b8 100644 --- a/source/blender/blenlib/tests/BLI_path_util_test.cc +++ b/source/blender/blenlib/tests/BLI_path_util_test.cc @@ -9,7 +9,7 @@ #include "BLI_fileops.h" #include "BLI_path_util.h" #include "BLI_string.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" /* -------------------------------------------------------------------- */ /** \name Local Utilities diff --git a/source/blender/blenlib/tests/BLI_stack_test.cc b/source/blender/blenlib/tests/BLI_stack_test.cc index 47f5d2122df..71699e85c18 100644 --- a/source/blender/blenlib/tests/BLI_stack_test.cc +++ b/source/blender/blenlib/tests/BLI_stack_test.cc @@ -5,6 +5,7 @@ #include "testing/testing.h" #include +#include "BLI_array.h" #include "BLI_stack.h" #include "BLI_utildefines.h" diff --git a/source/blender/blenlib/tests/BLI_string_test.cc b/source/blender/blenlib/tests/BLI_string_test.cc index 41b670433fa..b7c30857813 100644 --- a/source/blender/blenlib/tests/BLI_string_test.cc +++ b/source/blender/blenlib/tests/BLI_string_test.cc @@ -15,7 +15,7 @@ #include "BLI_string.h" #include "BLI_string_utf8.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_utildefines.h" using std::initializer_list; diff --git a/source/blender/blenlib/tests/BLI_string_utils_test.cc b/source/blender/blenlib/tests/BLI_string_utils_test.cc deleted file mode 100644 index ab3da8baac3..00000000000 --- a/source/blender/blenlib/tests/BLI_string_utils_test.cc +++ /dev/null @@ -1,81 +0,0 @@ -/* SPDX-FileCopyrightText: 2023 Blender Authors - * - * SPDX-License-Identifier: Apache-2.0 */ - -#include "BLI_string_utils.hh" - -#include - -#include "testing/testing.h" - -#include "BLI_vector.hh" - -namespace blender { - -static bool unique_check_func(void *arg, const char *name) -{ - const Vector *current_names = static_cast *>(arg); - return current_names->contains(name); -} - -TEST(BLI_string_utils, BLI_uniquename_cb) -{ - const Vector current_names{"Foo", "Bar", "Bar.003", "Baz.001", "Big.999"}; - - /* C version. */ - { - void *arg = const_cast(static_cast(¤t_names)); - - { - char name[64] = ""; - BLI_uniquename_cb(unique_check_func, arg, "Default Name", '.', name, sizeof(name)); - EXPECT_STREQ(name, "Default Name"); - } - - { - char name[64] = "Baz"; - BLI_uniquename_cb(unique_check_func, arg, "Default Name", '.', name, sizeof(name)); - EXPECT_STREQ(name, "Baz"); - } - - { - char name[64] = "Foo"; - BLI_uniquename_cb(unique_check_func, arg, "Default Name", '.', name, sizeof(name)); - EXPECT_STREQ(name, "Foo.001"); - } - - { - char name[64] = "Baz.001"; - BLI_uniquename_cb(unique_check_func, arg, "Default Name", '.', name, sizeof(name)); - EXPECT_STREQ(name, "Baz.002"); - } - - { - char name[64] = "Bar.003"; - BLI_uniquename_cb(unique_check_func, arg, "Default Name", '.', name, sizeof(name)); - EXPECT_STREQ(name, "Bar.004"); - } - - { - char name[64] = "Big.999"; - BLI_uniquename_cb(unique_check_func, arg, "Default Name", '.', name, sizeof(name)); - EXPECT_STREQ(name, "Big.1000"); - } - } - - /* C++ version. */ - { - const auto unique_check = [&](const blender::StringRef name) -> bool { - return current_names.contains(name); - }; - - EXPECT_EQ(BLI_uniquename_cb(unique_check, '.', ""), ""); - EXPECT_EQ(BLI_uniquename_cb(unique_check, '.', "Baz"), "Baz"); - EXPECT_EQ(BLI_uniquename_cb(unique_check, '.', "Foo"), "Foo.001"); - EXPECT_EQ(BLI_uniquename_cb(unique_check, '.', "Baz.001"), "Baz.002"); - EXPECT_EQ(BLI_uniquename_cb(unique_check, '.', "Bar.003"), "Bar.004"); - EXPECT_EQ(BLI_uniquename_cb(unique_check, '.', "Big.999"), "Big.1000"); - } -} - -} // namespace blender \ No newline at end of file diff --git a/source/blender/blenloader/intern/versioning_260.cc b/source/blender/blenloader/intern/versioning_260.cc index f75fc0b7bc0..ac5dbbfca4d 100644 --- a/source/blender/blenloader/intern/versioning_260.cc +++ b/source/blender/blenloader/intern/versioning_260.cc @@ -47,7 +47,7 @@ #include "BLI_math_matrix.h" #include "BLI_math_rotation.h" #include "BLI_math_vector.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLT_translation.h" @@ -78,7 +78,7 @@ #include "IMB_imbuf.h" /* for proxy / time-code versioning stuff. */ #include "NOD_common.h" -#include "NOD_composite.hh" +#include "NOD_composite.h" #include "NOD_texture.h" #include "BLO_readfile.h" diff --git a/source/blender/blenloader/intern/versioning_270.cc b/source/blender/blenloader/intern/versioning_270.cc index 24e58b003e7..20a7568e74c 100644 --- a/source/blender/blenloader/intern/versioning_270.cc +++ b/source/blender/blenloader/intern/versioning_270.cc @@ -12,8 +12,6 @@ /* for MinGW32 definition of NULL, could use BLI_blenlib.h instead too */ #include -#include - /* allow readfile to use deprecated functionality */ #define DNA_DEPRECATED_ALLOW @@ -69,14 +67,14 @@ #include "BLI_math_rotation.h" #include "BLI_math_vector.h" #include "BLI_string.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLT_translation.h" #include "BLO_readfile.h" #include "NOD_common.h" -#include "NOD_composite.hh" +#include "NOD_composite.h" #include "NOD_socket.hh" #include "readfile.hh" @@ -529,9 +527,9 @@ void blo_do_versions_270(FileData *fd, Library * /*lib*/, Main *bmain) } if (!MAIN_VERSION_FILE_ATLEAST(bmain, 270, 2)) { - /* Mesh smoothresh_legacy deg->rad. */ + /* Mesh smoothresh deg->rad. */ LISTBASE_FOREACH (Mesh *, me, &bmain->meshes) { - me->smoothresh_legacy = DEG2RADF(me->smoothresh_legacy); + me->smoothresh = DEG2RADF(me->smoothresh); } } @@ -807,9 +805,7 @@ void blo_do_versions_270(FileData *fd, Library * /*lib*/, Main *bmain) if (!MAIN_VERSION_FILE_ATLEAST(bmain, 273, 8)) { LISTBASE_FOREACH (Object *, ob, &bmain->objects) { LISTBASE_FOREACH (ModifierData *, md, &ob->modifiers) { - const std::string old_name = md->name; - BKE_modifier_unique_name(&ob->modifiers, md); - if (old_name != md->name) { + if (BKE_modifier_unique_name(&ob->modifiers, md)) { printf( "Warning: Object '%s' had several modifiers with the " "same name, renamed one of them to '%s'.\n", diff --git a/source/blender/blenloader/intern/versioning_300.cc b/source/blender/blenloader/intern/versioning_300.cc index 6853bb96263..b373b2b938c 100644 --- a/source/blender/blenloader/intern/versioning_300.cc +++ b/source/blender/blenloader/intern/versioning_300.cc @@ -21,7 +21,7 @@ #include "BLI_multi_value_map.hh" #include "BLI_path_util.h" #include "BLI_string.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_utildefines.h" /* Define macros in `DNA_genfile.h`. */ diff --git a/source/blender/blenloader/intern/versioning_400.cc b/source/blender/blenloader/intern/versioning_400.cc index 5c31e985615..31c1b4b2931 100644 --- a/source/blender/blenloader/intern/versioning_400.cc +++ b/source/blender/blenloader/intern/versioning_400.cc @@ -22,7 +22,6 @@ #include "DNA_defaults.h" #include "DNA_light_types.h" #include "DNA_lightprobe_types.h" -#include "DNA_material_types.h" #include "DNA_modifier_types.h" #include "DNA_movieclip_types.h" #include "DNA_scene_types.h" @@ -1060,30 +1059,6 @@ static void enable_geometry_nodes_is_modifier(Main &bmain) } } -static void versioning_grease_pencil_stroke_radii_scaling(GreasePencil *grease_pencil) -{ - using namespace blender; - /* Previously, Grease Pencil used a radius convention where 1 `px` = 0.001 units. This `px` was - * the brush size which would be stored in the stroke thickness and then scaled by the point - * pressure factor. Finally, the render engine would divide this thickness value by 2000 (we're - * going from a thickness to a radius, hence the factor of two) to convert back into blender - * units. - * Store the radius now directly in blender units. This makes it consistent with how hair curves - * handle the radius. */ - for (GreasePencilDrawingBase *base : grease_pencil->drawings()) { - if (base->type != GP_DRAWING) { - continue; - } - bke::greasepencil::Drawing &drawing = reinterpret_cast(base)->wrap(); - MutableSpan radii = drawing.radii_for_write(); - threading::parallel_for(radii.index_range(), 8192, [&](const IndexRange range) { - for (const int i : range) { - radii[i] /= 2000.0f; - } - }); - } -} - void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain) { if (!MAIN_VERSION_FILE_ATLEAST(bmain, 400, 1)) { @@ -1192,6 +1167,12 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain) } /* Set default bake resolution. */ + if (!DNA_struct_member_exists(fd->filesdna, "LightProbe", "int", "resolution")) { + LISTBASE_FOREACH (LightProbe *, lightprobe, &bmain->lightprobes) { + lightprobe->resolution = LIGHT_PROBE_RESOLUTION_1024; + } + } + if (!DNA_struct_member_exists(fd->filesdna, "World", "int", "probe_resolution")) { LISTBASE_FOREACH (World *, world, &bmain->worlds) { world->probe_resolution = LIGHT_PROBE_RESOLUTION_1024; @@ -1711,12 +1692,6 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain) } } - if (!MAIN_VERSION_FILE_ATLEAST(bmain, 401, 1)) { - LISTBASE_FOREACH (GreasePencil *, grease_pencil, &bmain->grease_pencils) { - versioning_grease_pencil_stroke_radii_scaling(grease_pencil); - } - } - /** * Versioning code until next subversion bump goes here. * @@ -1729,20 +1704,5 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain) */ { /* Keep this block, even when empty. */ - - if (!DNA_struct_member_exists(fd->filesdna, "SceneEEVEE", "int", "volumetric_ray_depth")) { - SceneEEVEE default_eevee = *DNA_struct_default_get(SceneEEVEE); - LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { - scene->eevee.volumetric_ray_depth = default_eevee.volumetric_ray_depth; - } - } - - if (!DNA_struct_member_exists(fd->filesdna, "Material", "char", "surface_render_method")) { - LISTBASE_FOREACH (Material *, mat, &bmain->materials) { - mat->surface_render_method = (mat->blend_method == MA_BM_BLEND) ? - MA_SURFACE_METHOD_FORWARD : - MA_SURFACE_METHOD_DEFERRED; - } - } } } diff --git a/source/blender/blenloader/intern/versioning_common.cc b/source/blender/blenloader/intern/versioning_common.cc index d68be18250e..6a45b329e94 100644 --- a/source/blender/blenloader/intern/versioning_common.cc +++ b/source/blender/blenloader/intern/versioning_common.cc @@ -25,7 +25,6 @@ #include "BKE_lib_override.hh" #include "BKE_main.h" #include "BKE_main_namemap.h" -#include "BKE_mesh_legacy_convert.hh" #include "BKE_node.hh" #include "BKE_node_runtime.hh" @@ -527,8 +526,4 @@ void do_versions_after_setup(Main *new_bmain, BlendFileReadReport *reports) /* Does not add any new IDs, but needs the full Main data-base. */ BKE_lib_override_library_main_hierarchy_root_ensure(new_bmain); } - - if (!blendfile_or_libraries_versions_atleast(new_bmain, 401, 2)) { - BKE_main_mesh_legacy_convert_auto_smooth(*new_bmain); - } } diff --git a/source/blender/blenloader/intern/versioning_defaults.cc b/source/blender/blenloader/intern/versioning_defaults.cc index 7517b2ff58b..d8d234c0aa6 100644 --- a/source/blender/blenloader/intern/versioning_defaults.cc +++ b/source/blender/blenloader/intern/versioning_defaults.cc @@ -13,8 +13,6 @@ * To update preference defaults see `userdef_default.c`. */ -#define DNA_DEPRECATED_ALLOW - #include "MEM_guardedalloc.h" #include "BLI_listbase.h" @@ -593,7 +591,7 @@ void BLO_update_defaults_startup_blend(Main *bmain, const char *app_template) LISTBASE_FOREACH (Mesh *, mesh, &bmain->meshes) { /* Match default for new meshes. */ - mesh->smoothresh_legacy = DEG2RADF(30); + mesh->smoothresh = DEG2RADF(30); /* Match voxel remesher options for all existing meshes in templates. */ mesh->flag |= ME_REMESH_REPROJECT_VOLUME | ME_REMESH_REPROJECT_PAINT_MASK | ME_REMESH_REPROJECT_SCULPT_FACE_SETS | ME_REMESH_REPROJECT_VERTEX_COLORS; diff --git a/source/blender/blenloader/intern/versioning_legacy.cc b/source/blender/blenloader/intern/versioning_legacy.cc index 71a86b672c8..7370d3c9ea0 100644 --- a/source/blender/blenloader/intern/versioning_legacy.cc +++ b/source/blender/blenloader/intern/versioning_legacy.cc @@ -635,7 +635,7 @@ void blo_do_versions_pre250(FileData *fd, Library *lib, Main *bmain) if (bmain->versionfile <= 164) { Mesh *me = static_cast(bmain->meshes.first); while (me) { - me->smoothresh_legacy = 30; + me->smoothresh = 30; me = static_cast(me->id.next); } } diff --git a/source/blender/blenloader/intern/versioning_userdef.cc b/source/blender/blenloader/intern/versioning_userdef.cc index ad3dcfcbf73..7e785184212 100644 --- a/source/blender/blenloader/intern/versioning_userdef.cc +++ b/source/blender/blenloader/intern/versioning_userdef.cc @@ -15,7 +15,7 @@ #include "BLI_math_vector.h" #include "BLI_string.h" #include "BLI_string_utf8.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_utildefines.h" #include "DNA_anim_types.h" @@ -885,15 +885,6 @@ void blo_do_versions_userdef(UserDef *userdef) userdef->text_render |= USER_TEXT_RENDER_SUBPIXELAA; } - if (!USER_VERSION_ATLEAST(401, 3)) { - LISTBASE_FOREACH (uiStyle *, style, &userdef->uistyles) { - style->paneltitle.character_weight = 400; - style->grouplabel.character_weight = 400; - style->widgetlabel.character_weight = 400; - style->widget.character_weight = 400; - } - } - /** * Versioning code until next subversion bump goes here. * diff --git a/source/blender/blenloader/intern/writefile.cc b/source/blender/blenloader/intern/writefile.cc index e2ba04fd40b..d8fa94a2f36 100644 --- a/source/blender/blenloader/intern/writefile.cc +++ b/source/blender/blenloader/intern/writefile.cc @@ -149,6 +149,11 @@ static CLG_LogRef LOG = {"blo.writefile"}; /** \name Internal Write Wrapper's (Abstracts Compression) * \{ */ +enum eWriteWrapType { + WW_WRAP_NONE = 1, + WW_WRAP_ZSTD, +}; + struct ZstdFrame { ZstdFrame *next, *prev; @@ -156,94 +161,70 @@ struct ZstdFrame { uint32_t uncompressed_size; }; -class WriteWrap { - public: - virtual bool open(const char *filepath) = 0; - virtual bool close() = 0; - virtual bool write(const void *buf, size_t buf_len) = 0; +struct WriteWrap { + /* callbacks */ + bool (*open)(WriteWrap *ww, const char *filepath); + bool (*close)(WriteWrap *ww); + size_t (*write)(WriteWrap *ww, const char *data, size_t data_len); - /** Buffer output (we only want when output isn't already buffered). */ - bool use_buf = true; + /* Buffer output (we only want when output isn't already buffered). */ + bool use_buf; + + /* internal */ + int file_handle; + struct { + ListBase threadpool; + ListBase tasks; + ThreadMutex mutex; + ThreadCondition condition; + int next_frame; + int num_frames; + + int level; + ListBase frames; + + bool write_error; + } zstd; }; -class RawWriteWrap : public WriteWrap { - public: - bool open(const char *filepath) override; - bool close() override; - bool write(const void *buf, size_t buf_len) override; - - private: - int file_handle = 0; -}; - -bool RawWriteWrap::open(const char *filepath) +/* none */ +static bool ww_open_none(WriteWrap *ww, const char *filepath) { int file; file = BLI_open(filepath, O_BINARY + O_WRONLY + O_CREAT + O_TRUNC, 0666); if (file != -1) { - file_handle = file; + ww->file_handle = file; return true; } return false; } -bool RawWriteWrap::close() +static bool ww_close_none(WriteWrap *ww) { - return (::close(file_handle) != -1); + return (close(ww->file_handle) != -1); } -bool RawWriteWrap::write(const void *buf, size_t buf_len) +static size_t ww_write_none(WriteWrap *ww, const char *buf, size_t buf_len) { - return ::write(file_handle, buf, buf_len) == buf_len; + return write(ww->file_handle, buf, buf_len); } -class ZstdWriteWrap : public WriteWrap { - WriteWrap &base_wrap; +/* zstd */ - ListBase threadpool = {}; - ListBase tasks = {}; - ThreadMutex mutex = {}; - ThreadCondition condition = {}; - int next_frame = 0; - int num_frames = 0; - - int level = 0; - ListBase frames = {}; - - bool write_error = false; - - public: - ZstdWriteWrap(WriteWrap &base_wrap) : base_wrap(base_wrap) {} - - bool open(const char *filepath) override; - bool close() override; - bool write(const void *buf, size_t buf_len) override; - - private: - struct ZstdWriteBlockTask; - void write_task(ZstdWriteBlockTask *task); - void write_u32_le(uint32_t val); - void write_seekable_frames(); -}; - -struct ZstdWriteWrap::ZstdWriteBlockTask { +struct ZstdWriteBlockTask { ZstdWriteBlockTask *next, *prev; void *data; size_t size; int frame_number; - ZstdWriteWrap *ww; - - static void *write_task(void *userdata) - { - auto *task = static_cast(userdata); - task->ww->write_task(task); - return nullptr; - } + WriteWrap *ww; }; -void ZstdWriteWrap::write_task(ZstdWriteBlockTask *task) +static void *zstd_write_task(void *userdata) { + ZstdWriteBlockTask *task = static_cast(userdata); + WriteWrap *ww = task->ww; + size_t out_buf_len = ZSTD_compressBound(task->size); void *out_buf = MEM_mallocN(out_buf_len, "Zstd out buffer"); size_t out_size = ZSTD_compress( @@ -251,57 +232,58 @@ void ZstdWriteWrap::write_task(ZstdWriteBlockTask *task) MEM_freeN(task->data); - BLI_mutex_lock(&mutex); + BLI_mutex_lock(&ww->zstd.mutex); - while (next_frame != task->frame_number) { - BLI_condition_wait(&condition, &mutex); + while (ww->zstd.next_frame != task->frame_number) { + BLI_condition_wait(&ww->zstd.condition, &ww->zstd.mutex); } if (ZSTD_isError(out_size)) { - write_error = true; + ww->zstd.write_error = true; } else { - if (base_wrap.write(out_buf, out_size)) { + if (ww_write_none(ww, static_cast(out_buf), out_size) == out_size) { ZstdFrame *frameinfo = static_cast( MEM_mallocN(sizeof(ZstdFrame), "zstd frameinfo")); frameinfo->uncompressed_size = task->size; frameinfo->compressed_size = out_size; - BLI_addtail(&frames, frameinfo); + BLI_addtail(&ww->zstd.frames, frameinfo); } else { - write_error = true; + ww->zstd.write_error = true; } } - next_frame++; + ww->zstd.next_frame++; - BLI_mutex_unlock(&mutex); - BLI_condition_notify_all(&condition); + BLI_mutex_unlock(&ww->zstd.mutex); + BLI_condition_notify_all(&ww->zstd.condition); MEM_freeN(out_buf); + return nullptr; } -bool ZstdWriteWrap::open(const char *filepath) +static bool ww_open_zstd(WriteWrap *ww, const char *filepath) { - if (!base_wrap.open(filepath)) { + if (!ww_open_none(ww, filepath)) { return false; } /* Leave one thread open for the main writing logic, unless we only have one HW thread. */ int num_threads = max_ii(1, BLI_system_thread_count() - 1); - BLI_threadpool_init(&threadpool, ZstdWriteBlockTask::write_task, num_threads); - BLI_mutex_init(&mutex); - BLI_condition_init(&condition); + BLI_threadpool_init(&ww->zstd.threadpool, zstd_write_task, num_threads); + BLI_mutex_init(&ww->zstd.mutex); + BLI_condition_init(&ww->zstd.condition); return true; } -void ZstdWriteWrap::write_u32_le(uint32_t val) +static void zstd_write_u32_le(WriteWrap *ww, uint32_t val) { #ifdef __BIG_ENDIAN__ BLI_endian_switch_uint32(&val); #endif - base_wrap.write(&val, sizeof(uint32_t)); + ww_write_none(ww, (char *)&val, sizeof(uint32_t)); } /* In order to implement efficient seeking when reading the .blend, we append @@ -312,49 +294,49 @@ void ZstdWriteWrap::write_u32_le(uint32_t val) * If this information is not present in a file (e.g. if it was compressed * with external tools), it can still be opened in Blender, but seeking will * not be supported, so more memory might be needed. */ -void ZstdWriteWrap::write_seekable_frames() +static void zstd_write_seekable_frames(WriteWrap *ww) { /* Write seek table header (magic number and frame size). */ - write_u32_le(0x184D2A5E); + zstd_write_u32_le(ww, 0x184D2A5E); - /* The actual frame number might not match num_frames if there was a write error. */ - const uint32_t num_frames = BLI_listbase_count(&frames); + /* The actual frame number might not match ww->zstd.num_frames if there was a write error. */ + const uint32_t num_frames = BLI_listbase_count(&ww->zstd.frames); /* Each frame consists of two u32, so 8 bytes each. * After the frames, a footer containing two u32 and one byte (9 bytes total) is written. */ const uint32_t frame_size = num_frames * 8 + 9; - write_u32_le(frame_size); + zstd_write_u32_le(ww, frame_size); /* Write seek table entries. */ - LISTBASE_FOREACH (ZstdFrame *, frame, &frames) { - write_u32_le(frame->compressed_size); - write_u32_le(frame->uncompressed_size); + LISTBASE_FOREACH (ZstdFrame *, frame, &ww->zstd.frames) { + zstd_write_u32_le(ww, frame->compressed_size); + zstd_write_u32_le(ww, frame->uncompressed_size); } /* Write seek table footer (number of frames, option flags and second magic number). */ - write_u32_le(num_frames); + zstd_write_u32_le(ww, num_frames); const char flags = 0; /* We don't store checksums for each frame. */ - base_wrap.write(&flags, 1); - write_u32_le(0x8F92EAB1); + ww_write_none(ww, &flags, 1); + zstd_write_u32_le(ww, 0x8F92EAB1); } -bool ZstdWriteWrap::close() +static bool ww_close_zstd(WriteWrap *ww) { - BLI_threadpool_end(&threadpool); - BLI_freelistN(&tasks); + BLI_threadpool_end(&ww->zstd.threadpool); + BLI_freelistN(&ww->zstd.tasks); - BLI_mutex_end(&mutex); - BLI_condition_end(&condition); + BLI_mutex_end(&ww->zstd.mutex); + BLI_condition_end(&ww->zstd.condition); - write_seekable_frames(); - BLI_freelistN(&frames); + zstd_write_seekable_frames(ww); + BLI_freelistN(&ww->zstd.frames); - return base_wrap.close() && !write_error; + return ww_close_none(ww) && !ww->zstd.write_error; } -bool ZstdWriteWrap::write(const void *buf, size_t buf_len) +static size_t ww_write_zstd(WriteWrap *ww, const char *buf, size_t buf_len) { - if (write_error) { - return false; + if (ww->zstd.write_error) { + return 0; } ZstdWriteBlockTask *task = static_cast( @@ -362,30 +344,54 @@ bool ZstdWriteWrap::write(const void *buf, size_t buf_len) task->data = MEM_mallocN(buf_len, __func__); memcpy(task->data, buf, buf_len); task->size = buf_len; - task->frame_number = num_frames++; - task->ww = this; + task->frame_number = ww->zstd.num_frames++; + task->ww = ww; - BLI_mutex_lock(&mutex); - BLI_addtail(&tasks, task); + BLI_mutex_lock(&ww->zstd.mutex); + BLI_addtail(&ww->zstd.tasks, task); /* If there's a free worker thread, just push the block into that thread. * Otherwise, we wait for the earliest thread to finish. * We look up the earliest thread while holding the mutex, but release it * before joining the thread to prevent a deadlock. */ - ZstdWriteBlockTask *first_task = static_cast(tasks.first); - BLI_mutex_unlock(&mutex); - if (!BLI_available_threads(&threadpool)) { - BLI_threadpool_remove(&threadpool, first_task); + ZstdWriteBlockTask *first_task = static_cast(ww->zstd.tasks.first); + BLI_mutex_unlock(&ww->zstd.mutex); + if (!BLI_available_threads(&ww->zstd.threadpool)) { + BLI_threadpool_remove(&ww->zstd.threadpool, first_task); /* If the task list was empty before we pushed our task, there should * always be a free thread. */ BLI_assert(first_task != task); - BLI_remlink(&tasks, first_task); + BLI_remlink(&ww->zstd.tasks, first_task); MEM_freeN(first_task); } - BLI_threadpool_insert(&threadpool, task); + BLI_threadpool_insert(&ww->zstd.threadpool, task); - return true; + return buf_len; +} + +/* --- end compression types --- */ + +static void ww_handle_init(eWriteWrapType ww_type, WriteWrap *r_ww) +{ + memset(r_ww, 0, sizeof(*r_ww)); + + switch (ww_type) { + case WW_WRAP_ZSTD: { + r_ww->open = ww_open_zstd; + r_ww->close = ww_close_zstd; + r_ww->write = ww_write_zstd; + r_ww->use_buf = true; + break; + } + default: { + r_ww->open = ww_open_none; + r_ww->close = ww_close_none; + r_ww->write = ww_write_none; + r_ww->use_buf = true; + break; + } + } } /** \} */ @@ -477,7 +483,7 @@ static void writedata_do_write(WriteData *wd, const void *mem, size_t memlen) BLO_memfile_chunk_add(&wd->mem, static_cast(mem), memlen); } else { - if (!wd->ww->write(mem, memlen)) { + if (wd->ww->write(wd->ww, static_cast(mem), memlen) != memlen) { wd->error = true; } } @@ -1457,17 +1463,23 @@ static void write_file_main_validate_post(Main *bmain, ReportList *reports) } } -static bool BLO_write_file_impl(Main *mainvar, - const char *filepath, - const int write_flags, - const BlendFileWriteParams *params, - ReportList *reports, - WriteWrap &ww) +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name File Writing (Public) + * \{ */ + +bool BLO_write_file(Main *mainvar, + const char *filepath, + const int write_flags, + const BlendFileWriteParams *params, + ReportList *reports) { BLI_assert(!BLI_path_is_rel(filepath)); BLI_assert(BLI_path_is_abs_from_cwd(filepath)); char tempname[FILE_MAX + 1]; + WriteWrap ww; eBLO_WritePathRemap remap_mode = params->remap_mode; const bool use_save_versions = params->use_save_versions; @@ -1486,7 +1498,9 @@ static bool BLO_write_file_impl(Main *mainvar, /* open temporary file, so we preserve the original in case we crash */ SNPRINTF(tempname, "%s@", filepath); - if (ww.open(tempname) == false) { + ww_handle_init((write_flags & G_FILE_COMPRESS) ? WW_WRAP_ZSTD : WW_WRAP_NONE, &ww); + + if (ww.open(&ww, tempname) == false) { BKE_reportf( reports, RPT_ERROR, "Cannot open file %s for writing: %s", tempname, strerror(errno)); return false; @@ -1579,7 +1593,7 @@ static bool BLO_write_file_impl(Main *mainvar, const bool err = write_file_handle( mainvar, &ww, nullptr, nullptr, write_flags, use_userdef, thumb); - ww.close(); + ww.close(&ww); if (UNLIKELY(path_list_backup)) { BKE_bpath_list_restore(mainvar, path_list_flag, path_list_backup); @@ -1612,26 +1626,6 @@ static bool BLO_write_file_impl(Main *mainvar, return true; } -/* -------------------------------------------------------------------- */ -/** \name File Writing (Public) - * \{ */ - -bool BLO_write_file(Main *mainvar, - const char *filepath, - const int write_flags, - const BlendFileWriteParams *params, - ReportList *reports) -{ - RawWriteWrap raw_wrap; - - if (write_flags & G_FILE_COMPRESS) { - ZstdWriteWrap zstd_wrap(raw_wrap); - return BLO_write_file_impl(mainvar, filepath, write_flags, params, reports, zstd_wrap); - } - - return BLO_write_file_impl(mainvar, filepath, write_flags, params, reports, raw_wrap); -} - bool BLO_write_file_mem(Main *mainvar, MemFile *compare, MemFile *current, int write_flags) { bool use_userdef = false; diff --git a/source/blender/blenloader/tests/blendfile_loading_base_test.cc b/source/blender/blenloader/tests/blendfile_loading_base_test.cc index 8ee1f8b0e64..c534d0f5a22 100644 --- a/source/blender/blenloader/tests/blendfile_loading_base_test.cc +++ b/source/blender/blenloader/tests/blendfile_loading_base_test.cc @@ -120,7 +120,7 @@ bool BlendfileLoadingBaseTest::blendfile_load(const char *filepath) char abspath[FILE_MAX]; BLI_path_join(abspath, sizeof(abspath), test_assets_dir.c_str(), filepath); - BlendFileReadReport bf_reports = {}; + BlendFileReadReport bf_reports = {nullptr}; bfile = BLO_read_from_file(abspath, BLO_READ_SKIP_NONE, &bf_reports); if (bfile == nullptr) { ADD_FAILURE() << "Unable to load file '" << filepath << "' from test assets dir '" @@ -143,6 +143,10 @@ void BlendfileLoadingBaseTest::blendfile_free() return; } + wmWindowManager *wm = static_cast(bfile->main->wm.first); + if (wm != nullptr) { + wm_close_and_free(nullptr, wm); + } BLO_blendfiledata_free(bfile); bfile = nullptr; } diff --git a/source/blender/blentranslation/CMakeLists.txt b/source/blender/blentranslation/CMakeLists.txt index 841bc181912..367284fabd5 100644 --- a/source/blender/blentranslation/CMakeLists.txt +++ b/source/blender/blentranslation/CMakeLists.txt @@ -42,8 +42,10 @@ if(WITH_PYTHON) ) endif() -if(WITH_INPUT_IME) - add_definitions(-DWITH_INPUT_IME) +if(WIN32 OR APPLE) + if(WITH_INPUT_IME) + add_definitions(-DWITH_INPUT_IME) + endif() endif() blender_add_lib(bf_blentranslation "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") diff --git a/source/blender/bmesh/intern/bmesh_core.cc b/source/blender/bmesh/intern/bmesh_core.cc index b05e730879b..c78226c4169 100644 --- a/source/blender/bmesh/intern/bmesh_core.cc +++ b/source/blender/bmesh/intern/bmesh_core.cc @@ -11,10 +11,10 @@ #include "MEM_guardedalloc.h" #include "BLI_alloca.h" +#include "BLI_array.h" #include "BLI_linklist_stack.h" #include "BLI_math_vector.h" #include "BLI_utildefines_stack.h" -#include "BLI_vector.hh" #include "BLT_translation.h" @@ -26,8 +26,6 @@ #include "bmesh.h" #include "intern/bmesh_private.h" -using blender::Vector; - /* use so valgrinds memcheck alerts us when undefined index is used. * TESTING ONLY! */ // #define USE_DEBUG_INDEX_MEMCHECK @@ -400,8 +398,8 @@ BLI_INLINE BMFace *bm_face_create__internal(BMesh *bm) } BMFace *BM_face_create(BMesh *bm, - BMVert *const *verts, - BMEdge *const *edges, + BMVert **verts, + BMEdge **edges, const int len, const BMFace *f_example, const eBMCreateFlag create_flag) @@ -1141,6 +1139,12 @@ BMFace *BM_faces_join(BMesh *bm, BMFace **faces, int totface, const bool do_del) #endif BMLoop *l_iter; BMLoop *l_first; + BMEdge **edges = nullptr; + BMEdge **deledges = nullptr; + BMVert **delverts = nullptr; + BLI_array_staticdeclare(edges, BM_DEFAULT_NGON_STACK_SIZE); + BLI_array_staticdeclare(deledges, BM_DEFAULT_NGON_STACK_SIZE); + BLI_array_staticdeclare(delverts, BM_DEFAULT_NGON_STACK_SIZE); BMVert *v1 = nullptr, *v2 = nullptr; int i; const int cd_loop_mdisp_offset = CustomData_get_offset(&bm->ldata, CD_MDISPS); @@ -1156,10 +1160,6 @@ BMFace *BM_faces_join(BMesh *bm, BMFace **faces, int totface, const bool do_del) bm_elements_systag_enable(faces, totface, _FLAG_JF); - Vector edges; - Vector deledges; - Vector delverts; - for (i = 0; i < totface; i++) { f = faces[i]; l_iter = l_first = BM_FACE_FIRST_LOOP(f); @@ -1171,7 +1171,7 @@ BMFace *BM_faces_join(BMesh *bm, BMFace **faces, int totface, const bool do_del) goto error; } else if (rlen == 1) { - edges.append(l_iter->e); + BLI_array_append(edges, l_iter->e); if (!v1) { v1 = l_iter->v; @@ -1187,7 +1187,7 @@ BMFace *BM_faces_join(BMesh *bm, BMFace **faces, int totface, const bool do_del) * else this will remove the face as well - campbell */ if (!BM_edge_face_count_is_over(l_iter->e, 2)) { if (do_del) { - deledges.append(l_iter->e); + BLI_array_append(deledges, l_iter->e); } BM_ELEM_API_FLAG_ENABLE(l_iter->e, _FLAG_JF); } @@ -1195,14 +1195,14 @@ BMFace *BM_faces_join(BMesh *bm, BMFace **faces, int totface, const bool do_del) else { if (d1 && !BM_ELEM_API_FLAG_TEST(l_iter->e->v1, _FLAG_JF)) { if (do_del) { - delverts.append(l_iter->e->v1); + BLI_array_append(delverts, l_iter->e->v1); } BM_ELEM_API_FLAG_ENABLE(l_iter->e->v1, _FLAG_JF); } if (d2 && !BM_ELEM_API_FLAG_TEST(l_iter->e->v2, _FLAG_JF)) { if (do_del) { - delverts.append(l_iter->e->v2); + BLI_array_append(delverts, l_iter->e->v2); } BM_ELEM_API_FLAG_ENABLE(l_iter->e->v2, _FLAG_JF); } @@ -1223,9 +1223,9 @@ BMFace *BM_faces_join(BMesh *bm, BMFace **faces, int totface, const bool do_del) } /* create region face */ - f_new = !edges.is_empty() ? + f_new = BLI_array_len(edges) ? BM_face_create_ngon( - bm, v1, v2, edges.data(), edges.size(), faces[0], BM_CREATE_NOP) : + bm, v1, v2, edges, BLI_array_len(edges), faces[0], BM_CREATE_NOP) : nullptr; if (UNLIKELY(f_new == nullptr)) { /* Invalid boundary region to join faces */ @@ -1292,12 +1292,12 @@ BMFace *BM_faces_join(BMesh *bm, BMFace **faces, int totface, const bool do_del) /* delete old geometry */ if (do_del) { - for (BMEdge *edge : deledges) { - BM_edge_kill(bm, edge); + for (i = 0; i < BLI_array_len(deledges); i++) { + BM_edge_kill(bm, deledges[i]); } - for (BMVert *vert : delverts) { - BM_vert_kill(bm, vert); + for (i = 0; i < BLI_array_len(delverts); i++) { + BM_vert_kill(bm, delverts[i]); } } else { @@ -1307,11 +1307,18 @@ BMFace *BM_faces_join(BMesh *bm, BMFace **faces, int totface, const bool do_del) } } + BLI_array_free(edges); + BLI_array_free(deledges); + BLI_array_free(delverts); + BM_CHECK_ELEMENT(f_new); return f_new; error: bm_elements_systag_disable(faces, totface, _FLAG_JF); + BLI_array_free(edges); + BLI_array_free(deledges); + BLI_array_free(delverts); return nullptr; } diff --git a/source/blender/bmesh/intern/bmesh_core.h b/source/blender/bmesh/intern/bmesh_core.h index 2aa14b36d8d..f85ce7f1580 100644 --- a/source/blender/bmesh/intern/bmesh_core.h +++ b/source/blender/bmesh/intern/bmesh_core.h @@ -52,8 +52,8 @@ BMEdge *BM_edge_create( * \param create_flag: Options for creating the face */ BMFace *BM_face_create(BMesh *bm, - BMVert *const *verts, - BMEdge *const *edges, + BMVert **verts, + BMEdge **edges, int len, const BMFace *f_example, eBMCreateFlag create_flag); diff --git a/source/blender/bmesh/intern/bmesh_mesh_normals.cc b/source/blender/bmesh/intern/bmesh_mesh_normals.cc index bd7d8fc90e5..59316dcb17c 100644 --- a/source/blender/bmesh/intern/bmesh_mesh_normals.cc +++ b/source/blender/bmesh/intern/bmesh_mesh_normals.cc @@ -1697,12 +1697,14 @@ void BM_loops_calc_normal_vcos(BMesh *bm, const float (*vnos)[3], const float (*fnos)[3], const bool use_split_normals, + const float split_angle, float (*r_lnos)[3], MLoopNorSpaceArray *r_lnors_spacearr, short (*clnors_data)[2], const int cd_loop_clnors_offset, const bool do_rebuild) { + const bool has_clnors = clnors_data || (cd_loop_clnors_offset != -1); if (use_split_normals) { bm_mesh_loops_calc_normals(bm, @@ -1713,7 +1715,7 @@ void BM_loops_calc_normal_vcos(BMesh *bm, clnors_data, cd_loop_clnors_offset, do_rebuild, - -1.0f); + has_clnors ? -1.0f : cosf(split_angle)); } else { BLI_assert(!r_lnors_spacearr); @@ -1742,6 +1744,7 @@ void BM_lnorspacearr_store(BMesh *bm, float (*r_lnors)[3]) nullptr, nullptr, true, + M_PI, r_lnors, bm->lnor_spacearr, nullptr, @@ -1868,6 +1871,7 @@ void BM_lnorspace_rebuild(BMesh *bm, bool preserve_clnor) nullptr, nullptr, true, + M_PI, r_lnors, bm->lnor_spacearr, nullptr, @@ -1943,8 +1947,17 @@ void BM_lnorspace_err(BMesh *bm) int cd_loop_clnors_offset = CustomData_get_offset(&bm->ldata, CD_CUSTOMLOOPNORMAL); float(*lnors)[3] = static_cast(MEM_callocN(sizeof(*lnors) * bm->totloop, __func__)); - BM_loops_calc_normal_vcos( - bm, nullptr, nullptr, nullptr, true, lnors, temp, nullptr, cd_loop_clnors_offset, true); + BM_loops_calc_normal_vcos(bm, + nullptr, + nullptr, + nullptr, + true, + M_PI, + lnors, + temp, + nullptr, + cd_loop_clnors_offset, + true); for (int i = 0; i < bm->totloop; i++) { int j = 0; diff --git a/source/blender/bmesh/intern/bmesh_mesh_normals.h b/source/blender/bmesh/intern/bmesh_mesh_normals.h index d56eddd83e5..b2b94d3591e 100644 --- a/source/blender/bmesh/intern/bmesh_mesh_normals.h +++ b/source/blender/bmesh/intern/bmesh_mesh_normals.h @@ -62,6 +62,7 @@ void BM_loops_calc_normal_vcos(BMesh *bm, const float (*vnos)[3], const float (*fnos)[3], bool use_split_normals, + float split_angle, float (*r_lnos)[3], struct MLoopNorSpaceArray *r_lnors_spacearr, short (*clnors_data)[2], diff --git a/source/blender/bmesh/intern/bmesh_mesh_validate.cc b/source/blender/bmesh/intern/bmesh_mesh_validate.cc index 750e2321a78..05a0283b280 100644 --- a/source/blender/bmesh/intern/bmesh_mesh_validate.cc +++ b/source/blender/bmesh/intern/bmesh_mesh_validate.cc @@ -13,7 +13,6 @@ # include "BLI_map.hh" # include "BLI_ordered_edge.hh" -# include "BLI_set.hh" # include "BLI_utildefines.h" # include "bmesh.h" @@ -37,17 +36,6 @@ (void)0 # endif -template<> struct blender::DefaultHash> { - uint64_t operator()(const blender::Set &value) const - { - uint64_t hash = 0; - for (const BMVert *vert : value) { - hash = get_default_hash_2(hash, vert); - } - return hash; - } -}; - bool BM_mesh_validate(BMesh *bm) { blender::Map edge_hash; @@ -131,7 +119,6 @@ bool BM_mesh_validate(BMesh *bm) } /* face structure */ - blender::Map, int> face_map; BM_ITER_MESH_INDEX (f, &iter, bm, BM_FACES_OF_MESH, i) { BMLoop *l_iter; BMLoop *l_first; @@ -151,8 +138,6 @@ bool BM_mesh_validate(BMesh *bm) j = 0; - blender::Set face_verts; - l_iter = l_first = BM_FACE_FIRST_LOOP(f); do { if (BM_elem_flag_test(l_iter, BM_ELEM_INTERNAL_TAG)) { @@ -190,17 +175,9 @@ bool BM_mesh_validate(BMesh *bm) BM_elem_flag_enable(l_iter, BM_ELEM_INTERNAL_TAG); BM_elem_flag_enable(l_iter->v, BM_ELEM_INTERNAL_TAG); BM_elem_flag_enable(l_iter->e, BM_ELEM_INTERNAL_TAG); - - face_verts.add(l_iter->v); - j++; } while ((l_iter = l_iter->next) != l_first); - face_map.add_or_modify( - std::move(face_verts), - [&](int *value) { *value = i; }, - [&](int *value) { ERRMSG("face %d: duplicate of %d", i, *value); }); - if (j != f->len) { ERRMSG("face %d: has length of %d but should be %d", i, f->len, j); } diff --git a/source/blender/bmesh/intern/bmesh_mods.cc b/source/blender/bmesh/intern/bmesh_mods.cc index c986591a903..ecdf6c16457 100644 --- a/source/blender/bmesh/intern/bmesh_mods.cc +++ b/source/blender/bmesh/intern/bmesh_mods.cc @@ -11,16 +11,14 @@ #include "MEM_guardedalloc.h" +#include "BLI_array.h" #include "BLI_math_vector.h" -#include "BLI_vector.hh" #include "BKE_customdata.h" #include "bmesh.h" #include "intern/bmesh_private.h" -using blender::Vector; - bool BM_vert_dissolve(BMesh *bm, BMVert *v) { /* logic for 3 or more is identical */ @@ -363,15 +361,16 @@ BMEdge *BM_vert_collapse_faces(BMesh *bm, if (join_faces) { BMIter fiter; + BMFace **faces = nullptr; BMFace *f; + BLI_array_staticdeclare(faces, BM_DEFAULT_ITER_STACK_SIZE); - Vector faces; BM_ITER_ELEM (f, &fiter, v_kill, BM_FACES_OF_VERT) { - faces.append(f); + BLI_array_append(faces, f); } - if (faces.size() >= 2) { - BMFace *f2 = BM_faces_join(bm, faces.data(), faces.size(), true); + if (BLI_array_len(faces) >= 2) { + BMFace *f2 = BM_faces_join(bm, faces, BLI_array_len(faces), true); if (f2) { BMLoop *l_a, *l_b; @@ -384,6 +383,10 @@ BMEdge *BM_vert_collapse_faces(BMesh *bm, } } } + + BLI_assert(BLI_array_len(faces) < 8); + + BLI_array_free(faces); } else { /* single face or no faces */ @@ -448,8 +451,8 @@ BMVert *BM_edge_split(BMesh *bm, BMEdge *e, BMVert *v, BMEdge **r_e, float fac) { BMVert *v_new, *v_other; BMEdge *e_new; - - Vector oldfaces; + BMFace **oldfaces = nullptr; + BLI_array_staticdeclare(oldfaces, 32); const int cd_loop_mdisp_offset = BM_edge_is_wire(e) ? -1 : CustomData_get_offset(&bm->ldata, CD_MDISPS); @@ -458,14 +461,17 @@ BMVert *BM_edge_split(BMesh *bm, BMEdge *e, BMVert *v, BMEdge **r_e, float fac) /* do we have a multi-res layer? */ if (cd_loop_mdisp_offset != -1) { - BMLoop *l = e->l; + BMLoop *l; + int i; + + l = e->l; do { - oldfaces.append(l->f); + BLI_array_append(oldfaces, l->f); l = l->radial_next; } while (l != e->l); /* flag existing faces so we can differentiate oldfaces from new faces */ - for (int64_t i = 0; i < oldfaces.size(); i++) { + for (i = 0; i < BLI_array_len(oldfaces); i++) { BM_ELEM_API_FLAG_ENABLE(oldfaces[i], _FLAG_OVERLAP); oldfaces[i] = BM_face_copy(bm, bm, oldfaces[i], true, true); BM_ELEM_API_FLAG_DISABLE(oldfaces[i], _FLAG_OVERLAP); @@ -493,15 +499,19 @@ BMVert *BM_edge_split(BMesh *bm, BMEdge *e, BMVert *v, BMEdge **r_e, float fac) BM_data_interp_from_verts(bm, v, v_other, v_new, fac); if (cd_loop_mdisp_offset != -1) { + int i, j; + /* interpolate new/changed loop data from copied old faces */ - for (BMFace *oldface : oldfaces) { + for (i = 0; i < BLI_array_len(oldfaces); i++) { float f_center_old[3]; - BM_face_calc_center_median(oldface, f_center_old); + BM_face_calc_center_median(oldfaces[i], f_center_old); - for (int j = 0; j < 2; j++) { + for (j = 0; j < 2; j++) { BMEdge *e1 = j ? e_new : e; - BMLoop *l = e1->l; + BMLoop *l; + + l = e1->l; if (UNLIKELY(!l)) { BMESH_ASSERT(0); @@ -515,7 +525,7 @@ BMVert *BM_edge_split(BMesh *bm, BMEdge *e, BMVert *v, BMEdge **r_e, float fac) BM_face_calc_center_median(l->f, f_center); BM_face_interp_multires_ex( - bm, l->f, oldface, f_center, f_center_old, cd_loop_mdisp_offset); + bm, l->f, oldfaces[i], f_center, f_center_old, cd_loop_mdisp_offset); } l = l->radial_next; } while (l != e1->l); @@ -523,13 +533,13 @@ BMVert *BM_edge_split(BMesh *bm, BMEdge *e, BMVert *v, BMEdge **r_e, float fac) } /* destroy the old faces */ - for (BMFace *oldface : oldfaces) { - BM_face_verts_kill(bm, oldface); + for (i = 0; i < BLI_array_len(oldfaces); i++) { + BM_face_verts_kill(bm, oldfaces[i]); } /* fix boundaries a bit, doesn't work too well quite yet */ #if 0 - for (int j = 0; j < 2; j++) { + for (j = 0; j < 2; j++) { BMEdge *e1 = j ? e_new : e; BMLoop *l, *l2; @@ -545,6 +555,8 @@ BMVert *BM_edge_split(BMesh *bm, BMEdge *e, BMVert *v, BMEdge **r_e, float fac) } while (l != e1->l); } #endif + + BLI_array_free(oldfaces); } return v_new; @@ -573,6 +585,55 @@ void BM_edge_verts_swap(BMEdge *e) SWAP(BMDiskLink, e->v1_disk_link, e->v2_disk_link); } +#if 0 +/** + * Checks if a face is valid in the data structure + */ +bool BM_face_validate(BMFace *face, FILE *err) +{ + BMIter iter; + BLI_array_declare(verts); + BMVert **verts = nullptr; + BMLoop *l; + int i, j; + bool ret = true; + + if (face->len == 2) { + fprintf(err, "WARNING: found two-edged face. face ptr: %p\n", face); + fflush(err); + } + + BLI_array_grow_items(verts, face->len); + BM_ITER_ELEM_INDEX (l, &iter, face, BM_LOOPS_OF_FACE, i) { + verts[i] = l->v; + if (l->e->v1 == l->e->v2) { + fprintf(err, "Found bmesh edge with identical verts!\n"); + fprintf(err, " edge ptr: %p, vert: %p\n", l->e, l->e->v1); + fflush(err); + ret = false; + } + } + + for (i = 0; i < face->len; i++) { + for (j = 0; j < face->len; j++) { + if (j == i) { + continue; + } + + if (verts[i] == verts[j]) { + fprintf(err, "Found duplicate verts in bmesh face!\n"); + fprintf(err, " face ptr: %p, vert: %p\n", face, verts[i]); + fflush(err); + ret = false; + } + } + } + + BLI_array_free(verts); + return ret; +} +#endif + void BM_edge_calc_rotate(BMEdge *e, const bool ccw, BMLoop **r_l1, BMLoop **r_l2) { BMVert *v1, *v2; diff --git a/source/blender/bmesh/intern/bmesh_mods.h b/source/blender/bmesh/intern/bmesh_mods.h index 395f4655db7..3db4a174288 100644 --- a/source/blender/bmesh/intern/bmesh_mods.h +++ b/source/blender/bmesh/intern/bmesh_mods.h @@ -195,6 +195,8 @@ BMVert *BM_edge_split_n(BMesh *bm, BMEdge *e, int numcuts, BMVert **r_varr); */ void BM_edge_verts_swap(BMEdge *e); +bool BM_face_validate(BMFace *face, FILE *err); + /** * Calculate the 2 loops which _would_ make up the newly rotated Edge * but don't actually change anything. diff --git a/source/blender/bmesh/intern/bmesh_opdefines.cc b/source/blender/bmesh/intern/bmesh_opdefines.cc index 573119449c2..545cec9eb54 100644 --- a/source/blender/bmesh/intern/bmesh_opdefines.cc +++ b/source/blender/bmesh/intern/bmesh_opdefines.cc @@ -1816,6 +1816,7 @@ static BMOpDefine bmo_bevel_def = { {"miter_inner", BMO_OP_SLOT_INT, to_subtype_union(BMO_OP_SLOT_SUBTYPE_INT_ENUM), bmo_enum_bevel_miter_type}, /* outer miter kind */ {"spread", BMO_OP_SLOT_FLT}, /* amount to offset beveled edge */ + {"smoothresh", BMO_OP_SLOT_FLT}, /* for passing mesh's smoothresh, used in hardening */ {"custom_profile", BMO_OP_SLOT_PTR, to_subtype_union(BMO_OP_SLOT_SUBTYPE_PTR_STRUCT)}, /* CurveProfile, if None ignored */ {"vmesh_method", BMO_OP_SLOT_INT, to_subtype_union(BMO_OP_SLOT_SUBTYPE_INT_ENUM), bmo_enum_bevel_vmesh_method}, /* The method to use to create meshes at intersections. */ diff --git a/source/blender/bmesh/intern/bmesh_query.cc b/source/blender/bmesh/intern/bmesh_query.cc index 94f282a84b7..800dfaecc07 100644 --- a/source/blender/bmesh/intern/bmesh_query.cc +++ b/source/blender/bmesh/intern/bmesh_query.cc @@ -1618,7 +1618,7 @@ BMLoop *BM_edge_find_first_loop_visible(BMEdge *e) return nullptr; } -BMFace *BM_face_exists(BMVert *const *varr, int len) +BMFace *BM_face_exists(BMVert **varr, int len) { if (varr[0]->e) { BMEdge *e_iter, *e_first; diff --git a/source/blender/bmesh/intern/bmesh_query.h b/source/blender/bmesh/intern/bmesh_query.h index 3b2cb16269d..da2e5d48797 100644 --- a/source/blender/bmesh/intern/bmesh_query.h +++ b/source/blender/bmesh/intern/bmesh_query.h @@ -538,7 +538,7 @@ BMEdge *BM_edge_find_double(BMEdge *e) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); * * \note there used to be a BM_face_exists_overlap function that checks for partial overlap. */ -BMFace *BM_face_exists(BMVert *const *varr, int len) ATTR_NONNULL(1); +BMFace *BM_face_exists(BMVert **varr, int len) ATTR_NONNULL(1); /** * Check if the face has an exact duplicate (both winding directions). */ diff --git a/source/blender/bmesh/operators/bmo_bevel.cc b/source/blender/bmesh/operators/bmo_bevel.cc index 58c839f1d20..1532459dd58 100644 --- a/source/blender/bmesh/operators/bmo_bevel.cc +++ b/source/blender/bmesh/operators/bmo_bevel.cc @@ -35,6 +35,7 @@ void bmo_bevel_exec(BMesh *bm, BMOperator *op) const int miter_outer = BMO_slot_int_get(op->slots_in, "miter_outer"); const int miter_inner = BMO_slot_int_get(op->slots_in, "miter_inner"); const float spread = BMO_slot_float_get(op->slots_in, "spread"); + const float smoothresh = BMO_slot_float_get(op->slots_in, "smoothresh"); const CurveProfile *custom_profile = static_cast( BMO_slot_ptr_get(op->slots_in, "custom_profile")); const int vmesh_method = BMO_slot_int_get(op->slots_in, "vmesh_method"); @@ -81,6 +82,7 @@ void bmo_bevel_exec(BMesh *bm, BMOperator *op) miter_outer, miter_inner, spread, + smoothresh, custom_profile, vmesh_method); diff --git a/source/blender/bmesh/operators/bmo_dissolve.cc b/source/blender/bmesh/operators/bmo_dissolve.cc index 0dd375bc22e..1be07bb0c27 100644 --- a/source/blender/bmesh/operators/bmo_dissolve.cc +++ b/source/blender/bmesh/operators/bmo_dissolve.cc @@ -10,17 +10,15 @@ #include "MEM_guardedalloc.h" +#include "BLI_array.h" #include "BLI_math_vector.h" #include "BLI_stack.h" -#include "BLI_vector.hh" #include "bmesh.h" #include "bmesh_tools.h" #include "intern/bmesh_operators_private.h" -using blender::Vector; - /* ***_ISGC: mark for garbage-collection */ #define FACE_MARK 1 @@ -121,8 +119,17 @@ void bmo_dissolve_faces_exec(BMesh *bm, BMOperator *op) { BMOIter oiter; BMFace *f; + /* List of face arrays, the first element in each array in the length. */ + struct { + BMFace **faces; + int faces_len; + } *regions = nullptr, *region; + BMFace **faces = nullptr; + BLI_array_declare(regions); + BLI_array_declare(faces); BMFace *act_face = bm->act_face; BMWalker regwalker; + int i; const bool use_verts = BMO_slot_bool_get(op->slots_in, "use_verts"); @@ -139,11 +146,9 @@ void bmo_dissolve_faces_exec(BMesh *bm, BMOperator *op) BMO_slot_buffer_flag_enable(bm, op->slots_in, "faces", BM_FACE, FACE_MARK | FACE_TAG); - /* List of regions which are themselves a list of faces. */ - Vector> regions; - /* collect region */ BMO_ITER (f, &oiter, op->slots_in, "faces", BM_FACE) { + BMFace *f_iter; if (!BMO_face_flag_test(bm, f, FACE_TAG)) { continue; } @@ -163,21 +168,29 @@ void bmo_dissolve_faces_exec(BMesh *bm, BMOperator *op) if ((faces_init[0] = static_cast(BMW_begin(®walker, f))) && (faces_init[1] = static_cast(BMW_step(®walker)))) { - Vector faces; - faces.append(faces_init[0]); - faces.append(faces_init[1]); - BMFace *f_iter; + BLI_assert(BLI_array_len(faces) == 0); + + BLI_array_append(faces, faces_init[0]); + BLI_array_append(faces, faces_init[1]); + while ((f_iter = static_cast(BMW_step(®walker)))) { - faces.append(f_iter); + BLI_array_append(faces, f_iter); } - for (BMFace *face : faces) { - BMO_face_flag_disable(bm, face, FACE_TAG); - BMO_face_flag_enable(bm, face, FACE_ORIG); + for (i = 0; i < BLI_array_len(faces); i++) { + f_iter = faces[i]; + BMO_face_flag_disable(bm, f_iter, FACE_TAG); + BMO_face_flag_enable(bm, f_iter, FACE_ORIG); } - regions.append_as(std::move(faces)); + region = BLI_array_append_ret(regions); + region->faces = faces; + region->faces_len = BLI_array_len(faces); + + BLI_array_clear(faces); + /* Forces a new allocation. */ + faces = nullptr; } BMW_end(®walker); @@ -186,10 +199,13 @@ void bmo_dissolve_faces_exec(BMesh *bm, BMOperator *op) /* track how many faces we should end up with */ int totface_target = bm->totface; - for (Vector &faces : regions) { - const int64_t faces_len = faces.size(); + for (i = 0; i < BLI_array_len(regions); i++) { + region = ®ions[i]; - BMFace *f_new = BM_faces_join(bm, faces.data(), faces_len, true); + const int faces_len = region->faces_len; + faces = region->faces; + + BMFace *f_new = BM_faces_join(bm, faces, faces_len, true); if (f_new != nullptr) { /* Maintain the active face. */ if (act_face && bm->act_face == nullptr) { @@ -210,8 +226,8 @@ void bmo_dissolve_faces_exec(BMesh *bm, BMOperator *op) * This could optionally do a partial merge, where some faces are joined. */ /* Prevent these faces from being removed. */ - for (BMFace *face : faces) { - BMO_face_flag_disable(bm, face, FACE_ORIG); + for (int j = 0; j < faces_len; j++) { + BMO_face_flag_disable(bm, faces[j], FACE_ORIG); } } } @@ -237,6 +253,13 @@ void bmo_dissolve_faces_exec(BMesh *bm, BMOperator *op) BLI_assert(!BMO_error_occurred_at_level(bm, BMO_ERROR_FATAL)); BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "region.out", BM_FACE, FACE_NEW); + + /* free/cleanup */ + for (i = 0; i < BLI_array_len(regions); i++) { + MEM_freeN(regions[i].faces); + } + + BLI_array_free(regions); } void bmo_dissolve_edges_exec(BMesh *bm, BMOperator *op) diff --git a/source/blender/bmesh/operators/bmo_edgenet.cc b/source/blender/bmesh/operators/bmo_edgenet.cc index 076ff64eb51..db32e0ed59e 100644 --- a/source/blender/bmesh/operators/bmo_edgenet.cc +++ b/source/blender/bmesh/operators/bmo_edgenet.cc @@ -10,16 +10,14 @@ #include "MEM_guardedalloc.h" +#include "BLI_array.h" #include "BLI_math_vector.h" -#include "BLI_vector.hh" #include "bmesh.h" #include "bmesh_tools.h" #include "intern/bmesh_operators_private.h" /* own include */ -using blender::Vector; - #define EDGE_MARK 1 #define EDGE_VIS 2 @@ -98,6 +96,10 @@ void bmo_edgenet_prepare_exec(BMesh *bm, BMOperator *op) { BMOIter siter; BMEdge *e; + BMEdge **edges1 = nullptr, **edges2 = nullptr, **edges; + BLI_array_declare(edges1); + BLI_array_declare(edges2); + BLI_array_declare(edges); bool ok = true; int i, count; @@ -124,10 +126,6 @@ void bmo_edgenet_prepare_exec(BMesh *bm, BMOperator *op) return; } - Vector edges1; - Vector edges2; - Vector *edges; - /* find connected loops within the input edge */ count = 0; while (true) { @@ -146,10 +144,10 @@ void bmo_edgenet_prepare_exec(BMesh *bm, BMOperator *op) } if (!count) { - edges = &edges1; + edges = edges1; } else if (count == 1) { - edges = &edges2; + edges = edges2; } else { break; @@ -158,50 +156,69 @@ void bmo_edgenet_prepare_exec(BMesh *bm, BMOperator *op) i = 0; while (e) { BMO_edge_flag_enable(bm, e, EDGE_VIS); - edges->append(e); + BLI_array_grow_one(edges); + edges[i] = e; e = edge_next(bm, e); i++; } + if (!count) { + edges1 = edges; + BLI_array_len_set(edges1, BLI_array_len(edges)); + } + else { + edges2 = edges; + BLI_array_len_set(edges2, BLI_array_len(edges)); + } + + BLI_array_clear(edges); count++; } - if (edges1.size() > 2 && BM_edge_share_vert_check(edges1.first(), edges1.last())) { - if (edges2.size() > 2 && BM_edge_share_vert_check(edges2.first(), edges2.last())) { + if (edges1 && BLI_array_len(edges1) > 2 && + BM_edge_share_vert_check(edges1[0], edges1[BLI_array_len(edges1) - 1])) + { + if (edges2 && BLI_array_len(edges2) > 2 && + BM_edge_share_vert_check(edges2[0], edges2[BLI_array_len(edges2) - 1])) + { + BLI_array_free(edges1); + BLI_array_free(edges2); return; } edges1 = edges2; - edges2.clear(); + edges2 = nullptr; } - if (edges2.size() > 2 && BM_edge_share_vert_check(edges2.first(), edges2.last())) { - edges2.clear(); + if (edges2 && BLI_array_len(edges2) > 2 && + BM_edge_share_vert_check(edges2[0], edges2[BLI_array_len(edges2) - 1])) + { + edges2 = nullptr; } /* two unconnected loops, connect the */ - if (!edges1.is_empty() && !edges2.is_empty()) { + if (edges1 && edges2) { BMVert *v1, *v2, *v3, *v4; float dvec1[3]; float dvec2[3]; - if (edges1.size() == 1) { + if (BLI_array_len(edges1) == 1) { v1 = edges1[0]->v1; v2 = edges1[0]->v2; } else { v1 = BM_vert_in_edge(edges1[1], edges1[0]->v1) ? edges1[0]->v2 : edges1[0]->v1; - i = edges1.size() - 1; + i = BLI_array_len(edges1) - 1; v2 = BM_vert_in_edge(edges1[i - 1], edges1[i]->v1) ? edges1[i]->v2 : edges1[i]->v1; } - if (edges2.size() == 1) { + if (BLI_array_len(edges2) == 1) { v3 = edges2[0]->v1; v4 = edges2[0]->v2; } else { v3 = BM_vert_in_edge(edges2[1], edges2[0]->v1) ? edges2[0]->v2 : edges2[0]->v1; - i = edges2.size() - 1; + i = BLI_array_len(edges2) - 1; v4 = BM_vert_in_edge(edges2[i - 1], edges2[i]->v1) ? edges2[i]->v2 : edges2[i]->v1; } @@ -229,12 +246,12 @@ void bmo_edgenet_prepare_exec(BMesh *bm, BMOperator *op) e = BM_edge_create(bm, v2, v4, nullptr, BM_CREATE_NO_DOUBLE); BMO_edge_flag_enable(bm, e, ELE_NEW); } - else if (!edges1.is_empty()) { + else if (edges1) { BMVert *v1, *v2; - if (edges1.size() > 1) { + if (BLI_array_len(edges1) > 1) { v1 = BM_vert_in_edge(edges1[1], edges1[0]->v1) ? edges1[0]->v2 : edges1[0]->v1; - i = edges1.size() - 1; + i = BLI_array_len(edges1) - 1; v2 = BM_vert_in_edge(edges1[i - 1], edges1[i]->v1) ? edges1[i]->v2 : edges1[i]->v1; e = BM_edge_create(bm, v1, v2, nullptr, BM_CREATE_NO_DOUBLE); BMO_edge_flag_enable(bm, e, ELE_NEW); @@ -242,4 +259,7 @@ void bmo_edgenet_prepare_exec(BMesh *bm, BMOperator *op) } BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "edges.out", BM_EDGE, ELE_NEW); + + BLI_array_free(edges1); + BLI_array_free(edges2); } diff --git a/source/blender/bmesh/operators/bmo_extrude.cc b/source/blender/bmesh/operators/bmo_extrude.cc index e3f9ac5eaf1..aea7a3a504b 100644 --- a/source/blender/bmesh/operators/bmo_extrude.cc +++ b/source/blender/bmesh/operators/bmo_extrude.cc @@ -672,7 +672,7 @@ static void calc_solidify_normals(BMesh *bm) continue; } - i = edge_face_count[BM_elem_index_get(e)]; + i = edge_face_count[BM_elem_index_get(e)]++; if (i == 0 || i > 2) { /* Edge & vertices are non-manifold even when considering diff --git a/source/blender/bmesh/operators/bmo_hull.cc b/source/blender/bmesh/operators/bmo_hull.cc index b9ba01651e8..a534e9a2693 100644 --- a/source/blender/bmesh/operators/bmo_hull.cc +++ b/source/blender/bmesh/operators/bmo_hull.cc @@ -12,9 +12,9 @@ # include "MEM_guardedalloc.h" +# include "BLI_array.h" # include "BLI_listbase.h" # include "BLI_math_geom.h" -# include "BLI_vector.hh" # include "RBI_hull_api.h" @@ -25,8 +25,6 @@ # include "intern/bmesh_operators_private.h" /* own include */ -using blender::Vector; - /* Internal operator flags */ enum { HULL_FLAG_INPUT = (1 << 0), @@ -466,6 +464,9 @@ static BMVert **hull_verts_from_bullet(plConvexHull hull, static void hull_from_bullet(BMesh *bm, BMOperator *op, BLI_mempool *hull_triangles) { + int *fvi = nullptr; + BLI_array_declare(fvi); + BMVert **input_verts; float(*coords)[3]; BMVert **hull_verts; @@ -482,7 +483,6 @@ static void hull_from_bullet(BMesh *bm, BMOperator *op, BLI_mempool *hull_triang hull_verts = hull_verts_from_bullet(hull, input_verts, num_input_verts); count = plConvexHullNumFaces(hull); - Vector fvi; for (i = 0; i < count; i++) { const int len = plConvexHullGetFaceSize(hull, i); @@ -491,8 +491,9 @@ static void hull_from_bullet(BMesh *bm, BMOperator *op, BLI_mempool *hull_triang int j; /* Get face vertex indices */ - fvi.reinitialize(len); - plConvexHullGetFaceVertices(hull, i, fvi.data()); + BLI_array_clear(fvi); + BLI_array_grow_items(fvi, len); + plConvexHullGetFaceVertices(hull, i, fvi); /* NOTE: here we throw away any NGons from Bullet and turn * them into triangle fans. Would be nice to use these @@ -508,6 +509,8 @@ static void hull_from_bullet(BMesh *bm, BMOperator *op, BLI_mempool *hull_triang } } + BLI_array_free(fvi); + plConvexHullDelete(hull); MEM_freeN(hull_verts); diff --git a/source/blender/bmesh/operators/bmo_subdivide.cc b/source/blender/bmesh/operators/bmo_subdivide.cc index 60639acdf65..a8c1c5bc11a 100644 --- a/source/blender/bmesh/operators/bmo_subdivide.cc +++ b/source/blender/bmesh/operators/bmo_subdivide.cc @@ -8,16 +8,14 @@ * Edge based subdivision with various subdivision patterns. */ -#include - #include "MEM_guardedalloc.h" +#include "BLI_array.h" #include "BLI_math_geom.h" #include "BLI_math_vector.h" #include "BLI_noise.h" #include "BLI_rand.h" #include "BLI_stack.h" -#include "BLI_vector.hh" #include "BKE_customdata.h" @@ -25,8 +23,6 @@ #include "intern/bmesh_operators_private.h" #include "intern/bmesh_private.h" -using blender::Vector; - struct SubDParams { int numcuts; float smooth; @@ -915,10 +911,17 @@ void bmo_subdivide_edges_exec(BMesh *bm, BMOperator *op) SubDParams params; BLI_Stack *facedata; BMIter viter, fiter, liter; - BMVert *v; + BMVert *v, **verts = nullptr; BMEdge *edge; + BMEdge **edges = nullptr; + BLI_array_declare(edges); + BMLoop *(*loops_split)[2] = nullptr; + BLI_array_declare(loops_split); + BMLoop **loops = nullptr; + BLI_array_declare(loops); BMLoop *l_new, *l; BMFace *face; + BLI_array_declare(verts); float smooth, fractal, along_normal; bool use_sphere, use_single_edge, use_grid_fill, use_only_quads; int cornertype, seed, i, j, a, b, numcuts, totesel, smooth_falloff; @@ -1014,9 +1017,6 @@ void bmo_subdivide_edges_exec(BMesh *bm, BMOperator *op) facedata = BLI_stack_new(sizeof(SubDFaceData), __func__); - Vector verts; - Vector edges; - BM_ITER_MESH (face, &fiter, bm, BM_FACES_OF_MESH) { BMEdge *e1 = nullptr, *e2 = nullptr; float vec1[3], vec2[3]; @@ -1028,8 +1028,12 @@ void bmo_subdivide_edges_exec(BMesh *bm, BMOperator *op) } /* figure out which pattern to use */ - verts.reinitialize(face->len); - edges.reinitialize(face->len); + + BLI_array_clear(edges); + BLI_array_clear(verts); + + BLI_array_grow_items(edges, face->len); + BLI_array_grow_items(verts, face->len); totesel = 0; BM_ITER_ELEM_INDEX (l_new, &liter, face, BM_LOOPS_OF_FACE, i) { @@ -1153,27 +1157,30 @@ void bmo_subdivide_edges_exec(BMesh *bm, BMOperator *op) copy_v3_v3(v->co, co); } - using LoopPair = std::array; - Vector loops; - Vector loops_split; for (; !BLI_stack_is_empty(facedata); BLI_stack_discard(facedata)) { SubDFaceData *fd = static_cast(BLI_stack_peek(facedata)); face = fd->face; /* figure out which pattern to use */ + BLI_array_clear(verts); + pat = fd->pat; if (!pat && fd->totedgesel == 2) { + int vlen; + /* ok, no pattern. we still may be able to do something */ + BLI_array_clear(loops); + BLI_array_clear(loops_split); /* for case of two edges, connecting them shouldn't be too hard */ - loops.reinitialize(face->len); + BLI_array_grow_items(loops, face->len); BM_ITER_ELEM_INDEX (l, &liter, face, BM_LOOPS_OF_FACE, a) { loops[a] = l; } - const int64_t vlen = loops.size(); + vlen = BLI_array_len(loops); /* find the boundary of one of the split edges */ for (a = 0; a < vlen; a++) { @@ -1203,7 +1210,7 @@ void bmo_subdivide_edges_exec(BMesh *bm, BMOperator *op) b += numcuts - 1; - loops_split.reinitialize(numcuts); + BLI_array_grow_items(loops_split, numcuts); for (j = 0; j < numcuts; j++) { bool ok = true; @@ -1255,11 +1262,12 @@ void bmo_subdivide_edges_exec(BMesh *bm, BMOperator *op) */ // BM_face_splits_check_legal(bm, face, loops_split, BLI_array_len(loops_split)); - for (const LoopPair &loop_split : loops_split) { - if (loop_split[0]) { + for (j = 0; j < BLI_array_len(loops_split); j++) { + if (loops_split[j][0]) { BMFace *f_new; - BLI_assert(BM_edge_exists(loop_split[0]->v, loop_split[1]->v) == nullptr); - f_new = BM_face_split(bm, face, loop_split[0], loop_split[1], &l_new, nullptr, false); + BLI_assert(BM_edge_exists(loops_split[j][0]->v, loops_split[j][1]->v) == nullptr); + f_new = BM_face_split( + bm, face, loops_split[j][0], loops_split[j][1], &l_new, nullptr, false); if (f_new) { BMO_edge_flag_enable(bm, l_new->e, ELE_INNER); } @@ -1280,14 +1288,15 @@ void bmo_subdivide_edges_exec(BMesh *bm, BMOperator *op) } } - verts.reinitialize(face->len); + BLI_array_grow_items(verts, face->len); + BM_ITER_ELEM_INDEX (l_new, &liter, face, BM_LOOPS_OF_FACE, j) { b = (j - a + face->len) % face->len; verts[b] = l_new->v; } BM_CHECK_ELEMENT(face); - pat->connectexec(bm, face, verts.data(), ¶ms); + pat->connectexec(bm, face, verts, ¶ms); } /* copy original-geometry displacements to current coordinates */ @@ -1300,6 +1309,14 @@ void bmo_subdivide_edges_exec(BMesh *bm, BMOperator *op) BM_data_layer_free_n(bm, &bm->vdata, CD_SHAPEKEY, params.shape_info.tmpkey); BLI_stack_free(facedata); + if (edges) { + BLI_array_free(edges); + } + if (verts) { + BLI_array_free(verts); + } + BLI_array_free(loops_split); + BLI_array_free(loops); BMO_slot_buffer_from_enabled_flag( bm, op, op->slots_out, "geom_inner.out", BM_ALL_NOLOOP, ELE_INNER); diff --git a/source/blender/bmesh/tools/bmesh_bevel.cc b/source/blender/bmesh/tools/bmesh_bevel.cc index 2008e22bf03..9cd864cb295 100644 --- a/source/blender/bmesh/tools/bmesh_bevel.cc +++ b/source/blender/bmesh/tools/bmesh_bevel.cc @@ -16,13 +16,13 @@ #include "DNA_scene_types.h" #include "BLI_alloca.h" +#include "BLI_array.h" #include "BLI_math_geom.h" #include "BLI_math_matrix.h" #include "BLI_math_rotation.h" #include "BLI_math_vector.h" #include "BLI_memarena.h" #include "BLI_utildefines.h" -#include "BLI_vector.hh" #include "BKE_curveprofile.h" #include "BKE_customdata.h" @@ -36,8 +36,6 @@ #include "./intern/bmesh_private.h" -using blender::Vector; - // #define BEVEL_DEBUG_TIME #ifdef BEVEL_DEBUG_TIME # include "PIL_time.h" @@ -378,6 +376,8 @@ struct BevelParams { int vmesh_method; /** Amount to spread when doing inside miter. */ float spread; + /** Mesh's smoothresh, used if hardening. */ + float smoothresh; }; // #pragma GCC diagnostic ignored "-Wpadded" @@ -2495,6 +2495,7 @@ static void bevel_harden_normals(BevelParams *bp, BMesh *bm) * To get that to happen, we have to mark the sharpen the edges that are only sharp because * of the angle test -- otherwise would be smooth. */ if (cd_clnors_offset == -1) { + BM_edges_sharp_from_angle_set(bm, bp->smoothresh); bevel_edges_sharp_boundary(bm, bp); } @@ -4923,9 +4924,12 @@ static BMFace *frep_for_center_poly(BevelParams *bp, BevVert *bv) static void build_center_ngon(BevelParams *bp, BMesh *bm, BevVert *bv, int mat_nr) { VMesh *vm = bv->vmesh; - Vector vv; - Vector vf; - Vector ve; + BMVert **vv = nullptr; + BMFace **vf = nullptr; + BMEdge **ve = nullptr; + BLI_array_staticdeclare(vv, BM_DEFAULT_NGON_STACK_SIZE); + BLI_array_staticdeclare(vf, BM_DEFAULT_NGON_STACK_SIZE); + BLI_array_staticdeclare(ve, BM_DEFAULT_NGON_STACK_SIZE); int ns2 = vm->seg / 2; BMFace *frep; @@ -4943,24 +4947,28 @@ static void build_center_ngon(BevelParams *bp, BMesh *bm, BevVert *bv, int mat_n BoundVert *v = vm->boundstart; do { int i = v->index; - vv.append(mesh_vert(vm, i, ns2, ns2)->v); + BLI_array_append(vv, mesh_vert(vm, i, ns2, ns2)->v); if (frep) { - vf.append(frep); + BLI_array_append(vf, frep); if (ELEM(v, frep_unsnapped[0], frep_unsnapped[1], frep_unsnapped[2])) { - ve.append(nullptr); + BLI_array_append(ve, nullptr); } else { BMEdge *frep_e = find_closer_edge(mesh_vert(vm, i, ns2, ns2)->v->co, frep_e1, frep_e2); - ve.append(frep_e); + BLI_array_append(ve, frep_e); } } else { - vf.append(boundvert_rep_face(v, nullptr)); - ve.append(nullptr); + BLI_array_append(vf, boundvert_rep_face(v, nullptr)); + BLI_array_append(ve, nullptr); } } while ((v = v->next) != vm->boundstart); - BMFace *f = bev_create_ngon(bm, vv.data(), vv.size(), vf.data(), frep, ve.data(), mat_nr, true); + BMFace *f = bev_create_ngon(bm, vv, BLI_array_len(vv), vf, frep, ve, mat_nr, true); record_face_kind(bp, f, F_VERT); + + BLI_array_free(vv); + BLI_array_free(vf); + BLI_array_free(ve); } /** @@ -5690,6 +5698,10 @@ static void bevel_build_cutoff(BevelParams *bp, BMesh *bm, BevVert *bv) bndv = bv->vmesh->boundstart; do { int i = bndv->index; + BMEdge **bmedges = nullptr; + BMFace **bmfaces = nullptr; + BLI_array_staticdeclare(bmedges, BM_DEFAULT_NGON_STACK_SIZE); + BLI_array_staticdeclare(bmfaces, BM_DEFAULT_NGON_STACK_SIZE); /* Add the first corner vertex under this boundvert. */ face_bmverts[0] = mesh_vert(bv->vmesh, i, 1, 0)->v; @@ -5723,31 +5735,45 @@ static void bevel_build_cutoff(BevelParams *bp, BMesh *bm, BevVert *bv) bev_create_ngon(bm, face_bmverts, bp->seg + 2 + build_center_face, + bmfaces, nullptr, - nullptr, - nullptr, + bmedges, bp->mat_nr, true); + + BLI_array_free(bmedges); + BLI_array_free(bmfaces); } while ((bndv = bndv->next) != bv->vmesh->boundstart); /* Create the bottom face if it should be built, reusing previous face_bmverts allocation. */ if (build_center_face) { + BMEdge **bmedges = nullptr; + BMFace **bmfaces = nullptr; + BLI_array_staticdeclare(bmedges, BM_DEFAULT_NGON_STACK_SIZE); + BLI_array_staticdeclare(bmfaces, BM_DEFAULT_NGON_STACK_SIZE); + /* Add all of the corner vertices to this face. */ for (int i = 0; i < n_bndv; i++) { /* Add verts from each cutoff face. */ face_bmverts[i] = mesh_vert(bv->vmesh, i, 1, 0)->v; } + // BLI_array_append(bmfaces, repface); + bev_create_ngon(bm, face_bmverts, n_bndv, bmfaces, nullptr, bmedges, bp->mat_nr, true); - bev_create_ngon(bm, face_bmverts, n_bndv, nullptr, nullptr, nullptr, bp->mat_nr, true); + BLI_array_free(bmedges); + BLI_array_free(bmfaces); } } static BMFace *bevel_build_poly(BevelParams *bp, BMesh *bm, BevVert *bv) { VMesh *vm = bv->vmesh; - Vector bmverts; - Vector bmedges; - Vector bmfaces; + BMVert **bmverts = nullptr; + BMEdge **bmedges = nullptr; + BMFace **bmfaces = nullptr; + BLI_array_staticdeclare(bmverts, BM_DEFAULT_NGON_STACK_SIZE); + BLI_array_staticdeclare(bmedges, BM_DEFAULT_NGON_STACK_SIZE); + BLI_array_staticdeclare(bmfaces, BM_DEFAULT_NGON_STACK_SIZE); BMFace *repface; BMEdge *repface_e1, *repface_e2; @@ -5766,34 +5792,34 @@ static BMFace *bevel_build_poly(BevelParams *bp, BMesh *bm, BevVert *bv) do { /* Accumulate vertices for vertex ngon. */ /* Also accumulate faces in which uv interpolation is to happen for each. */ - bmverts.append(bndv->nv.v); + BLI_array_append(bmverts, bndv->nv.v); if (repface) { - bmfaces.append(repface); + BLI_array_append(bmfaces, repface); if (ELEM(bndv, unsnapped[0], unsnapped[1], unsnapped[2])) { - bmedges.append(nullptr); + BLI_array_append(bmedges, nullptr); } else { BMEdge *frep_e = find_closer_edge(bndv->nv.v->co, repface_e1, repface_e2); - bmedges.append(frep_e); + BLI_array_append(bmedges, frep_e); } } else { - bmfaces.append(boundvert_rep_face(bndv, nullptr)); - bmedges.append(nullptr); + BLI_array_append(bmfaces, boundvert_rep_face(bndv, nullptr)); + BLI_array_append(bmedges, nullptr); } n++; if (bndv->ebev && bndv->ebev->seg > 1) { for (int k = 1; k < bndv->ebev->seg; k++) { - bmverts.append(mesh_vert(vm, bndv->index, 0, k)->v); + BLI_array_append(bmverts, mesh_vert(vm, bndv->index, 0, k)->v); if (repface) { - bmfaces.append(repface); + BLI_array_append(bmfaces, repface); BMEdge *frep_e = find_closer_edge( mesh_vert(vm, bndv->index, 0, k)->v->co, repface_e1, repface_e2); - bmedges.append(k < bndv->ebev->seg / 2 ? nullptr : frep_e); + BLI_array_append(bmedges, k < bndv->ebev->seg / 2 ? nullptr : frep_e); } else { - bmfaces.append(boundvert_rep_face(bndv, nullptr)); - bmedges.append(nullptr); + BLI_array_append(bmfaces, boundvert_rep_face(bndv, nullptr)); + BLI_array_append(bmedges, nullptr); } n++; } @@ -5802,13 +5828,15 @@ static BMFace *bevel_build_poly(BevelParams *bp, BMesh *bm, BevVert *bv) BMFace *f; if (n > 2) { - f = bev_create_ngon( - bm, bmverts.data(), n, bmfaces.data(), repface, bmedges.data(), bp->mat_nr, true); + f = bev_create_ngon(bm, bmverts, n, bmfaces, repface, bmedges, bp->mat_nr, true); record_face_kind(bp, f, F_VERT); } else { f = nullptr; } + BLI_array_free(bmverts); + BLI_array_free(bmedges); + BLI_array_free(bmfaces); return f; } @@ -6080,8 +6108,10 @@ static float edge_face_angle(EdgeHalf *e) */ static int bevel_edge_order_extend(BMesh *bm, BevVert *bv, int i) { - Vector sucs; /* Likely very few faces attached to same edge. */ - Vector save_path; + BMEdge **sucs = nullptr; + BMEdge **save_path = nullptr; + BLI_array_staticdeclare(sucs, 4); /* Likely very few faces attached to same edge. */ + BLI_array_staticdeclare(save_path, BM_DEFAULT_NGON_STACK_SIZE); /* Fill sucs with all unmarked edges of bmesh. */ BMEdge *bme = bv->edges[i].e; @@ -6090,10 +6120,10 @@ static int bevel_edge_order_extend(BMesh *bm, BevVert *bv, int i) BM_ITER_ELEM (l, &iter, bme, BM_LOOPS_OF_EDGE) { BMEdge *bme2 = (l->v == bv->v) ? l->prev->e : l->next->e; if (!BM_BEVEL_EDGE_TAG_TEST(bme2)) { - sucs.append(bme2); + BLI_array_append(sucs, bme2); } } - const int64_t nsucs = sucs.size(); + int nsucs = BLI_array_len(sucs); int bestj = i; int j = i; @@ -6109,9 +6139,9 @@ static int bevel_edge_order_extend(BMesh *bm, BevVert *bv, int i) (tryj == bestj && edges_face_connected_at_vert(bv->edges[tryj].e, bv->edges[0].e))) { bestj = tryj; - save_path.clear(); + BLI_array_clear(save_path); for (int k = j + 1; k <= bestj; k++) { - save_path.append(bv->edges[k].e); + BLI_array_append(save_path, bv->edges[k].e); } } /* Now reset to path only-going-to-j state. */ @@ -6130,6 +6160,8 @@ static int bevel_edge_order_extend(BMesh *bm, BevVert *bv, int i) BM_BEVEL_EDGE_TAG_ENABLE(bv->edges[k].e); } } + BLI_array_free(sucs); + BLI_array_free(save_path); return bestj; } @@ -6589,9 +6621,12 @@ static BevVert *bevel_vert_construct(BMesh *bm, BevelParams *bp, BMVert *v) static bool bev_rebuild_polygon(BMesh *bm, BevelParams *bp, BMFace *f) { bool do_rebuild = false; - Vector vv; - Vector vv_fix; - Vector ee; + BMVert **vv = nullptr; + BMVert **vv_fix = nullptr; + BMEdge **ee = nullptr; + BLI_array_staticdeclare(vv, BM_DEFAULT_NGON_STACK_SIZE); + BLI_array_staticdeclare(vv_fix, BM_DEFAULT_NGON_STACK_SIZE); + BLI_array_staticdeclare(ee, BM_DEFAULT_NGON_STACK_SIZE); BMIter liter; BMLoop *l; @@ -6654,8 +6689,8 @@ static bool bev_rebuild_polygon(BMesh *bm, BevelParams *bp, BMFace *f) BLI_assert(vstart != nullptr && vend != nullptr); BoundVert *v = vstart; if (!on_profile_start) { - vv.append(v->nv.v); - ee.append(bme); + BLI_array_append(vv, v->nv.v); + BLI_array_append(ee, bme); } while (v != vend) { /* Check for special case: multi-segment 3rd face opposite a beveled edge with no vmesh. */ @@ -6679,10 +6714,10 @@ static bool bev_rebuild_polygon(BMesh *bm, BevelParams *bp, BMFace *f) for (int k = kstart; k <= kend; k++) { BMVert *bmv = mesh_vert(vm, i, 0, k)->v; if (bmv) { - vv.append(bmv); - ee.append(bme); /* TODO: Maybe better edge here. */ + BLI_array_append(vv, bmv); + BLI_array_append(ee, bme); /* TODO: Maybe better edge here. */ if (corner3special && v->ebev && !bv->any_seam && k != vm->seg) { - vv_fix.append(bmv); + BLI_array_append(vv_fix, bmv); } } } @@ -6708,10 +6743,10 @@ static bool bev_rebuild_polygon(BMesh *bm, BevelParams *bp, BMFace *f) for (int k = kstart; k >= kend; k--) { BMVert *bmv = mesh_vert(vm, i, 0, k)->v; if (bmv) { - vv.append(bmv); - ee.append(bme); + BLI_array_append(vv, bmv); + BLI_array_append(ee, bme); if (corner3special && v->ebev && !bv->any_seam && k != 0) { - vv_fix.append(bmv); + BLI_array_append(vv_fix, bmv); } } } @@ -6721,20 +6756,20 @@ static bool bev_rebuild_polygon(BMesh *bm, BevelParams *bp, BMFace *f) do_rebuild = true; } else { - vv.append(l->v); - ee.append(l->e); + BLI_array_append(vv, l->v); + BLI_array_append(ee, l->e); } } if (do_rebuild) { - const int64_t n = vv.size(); - BMFace *f_new = bev_create_ngon(bm, vv.data(), n, nullptr, f, nullptr, -1, true); + int n = BLI_array_len(vv); + BMFace *f_new = bev_create_ngon(bm, vv, n, nullptr, f, nullptr, -1, true); - for (int64_t k = 0; k < vv_fix.size(); k++) { + for (int k = 0; k < BLI_array_len(vv_fix); k++) { bev_merge_uvs(bm, vv_fix[k]); } /* Copy attributes from old edges. */ - BLI_assert(n == ee.size()); + BLI_assert(n == BLI_array_len(ee)); BMEdge *bme_prev = ee[n - 1]; for (int k = 0; k < n; k++) { BMEdge *bme_new = BM_edge_exists(vv[k], vv[(k + 1) % n]); @@ -6784,6 +6819,9 @@ static bool bev_rebuild_polygon(BMesh *bm, BevelParams *bp, BMFace *f) } } + BLI_array_free(vv); + BLI_array_free(vv_fix); + BLI_array_free(ee); return do_rebuild; } @@ -7716,6 +7754,7 @@ void BM_mesh_bevel(BMesh *bm, const int miter_outer, const int miter_inner, const float spread, + const float smoothresh, const CurveProfile *custom_profile, const int vmesh_method) { @@ -7752,6 +7791,7 @@ void BM_mesh_bevel(BMesh *bm, bp.miter_outer = miter_outer; bp.miter_inner = miter_inner; bp.spread = spread; + bp.smoothresh = smoothresh; bp.face_hash = nullptr; bp.profile_type = profile_type; bp.custom_profile = custom_profile; diff --git a/source/blender/bmesh/tools/bmesh_bevel.h b/source/blender/bmesh/tools/bmesh_bevel.h index 2f9f15e3bc5..fa6c497f750 100644 --- a/source/blender/bmesh/tools/bmesh_bevel.h +++ b/source/blender/bmesh/tools/bmesh_bevel.h @@ -47,6 +47,7 @@ void BM_mesh_bevel(BMesh *bm, int miter_outer, int miter_inner, float spread, + float smoothresh, const struct CurveProfile *custom_profile, int vmesh_method); diff --git a/source/blender/bmesh/tools/bmesh_boolean.cc b/source/blender/bmesh/tools/bmesh_boolean.cc index e211b64c5df..2ccc7e9a3e5 100644 --- a/source/blender/bmesh/tools/bmesh_boolean.cc +++ b/source/blender/bmesh/tools/bmesh_boolean.cc @@ -216,7 +216,7 @@ static bool apply_mesh_output_to_bmesh(BMesh *bm, IMesh &m_out, bool keep_hidden /* Save the original #BMEdge's so we can use them as examples. */ Array old_edges(bm->totedge); - std::copy_n(bm->etable, bm->totedge, old_edges.begin()); + std::copy(bm->etable, bm->etable + bm->totedge, old_edges.begin()); /* Reuse or make new #BMFace's, as the faces are identical to old ones or not. * If reusing, mark them as "keep". First find the maximum face length diff --git a/source/blender/bmesh/tools/bmesh_decimate_collapse.cc b/source/blender/bmesh/tools/bmesh_decimate_collapse.cc index 107a2e221fd..df77c08f260 100644 --- a/source/blender/bmesh/tools/bmesh_decimate_collapse.cc +++ b/source/blender/bmesh/tools/bmesh_decimate_collapse.cc @@ -1380,7 +1380,8 @@ void BM_mesh_decimate_collapse(BMesh *bm, while ((bm->totface > face_tot_target) && (BLI_heap_is_empty(eheap) == false) && (BLI_heap_top_value(eheap) != COST_INVALID)) { - /* NOTE: + /** + * \note * - `eheap_table[e_index_mirr]` is only removed from the heap at the last moment * since its possible (in theory) for collapsing `e` to remove `e_mirr`. * - edges sharing a vertex are ignored, so the pivot vertex isn't moved to one side. diff --git a/source/blender/compositor/CMakeLists.txt b/source/blender/compositor/CMakeLists.txt index da8709a32ff..4b7bcda220e 100644 --- a/source/blender/compositor/CMakeLists.txt +++ b/source/blender/compositor/CMakeLists.txt @@ -33,7 +33,7 @@ if(WITH_COMPOSITOR_CPU) ) set(SRC - COM_compositor.hh + COM_compositor.h COM_defines.h intern/COM_BufferArea.h diff --git a/source/blender/compositor/COM_compositor.hh b/source/blender/compositor/COM_compositor.h similarity index 98% rename from source/blender/compositor/COM_compositor.hh rename to source/blender/compositor/COM_compositor.h index 956554463ce..3e4c3d0820b 100644 --- a/source/blender/compositor/COM_compositor.hh +++ b/source/blender/compositor/COM_compositor.h @@ -7,6 +7,10 @@ #include "DNA_color_types.h" #include "DNA_node_types.h" +#ifdef __cplusplus +extern "C" { +#endif + struct Render; /* Keep ascii art. */ @@ -251,7 +255,7 @@ struct Render; * the WorkScheduler controls the devices and selects the device where a * WorkPackage will be calculated. * - * \subsection WS_Devices Work-scheduler + * \subsection WS_Devices Workscheduler * The WorkScheduler controls all Devices. * When initializing the compositor the WorkScheduler selects all * devices that will be used during compositor. @@ -262,16 +266,16 @@ struct Render; * If this is the case the chunk will be added to the work-list for OpenCLDevice's * otherwise the chunk will be added to the work-list of CPUDevices. * - * A thread will read the work-list and sends a work-package to its device. + * A thread will read the work-list and sends a workpackage to its device. * * \see WorkScheduler.schedule method that is called to schedule a chunk * \see Device.execute method called to execute a chunk * * \subsection CPUDevice CPUDevice - * When a CPUDevice gets a WorkPackage the Device will get the input-buffer that is needed to + * When a CPUDevice gets a WorkPackage the Device will get the inputbuffer that is needed to * calculate the chunk. Allocation is already done by the ExecutionGroup. - * The output-buffer of the chunk is being created. - * The OutputOperation of the ExecutionGroup is called to execute the area of the output-buffer. + * The outputbuffer of the chunk is being created. + * The OutputOperation of the ExecutionGroup is called to execute the area of the outputbuffer. * * \see ExecutionGroup * \see NodeOperation.execute_region executes a single chunk of a NodeOperation @@ -345,3 +349,7 @@ void COM_deinitialize(void); * To deinitialize the compositor use the COM_deinitialize method. */ // void COM_clear_caches(void); // NOT YET WRITTEN + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/compositor/intern/COM_ExecutionGroup.h b/source/blender/compositor/intern/COM_ExecutionGroup.h index 27c1c22fe19..857d1dc6e02 100644 --- a/source/blender/compositor/intern/COM_ExecutionGroup.h +++ b/source/blender/compositor/intern/COM_ExecutionGroup.h @@ -305,15 +305,15 @@ class ExecutionGroup { void init_execution(); /** - * \brief get all input-buffers needed to calculate an chunk - * \note all input-buffers must be executed + * \brief get all inputbuffers needed to calculate an chunk + * \note all inputbuffers must be executed * \param chunk_number: the chunk to be calculated * \return `(MemoryBuffer **)` the input-buffers. */ MemoryBuffer **get_input_buffers_opencl(int chunk_number); /** - * \brief allocate the output-buffer of a chunk + * \brief allocate the outputbuffer of a chunk * \param chunk_number: the number of the chunk in the ExecutionGroup * \param rect: the rect of that chunk * \see determine_chunk_rect diff --git a/source/blender/compositor/intern/COM_MultiThreadedRowOperation.h b/source/blender/compositor/intern/COM_MultiThreadedRowOperation.h index c88d231fa80..48e376444ab 100644 --- a/source/blender/compositor/intern/COM_MultiThreadedRowOperation.h +++ b/source/blender/compositor/intern/COM_MultiThreadedRowOperation.h @@ -4,6 +4,8 @@ #pragma once +#include "BLI_array.h" + #include "COM_MultiThreadedOperation.h" namespace blender::compositor { diff --git a/source/blender/compositor/intern/COM_NodeOperation.h b/source/blender/compositor/intern/COM_NodeOperation.h index 4a7cc800766..abf1eacf666 100644 --- a/source/blender/compositor/intern/COM_NodeOperation.h +++ b/source/blender/compositor/intern/COM_NodeOperation.h @@ -470,7 +470,7 @@ class NodeOperation { * \param rect: the rectangle of the chunk (location and size) * \param chunk_number: the chunk_number to be calculated * \param memory_buffers: all input MemoryBuffer's needed - * \param output_buffer: the output-buffer to write to + * \param output_buffer: the outputbuffer to write to */ virtual void execute_opencl_region(OpenCLDevice * /*device*/, rcti * /*rect*/, diff --git a/source/blender/compositor/intern/COM_compositor.cc b/source/blender/compositor/intern/COM_compositor.cc index 80a3c75e6aa..718e11cf8df 100644 --- a/source/blender/compositor/intern/COM_compositor.cc +++ b/source/blender/compositor/intern/COM_compositor.cc @@ -12,7 +12,7 @@ #include "COM_ExecutionSystem.h" #include "COM_WorkScheduler.h" -#include "COM_compositor.hh" +#include "COM_compositor.h" #include "RE_compositor.hh" diff --git a/source/blender/compositor/nodes/COM_CryptomatteNode.cc b/source/blender/compositor/nodes/COM_CryptomatteNode.cc index 1949f2cf40d..d3283bfdd8b 100644 --- a/source/blender/compositor/nodes/COM_CryptomatteNode.cc +++ b/source/blender/compositor/nodes/COM_CryptomatteNode.cc @@ -6,7 +6,7 @@ #include "BKE_node.hh" -#include "NOD_composite.hh" +#include "NOD_composite.h" #include "COM_ConvertOperation.h" #include "COM_CryptomatteNode.h" @@ -104,9 +104,6 @@ void CryptomatteNode::input_operations_from_render_source( RenderResult *render_result = render ? RE_AcquireResultRead(render) : nullptr; if (!render_result) { - if (render) { - RE_ReleaseResult(render); - } return; } diff --git a/source/blender/compositor/nodes/COM_KeyingScreenNode.cc b/source/blender/compositor/nodes/COM_KeyingScreenNode.cc index a966c3181e0..01100d8c939 100644 --- a/source/blender/compositor/nodes/COM_KeyingScreenNode.cc +++ b/source/blender/compositor/nodes/COM_KeyingScreenNode.cc @@ -25,7 +25,6 @@ void KeyingScreenNode::convert_to_operations(NodeConverter &converter, KeyingScreenOperation *operation = new KeyingScreenOperation(); operation->set_movie_clip(clip); operation->set_tracking_object(keyingscreen_data->tracking_object); - operation->set_smoothness(keyingscreen_data->smoothness); operation->set_framenumber(context.get_framenumber()); converter.add_operation(operation); diff --git a/source/blender/compositor/nodes/COM_KuwaharaNode.cc b/source/blender/compositor/nodes/COM_KuwaharaNode.cc index c5c3ee4506d..a1ef1fe686f 100644 --- a/source/blender/compositor/nodes/COM_KuwaharaNode.cc +++ b/source/blender/compositor/nodes/COM_KuwaharaNode.cc @@ -24,10 +24,10 @@ void KuwaharaNode::convert_to_operations(NodeConverter &converter, switch (data->variation) { case CMP_NODE_KUWAHARA_CLASSIC: { KuwaharaClassicOperation *operation = new KuwaharaClassicOperation(); + operation->set_kernel_size(data->size); converter.add_operation(operation); converter.map_input_socket(get_input_socket(0), operation->get_input_socket(0)); - converter.map_input_socket(get_input_socket(1), operation->get_input_socket(1)); converter.map_output_socket(get_output_socket(0), operation->get_output_socket()); break; } @@ -68,10 +68,8 @@ void KuwaharaNode::convert_to_operations(NodeConverter &converter, converter.add_operation(kuwahara_anisotropic_operation); converter.map_input_socket(get_input_socket(0), kuwahara_anisotropic_operation->get_input_socket(0)); - converter.map_input_socket(get_input_socket(1), - kuwahara_anisotropic_operation->get_input_socket(1)); converter.add_link(blur_y_operation->get_output_socket(0), - kuwahara_anisotropic_operation->get_input_socket(2)); + kuwahara_anisotropic_operation->get_input_socket(1)); converter.map_output_socket(get_output_socket(0), kuwahara_anisotropic_operation->get_output_socket(0)); diff --git a/source/blender/compositor/operations/COM_KeyingScreenOperation.cc b/source/blender/compositor/operations/COM_KeyingScreenOperation.cc index 95d3db1d0ba..1bf7e6bbae7 100644 --- a/source/blender/compositor/operations/COM_KeyingScreenOperation.cc +++ b/source/blender/compositor/operations/COM_KeyingScreenOperation.cc @@ -6,11 +6,8 @@ #include "DNA_defaults.h" -#include "BLI_array.hh" #include "BLI_math_color.h" #include "BLI_math_geom.h" -#include "BLI_math_vector.hh" -#include "BLI_math_vector_types.hh" #include "BKE_movieclip.h" #include "BKE_tracking.h" @@ -27,32 +24,58 @@ KeyingScreenOperation::KeyingScreenOperation() framenumber_ = 0; tracking_object_[0] = 0; flags_.complex = true; - cached_marker_points_ = nullptr; + cached_triangulation_ = nullptr; } void KeyingScreenOperation::init_execution() { init_mutex(); if (execution_model_ == eExecutionModel::FullFrame) { - BLI_assert(cached_marker_points_ == nullptr); + BLI_assert(cached_triangulation_ == nullptr); if (movie_clip_) { - cached_marker_points_ = compute_marker_points(); + cached_triangulation_ = build_voronoi_triangulation(); } } else { - cached_marker_points_ = nullptr; + cached_triangulation_ = nullptr; } } void KeyingScreenOperation::deinit_execution() { - delete cached_marker_points_; - cached_marker_points_ = nullptr; + if (cached_triangulation_) { + TriangulationData *triangulation = cached_triangulation_; + + if (triangulation->triangulated_points) { + MEM_freeN(triangulation->triangulated_points); + } + + if (triangulation->triangles) { + MEM_freeN(triangulation->triangles); + } + + if (triangulation->triangles_AABB) { + MEM_freeN(triangulation->triangles_AABB); + } + + MEM_freeN(cached_triangulation_); + + cached_triangulation_ = nullptr; + } } -Array *KeyingScreenOperation::compute_marker_points() +KeyingScreenOperation::TriangulationData *KeyingScreenOperation::build_voronoi_triangulation() { + MovieClipUser user = *DNA_struct_default_get(MovieClipUser); + TriangulationData *triangulation; MovieTracking *tracking = &movie_clip_->tracking; + ImBuf *ibuf; + ListBase edges = {nullptr, nullptr}; + int sites_total; + int i; + int width = this->get_width(); + int height = this->get_height(); + int clip_frame = BKE_movieclip_remap_scene_to_clip_frame(movie_clip_, framenumber_); const MovieTrackingObject *tracking_object = nullptr; if (tracking_object_[0]) { @@ -64,12 +87,10 @@ Array *KeyingScreenOperation::compute_marker else { tracking_object = BKE_tracking_object_get_active(tracking); } - BLI_assert(tracking_object != nullptr); - - int clip_frame = BKE_movieclip_remap_scene_to_clip_frame(movie_clip_, framenumber_); + BLI_assert(tracking_object != 0); /* count sites */ - int sites_total = 0; + sites_total = 0; LISTBASE_FOREACH (MovieTrackingTrack *, track, &tracking_object->tracks) { const MovieTrackingMarker *marker = BKE_tracking_marker_get(track, clip_frame); @@ -91,16 +112,17 @@ Array *KeyingScreenOperation::compute_marker return nullptr; } - MovieClipUser user = *DNA_struct_default_get(MovieClipUser); BKE_movieclip_user_set_frame(&user, clip_frame); - ImBuf *ibuf = BKE_movieclip_get_ibuf(movie_clip_, &user); + ibuf = BKE_movieclip_get_ibuf(movie_clip_, &user); if (!ibuf) { return nullptr; } - Array *marker_points = new Array(sites_total); + triangulation = MEM_cnew("keying screen triangulation data"); + VoronoiSite *sites = (VoronoiSite *)MEM_callocN(sizeof(VoronoiSite) * sites_total, + "keyingscreen voronoi sites"); int track_index = 0; LISTBASE_FOREACH_INDEX (MovieTrackingTrack *, track, &tracking_object->tracks, track_index) { const MovieTrackingMarker *marker = BKE_tracking_marker_get(track, clip_frame); @@ -108,57 +130,155 @@ Array *KeyingScreenOperation::compute_marker continue; } - const float2 position = float2(marker->pos) + float2(track->offset); + float pos[2]; + add_v2_v2v2(pos, marker->pos, track->offset); - if (!IN_RANGE_INCL(position.x, 0.0f, 1.0f) || !IN_RANGE_INCL(position.y, 0.0f, 1.0f)) { + if (!IN_RANGE_INCL(pos[0], 0.0f, 1.0f) || !IN_RANGE_INCL(pos[1], 0.0f, 1.0f)) { continue; } ImBuf *pattern_ibuf = BKE_tracking_get_pattern_imbuf(ibuf, track, marker, true, false); - MarkerPoint &marker_point = (*marker_points)[track_index]; - marker_point.position = position; + VoronoiSite *site = &sites[track_index]; + zero_v3(site->color); - marker_point.color = float4(0.0f); if (pattern_ibuf) { for (int j = 0; j < pattern_ibuf->x * pattern_ibuf->y; j++) { if (pattern_ibuf->float_buffer.data) { - marker_point.color += float4(&pattern_ibuf->float_buffer.data[4 * j]); + add_v3_v3(site->color, &pattern_ibuf->float_buffer.data[4 * j]); } else { uchar *rrgb = pattern_ibuf->byte_buffer.data; - marker_point.color += float4(srgb_to_linearrgb(float(rrgb[4 * j + 0]) / 255.0f), - srgb_to_linearrgb(float(rrgb[4 * j + 1]) / 255.0f), - srgb_to_linearrgb(float(rrgb[4 * j + 2]) / 255.0f), - srgb_to_linearrgb(float(rrgb[4 * j + 3]) / 255.0f)); + + site->color[0] += srgb_to_linearrgb(float(rrgb[4 * j + 0]) / 255.0f); + site->color[1] += srgb_to_linearrgb(float(rrgb[4 * j + 1]) / 255.0f); + site->color[2] += srgb_to_linearrgb(float(rrgb[4 * j + 2]) / 255.0f); } } - marker_point.color /= pattern_ibuf->x * pattern_ibuf->y; + mul_v3_fl(site->color, 1.0f / (pattern_ibuf->x * pattern_ibuf->y)); IMB_freeImBuf(pattern_ibuf); } + + site->co[0] = pos[0] * width; + site->co[1] = pos[1] * height; + + site++; } IMB_freeImBuf(ibuf); - return marker_points; + BLI_voronoi_compute(sites, sites_total, width, height, &edges); + + BLI_voronoi_triangulate(sites, + sites_total, + &edges, + width, + height, + &triangulation->triangulated_points, + &triangulation->triangulated_points_total, + &triangulation->triangles, + &triangulation->triangles_total); + + MEM_freeN(sites); + BLI_freelistN(&edges); + + if (triangulation->triangles_total) { + rcti *rect; + rect = triangulation->triangles_AABB = (rcti *)MEM_callocN( + sizeof(rcti) * triangulation->triangles_total, "voronoi triangulation AABB"); + + for (i = 0; i < triangulation->triangles_total; i++, rect++) { + int *triangle = triangulation->triangles[i]; + VoronoiTriangulationPoint *a = &triangulation->triangulated_points[triangle[0]], + *b = &triangulation->triangulated_points[triangle[1]], + *c = &triangulation->triangulated_points[triangle[2]]; + + float min[2], max[2]; + + INIT_MINMAX2(min, max); + + minmax_v2v2_v2(min, max, a->co); + minmax_v2v2_v2(min, max, b->co); + minmax_v2v2_v2(min, max, c->co); + + rect->xmin = int(min[0]); + rect->ymin = int(min[1]); + + rect->xmax = int(max[0]) + 1; + rect->ymax = int(max[1]) + 1; + } + } + + return triangulation; } -void *KeyingScreenOperation::initialize_tile_data(rcti * /* rect*/) +KeyingScreenOperation::TileData *KeyingScreenOperation::triangulate(const rcti *rect) +{ + TileData *tile_data; + TriangulationData *triangulation; + int triangles_allocated = 0; + int chunk_size = 20; + int i; + + triangulation = cached_triangulation_; + + if (!triangulation) { + return nullptr; + } + + tile_data = MEM_cnew("keying screen tile data"); + + for (i = 0; i < triangulation->triangles_total; i++) { + if (BLI_rcti_isect(rect, &triangulation->triangles_AABB[i], nullptr)) { + tile_data->triangles_total++; + + if (tile_data->triangles_total > triangles_allocated) { + if (!tile_data->triangles) { + tile_data->triangles = (int *)MEM_mallocN(sizeof(int) * chunk_size, + "keying screen tile triangles chunk"); + } + else { + tile_data->triangles = (int *)MEM_reallocN( + tile_data->triangles, sizeof(int) * (triangles_allocated + chunk_size)); + } + + triangles_allocated += chunk_size; + } + + tile_data->triangles[tile_data->triangles_total - 1] = i; + } + } + + return tile_data; +} + +void *KeyingScreenOperation::initialize_tile_data(rcti *rect) { if (movie_clip_ == nullptr) { return nullptr; } - if (!cached_marker_points_) { + if (!cached_triangulation_) { lock_mutex(); - if (cached_marker_points_ == nullptr) { - cached_marker_points_ = compute_marker_points(); + if (cached_triangulation_ == nullptr) { + cached_triangulation_ = build_voronoi_triangulation(); } unlock_mutex(); } - return nullptr; + return triangulate(rect); +} + +void KeyingScreenOperation::deinitialize_tile_data(rcti * /*rect*/, void *data) +{ + TileData *tile_data = (TileData *)data; + + if (tile_data->triangles) { + MEM_freeN(tile_data->triangles); + } + + MEM_freeN(tile_data); } void KeyingScreenOperation::determine_canvas(const rcti &preferred_area, rcti &r_area) @@ -178,59 +298,95 @@ void KeyingScreenOperation::determine_canvas(const rcti &preferred_area, rcti &r } } -void KeyingScreenOperation::execute_pixel(float output[4], int x, int y, void * /* data */) +void KeyingScreenOperation::execute_pixel(float output[4], int x, int y, void *data) { - if (!cached_marker_points_) { - copy_v4_fl(output, 0.0f); - return; + output[0] = 0.0f; + output[1] = 0.0f; + output[2] = 0.0f; + output[3] = 1.0f; + + if (movie_clip_ && data) { + TriangulationData *triangulation = cached_triangulation_; + TileData *tile_data = (TileData *)data; + int i; + float co[2] = {float(x), float(y)}; + + for (i = 0; i < tile_data->triangles_total; i++) { + int triangle_idx = tile_data->triangles[i]; + rcti *rect = &triangulation->triangles_AABB[triangle_idx]; + + if (IN_RANGE_INCL(x, rect->xmin, rect->xmax) && IN_RANGE_INCL(y, rect->ymin, rect->ymax)) { + int *triangle = triangulation->triangles[triangle_idx]; + VoronoiTriangulationPoint *a = &triangulation->triangulated_points[triangle[0]], + *b = &triangulation->triangulated_points[triangle[1]], + *c = &triangulation->triangulated_points[triangle[2]]; + float w[3]; + + if (barycentric_coords_v2(a->co, b->co, c->co, co, w)) { + if (barycentric_inside_triangle_v2(w)) { + output[0] = a->color[0] * w[0] + b->color[0] * w[1] + c->color[0] * w[2]; + output[1] = a->color[1] * w[0] + b->color[1] * w[1] + c->color[1] * w[2]; + output[2] = a->color[2] * w[0] + b->color[2] * w[1] + c->color[2] * w[2]; + + break; + } + } + } + } } - - const int2 size = int2(this->get_width(), this->get_height()); - const float2 normalized_pixel_location = float2(x, y) / float2(size); - const float squared_shape_parameter = math::square(1.0f / smoothness_); - - float4 weighted_sum = float4(0.0f); - float sum_of_weights = 0.0f; - for (const MarkerPoint &marker_point : *cached_marker_points_) { - const float2 difference = normalized_pixel_location - marker_point.position; - const float squared_distance = math::dot(difference, difference); - const float gaussian = math::exp(-squared_distance * squared_shape_parameter); - weighted_sum += marker_point.color * gaussian; - sum_of_weights += gaussian; - } - weighted_sum /= sum_of_weights; - - copy_v4_v4(output, weighted_sum); } void KeyingScreenOperation::update_memory_buffer_partial(MemoryBuffer *output, const rcti &area, Span inputs) { - if (!cached_marker_points_) { - output->fill(area, COM_COLOR_TRANSPARENT); + if (movie_clip_ == nullptr) { + output->fill(area, COM_COLOR_BLACK); return; } - const int2 size = int2(this->get_width(), this->get_height()); - const float squared_shape_parameter = math::square(1.0f / smoothness_); + TileData *tri_area = this->triangulate(&area); + BLI_assert(tri_area != nullptr); + const int *triangles = tri_area->triangles; + const int num_triangles = tri_area->triangles_total; + const TriangulationData *triangulation = cached_triangulation_; for (BuffersIterator it = output->iterate_with(inputs, area); !it.is_end(); ++it) { - const float2 normalized_pixel_location = float2(it.x, it.y) / float2(size); + copy_v4_v4(it.out, COM_COLOR_BLACK); - float4 weighted_sum = float4(0.0f); - float sum_of_weights = 0.0f; - for (const MarkerPoint &marker_point : *cached_marker_points_) { - const float2 difference = normalized_pixel_location - marker_point.position; - const float squared_distance = math::dot(difference, difference); - const float gaussian = math::exp(-squared_distance * squared_shape_parameter); - weighted_sum += marker_point.color * gaussian; - sum_of_weights += gaussian; + const float co[2] = {float(it.x), float(it.y)}; + for (int i = 0; i < num_triangles; i++) { + const int triangle_idx = triangles[i]; + const rcti *rect = &triangulation->triangles_AABB[triangle_idx]; + + if (!BLI_rcti_isect_pt(rect, it.x, it.y)) { + continue; + } + + const int *triangle = triangulation->triangles[triangle_idx]; + const VoronoiTriangulationPoint &a = triangulation->triangulated_points[triangle[0]]; + const VoronoiTriangulationPoint &b = triangulation->triangulated_points[triangle[1]]; + const VoronoiTriangulationPoint &c = triangulation->triangulated_points[triangle[2]]; + + float w[3]; + if (!barycentric_coords_v2(a.co, b.co, c.co, co, w)) { + continue; + } + + if (barycentric_inside_triangle_v2(w)) { + it.out[0] = a.color[0] * w[0] + b.color[0] * w[1] + c.color[0] * w[2]; + it.out[1] = a.color[1] * w[0] + b.color[1] * w[1] + c.color[1] * w[2]; + it.out[2] = a.color[2] * w[0] + b.color[2] * w[1] + c.color[2] * w[2]; + break; + } } - weighted_sum /= sum_of_weights; - - copy_v4_v4(it.out, weighted_sum); } + + if (tri_area->triangles) { + MEM_freeN(tri_area->triangles); + } + + MEM_freeN(tri_area); } } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_KeyingScreenOperation.h b/source/blender/compositor/operations/COM_KeyingScreenOperation.h index aae53312c86..183b5e56eed 100644 --- a/source/blender/compositor/operations/COM_KeyingScreenOperation.h +++ b/source/blender/compositor/operations/COM_KeyingScreenOperation.h @@ -10,12 +10,11 @@ #include "DNA_movieclip_types.h" -#include "BLI_array.hh" #include "BLI_listbase.h" -#include "BLI_math_base.hh" -#include "BLI_math_vector_types.hh" #include "BLI_string.h" +#include "BLI_voronoi_2d.h" + namespace blender::compositor { /** @@ -23,15 +22,22 @@ namespace blender::compositor { */ class KeyingScreenOperation : public MultiThreadedOperation { protected: - struct MarkerPoint { - float2 position; - float4 color; - }; + typedef struct TriangulationData { + VoronoiTriangulationPoint *triangulated_points; + int (*triangles)[3]; + int triangulated_points_total, triangles_total; + rcti *triangles_AABB; + } TriangulationData; + + /* TODO(manzanilla): rename to #TrianguledArea on removing tiled implementation. */ + typedef struct TileData { + int *triangles; + int triangles_total; + } TileData; MovieClip *movie_clip_; - float smoothness_; int framenumber_; - Array *cached_marker_points_; + TriangulationData *cached_triangulation_; char tracking_object_[64]; /** @@ -39,7 +45,7 @@ class KeyingScreenOperation : public MultiThreadedOperation { */ void determine_canvas(const rcti &preferred_area, rcti &r_area) override; - Array *compute_marker_points(); + TriangulationData *build_voronoi_triangulation(); public: KeyingScreenOperation(); @@ -48,6 +54,7 @@ class KeyingScreenOperation : public MultiThreadedOperation { void deinit_execution() override; void *initialize_tile_data(rcti *rect) override; + void deinitialize_tile_data(rcti *rect, void *data) override; void set_movie_clip(MovieClip *clip) { @@ -57,10 +64,6 @@ class KeyingScreenOperation : public MultiThreadedOperation { { BLI_strncpy(tracking_object_, object, sizeof(tracking_object_)); } - void set_smoothness(float smoothness) - { - smoothness_ = math::interpolate(0.15f, 1.0f, smoothness); - } void set_framenumber(int framenumber) { framenumber_ = framenumber; @@ -71,6 +74,9 @@ class KeyingScreenOperation : public MultiThreadedOperation { void update_memory_buffer_partial(MemoryBuffer *output, const rcti &area, Span inputs) override; + + private: + TileData *triangulate(const rcti *rect); }; } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.cc b/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.cc index a131edf13a2..b9077f1d6ba 100644 --- a/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.cc +++ b/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.cc @@ -16,7 +16,6 @@ namespace blender::compositor { KuwaharaAnisotropicOperation::KuwaharaAnisotropicOperation() { this->add_input_socket(DataType::Color); - this->add_input_socket(DataType::Value); this->add_input_socket(DataType::Color); this->add_output_socket(DataType::Color); this->flags_.is_fullframe_operation = true; @@ -25,14 +24,12 @@ KuwaharaAnisotropicOperation::KuwaharaAnisotropicOperation() void KuwaharaAnisotropicOperation::init_execution() { image_reader_ = this->get_input_socket_reader(0); - size_reader_ = this->get_input_socket_reader(1); - structure_tensor_reader_ = this->get_input_socket_reader(2); + structure_tensor_reader_ = this->get_input_socket_reader(1); } void KuwaharaAnisotropicOperation::deinit_execution() { image_reader_ = nullptr; - size_reader_ = nullptr; structure_tensor_reader_ = nullptr; } @@ -90,18 +87,14 @@ void KuwaharaAnisotropicOperation::execute_pixel_sampled(float output[4], float eigenvalue_difference = first_eigenvalue - second_eigenvalue; float anisotropy = eigenvalue_sum > 0.0f ? eigenvalue_difference / eigenvalue_sum : 0.0f; - float4 size; - size_reader_->read(size, x, y, nullptr); - float radius = max(0.0f, size.x); - /* Compute the width and height of an ellipse that is more width-elongated for high anisotropy * and more circular for low anisotropy, controlled using the eccentricity factor. Since the * anisotropy is in the [0, 1] range, the width factor tends to 1 as the eccentricity tends to * infinity and tends to infinity when the eccentricity tends to zero. This is based on the * equations in section "3.2. Anisotropic Kuwahara Filtering" of the paper. */ float ellipse_width_factor = (get_eccentricity() + anisotropy) / get_eccentricity(); - float ellipse_width = ellipse_width_factor * radius; - float ellipse_height = radius / ellipse_width_factor; + float ellipse_width = ellipse_width_factor * data.size; + float ellipse_height = data.size / ellipse_width_factor; /* Compute the cosine and sine of the angle that the eigenvector makes with the x axis. Since * the eigenvector is normalized, its x and y components are the cosine and sine of the angle @@ -133,7 +126,7 @@ void KuwaharaAnisotropicOperation::execute_pixel_sampled(float output[4], * section "3 Alternative Weighting Functions" of the polynomial weights paper. More on this * later in the code. */ const int number_of_sectors = 8; - float sector_center_overlap_parameter = 2.0f / radius; + float sector_center_overlap_parameter = 2.0f / data.size; float sector_envelope_angle = ((3.0f / 2.0f) * M_PI) / number_of_sectors; float cross_sector_overlap_parameter = (sector_center_overlap_parameter + cos(sector_envelope_angle)) / @@ -314,7 +307,7 @@ void KuwaharaAnisotropicOperation::update_memory_buffer_partial(MemoryBuffer *ou for (BuffersIterator it = output->iterate_with(inputs, area); !it.is_end(); ++it) { /* The structure tensor is encoded in a float4 using a column major storage order, as can be * seen in the KuwaharaAnisotropicStructureTensorOperation. */ - float4 encoded_structure_tensor = float4(inputs[2]->get_elem(it.x, it.y)); + float4 encoded_structure_tensor = float4(inputs[1]->get_elem(it.x, it.y)); float dxdx = encoded_structure_tensor.x; float dxdy = encoded_structure_tensor.y; float dydy = encoded_structure_tensor.w; @@ -341,16 +334,14 @@ void KuwaharaAnisotropicOperation::update_memory_buffer_partial(MemoryBuffer *ou float eigenvalue_difference = first_eigenvalue - second_eigenvalue; float anisotropy = eigenvalue_sum > 0.0f ? eigenvalue_difference / eigenvalue_sum : 0.0f; - float radius = max(0.0f, *inputs[1]->get_elem(it.x, it.y)); - /* Compute the width and height of an ellipse that is more width-elongated for high anisotropy * and more circular for low anisotropy, controlled using the eccentricity factor. Since the * anisotropy is in the [0, 1] range, the width factor tends to 1 as the eccentricity tends to * infinity and tends to infinity when the eccentricity tends to zero. This is based on the * equations in section "3.2. Anisotropic Kuwahara Filtering" of the paper. */ float ellipse_width_factor = (get_eccentricity() + anisotropy) / get_eccentricity(); - float ellipse_width = ellipse_width_factor * radius; - float ellipse_height = radius / ellipse_width_factor; + float ellipse_width = ellipse_width_factor * data.size; + float ellipse_height = data.size / ellipse_width_factor; /* Compute the cosine and sine of the angle that the eigenvector makes with the x axis. Since * the eigenvector is normalized, its x and y components are the cosine and sine of the angle @@ -383,7 +374,7 @@ void KuwaharaAnisotropicOperation::update_memory_buffer_partial(MemoryBuffer *ou * section "3 Alternative Weighting Functions" of the polynomial weights paper. More on this * later in the code. */ int number_of_sectors = 8; - float sector_center_overlap_parameter = 2.0f / radius; + float sector_center_overlap_parameter = 2.0f / data.size; float sector_envelope_angle = ((3.0f / 2.0f) * M_PI) / number_of_sectors; float cross_sector_overlap_parameter = (sector_center_overlap_parameter + cos(sector_envelope_angle)) / diff --git a/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.h b/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.h index 76b41c04cb9..ba3f278c0f8 100644 --- a/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.h +++ b/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.h @@ -12,7 +12,6 @@ namespace blender::compositor { class KuwaharaAnisotropicOperation : public MultiThreadedOperation { SocketReader *image_reader_; - SocketReader *size_reader_; SocketReader *structure_tensor_reader_; public: diff --git a/source/blender/compositor/operations/COM_KuwaharaClassicOperation.cc b/source/blender/compositor/operations/COM_KuwaharaClassicOperation.cc index ad9c97286e9..aa6a83ec7db 100644 --- a/source/blender/compositor/operations/COM_KuwaharaClassicOperation.cc +++ b/source/blender/compositor/operations/COM_KuwaharaClassicOperation.cc @@ -2,7 +2,6 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ -#include "BLI_math_base.hh" #include "BLI_math_vector.hh" #include "BLI_math_vector_types.hh" @@ -15,8 +14,8 @@ namespace blender::compositor { KuwaharaClassicOperation::KuwaharaClassicOperation() { this->add_input_socket(DataType::Color); - this->add_input_socket(DataType::Value); this->add_output_socket(DataType::Color); + this->set_kernel_size(4); this->flags_.is_fullframe_operation = true; } @@ -24,13 +23,11 @@ KuwaharaClassicOperation::KuwaharaClassicOperation() void KuwaharaClassicOperation::init_execution() { image_reader_ = this->get_input_socket_reader(0); - size_reader_ = this->get_input_socket_reader(1); } void KuwaharaClassicOperation::deinit_execution() { image_reader_ = nullptr; - size_reader_ = nullptr; } void KuwaharaClassicOperation::execute_pixel_sampled(float output[4], @@ -42,13 +39,9 @@ void KuwaharaClassicOperation::execute_pixel_sampled(float output[4], float4 mean_of_squared_color[] = {float4(0.0f), float4(0.0f), float4(0.0f), float4(0.0f)}; int quadrant_pixel_count[] = {0, 0, 0, 0}; - float4 size; - size_reader_->read_sampled(size, x, y, sampler); - const int kernel_size = int(math::max(0.0f, size[0])); - /* Split surroundings of pixel into 4 overlapping regions. */ - for (int dy = -kernel_size; dy <= kernel_size; dy++) { - for (int dx = -kernel_size; dx <= kernel_size; dx++) { + for (int dy = -kernel_size_; dy <= kernel_size_; dy++) { + for (int dx = -kernel_size_; dx <= kernel_size_; dx++) { int xx = x + dx; int yy = y + dy; @@ -109,12 +102,21 @@ void KuwaharaClassicOperation::execute_pixel_sampled(float output[4], output[3] = mean_of_color[min_index].w; /* Also apply filter to alpha channel. */ } +void KuwaharaClassicOperation::set_kernel_size(int kernel_size) +{ + kernel_size_ = kernel_size; +} + +int KuwaharaClassicOperation::get_kernel_size() +{ + return kernel_size_; +} + void KuwaharaClassicOperation::update_memory_buffer_partial(MemoryBuffer *output, const rcti &area, Span inputs) { MemoryBuffer *image = inputs[0]; - MemoryBuffer *size_image = inputs[1]; for (BuffersIterator it = output->iterate_with(inputs, area); !it.is_end(); ++it) { const int x = it.x; @@ -124,11 +126,9 @@ void KuwaharaClassicOperation::update_memory_buffer_partial(MemoryBuffer *output float4 mean_of_squared_color[] = {float4(0.0f), float4(0.0f), float4(0.0f), float4(0.0f)}; int quadrant_pixel_count[] = {0, 0, 0, 0}; - const int kernel_size = int(math::max(0.0f, *size_image->get_elem(x, y))); - /* Split surroundings of pixel into 4 overlapping regions. */ - for (int dy = -kernel_size; dy <= kernel_size; dy++) { - for (int dx = -kernel_size; dx <= kernel_size; dx++) { + for (int dy = -kernel_size_; dy <= kernel_size_; dy++) { + for (int dx = -kernel_size_; dx <= kernel_size_; dx++) { int xx = x + dx; int yy = y + dy; diff --git a/source/blender/compositor/operations/COM_KuwaharaClassicOperation.h b/source/blender/compositor/operations/COM_KuwaharaClassicOperation.h index bff4fc8c4f8..30d03f0f9d7 100644 --- a/source/blender/compositor/operations/COM_KuwaharaClassicOperation.h +++ b/source/blender/compositor/operations/COM_KuwaharaClassicOperation.h @@ -10,7 +10,8 @@ namespace blender::compositor { class KuwaharaClassicOperation : public MultiThreadedOperation { SocketReader *image_reader_; - SocketReader *size_reader_; + + int kernel_size_; public: KuwaharaClassicOperation(); @@ -19,6 +20,9 @@ class KuwaharaClassicOperation : public MultiThreadedOperation { void deinit_execution() override; void execute_pixel_sampled(float output[4], float x, float y, PixelSampler sampler) override; + void set_kernel_size(int kernel_size); + int get_kernel_size(); + void update_memory_buffer_partial(MemoryBuffer *output, const rcti &area, Span inputs) override; diff --git a/source/blender/compositor/operations/COM_WriteBufferOperation.cc b/source/blender/compositor/operations/COM_WriteBufferOperation.cc index d53abfd0094..8cec215eede 100644 --- a/source/blender/compositor/operations/COM_WriteBufferOperation.cc +++ b/source/blender/compositor/operations/COM_WriteBufferOperation.cc @@ -103,10 +103,10 @@ void WriteBufferOperation::execute_opencl_region(OpenCLDevice *device, float *output_float_buffer = output_buffer->get_buffer(); cl_int error; /* - * 1. create cl_mem from output_buffer. - * 2. call NodeOperation (input) executeOpenCLChunk(...). - * 3. schedule read back from OPENCL to main device (output_buffer). - * 4. schedule native callback. + * 1. create cl_mem from outputbuffer + * 2. call NodeOperation (input) executeOpenCLChunk(.....) + * 3. schedule read back from opencl to main device (outputbuffer) + * 4. schedule native callback * * NOTE: list of cl_mem will be filled by 2, and needs to be cleaned up by 4 */ diff --git a/source/blender/compositor/realtime_compositor/CMakeLists.txt b/source/blender/compositor/realtime_compositor/CMakeLists.txt index ee9a03ebef3..0fd8fab2383 100644 --- a/source/blender/compositor/realtime_compositor/CMakeLists.txt +++ b/source/blender/compositor/realtime_compositor/CMakeLists.txt @@ -67,26 +67,21 @@ set(SRC algorithms/intern/morphological_distance.cc algorithms/intern/morphological_distance_feather.cc algorithms/intern/parallel_reduction.cc - algorithms/intern/realize_on_domain.cc algorithms/intern/smaa.cc algorithms/intern/summed_area_table.cc algorithms/intern/symmetric_separable_blur.cc - algorithms/intern/transform.cc algorithms/COM_algorithm_jump_flooding.hh algorithms/COM_algorithm_morphological_distance.hh algorithms/COM_algorithm_morphological_distance_feather.hh algorithms/COM_algorithm_parallel_reduction.hh - algorithms/COM_algorithm_realize_on_domain.hh algorithms/COM_algorithm_smaa.hh algorithms/COM_algorithm_summed_area_table.hh algorithms/COM_algorithm_symmetric_separable_blur.hh - algorithms/COM_algorithm_transform.hh cached_resources/intern/cached_mask.cc cached_resources/intern/cached_texture.cc cached_resources/intern/distortion_grid.cc - cached_resources/intern/keying_screen.cc cached_resources/intern/morphological_distance_feather_weights.cc cached_resources/intern/ocio_color_space_conversion_shader.cc cached_resources/intern/smaa_precomputed_textures.cc @@ -97,7 +92,6 @@ set(SRC cached_resources/COM_cached_resource.hh cached_resources/COM_cached_texture.hh cached_resources/COM_distortion_grid.hh - cached_resources/COM_keying_screen.hh cached_resources/COM_morphological_distance_feather_weights.hh cached_resources/COM_ocio_color_space_conversion_shader.hh cached_resources/COM_smaa_precomputed_textures.hh @@ -155,7 +149,6 @@ set(GLSL_SRC shaders/compositor_keying_compute_matte.glsl shaders/compositor_keying_extract_chroma.glsl shaders/compositor_keying_replace_chroma.glsl - shaders/compositor_keying_screen.glsl shaders/compositor_keying_tweak_matte.glsl shaders/compositor_kuwahara_anisotropic.glsl shaders/compositor_kuwahara_anisotropic_compute_structure_tensor.glsl @@ -171,7 +164,7 @@ set(GLSL_SRC shaders/compositor_plane_deform.glsl shaders/compositor_plane_deform_motion_blur.glsl shaders/compositor_projector_lens_distortion.glsl - shaders/compositor_read_input.glsl + shaders/compositor_read_pass.glsl shaders/compositor_realize_on_domain.glsl shaders/compositor_screen_lens_distortion.glsl shaders/compositor_smaa_blending_weight_calculation.glsl @@ -274,7 +267,6 @@ set(SRC_SHADER_CREATE_INFOS shaders/infos/compositor_inpaint_info.hh shaders/infos/compositor_jump_flooding_info.hh shaders/infos/compositor_keying_info.hh - shaders/infos/compositor_keying_screen_info.hh shaders/infos/compositor_kuwahara_info.hh shaders/infos/compositor_map_uv_info.hh shaders/infos/compositor_morphological_distance_feather_info.hh @@ -287,7 +279,7 @@ set(SRC_SHADER_CREATE_INFOS shaders/infos/compositor_plane_deform_info.hh shaders/infos/compositor_plane_deform_motion_blur_info.hh shaders/infos/compositor_projector_lens_distortion_info.hh - shaders/infos/compositor_read_input_info.hh + shaders/infos/compositor_read_pass_info.hh shaders/infos/compositor_realize_on_domain_info.hh shaders/infos/compositor_screen_lens_distortion_info.hh shaders/infos/compositor_smaa_info.hh diff --git a/source/blender/compositor/realtime_compositor/COM_context.hh b/source/blender/compositor/realtime_compositor/COM_context.hh index c8afc8ab2c7..d79b36cf358 100644 --- a/source/blender/compositor/realtime_compositor/COM_context.hh +++ b/source/blender/compositor/realtime_compositor/COM_context.hh @@ -75,10 +75,9 @@ class Context { * the composite output node to get its target texture. */ virtual GPUTexture *get_output_texture() = 0; - /* Get the texture where the result of the compositor viewer should be written, given the size of - * the result to be viewed. This should be called by viewer output nodes to get their target - * texture. */ - virtual GPUTexture *get_viewer_output_texture(int2 size) = 0; + /* Get the texture where the result of the compositor viewer should be written. This should be + * called by viewer output nodes to get their target texture. */ + virtual GPUTexture *get_viewer_output_texture() = 0; /* Get the texture where the given render pass is stored. This should be called by the Render * Layer node to populate its outputs. */ diff --git a/source/blender/compositor/realtime_compositor/COM_input_descriptor.hh b/source/blender/compositor/realtime_compositor/COM_input_descriptor.hh index e0b2d9a938d..25fd395106d 100644 --- a/source/blender/compositor/realtime_compositor/COM_input_descriptor.hh +++ b/source/blender/compositor/realtime_compositor/COM_input_descriptor.hh @@ -16,6 +16,14 @@ namespace blender::realtime_compositor { struct InputRealizationOptions { /* The input should be realized on the operation domain of the operation. */ bool realize_on_operation_domain : 1; + /* The input should be realized on a domain that is identical to the domain of the input but with + * an identity rotation and an increased size that completely fits the image after rotation. This + * is useful for operations that are not rotation invariant. */ + bool realize_rotation : 1; + /* The input should be realized on a domain that is identical to the domain of the input but with + * an identity scale and an increased/decreased size that completely fits the image after + * scaling. This is useful for operations that are not scale invariant. */ + bool realize_scale : 1; }; /* ------------------------------------------------------------------------------------------------ diff --git a/source/blender/compositor/realtime_compositor/COM_realize_on_domain_operation.hh b/source/blender/compositor/realtime_compositor/COM_realize_on_domain_operation.hh index eea55b9ee34..c950a44c211 100644 --- a/source/blender/compositor/realtime_compositor/COM_realize_on_domain_operation.hh +++ b/source/blender/compositor/realtime_compositor/COM_realize_on_domain_operation.hh @@ -42,6 +42,40 @@ class RealizeOnDomainOperation : public SimpleOperation { protected: /* The operation domain is just the target domain. */ Domain compute_domain() override; + + private: + /* Get the realization shader of the appropriate type. */ + GPUShader *get_realization_shader(); +}; + +/* ------------------------------------------------------------------------------------------------ + * Realize Transformation Operation + * + * A simple operation that realizes its input on a domain such that its transformations become + * identity and its size is increased/decreased to adapt to the new domain. The transformations + * that become identity are the ones marked to be realized in the given InputRealizationOptions. + * For instance, if InputRealizationOptions.realize_rotation is true and the input is rotated, the + * output of the operation will be a result of zero rotation but expanded size to account for the + * bounding box of the input after rotation. This is useful for operations that are not rotation + * invariant and thus require an input of zero rotation for correct operation. + * + * Notice that this class is not an actual operation, but constructs a RealizeOnDomainOperation + * with the appreciate domain in its construct_if_needed static constructor. */ +class RealizeTransformationOperation { + public: + /* Determine if a realize transformation operation is needed for the input with the given result + * and descriptor. If it is not needed, return a null pointer. If it is needed, return an + * instance of RealizeOnDomainOperation with the appropriate domain. */ + static SimpleOperation *construct_if_needed(Context &context, + const Result &input_result, + const InputDescriptor &input_descriptor); + + private: + /* Given the domain of an input and its realization options, compute a domain such that the + * appropriate transformations specified in the realization options become identity and the size + * of the domain is increased/reduced to adapt to the new domain. */ + static Domain compute_target_domain(const Domain &input_domain, + const InputRealizationOptions &realization_options); }; } // namespace blender::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/COM_static_cache_manager.hh b/source/blender/compositor/realtime_compositor/COM_static_cache_manager.hh index a5b0d95114b..fd3689f7c38 100644 --- a/source/blender/compositor/realtime_compositor/COM_static_cache_manager.hh +++ b/source/blender/compositor/realtime_compositor/COM_static_cache_manager.hh @@ -7,7 +7,6 @@ #include "COM_cached_mask.hh" #include "COM_cached_texture.hh" #include "COM_distortion_grid.hh" -#include "COM_keying_screen.hh" #include "COM_morphological_distance_feather_weights.hh" #include "COM_ocio_color_space_conversion_shader.hh" #include "COM_smaa_precomputed_textures.hh" @@ -48,7 +47,6 @@ class StaticCacheManager { SMAAPrecomputedTexturesContainer smaa_precomputed_textures; OCIOColorSpaceConversionShaderContainer ocio_color_space_conversion_shaders; DistortionGridContainer distortion_grids; - KeyingScreenContainer keying_screens; /* Reset the cache manager by deleting the cached resources that are no longer needed because * they weren't used in the last evaluation and prepare the remaining cached resources to track diff --git a/source/blender/compositor/realtime_compositor/algorithms/COM_algorithm_jump_flooding.hh b/source/blender/compositor/realtime_compositor/algorithms/COM_algorithm_jump_flooding.hh index f98806d3324..7c35fdbfff5 100644 --- a/source/blender/compositor/realtime_compositor/algorithms/COM_algorithm_jump_flooding.hh +++ b/source/blender/compositor/realtime_compositor/algorithms/COM_algorithm_jump_flooding.hh @@ -10,20 +10,22 @@ namespace blender::realtime_compositor { /* Computes a jump flooding table from the given input and writes the result to the output. A jump - * flooding table computes for each pixel the texel location of the closest "seed pixel". A seed - * pixel is a pixel that is marked as such in the input, more on this later. This table is useful - * to compute a Voronoi diagram where the centroids are the seed pixels, it can be used to - * accurately approximate an euclidean distance transform, finally, it can be used to flood fill - * regions of an image. + * flooding table computes for each pixel the location of the closest "seed pixel" as well as the + * distance to it. A seed pixel is a pixel that is marked as such in the input, more on this later. + * This table is useful to compute a Voronoi diagram where the centroids are the seed pixels, it + * can be used to accurately approximate an euclidean distance transform, finally, it can be used + * to flood fill regions of an image. * * The input is expected to be initialized by the initialize_jump_flooding_value function from the * gpu_shader_compositor_jump_flooding_lib.glsl library. Seed pixels should specify true for the * is_seed argument, and false otherwise. The texel input should be the texel location of the - * pixel. Both the input and output results should be of type ResultType::Int2. + * pixel. * * To compute a Voronoi diagram, the pixels lying at the centroid of the Voronoi cell should be * marked as seed pixels. To compute an euclidean distance transform of a region or flood fill a - * region, the boundary pixels of the region should be marked as seed. + * region, the boundary pixels of the region should be marked as seed. The closest seed pixel and + * the distance to it can be retrieved from the table using the extract_jump_flooding_* functions + * from the gpu_shader_compositor_jump_flooding_lib.glsl library. * * The algorithm is based on the paper: * diff --git a/source/blender/compositor/realtime_compositor/algorithms/COM_algorithm_realize_on_domain.hh b/source/blender/compositor/realtime_compositor/algorithms/COM_algorithm_realize_on_domain.hh deleted file mode 100644 index 29567ce03a2..00000000000 --- a/source/blender/compositor/realtime_compositor/algorithms/COM_algorithm_realize_on_domain.hh +++ /dev/null @@ -1,28 +0,0 @@ -/* SPDX-FileCopyrightText: 2023 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -#pragma once - -#include "BLI_math_matrix_types.hh" - -#include "COM_context.hh" -#include "COM_domain.hh" -#include "COM_result.hh" - -namespace blender::realtime_compositor { - -/* Projects the input on a target domain, copies the area of the input that intersects the target - * domain, and fill the rest with zeros or repetitions of the input depending on the realization - * options. The transformation and realization options of the input are ignored and the given - * input_transformation and realization_options are used instead to allow the caller to change them - * without mutating the input result directly. See the discussion in COM_domain.hh for more - * information on what realization on domain means. */ -void realize_on_domain(Context &context, - Result &input, - Result &output, - const Domain &domain, - const float3x3 &input_transformation, - const RealizationOptions &realization_options); - -} // namespace blender::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/algorithms/COM_algorithm_transform.hh b/source/blender/compositor/realtime_compositor/algorithms/COM_algorithm_transform.hh deleted file mode 100644 index 67f99e62e0e..00000000000 --- a/source/blender/compositor/realtime_compositor/algorithms/COM_algorithm_transform.hh +++ /dev/null @@ -1,33 +0,0 @@ -/* SPDX-FileCopyrightText: 2023 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -#pragma once - -#include "BLI_math_matrix_types.hh" - -#include "COM_context.hh" -#include "COM_domain.hh" -#include "COM_result.hh" - -namespace blender::realtime_compositor { - -/* Transforms the given result based on the given transformation and interpolation, writing the - * transformed result to the given output. - * - * The rotation and scale components of the transformation are realized and the size of the result - * is increased/reduced to adapt to the new transformation. For instance, if the transformation is - * a rotation, the input will be rotated and expanded in size to account for the bounding box of - * the input after rotation. The size of the returned result is bound and clipped by the maximum - * possible GPU texture size to avoid allocations that surpass hardware limits, which is typically - * 16k. - * - * The translation component of the transformation is delayed and only stored in the domain of the - * result to be realized later when needed. */ -void transform(Context &context, - Result &input, - Result &output, - float3x3 transformation, - Interpolation interpolation); - -} // namespace blender::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/algorithms/intern/jump_flooding.cc b/source/blender/compositor/realtime_compositor/algorithms/intern/jump_flooding.cc index 092a74c4fe0..621147ba5d3 100644 --- a/source/blender/compositor/realtime_compositor/algorithms/intern/jump_flooding.cc +++ b/source/blender/compositor/realtime_compositor/algorithms/intern/jump_flooding.cc @@ -4,7 +4,6 @@ #include -#include "BLI_assert.h" #include "BLI_math_base.h" #include "BLI_math_base.hh" @@ -37,18 +36,15 @@ static void jump_flooding_pass(Context &context, Result &input, Result &output, void jump_flooding(Context &context, Result &input, Result &output) { - BLI_assert(input.type() == ResultType::Int2); - BLI_assert(output.type() == ResultType::Int2); - /* First, run a jump flooding pass with a step size of 1. This initial pass is proposed by the * 1+FJA variant to improve accuracy. */ - Result initial_flooded_result = Result::Temporary(ResultType::Int2, context.texture_pool()); + Result initial_flooded_result = Result::Temporary(ResultType::Color, context.texture_pool()); initial_flooded_result.allocate_texture(input.domain()); jump_flooding_pass(context, input, initial_flooded_result, 1); /* We compute the result using a ping-pong buffer, so create an intermediate result. */ Result *result_to_flood = &initial_flooded_result; - Result intermediate_result = Result::Temporary(ResultType::Int2, context.texture_pool()); + Result intermediate_result = Result::Temporary(ResultType::Color, context.texture_pool()); intermediate_result.allocate_texture(input.domain()); Result *result_after_flooding = &intermediate_result; diff --git a/source/blender/compositor/realtime_compositor/algorithms/intern/realize_on_domain.cc b/source/blender/compositor/realtime_compositor/algorithms/intern/realize_on_domain.cc deleted file mode 100644 index 8e20073850e..00000000000 --- a/source/blender/compositor/realtime_compositor/algorithms/intern/realize_on_domain.cc +++ /dev/null @@ -1,113 +0,0 @@ -/* SPDX-FileCopyrightText: 2023 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -#include "BLI_math_angle_types.hh" -#include "BLI_math_matrix.hh" -#include "BLI_math_matrix_types.hh" -#include "BLI_math_vector_types.hh" -#include "BLI_utildefines.h" - -#include "GPU_capabilities.h" -#include "GPU_shader.h" -#include "GPU_texture.h" - -#include "COM_context.hh" -#include "COM_domain.hh" -#include "COM_result.hh" -#include "COM_utilities.hh" - -#include "COM_algorithm_realize_on_domain.hh" - -namespace blender::realtime_compositor { - -static const char *get_realization_shader(Result &input, - const RealizationOptions &realization_options) -{ - if (realization_options.interpolation == Interpolation::Bicubic) { - switch (input.type()) { - case ResultType::Color: - return "compositor_realize_on_domain_bicubic_color"; - case ResultType::Vector: - return "compositor_realize_on_domain_bicubic_vector"; - case ResultType::Float: - return "compositor_realize_on_domain_bicubic_float"; - case ResultType::Int2: - /* Realization does not support integer images. */ - break; - } - } - else { - switch (input.type()) { - case ResultType::Color: - return "compositor_realize_on_domain_color"; - case ResultType::Vector: - return "compositor_realize_on_domain_vector"; - case ResultType::Float: - return "compositor_realize_on_domain_float"; - case ResultType::Int2: - /* Realization does not support integer images. */ - break; - } - } - - BLI_assert_unreachable(); - return nullptr; -} - -void realize_on_domain(Context &context, - Result &input, - Result &output, - const Domain &domain, - const float3x3 &input_transformation, - const RealizationOptions &realization_options) -{ - - GPUShader *shader = context.shader_manager().get( - get_realization_shader(input, realization_options)); - GPU_shader_bind(shader); - - /* Transform the input space into the domain space. */ - const float3x3 local_transformation = math::invert(domain.transformation) * input_transformation; - - /* Set the origin of the transformation to be the center of the domain. */ - const float3x3 transformation = math::from_origin_transform( - local_transformation, float2(domain.size) / 2.0f); - - /* Invert the transformation because the shader transforms the domain coordinates instead of the - * input image itself and thus expect the inverse. */ - const float3x3 inverse_transformation = math::invert(transformation); - - GPU_shader_uniform_mat3_as_mat4(shader, "inverse_transformation", inverse_transformation.ptr()); - - /* The texture sampler should use bilinear interpolation for both the bilinear and bicubic - * cases, as the logic used by the bicubic realization shader expects textures to use bilinear - * interpolation. */ - const bool use_bilinear = ELEM( - realization_options.interpolation, Interpolation::Bilinear, Interpolation::Bicubic); - GPU_texture_filter_mode(input.texture(), use_bilinear); - - /* If the input repeats, set a repeating wrap mode for out-of-bound texture access. Otherwise, - * make out-of-bound texture access return zero by setting a clamp to border extend mode. */ - GPU_texture_extend_mode_x(input.texture(), - realization_options.repeat_x ? - GPU_SAMPLER_EXTEND_MODE_REPEAT : - GPU_SAMPLER_EXTEND_MODE_CLAMP_TO_BORDER); - GPU_texture_extend_mode_y(input.texture(), - realization_options.repeat_y ? - GPU_SAMPLER_EXTEND_MODE_REPEAT : - GPU_SAMPLER_EXTEND_MODE_CLAMP_TO_BORDER); - - input.bind_as_texture(shader, "input_tx"); - - output.allocate_texture(domain); - output.bind_as_image(shader, "domain_img"); - - compute_dispatch_threads_at_least(shader, domain.size); - - input.unbind_as_texture(); - output.unbind_as_image(); - GPU_shader_unbind(); -} - -} // namespace blender::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/algorithms/intern/transform.cc b/source/blender/compositor/realtime_compositor/algorithms/intern/transform.cc deleted file mode 100644 index c245796bee6..00000000000 --- a/source/blender/compositor/realtime_compositor/algorithms/intern/transform.cc +++ /dev/null @@ -1,87 +0,0 @@ -/* SPDX-FileCopyrightText: 2023 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -#include "BLI_math_angle_types.hh" -#include "BLI_math_matrix.hh" -#include "BLI_math_matrix_types.hh" -#include "BLI_math_vector_types.hh" - -#include "GPU_capabilities.h" -#include "GPU_shader.h" -#include "GPU_texture.h" - -#include "COM_algorithm_realize_on_domain.hh" -#include "COM_context.hh" -#include "COM_domain.hh" -#include "COM_result.hh" - -#include "COM_algorithm_transform.hh" - -namespace blender::realtime_compositor { - -/* Given a potentially transformed domain, compute a domain such that its rotation and scale become - * identity and the size of the domain is increased/reduced to adapt to the new transformation. For - * instance, if the domain is rotated, the returned domain will have zero rotation but expanded - * size to account for the bounding box of the domain after rotation. The size of the returned - * domain is bound and clipped by the maximum possible GPU texture size to avoid allocations that - * surpass hardware limits, which is typically 16k. */ -static Domain compute_realized_transformation_domain(const Domain &domain) -{ - math::AngleRadian rotation; - float2 translation, scale; - float2 size = float2(domain.size); - math::to_loc_rot_scale(domain.transformation, translation, rotation, scale); - - /* Set the rotation to zero and expand the domain size to fit the bounding box of the rotated - * result. */ - const float sine = math::abs(math::sin(rotation)); - const float cosine = math::abs(math::cos(rotation)); - size = float2(size.x * cosine + size.y * sine, size.x * sine + size.y * cosine); - rotation = 0.0f; - - /* Set the scale to 1 and scale the domain size to adapt to the new domain. */ - size *= scale; - scale = float2(1.0f); - - const float3x3 transformation = math::from_loc_rot_scale(translation, rotation, scale); - - const int2 domain_size = math::clamp( - int2(math::round(size)), int2(1), int2(GPU_max_texture_size())); - - return Domain(domain_size, transformation); -} - -void transform(Context &context, - Result &input, - Result &output, - float3x3 transformation, - Interpolation interpolation) -{ - math::AngleRadian rotation; - float2 translation, scale; - math::to_loc_rot_scale(transformation, translation, rotation, scale); - - /* Rotation and scale transformations are immediately realized. */ - if (rotation != 0.0f || scale != float2(1.0f)) { - RealizationOptions realization_options = input.get_realization_options(); - realization_options.interpolation = interpolation; - - Domain input_domain = input.domain(); - input_domain.transform(transformation); - - const Domain target_domain = compute_realized_transformation_domain(input_domain); - - realize_on_domain( - context, input, output, target_domain, input_domain.transformation, realization_options); - } - else { - input.pass_through(output); - const float3x3 translation_matrix = math::from_location(translation); - output.transform(translation_matrix); - } - - output.get_realization_options().interpolation = interpolation; -} - -} // namespace blender::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/cached_resources/COM_keying_screen.hh b/source/blender/compositor/realtime_compositor/cached_resources/COM_keying_screen.hh deleted file mode 100644 index 54e8f5c9c3b..00000000000 --- a/source/blender/compositor/realtime_compositor/cached_resources/COM_keying_screen.hh +++ /dev/null @@ -1,87 +0,0 @@ -/* SPDX-FileCopyrightText: 2023 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -#pragma once - -#include -#include -#include - -#include "BLI_map.hh" -#include "BLI_math_vector_types.hh" - -#include "GPU_shader.h" -#include "GPU_texture.h" - -#include "DNA_movieclip_types.h" -#include "DNA_tracking_types.h" - -#include "COM_cached_resource.hh" - -namespace blender::realtime_compositor { - -class Context; - -/* ------------------------------------------------------------------------------------------------ - * Keying Screen Key. - */ -class KeyingScreenKey { - public: - int2 frame; - float smoothness; - - KeyingScreenKey(int frame, float smoothness); - - uint64_t hash() const; -}; - -bool operator==(const KeyingScreenKey &a, const KeyingScreenKey &b); - -/* ------------------------------------------------------------------------------------------------- - * Keying Screen. - * - * A cached resource that computes and caches a GPU texture containing the keying screen computed - * by interpolating the markers of the given movie tracking object in the given movie clip. */ -class KeyingScreen : public CachedResource { - private: - GPUTexture *texture_ = nullptr; - - public: - KeyingScreen(Context &context, - MovieClip *movie_clip, - MovieTrackingObject *movie_tracking_object, - float smoothness); - - ~KeyingScreen(); - - void bind_as_texture(GPUShader *shader, const char *texture_name) const; - - void unbind_as_texture() const; - - GPUTexture *texture() const; -}; - -/* ------------------------------------------------------------------------------------------------ - * Keying Screen Container. - */ -class KeyingScreenContainer : CachedResourceContainer { - private: - Map>> map_; - - public: - void reset() override; - - /* Check if the given movie clip ID has changed since the last time it was retrieved through its - * recalculate flag, and if so, invalidate its corresponding cached keying screens and reset the - * recalculate flag to ready it to track the next change. Then, check if there is an available - * KeyingScreen cached resource with the given parameters in the container, if one exists, return - * it, otherwise, return a newly created one and add it to the container. In both cases, tag the - * cached resource as needed to keep it cached for the next evaluation. */ - KeyingScreen &get(Context &context, - MovieClip *movie_clip, - MovieTrackingObject *movie_tracking_object, - float smoothness); -}; - -} // namespace blender::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/cached_resources/intern/keying_screen.cc b/source/blender/compositor/realtime_compositor/cached_resources/intern/keying_screen.cc deleted file mode 100644 index 6fcfac5975a..00000000000 --- a/source/blender/compositor/realtime_compositor/cached_resources/intern/keying_screen.cc +++ /dev/null @@ -1,255 +0,0 @@ -/* SPDX-FileCopyrightText: 2023 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -#include -#include -#include - -#include "BLI_hash.hh" -#include "BLI_listbase.h" -#include "BLI_math_color.h" -#include "BLI_math_vector.hh" -#include "BLI_math_vector_types.hh" -#include "BLI_vector.hh" - -#include "IMB_imbuf.h" - -#include "DNA_defaults.h" -#include "DNA_movieclip_types.h" -#include "DNA_tracking_types.h" - -#include "GPU_shader.h" -#include "GPU_storage_buffer.h" -#include "GPU_texture.h" - -#include "BKE_movieclip.h" -#include "BKE_tracking.h" - -#include "COM_context.hh" -#include "COM_keying_screen.hh" -#include "COM_utilities.hh" - -namespace blender::realtime_compositor { - -/* -------------------------------------------------------------------- - * Keying Screen Key. - */ - -KeyingScreenKey::KeyingScreenKey(int frame, float smoothness) - : frame(frame), smoothness(smoothness) -{ -} - -uint64_t KeyingScreenKey::hash() const -{ - return get_default_hash_2(frame, smoothness); -} - -bool operator==(const KeyingScreenKey &a, const KeyingScreenKey &b) -{ - return a.frame == b.frame && a.smoothness == b.smoothness; -} - -/* -------------------------------------------------------------------- - * Keying Screen. - */ - -/* Computes the color and normalized positions of the keying screen markers in the given movie - * tracking object. The color is computed as the mean color of the search pattern of the marker. */ -static void compute_marker_points(MovieClip *movie_clip, - MovieClipUser &movie_clip_user, - MovieTrackingObject *movie_tracking_object, - Vector &marker_positions, - Vector &marker_colors) -{ - BLI_assert(marker_positions.size() == 0); - BLI_assert(marker_colors.size() == 0); - - ImBuf *image_buffer = BKE_movieclip_get_ibuf(movie_clip, &movie_clip_user); - if (!image_buffer) { - return; - } - - LISTBASE_FOREACH (MovieTrackingTrack *, track, &movie_tracking_object->tracks) { - const MovieTrackingMarker *marker = BKE_tracking_marker_get(track, movie_clip_user.framenr); - if (marker->flag & MARKER_DISABLED) { - continue; - } - - /* Skip out of bound markers since they have no corresponding color. */ - const float2 position = float2(marker->pos) + float2(track->offset); - if (math::clamp(position, float2(0.0f), float2(1.0f)) != position) { - continue; - } - - ImBuf *pattern_image_buffer = BKE_tracking_get_pattern_imbuf( - image_buffer, track, marker, true, false); - if (!pattern_image_buffer) { - continue; - } - - /* Find the mean color of the rectangular search pattern of the marker. */ - float4 mean_color = float4(0.0f); - for (int i = 0; i < pattern_image_buffer->x * pattern_image_buffer->y; i++) { - if (pattern_image_buffer->float_buffer.data) { - mean_color += float4(&pattern_image_buffer->float_buffer.data[i * 4]); - } - else { - float4 linear_color; - uchar4 srgb_color = uchar4(&pattern_image_buffer->byte_buffer.data[i * 4]); - srgb_to_linearrgb_uchar4(linear_color, srgb_color); - mean_color += linear_color; - } - } - mean_color /= pattern_image_buffer->x * pattern_image_buffer->y; - - marker_colors.append(mean_color); - marker_positions.append(position); - - IMB_freeImBuf(pattern_image_buffer); - } - - IMB_freeImBuf(image_buffer); -} - -/* Get a MovieClipUser with an initialized clip frame number. */ -static MovieClipUser get_movie_clip_user(Context &context, MovieClip *movie_clip) -{ - MovieClipUser movie_clip_user = *DNA_struct_default_get(MovieClipUser); - const int scene_frame = context.get_frame_number(); - const int clip_frame = BKE_movieclip_remap_scene_to_clip_frame(movie_clip, scene_frame); - BKE_movieclip_user_set_frame(&movie_clip_user, clip_frame); - return movie_clip_user; -} - -KeyingScreen::KeyingScreen(Context &context, - MovieClip *movie_clip, - MovieTrackingObject *movie_tracking_object, - float smoothness) -{ - int2 size; - MovieClipUser movie_clip_user = get_movie_clip_user(context, movie_clip); - BKE_movieclip_get_size(movie_clip, &movie_clip_user, &size.x, &size.y); - - Vector marker_positions; - Vector marker_colors; - compute_marker_points( - movie_clip, movie_clip_user, movie_tracking_object, marker_positions, marker_colors); - - GPUShader *shader = context.shader_manager().get("compositor_keying_screen"); - GPU_shader_bind(shader); - - GPU_shader_uniform_1f(shader, "smoothness", smoothness); - GPU_shader_uniform_1i(shader, "number_of_markers", marker_positions.size()); - - /* SSBO needs to be aligned to 16 bytes, and since sizeof(float2) is only 8 bytes, we need to add - * a dummy element at the end for odd sizes to satisfy the alignment requirement. Notice that the - * number_of_markers uniform was already assigned above to the original size, so the dummy - * element has no effect in the shader. Also notice that the marker colors are always 16 byte - * aligned since sizeof(float4) is 16 bytes, so not need to add anything there. */ - if (marker_positions.size() % 2 == 1) { - marker_positions.append(float2(0.0f)); - } - - GPUStorageBuf *positions_ssbo = GPU_storagebuf_create_ex(marker_positions.size() * - sizeof(float2), - marker_positions.data(), - GPU_USAGE_STATIC, - "Marker Positions"); - const int positions_ssbo_location = GPU_shader_get_ssbo_binding(shader, "marker_positions"); - GPU_storagebuf_bind(positions_ssbo, positions_ssbo_location); - - GPUStorageBuf *colors_ssbo = GPU_storagebuf_create_ex(marker_colors.size() * sizeof(float4), - marker_colors.data(), - GPU_USAGE_STATIC, - "Marker Colors"); - const int colors_ssbo_location = GPU_shader_get_ssbo_binding(shader, "marker_colors"); - GPU_storagebuf_bind(colors_ssbo, colors_ssbo_location); - - texture_ = GPU_texture_create_2d( - "Keying Screen", size.x, size.y, 1, GPU_RGBA16F, GPU_TEXTURE_USAGE_SHADER_READ, nullptr); - const int image_unit = GPU_shader_get_sampler_binding(shader, "output_img"); - GPU_texture_image_bind(texture_, image_unit); - - compute_dispatch_threads_at_least(shader, size); - - GPU_texture_image_unbind(texture_); - GPU_storagebuf_unbind(positions_ssbo); - GPU_storagebuf_unbind(colors_ssbo); - GPU_shader_unbind(); - - GPU_storagebuf_free(positions_ssbo); - GPU_storagebuf_free(colors_ssbo); -} - -KeyingScreen::~KeyingScreen() -{ - GPU_texture_free(texture_); -} - -void KeyingScreen::bind_as_texture(GPUShader *shader, const char *texture_name) const -{ - const int texture_image_unit = GPU_shader_get_sampler_binding(shader, texture_name); - GPU_texture_bind(texture_, texture_image_unit); -} - -void KeyingScreen::unbind_as_texture() const -{ - GPU_texture_unbind(texture_); -} - -GPUTexture *KeyingScreen::texture() const -{ - return texture_; -} - -/* -------------------------------------------------------------------- - * Keying Screen Container. - */ - -void KeyingScreenContainer::reset() -{ - /* First, delete all cached keying screens that are no longer needed. */ - for (auto &cached_keying_screens_for_id : map_.values()) { - cached_keying_screens_for_id.remove_if([](auto item) { return !item.value->needed; }); - } - map_.remove_if([](auto item) { return item.value.is_empty(); }); - - /* Second, reset the needed status of the remaining cached keying screens to false to ready them - * to track their needed status for the next evaluation. */ - for (auto &cached_keying_screens_for_id : map_.values()) { - for (auto &value : cached_keying_screens_for_id.values()) { - value->needed = false; - } - } -} - -KeyingScreen &KeyingScreenContainer::get(Context &context, - MovieClip *movie_clip, - MovieTrackingObject *movie_tracking_object, - float smoothness) -{ - const KeyingScreenKey key(context.get_frame_number(), smoothness); - - /* We concatenate the movie clip ID name with the tracking object name to cache multiple tracking - * objects per movie clip. */ - const std::string id_name = std::string(movie_clip->id.name) + - std::string(movie_tracking_object->name); - auto &cached_keying_screens_for_id = map_.lookup_or_add_default(id_name); - - /* Invalidate the keying screen cache for that MovieClip ID if it was changed and reset the - * recalculate flag. */ - if (context.query_id_recalc_flag(reinterpret_cast(movie_clip)) & ID_RECALC_ALL) { - cached_keying_screens_for_id.clear(); - } - - auto &keying_screen = *cached_keying_screens_for_id.lookup_or_add_cb(key, [&]() { - return std::make_unique(context, movie_clip, movie_tracking_object, smoothness); - }); - - keying_screen.needed = true; - return keying_screen; -} - -} // namespace blender::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/intern/operation.cc b/source/blender/compositor/realtime_compositor/intern/operation.cc index 680acd85ba8..caadb5b36b6 100644 --- a/source/blender/compositor/realtime_compositor/intern/operation.cc +++ b/source/blender/compositor/realtime_compositor/intern/operation.cc @@ -106,6 +106,12 @@ void Operation::add_and_evaluate_input_processors() add_and_evaluate_input_processor(identifier, conversion); } + for (const StringRef &identifier : results_mapped_to_inputs_.keys()) { + SimpleOperation *realize_transformation = RealizeTransformationOperation::construct_if_needed( + context(), get_input(identifier), get_input_descriptor(identifier)); + add_and_evaluate_input_processor(identifier, realize_transformation); + } + for (const StringRef &identifier : results_mapped_to_inputs_.keys()) { SimpleOperation *realize_on_domain = RealizeOnDomainOperation::construct_if_needed( context(), get_input(identifier), get_input_descriptor(identifier), compute_domain()); diff --git a/source/blender/compositor/realtime_compositor/intern/realize_on_domain_operation.cc b/source/blender/compositor/realtime_compositor/intern/realize_on_domain_operation.cc index 9e800971d87..1eea2042e46 100644 --- a/source/blender/compositor/realtime_compositor/intern/realize_on_domain_operation.cc +++ b/source/blender/compositor/realtime_compositor/intern/realize_on_domain_operation.cc @@ -2,13 +2,22 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ -#include "COM_algorithm_realize_on_domain.hh" +#include "BLI_assert.h" +#include "BLI_math_angle_types.hh" +#include "BLI_math_matrix.hh" +#include "BLI_math_matrix_types.hh" +#include "BLI_math_vector_types.hh" +#include "BLI_utildefines.h" + +#include "GPU_shader.h" +#include "GPU_texture.h" + #include "COM_context.hh" #include "COM_domain.hh" #include "COM_input_descriptor.hh" -#include "COM_result.hh" - #include "COM_realize_on_domain_operation.hh" +#include "COM_result.hh" +#include "COM_utilities.hh" namespace blender::realtime_compositor { @@ -29,12 +38,90 @@ RealizeOnDomainOperation::RealizeOnDomainOperation(Context &context, void RealizeOnDomainOperation::execute() { - realize_on_domain(context(), - get_input(), - get_result(), - domain_, - get_input().domain().transformation, - get_input().get_realization_options()); + Result &input = get_input(); + Result &result = get_result(); + + result.allocate_texture(domain_); + + GPUShader *shader = get_realization_shader(); + GPU_shader_bind(shader); + + /* Transform the input space into the domain space. */ + const float3x3 local_transformation = math::invert(domain_.transformation) * + input.domain().transformation; + + /* Set the origin of the transformation to be the center of the domain. */ + const float3x3 transformation = math::from_origin_transform( + local_transformation, float2(domain_.size) / 2.0f); + + /* Invert the transformation because the shader transforms the domain coordinates instead of the + * input image itself and thus expect the inverse. */ + const float3x3 inverse_transformation = math::invert(transformation); + + GPU_shader_uniform_mat3_as_mat4(shader, "inverse_transformation", inverse_transformation.ptr()); + + /* The texture sampler should use bilinear interpolation for both the bilinear and bicubic + * cases, as the logic used by the bicubic realization shader expects textures to use bilinear + * interpolation. */ + const bool use_bilinear = ELEM(input.get_realization_options().interpolation, + Interpolation::Bilinear, + Interpolation::Bicubic); + GPU_texture_filter_mode(input.texture(), use_bilinear); + + /* If the input repeats, set a repeating wrap mode for out-of-bound texture access. Otherwise, + * make out-of-bound texture access return zero by setting a clamp to border extend mode. */ + GPU_texture_extend_mode_x(input.texture(), + input.get_realization_options().repeat_x ? + GPU_SAMPLER_EXTEND_MODE_REPEAT : + GPU_SAMPLER_EXTEND_MODE_CLAMP_TO_BORDER); + GPU_texture_extend_mode_y(input.texture(), + input.get_realization_options().repeat_y ? + GPU_SAMPLER_EXTEND_MODE_REPEAT : + GPU_SAMPLER_EXTEND_MODE_CLAMP_TO_BORDER); + + input.bind_as_texture(shader, "input_tx"); + result.bind_as_image(shader, "domain_img"); + + compute_dispatch_threads_at_least(shader, domain_.size); + + input.unbind_as_texture(); + result.unbind_as_image(); + GPU_shader_unbind(); +} + +GPUShader *RealizeOnDomainOperation::get_realization_shader() +{ + if (get_input().get_realization_options().interpolation == Interpolation::Bicubic) { + switch (get_result().type()) { + case ResultType::Color: + return shader_manager().get("compositor_realize_on_domain_bicubic_color"); + case ResultType::Vector: + return shader_manager().get("compositor_realize_on_domain_bicubic_vector"); + case ResultType::Float: + return shader_manager().get("compositor_realize_on_domain_bicubic_float"); + default: + /* Other types are internal and needn't be handled by operations. */ + BLI_assert_unreachable(); + return nullptr; + } + } + else { + switch (get_result().type()) { + case ResultType::Color: + return shader_manager().get("compositor_realize_on_domain_color"); + case ResultType::Vector: + return shader_manager().get("compositor_realize_on_domain_vector"); + case ResultType::Float: + return shader_manager().get("compositor_realize_on_domain_float"); + default: + /* Other types are internal and needn't be handled by operations. */ + BLI_assert_unreachable(); + return nullptr; + } + } + + BLI_assert_unreachable(); + return nullptr; } Domain RealizeOnDomainOperation::compute_domain() @@ -74,4 +161,68 @@ SimpleOperation *RealizeOnDomainOperation::construct_if_needed( return new RealizeOnDomainOperation(context, operation_domain, input_descriptor.type); } +/* ------------------------------------------------------------------------------------------------ + * Realize Transformation Operation + */ + +Domain RealizeTransformationOperation::compute_target_domain( + const Domain &input_domain, const InputRealizationOptions &realization_options) +{ + if (!realization_options.realize_rotation && !realization_options.realize_scale) { + return input_domain; + } + + math::AngleRadian rotation; + float2 translation, scale; + float2 size = float2(input_domain.size); + math::to_loc_rot_scale(input_domain.transformation, translation, rotation, scale); + + /* Set the rotation to zero and expand the domain size to fit the bounding box of the rotated + * result. */ + if (realization_options.realize_rotation) { + const float sine = math::abs(math::sin(rotation)); + const float cosine = math::abs(math::cos(rotation)); + size = float2(size.x * sine + size.y * cosine, size.x * cosine + size.y * sine); + rotation = 0.0f; + } + + /* Set the scale to 1 and scale the domain size to adapt to the new domain. */ + if (realization_options.realize_scale) { + size *= scale; + scale = float2(1.0f); + } + + const float3x3 transformation = math::from_loc_rot_scale(translation, rotation, scale); + + return Domain(int2(math::ceil(size)), transformation); +} + +SimpleOperation *RealizeTransformationOperation::construct_if_needed( + Context &context, const Result &input_result, const InputDescriptor &input_descriptor) +{ + /* The input expects a single value and if no single value is provided, it will be ignored and a + * default value will be used, so no need to realize it and the operation is not needed. */ + if (input_descriptor.expects_single_value) { + return nullptr; + } + + /* Input result is a single value and does not need realization, the operation is not needed. */ + if (input_result.is_single_value()) { + return nullptr; + } + + const Domain target_domain = compute_target_domain(input_result.domain(), + input_descriptor.realization_options); + + /* The input have an identical domain to the target domain, either because the input doesn't need + * to realize its transformations or because it has identity transformations, so no need to + * realize it and the operation is not needed. */ + if (target_domain == input_result.domain()) { + return nullptr; + } + + /* Otherwise, realization on the target domain is needed. */ + return new RealizeOnDomainOperation(context, target_domain, input_descriptor.type); +} + } // namespace blender::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/intern/result.cc b/source/blender/compositor/realtime_compositor/intern/result.cc index 267fd5d4af5..aa4de890093 100644 --- a/source/blender/compositor/realtime_compositor/intern/result.cc +++ b/source/blender/compositor/realtime_compositor/intern/result.cc @@ -172,6 +172,7 @@ void Result::steal_data(Result &source) break; default: /* Other types are internal and do not support single values. */ + BLI_assert_unreachable(); break; } diff --git a/source/blender/compositor/realtime_compositor/intern/static_cache_manager.cc b/source/blender/compositor/realtime_compositor/intern/static_cache_manager.cc index 2b5273f2f56..0d8f738dce7 100644 --- a/source/blender/compositor/realtime_compositor/intern/static_cache_manager.cc +++ b/source/blender/compositor/realtime_compositor/intern/static_cache_manager.cc @@ -16,7 +16,6 @@ void StaticCacheManager::reset() smaa_precomputed_textures.reset(); ocio_color_space_conversion_shaders.reset(); distortion_grids.reset(); - keying_screens.reset(); } } // namespace blender::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/intern/utilities.cc b/source/blender/compositor/realtime_compositor/intern/utilities.cc index 57b11b76bf1..d01564cb012 100644 --- a/source/blender/compositor/realtime_compositor/intern/utilities.cc +++ b/source/blender/compositor/realtime_compositor/intern/utilities.cc @@ -129,6 +129,12 @@ InputDescriptor input_descriptor_from_input_socket(const bNodeSocket *socket) input_descriptor.realization_options.realize_on_operation_domain = bool( socket_declaration->compositor_realization_options() & CompositorInputRealizationOptions::RealizeOnOperationDomain); + input_descriptor.realization_options.realize_rotation = bool( + socket_declaration->compositor_realization_options() & + CompositorInputRealizationOptions::RealizeRotation); + input_descriptor.realization_options.realize_scale = bool( + socket_declaration->compositor_realization_options() & + CompositorInputRealizationOptions::RealizeScale); return input_descriptor; } diff --git a/source/blender/compositor/realtime_compositor/shaders/compositor_double_edge_mask_compute_boundary.glsl b/source/blender/compositor/realtime_compositor/shaders/compositor_double_edge_mask_compute_boundary.glsl index df655cff6a0..b12a2133a41 100644 --- a/source/blender/compositor/realtime_compositor/shaders/compositor_double_edge_mask_compute_boundary.glsl +++ b/source/blender/compositor/realtime_compositor/shaders/compositor_double_edge_mask_compute_boundary.glsl @@ -60,9 +60,9 @@ void main() bool is_outer_boundary = is_outer_masked && has_outer_non_masked_neighbours; /* Encode the boundary information in the format expected by the jump flooding algorithm. */ - ivec2 inner_jump_flooding_value = initialize_jump_flooding_value(texel, is_inner_boundary); - ivec2 outer_jump_flooding_value = initialize_jump_flooding_value(texel, is_outer_boundary); + vec4 inner_jump_flooding_value = initialize_jump_flooding_value(texel, is_inner_boundary); + vec4 outer_jump_flooding_value = initialize_jump_flooding_value(texel, is_outer_boundary); - imageStore(inner_boundary_img, texel, ivec4(inner_jump_flooding_value, ivec2(0))); - imageStore(outer_boundary_img, texel, ivec4(outer_jump_flooding_value, ivec2(0))); + imageStore(inner_boundary_img, texel, inner_jump_flooding_value); + imageStore(outer_boundary_img, texel, outer_jump_flooding_value); } diff --git a/source/blender/compositor/realtime_compositor/shaders/compositor_double_edge_mask_compute_gradient.glsl b/source/blender/compositor/realtime_compositor/shaders/compositor_double_edge_mask_compute_gradient.glsl index 43f7079aeca..307f5dd4a1c 100644 --- a/source/blender/compositor/realtime_compositor/shaders/compositor_double_edge_mask_compute_gradient.glsl +++ b/source/blender/compositor/realtime_compositor/shaders/compositor_double_edge_mask_compute_gradient.glsl @@ -38,11 +38,11 @@ void main() return; } - /* Compute the distances to the inner and outer boundaries from the jump flooding tables. */ - ivec2 inner_boundary_texel = texture_load(flooded_inner_boundary_tx, texel).xy; - ivec2 outer_boundary_texel = texture_load(flooded_outer_boundary_tx, texel).xy; - float distance_to_inner = distance(vec2(texel), vec2(inner_boundary_texel)); - float distance_to_outer = distance(vec2(texel), vec2(outer_boundary_texel)); + /* Extract the distances to the inner and outer boundaries from the jump flooding tables. */ + vec4 inner_flooding_value = texture_load(flooded_inner_boundary_tx, texel); + vec4 outer_flooding_value = texture_load(flooded_outer_boundary_tx, texel); + float distance_to_inner = extract_jump_flooding_distance_to_closest_seed(inner_flooding_value); + float distance_to_outer = extract_jump_flooding_distance_to_closest_seed(outer_flooding_value); float gradient = distance_to_outer / (distance_to_outer + distance_to_inner); diff --git a/source/blender/compositor/realtime_compositor/shaders/compositor_inpaint_compute_boundary.glsl b/source/blender/compositor/realtime_compositor/shaders/compositor_inpaint_compute_boundary.glsl index b4fb604a881..1cb9f26d477 100644 --- a/source/blender/compositor/realtime_compositor/shaders/compositor_inpaint_compute_boundary.glsl +++ b/source/blender/compositor/realtime_compositor/shaders/compositor_inpaint_compute_boundary.glsl @@ -2,7 +2,7 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ -/* The in-paint operation uses a jump flood algorithm to flood the region to be inpainted with the +/* The inpaint operation uses a jump flood algorithm to flood the region to be inpainted with the * pixels at its boundary. The algorithms expects an input image whose values are those returned by * the initialize_jump_flooding_value function, given the texel location and a boolean specifying * if the pixel is a boundary one. @@ -39,7 +39,7 @@ void main() bool is_boundary_pixel = is_opaque && has_transparent_neighbours; /* Encode the boundary information in the format expected by the jump flooding algorithm. */ - ivec2 jump_flooding_value = initialize_jump_flooding_value(texel, is_boundary_pixel); + vec4 jump_flooding_value = initialize_jump_flooding_value(texel, is_boundary_pixel); - imageStore(boundary_img, texel, ivec4(jump_flooding_value, ivec2(0))); + imageStore(boundary_img, texel, jump_flooding_value); } diff --git a/source/blender/compositor/realtime_compositor/shaders/compositor_inpaint_compute_region.glsl b/source/blender/compositor/realtime_compositor/shaders/compositor_inpaint_compute_region.glsl index 452af949078..0beea669322 100644 --- a/source/blender/compositor/realtime_compositor/shaders/compositor_inpaint_compute_region.glsl +++ b/source/blender/compositor/realtime_compositor/shaders/compositor_inpaint_compute_region.glsl @@ -21,11 +21,11 @@ void main() return; } - ivec2 closest_boundary_texel = texture_load(flooded_boundary_tx, texel).xy; - float distance_to_boundary = distance(vec2(texel), vec2(closest_boundary_texel)); + vec4 flooding_value = texture_load(flooded_boundary_tx, texel); + float distance_to_boundary = extract_jump_flooding_distance_to_closest_seed(flooding_value); /* Further than the user supplied distance, write a transparent color. */ - if (distance_to_boundary > max_distance) { + if (distance_to_boundary > distance) { imageStore(output_img, texel, vec4(0.0)); return; } @@ -45,7 +45,8 @@ void main() float weight = texture(gaussian_weights_tx, float(i / (blur_radius - 1))).x; { - ivec2 boundary_texel = texture_load(flooded_boundary_tx, ivec2(left_texel)).xy; + vec4 flooding_value = texture_load(flooded_boundary_tx, ivec2(left_texel)); + ivec2 boundary_texel = extract_jump_flooding_closest_seed_texel(flooding_value); accumulated_color += texture_load(input_tx, boundary_texel) * weight; accumulated_weight += weight; @@ -56,7 +57,8 @@ void main() /* When i is zero, we are accumulating the center pixel, which was already accumulated as the * left texel above, so no need to accumulate it again. */ if (i != 0) { - ivec2 boundary_texel = texture_load(flooded_boundary_tx, ivec2(right_texel)).xy; + vec4 flooding_value = texture_load(flooded_boundary_tx, ivec2(right_texel)); + ivec2 boundary_texel = extract_jump_flooding_closest_seed_texel(flooding_value); accumulated_color += texture_load(input_tx, boundary_texel) * weight; accumulated_weight += weight; diff --git a/source/blender/compositor/realtime_compositor/shaders/compositor_jump_flooding.glsl b/source/blender/compositor/realtime_compositor/shaders/compositor_jump_flooding.glsl index c573502b2b0..590ec124a3c 100644 --- a/source/blender/compositor/realtime_compositor/shaders/compositor_jump_flooding.glsl +++ b/source/blender/compositor/realtime_compositor/shaders/compositor_jump_flooding.glsl @@ -13,7 +13,7 @@ * noting that the nil special value in the paper is equivalent to JUMP_FLOODING_NON_FLOODED_VALUE. * * The `gpu_shader_compositor_jump_flooding_lib.glsl` library contains the necessary utility - * functions to initialize and encode the jump flooding values. */ + * functions to initialize, encode, and extract the information in the jump flooding values. */ #pragma BLENDER_REQUIRE(common_math_lib.glsl) #pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.glsl) @@ -26,7 +26,7 @@ void main() /* For each of the previously flooded pixels in the 3x3 window of the given step size around the * center pixel, find the position of the closest seed pixel that is closest to the current * center pixel. */ - ivec2 closest_seed_texel = ivec2(0.0); + vec2 closest_seed_position = vec2(0.0); float minimum_squared_distance = FLT_MAX; for (int j = -1; j <= 1; j++) { for (int i = -1; i <= 1; i++) { @@ -34,33 +34,32 @@ void main() /* Use #JUMP_FLOODING_NON_FLOODED_VALUE as a fallback value to exempt out of bound pixels * from the loop as can be seen in the following continue condition. */ - ivec4 fallback = ivec4(JUMP_FLOODING_NON_FLOODED_VALUE, ivec2(0)); - ivec2 jump_flooding_value = texture_load(input_tx, texel + offset, fallback).xy; + vec4 value = texture_load(input_tx, texel + offset, JUMP_FLOODING_NON_FLOODED_VALUE); /* The pixel is either not flooded yet or is out of bound, so skip it. */ - if (all(equal(jump_flooding_value, JUMP_FLOODING_NON_FLOODED_VALUE))) { + if (!is_jump_flooded(value)) { continue; } - /* The neighboring pixel is flooded, so its flooding value is the texel of the closest seed - * pixel to this neighboring pixel. */ - ivec2 closest_seed_texel_to_neighbor = jump_flooding_value; - - /* Compute the squared distance to the neighbor's closest seed pixel. */ - float squared_distance = distance_squared(vec2(closest_seed_texel_to_neighbor), vec2(texel)); + /* Extract the position of the closest seed pixel to this neighboring pixel and compute the + * squared distance from that position to the center pixel. */ + ivec2 position = extract_jump_flooding_closest_seed_texel(value); + float squared_distance = distance_squared(vec2(position), vec2(texel)); if (squared_distance < minimum_squared_distance) { minimum_squared_distance = squared_distance; - closest_seed_texel = closest_seed_texel_to_neighbor; + closest_seed_position = vec2(position); } } } /* If the minimum squared distance is still #FLT_MAX, that means the loop never got past the - * continue condition and thus no flooding happened. If flooding happened, we encode the closest - * seed texel in the format expected by the algorithm. */ + * continue condition and thus no flooding happened. If flooding happened, we write the closest + * seed position as well as the distance to it. */ bool flooding_happened = minimum_squared_distance != FLT_MAX; - ivec2 jump_flooding_value = encode_jump_flooding_value(closest_seed_texel, flooding_happened); + float minimum_distance = sqrt(minimum_squared_distance); + vec4 jump_flooding_value = encode_jump_flooding_value( + closest_seed_position, minimum_distance, flooding_happened); - imageStore(output_img, texel, ivec4(jump_flooding_value, ivec2(0))); + imageStore(output_img, texel, jump_flooding_value); } diff --git a/source/blender/compositor/realtime_compositor/shaders/compositor_keying_screen.glsl b/source/blender/compositor/realtime_compositor/shaders/compositor_keying_screen.glsl deleted file mode 100644 index 122de3c9916..00000000000 --- a/source/blender/compositor/realtime_compositor/shaders/compositor_keying_screen.glsl +++ /dev/null @@ -1,52 +0,0 @@ -/* SPDX-FileCopyrightText: 2023 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -#pragma BLENDER_REQUIRE(gpu_shader_math_base_lib.glsl) - -#define CACHE_SIZE (gl_WorkGroupSize.x * gl_WorkGroupSize.y) -shared vec2 cached_marker_positions[CACHE_SIZE]; -shared vec4 cached_marker_colors[CACHE_SIZE]; - -/* Cache the initial part of the marker SSBOs in shared memory to make the interpolation loop - * faster. */ -void populate_cache() -{ - if (int(gl_LocalInvocationIndex) < number_of_markers) { - cached_marker_positions[gl_LocalInvocationIndex] = marker_positions[gl_LocalInvocationIndex]; - cached_marker_colors[gl_LocalInvocationIndex] = marker_colors[gl_LocalInvocationIndex]; - } - barrier(); -} - -void main() -{ - ivec2 texel = ivec2(gl_GlobalInvocationID.xy); - - ivec2 size = imageSize(output_img); - vec2 normalized_pixel_location = (vec2(texel) + vec2(0.5)) / vec2(size); - float squared_shape_parameter = square(1.0 / smoothness); - - populate_cache(); - - /* Interpolate the markers using a Gaussian Radial Basis Function Interpolation with the - * reciprocal of the smoothness as the shaping parameter. Equal weights are assigned to all - * markers, so no RBF fitting is required. */ - float sum_of_weights = 0.0; - vec4 weighted_sum = vec4(0.0); - for (int i = 0; i < number_of_markers; i++) { - bool use_cache = i < int(CACHE_SIZE); - - vec2 marker_position = use_cache ? cached_marker_positions[i] : marker_positions[i]; - vec2 difference = normalized_pixel_location - marker_position; - float squared_distance = dot(difference, difference); - float gaussian = exp(-squared_distance * squared_shape_parameter); - - vec4 marker_color = use_cache ? cached_marker_colors[i] : marker_colors[i]; - weighted_sum += marker_color * gaussian; - sum_of_weights += gaussian; - } - weighted_sum /= sum_of_weights; - - imageStore(output_img, texel, weighted_sum); -} diff --git a/source/blender/compositor/realtime_compositor/shaders/compositor_kuwahara_anisotropic.glsl b/source/blender/compositor/realtime_compositor/shaders/compositor_kuwahara_anisotropic.glsl index a313e83dbe1..cae1e6a212d 100644 --- a/source/blender/compositor/realtime_compositor/shaders/compositor_kuwahara_anisotropic.glsl +++ b/source/blender/compositor/realtime_compositor/shaders/compositor_kuwahara_anisotropic.glsl @@ -56,12 +56,6 @@ void main() float eigenvalue_difference = first_eigenvalue - second_eigenvalue; float anisotropy = eigenvalue_sum > 0.0 ? eigenvalue_difference / eigenvalue_sum : 0.0; -#if defined(VARIABLE_SIZE) - float radius = max(0.0, texture_load(size_tx, texel).x); -#elif defined(CONSTANT_SIZE) - float radius = max(0.0, size); -#endif - /* Compute the width and height of an ellipse that is more width-elongated for high anisotropy * and more circular for low anisotropy, controlled using the eccentricity factor. Since the * anisotropy is in the [0, 1] range, the width factor tends to 1 as the eccentricity tends to diff --git a/source/blender/compositor/realtime_compositor/shaders/compositor_kuwahara_classic.glsl b/source/blender/compositor/realtime_compositor/shaders/compositor_kuwahara_classic.glsl index b5dbf889d17..a1d59be9a1d 100644 --- a/source/blender/compositor/realtime_compositor/shaders/compositor_kuwahara_classic.glsl +++ b/source/blender/compositor/realtime_compositor/shaders/compositor_kuwahara_classic.glsl @@ -10,12 +10,6 @@ void main() { ivec2 texel = ivec2(gl_GlobalInvocationID.xy); -#if defined(VARIABLE_SIZE) - int radius = max(0, int(texture_load(size_tx, texel).x)); -#elif defined(CONSTANT_SIZE) - int radius = max(0, size); -#endif - vec4 mean_of_squared_color_of_quadrants[4] = vec4[](vec4(0.0), vec4(0.0), vec4(0.0), vec4(0.0)); vec4 mean_of_color_of_quadrants[4] = vec4[](vec4(0.0), vec4(0.0), vec4(0.0), vec4(0.0)); diff --git a/source/blender/compositor/realtime_compositor/shaders/compositor_read_input.glsl b/source/blender/compositor/realtime_compositor/shaders/compositor_read_pass.glsl similarity index 61% rename from source/blender/compositor/realtime_compositor/shaders/compositor_read_input.glsl rename to source/blender/compositor/realtime_compositor/shaders/compositor_read_pass.glsl index c7dab03fe54..03b5d61272f 100644 --- a/source/blender/compositor/realtime_compositor/shaders/compositor_read_input.glsl +++ b/source/blender/compositor/realtime_compositor/shaders/compositor_read_pass.glsl @@ -7,6 +7,6 @@ void main() { ivec2 texel = ivec2(gl_GlobalInvocationID.xy); - vec4 input_color = texture_load(input_tx, texel + lower_bound); - imageStore(output_img, texel, READ_EXPRESSION(input_color)); + vec4 pass_color = texture_load(input_tx, texel + compositing_region_lower_bound); + imageStore(output_img, texel, READ_EXPRESSION(pass_color)); } diff --git a/source/blender/compositor/realtime_compositor/shaders/compositor_split_viewer.glsl b/source/blender/compositor/realtime_compositor/shaders/compositor_split_viewer.glsl index a1fbf12b77f..d71871e727a 100644 --- a/source/blender/compositor/realtime_compositor/shaders/compositor_split_viewer.glsl +++ b/source/blender/compositor/realtime_compositor/shaders/compositor_split_viewer.glsl @@ -14,5 +14,5 @@ void main() #endif vec4 color = condition ? texture_load(first_image_tx, texel) : texture_load(second_image_tx, texel); - imageStore(output_img, texel + lower_bound, color); + imageStore(output_img, texel + compositing_region_lower_bound, color); } diff --git a/source/blender/compositor/realtime_compositor/shaders/compositor_write_output.glsl b/source/blender/compositor/realtime_compositor/shaders/compositor_write_output.glsl index 0f53afec776..4bcd1e08f80 100644 --- a/source/blender/compositor/realtime_compositor/shaders/compositor_write_output.glsl +++ b/source/blender/compositor/realtime_compositor/shaders/compositor_write_output.glsl @@ -18,5 +18,5 @@ void main() vec4 output_color = vec4(input_color.rgb, alpha); #endif - imageStore(output_img, texel + lower_bound, output_color); + imageStore(output_img, texel + compositing_region_lower_bound, output_color); } diff --git a/source/blender/compositor/realtime_compositor/shaders/infos/compositor_convert_info.hh b/source/blender/compositor/realtime_compositor/shaders/infos/compositor_convert_info.hh index a44d97e6365..f3c9f2ce2c3 100644 --- a/source/blender/compositor/realtime_compositor/shaders/infos/compositor_convert_info.hh +++ b/source/blender/compositor/realtime_compositor/shaders/infos/compositor_convert_info.hh @@ -45,3 +45,21 @@ GPU_SHADER_CREATE_INFO(compositor_convert_vector_to_color) .image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_img") .define("CONVERT_EXPRESSION(value)", "vec4_from_vec3(value.xyz)") .do_static_compilation(true); + +GPU_SHADER_CREATE_INFO(compositor_extract_alpha_from_color) + .additional_info("compositor_convert_shared") + .image(0, GPU_R16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_img") + .define("CONVERT_EXPRESSION(value)", "vec4(value.a, vec3(0.0))") + .do_static_compilation(true); + +GPU_SHADER_CREATE_INFO(compositor_convert_color_to_half_color) + .additional_info("compositor_convert_shared") + .image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_img") + .define("CONVERT_EXPRESSION(value)", "value") + .do_static_compilation(true); + +GPU_SHADER_CREATE_INFO(compositor_convert_float_to_half_float) + .additional_info("compositor_convert_shared") + .image(0, GPU_R16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_img") + .define("CONVERT_EXPRESSION(value)", "vec4(value.r, vec3(0.0))") + .do_static_compilation(true); diff --git a/source/blender/compositor/realtime_compositor/shaders/infos/compositor_double_edge_mask_info.hh b/source/blender/compositor/realtime_compositor/shaders/infos/compositor_double_edge_mask_info.hh index 049a6502d5a..e6c078d64fb 100644 --- a/source/blender/compositor/realtime_compositor/shaders/infos/compositor_double_edge_mask_info.hh +++ b/source/blender/compositor/realtime_compositor/shaders/infos/compositor_double_edge_mask_info.hh @@ -10,8 +10,8 @@ GPU_SHADER_CREATE_INFO(compositor_double_edge_mask_compute_boundary) .push_constant(Type::BOOL, "include_edges_of_image") .sampler(0, ImageType::FLOAT_2D, "inner_mask_tx") .sampler(1, ImageType::FLOAT_2D, "outer_mask_tx") - .image(0, GPU_RG16I, Qualifier::WRITE, ImageType::INT_2D, "inner_boundary_img") - .image(1, GPU_RG16I, Qualifier::WRITE, ImageType::INT_2D, "outer_boundary_img") + .image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "inner_boundary_img") + .image(1, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "outer_boundary_img") .compute_source("compositor_double_edge_mask_compute_boundary.glsl") .do_static_compilation(true); @@ -19,8 +19,8 @@ GPU_SHADER_CREATE_INFO(compositor_double_edge_mask_compute_gradient) .local_group_size(16, 16) .sampler(0, ImageType::FLOAT_2D, "inner_mask_tx") .sampler(1, ImageType::FLOAT_2D, "outer_mask_tx") - .sampler(2, ImageType::INT_2D, "flooded_inner_boundary_tx") - .sampler(3, ImageType::INT_2D, "flooded_outer_boundary_tx") + .sampler(2, ImageType::FLOAT_2D, "flooded_inner_boundary_tx") + .sampler(3, ImageType::FLOAT_2D, "flooded_outer_boundary_tx") .image(0, GPU_R16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_img") .compute_source("compositor_double_edge_mask_compute_gradient.glsl") .do_static_compilation(true); diff --git a/source/blender/compositor/realtime_compositor/shaders/infos/compositor_inpaint_info.hh b/source/blender/compositor/realtime_compositor/shaders/infos/compositor_inpaint_info.hh index 4144cd3ad8c..8bf6b82125f 100644 --- a/source/blender/compositor/realtime_compositor/shaders/infos/compositor_inpaint_info.hh +++ b/source/blender/compositor/realtime_compositor/shaders/infos/compositor_inpaint_info.hh @@ -7,16 +7,16 @@ GPU_SHADER_CREATE_INFO(compositor_inpaint_compute_boundary) .local_group_size(16, 16) .sampler(0, ImageType::FLOAT_2D, "input_tx") - .image(0, GPU_RG16I, Qualifier::WRITE, ImageType::INT_2D, "boundary_img") + .image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "boundary_img") .compute_source("compositor_inpaint_compute_boundary.glsl") .do_static_compilation(true); GPU_SHADER_CREATE_INFO(compositor_inpaint_compute_region) .local_group_size(16, 16) - .push_constant(Type::INT, "max_distance") + .push_constant(Type::INT, "distance") .sampler(0, ImageType::FLOAT_2D, "input_tx") - .sampler(1, ImageType::INT_2D, "flooded_boundary_tx") - .sampler(2, ImageType::INT_1D, "gaussian_weights_tx") + .sampler(1, ImageType::FLOAT_2D, "flooded_boundary_tx") + .sampler(2, ImageType::FLOAT_1D, "gaussian_weights_tx") .image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_img") .compute_source("compositor_inpaint_compute_region.glsl") .do_static_compilation(true); diff --git a/source/blender/compositor/realtime_compositor/shaders/infos/compositor_jump_flooding_info.hh b/source/blender/compositor/realtime_compositor/shaders/infos/compositor_jump_flooding_info.hh index 90648beac22..44731f72a33 100644 --- a/source/blender/compositor/realtime_compositor/shaders/infos/compositor_jump_flooding_info.hh +++ b/source/blender/compositor/realtime_compositor/shaders/infos/compositor_jump_flooding_info.hh @@ -7,7 +7,7 @@ GPU_SHADER_CREATE_INFO(compositor_jump_flooding) .local_group_size(16, 16) .push_constant(Type::INT, "step_size") - .sampler(0, ImageType::INT_2D, "input_tx") - .image(0, GPU_RG16I, Qualifier::WRITE, ImageType::INT_2D, "output_img") + .sampler(0, ImageType::FLOAT_2D, "input_tx") + .image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_img") .compute_source("compositor_jump_flooding.glsl") .do_static_compilation(true); diff --git a/source/blender/compositor/realtime_compositor/shaders/infos/compositor_keying_screen_info.hh b/source/blender/compositor/realtime_compositor/shaders/infos/compositor_keying_screen_info.hh deleted file mode 100644 index ee1fd91e865..00000000000 --- a/source/blender/compositor/realtime_compositor/shaders/infos/compositor_keying_screen_info.hh +++ /dev/null @@ -1,15 +0,0 @@ -/* SPDX-FileCopyrightText: 2023 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -#include "gpu_shader_create_info.hh" - -GPU_SHADER_CREATE_INFO(compositor_keying_screen) - .local_group_size(16, 16) - .push_constant(Type::FLOAT, "smoothness") - .push_constant(Type::INT, "number_of_markers") - .storage_buf(0, Qualifier::READ, "vec2", "marker_positions[]") - .storage_buf(1, Qualifier::READ, "vec4", "marker_colors[]") - .image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_img") - .compute_source("compositor_keying_screen.glsl") - .do_static_compilation(true); diff --git a/source/blender/compositor/realtime_compositor/shaders/infos/compositor_kuwahara_info.hh b/source/blender/compositor/realtime_compositor/shaders/infos/compositor_kuwahara_info.hh index 9aa88260774..247852d1ec0 100644 --- a/source/blender/compositor/realtime_compositor/shaders/infos/compositor_kuwahara_info.hh +++ b/source/blender/compositor/realtime_compositor/shaders/infos/compositor_kuwahara_info.hh @@ -6,41 +6,20 @@ GPU_SHADER_CREATE_INFO(compositor_kuwahara_classic_shared) .local_group_size(16, 16) + .push_constant(Type::INT, "radius") .image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_img") .compute_source("compositor_kuwahara_classic.glsl"); -GPU_SHADER_CREATE_INFO(compositor_kuwahara_classic_convolution_shared) +GPU_SHADER_CREATE_INFO(compositor_kuwahara_classic) .additional_info("compositor_kuwahara_classic_shared") - .sampler(0, ImageType::FLOAT_2D, "input_tx"); - -GPU_SHADER_CREATE_INFO(compositor_kuwahara_classic_convolution_constant_size) - .additional_info("compositor_kuwahara_classic_convolution_shared") - .push_constant(Type::INT, "size") - .define("CONSTANT_SIZE") + .sampler(0, ImageType::FLOAT_2D, "input_tx") .do_static_compilation(true); -GPU_SHADER_CREATE_INFO(compositor_kuwahara_classic_convolution_variable_size) - .additional_info("compositor_kuwahara_classic_convolution_shared") - .sampler(1, ImageType::FLOAT_2D, "size_tx") - .define("VARIABLE_SIZE") - .do_static_compilation(true); - -GPU_SHADER_CREATE_INFO(compositor_kuwahara_classic_summed_area_table_shared) +GPU_SHADER_CREATE_INFO(compositor_kuwahara_classic_summed_area_table) .additional_info("compositor_kuwahara_classic_shared") .define("SUMMED_AREA_TABLE") .sampler(0, ImageType::FLOAT_2D, "table_tx") - .sampler(1, ImageType::FLOAT_2D, "squared_table_tx"); - -GPU_SHADER_CREATE_INFO(compositor_kuwahara_classic_summed_area_table_constant_size) - .additional_info("compositor_kuwahara_classic_summed_area_table_shared") - .push_constant(Type::INT, "size") - .define("CONSTANT_SIZE") - .do_static_compilation(true); - -GPU_SHADER_CREATE_INFO(compositor_kuwahara_classic_summed_area_table_variable_size) - .additional_info("compositor_kuwahara_classic_summed_area_table_shared") - .sampler(2, ImageType::FLOAT_2D, "size_tx") - .define("VARIABLE_SIZE") + .sampler(1, ImageType::FLOAT_2D, "squared_table_tx") .do_static_compilation(true); GPU_SHADER_CREATE_INFO(compositor_kuwahara_anisotropic_compute_structure_tensor) @@ -50,23 +29,13 @@ GPU_SHADER_CREATE_INFO(compositor_kuwahara_anisotropic_compute_structure_tensor) .compute_source("compositor_kuwahara_anisotropic_compute_structure_tensor.glsl") .do_static_compilation(true); -GPU_SHADER_CREATE_INFO(compositor_kuwahara_anisotropic_shared) +GPU_SHADER_CREATE_INFO(compositor_kuwahara_anisotropic) .local_group_size(16, 16) + .push_constant(Type::INT, "radius") .push_constant(Type::FLOAT, "eccentricity") .push_constant(Type::FLOAT, "sharpness") .sampler(0, ImageType::FLOAT_2D, "input_tx") .sampler(1, ImageType::FLOAT_2D, "structure_tensor_tx") .image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_img") - .compute_source("compositor_kuwahara_anisotropic.glsl"); - -GPU_SHADER_CREATE_INFO(compositor_kuwahara_anisotropic_constant_size) - .additional_info("compositor_kuwahara_anisotropic_shared") - .define("CONSTANT_SIZE") - .push_constant(Type::FLOAT, "size") - .do_static_compilation(true); - -GPU_SHADER_CREATE_INFO(compositor_kuwahara_anisotropic_variable_size) - .additional_info("compositor_kuwahara_anisotropic_shared") - .define("VARIABLE_SIZE") - .sampler(2, ImageType::FLOAT_2D, "size_tx") + .compute_source("compositor_kuwahara_anisotropic.glsl") .do_static_compilation(true); diff --git a/source/blender/compositor/realtime_compositor/shaders/infos/compositor_read_input_info.hh b/source/blender/compositor/realtime_compositor/shaders/infos/compositor_read_input_info.hh deleted file mode 100644 index a8e80ebb948..00000000000 --- a/source/blender/compositor/realtime_compositor/shaders/infos/compositor_read_input_info.hh +++ /dev/null @@ -1,35 +0,0 @@ -/* SPDX-FileCopyrightText: 2023 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -#include "gpu_shader_create_info.hh" - -GPU_SHADER_CREATE_INFO(compositor_read_input_shared) - .local_group_size(16, 16) - .push_constant(Type::IVEC2, "lower_bound") - .sampler(0, ImageType::FLOAT_2D, "input_tx") - .compute_source("compositor_read_input.glsl"); - -GPU_SHADER_CREATE_INFO(compositor_read_input_float) - .additional_info("compositor_read_input_shared") - .image(0, GPU_R16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_img") - .define("READ_EXPRESSION(input_color)", "vec4(input_color.r, vec3(0.0))") - .do_static_compilation(true); - -GPU_SHADER_CREATE_INFO(compositor_read_input_vector) - .additional_info("compositor_read_input_shared") - .image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_img") - .define("READ_EXPRESSION(input_color)", "input_color") - .do_static_compilation(true); - -GPU_SHADER_CREATE_INFO(compositor_read_input_color) - .additional_info("compositor_read_input_shared") - .image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_img") - .define("READ_EXPRESSION(input_color)", "input_color") - .do_static_compilation(true); - -GPU_SHADER_CREATE_INFO(compositor_read_input_alpha) - .additional_info("compositor_read_input_shared") - .image(0, GPU_R16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_img") - .define("READ_EXPRESSION(input_color)", "vec4(input_color.a, vec3(0.0))") - .do_static_compilation(true); diff --git a/source/blender/compositor/realtime_compositor/shaders/infos/compositor_read_pass_info.hh b/source/blender/compositor/realtime_compositor/shaders/infos/compositor_read_pass_info.hh new file mode 100644 index 00000000000..d7a7980993d --- /dev/null +++ b/source/blender/compositor/realtime_compositor/shaders/infos/compositor_read_pass_info.hh @@ -0,0 +1,35 @@ +/* SPDX-FileCopyrightText: 2023 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "gpu_shader_create_info.hh" + +GPU_SHADER_CREATE_INFO(compositor_read_pass_shared) + .local_group_size(16, 16) + .push_constant(Type::IVEC2, "compositing_region_lower_bound") + .sampler(0, ImageType::FLOAT_2D, "input_tx") + .compute_source("compositor_read_pass.glsl"); + +GPU_SHADER_CREATE_INFO(compositor_read_pass_float) + .additional_info("compositor_read_pass_shared") + .image(0, GPU_R16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_img") + .define("READ_EXPRESSION(pass_color)", "vec4(pass_color.r, vec3(0.0))") + .do_static_compilation(true); + +GPU_SHADER_CREATE_INFO(compositor_read_pass_vector) + .additional_info("compositor_read_pass_shared") + .image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_img") + .define("READ_EXPRESSION(pass_color)", "pass_color") + .do_static_compilation(true); + +GPU_SHADER_CREATE_INFO(compositor_read_pass_color) + .additional_info("compositor_read_pass_shared") + .image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_img") + .define("READ_EXPRESSION(pass_color)", "pass_color") + .do_static_compilation(true); + +GPU_SHADER_CREATE_INFO(compositor_read_pass_alpha) + .additional_info("compositor_read_pass_shared") + .image(0, GPU_R16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_img") + .define("READ_EXPRESSION(pass_color)", "vec4(pass_color.a, vec3(0.0))") + .do_static_compilation(true); diff --git a/source/blender/compositor/realtime_compositor/shaders/infos/compositor_split_viewer_info.hh b/source/blender/compositor/realtime_compositor/shaders/infos/compositor_split_viewer_info.hh index cda83dc4e26..9a6468b35bf 100644 --- a/source/blender/compositor/realtime_compositor/shaders/infos/compositor_split_viewer_info.hh +++ b/source/blender/compositor/realtime_compositor/shaders/infos/compositor_split_viewer_info.hh @@ -8,7 +8,7 @@ GPU_SHADER_CREATE_INFO(compositor_split_viewer_shared) .local_group_size(16, 16) .push_constant(Type::FLOAT, "split_ratio") .push_constant(Type::IVEC2, "view_size") - .push_constant(Type::IVEC2, "lower_bound") + .push_constant(Type::IVEC2, "compositing_region_lower_bound") .sampler(0, ImageType::FLOAT_2D, "first_image_tx") .sampler(1, ImageType::FLOAT_2D, "second_image_tx") .image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_img") diff --git a/source/blender/compositor/realtime_compositor/shaders/infos/compositor_write_output_info.hh b/source/blender/compositor/realtime_compositor/shaders/infos/compositor_write_output_info.hh index 9a4c5a5beee..7220f8920d1 100644 --- a/source/blender/compositor/realtime_compositor/shaders/infos/compositor_write_output_info.hh +++ b/source/blender/compositor/realtime_compositor/shaders/infos/compositor_write_output_info.hh @@ -6,7 +6,7 @@ GPU_SHADER_CREATE_INFO(compositor_write_output_shared) .local_group_size(16, 16) - .push_constant(Type::IVEC2, "lower_bound") + .push_constant(Type::IVEC2, "compositing_region_lower_bound") .sampler(0, ImageType::FLOAT_2D, "input_tx") .image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_img") .compute_source("compositor_write_output.glsl"); diff --git a/source/blender/compositor/realtime_compositor/shaders/library/gpu_shader_compositor_jump_flooding_lib.glsl b/source/blender/compositor/realtime_compositor/shaders/library/gpu_shader_compositor_jump_flooding_lib.glsl index 9673529f920..8f44d86da5a 100644 --- a/source/blender/compositor/realtime_compositor/shaders/library/gpu_shader_compositor_jump_flooding_lib.glsl +++ b/source/blender/compositor/realtime_compositor/shaders/library/gpu_shader_compositor_jump_flooding_lib.glsl @@ -4,19 +4,40 @@ /* A special value that indicates that the pixel has not be flooded yet, and consequently is not a * seed pixel. */ -#define JUMP_FLOODING_NON_FLOODED_VALUE ivec2(-1) +#define JUMP_FLOODING_NON_FLOODED_VALUE vec4(-1.0) -/* Given the texel location of the closest seed pixel and whether the pixel is flooded, encode that - * information in an ivec2. */ -ivec2 encode_jump_flooding_value(ivec2 closest_seed_texel, bool is_flooded) +/* Returns true if the pixel whose value is given was flooded, false otherwise. */ +bool is_jump_flooded(vec4 value) { - return is_flooded ? closest_seed_texel : JUMP_FLOODING_NON_FLOODED_VALUE; + return all(notEqual(value, JUMP_FLOODING_NON_FLOODED_VALUE)); +} + +/* Given the position of the closest seed, the distance to it, and whether the pixel is flooded, + * encode that information in a vec4 in a format expected by the algorithm and return it */ +vec4 encode_jump_flooding_value(vec2 position_of_closest_seed, float dist, bool is_flooded) +{ + if (is_flooded) { + return vec4(position_of_closest_seed, dist, 0.0); + } + return JUMP_FLOODING_NON_FLOODED_VALUE; } /* Initialize the pixel at the given texel location for the algorithm as being seed or background. * This essentially calls encode_jump_flooding_value with the texel location, because the pixel is - * the closest seed to itself. */ -ivec2 initialize_jump_flooding_value(ivec2 texel, bool is_seed) + * the closest seed to itself, and a distance of zero, because that's the distance to itself. */ +vec4 initialize_jump_flooding_value(ivec2 texel, bool is_seed) { - return encode_jump_flooding_value(texel, is_seed); + return encode_jump_flooding_value(vec2(texel), 0.0, is_seed); +} + +/* Extracts the texel location of the closest seed to the pixel of the given value. */ +ivec2 extract_jump_flooding_closest_seed_texel(vec4 value) +{ + return ivec2(value.xy); +} + +/* Extracts the distance to the closest seed to the pixel of the given value. */ +float extract_jump_flooding_distance_to_closest_seed(vec4 value) +{ + return value.z; } diff --git a/source/blender/compositor/realtime_compositor/shaders/library/gpu_shader_compositor_texture_utilities.glsl b/source/blender/compositor/realtime_compositor/shaders/library/gpu_shader_compositor_texture_utilities.glsl index 4da3e376cb6..f7ad7ba0834 100644 --- a/source/blender/compositor/realtime_compositor/shaders/library/gpu_shader_compositor_texture_utilities.glsl +++ b/source/blender/compositor/realtime_compositor/shaders/library/gpu_shader_compositor_texture_utilities.glsl @@ -37,26 +37,3 @@ vec4 texture_load(sampler2D sampler_2d, ivec2 texel, vec4 fallback) } return texelFetch(sampler_2d, texel, 0); } - -/* A shorthand for 2D textureSize with a zero LOD. */ -ivec2 texture_size(isampler2D sampler_2d) -{ - return textureSize(sampler_2d, 0); -} - -/* A shorthand for 2D texelFetch with zero LOD and bounded access clamped to border. */ -ivec4 texture_load(isampler2D sampler_2d, ivec2 texel) -{ - const ivec2 texture_bounds = texture_size(sampler_2d) - ivec2(1); - return texelFetch(sampler_2d, clamp(texel, ivec2(0), texture_bounds), 0); -} - -/* A shorthand for 2D texelFetch with zero LOD and a fallback value for out-of-bound access. */ -ivec4 texture_load(isampler2D sampler_2d, ivec2 texel, ivec4 fallback) -{ - const ivec2 texture_bounds = texture_size(sampler_2d) - ivec2(1); - if (any(lessThan(texel, ivec2(0))) || any(greaterThan(texel, texture_bounds))) { - return fallback; - } - return texelFetch(sampler_2d, texel, 0); -} diff --git a/source/blender/datatoc/CMakeLists.txt b/source/blender/datatoc/CMakeLists.txt index 3abd3601792..e9d2ce94c18 100644 --- a/source/blender/datatoc/CMakeLists.txt +++ b/source/blender/datatoc/CMakeLists.txt @@ -28,12 +28,12 @@ if(NOT WITH_HEADLESS) ../../../intern/utfconv ) - # for winstuff_dir.cc + # for winstuff_dir.c add_definitions(-DUSE_STANDALONE) list(APPEND SRC - ../blenlib/intern/winstuff_dir.cc - ../../../intern/utfconv/utfconv.cc + ../blenlib/intern/winstuff_dir.c + ../../../intern/utfconv/utfconv.c ) endif() diff --git a/source/blender/draw/CMakeLists.txt b/source/blender/draw/CMakeLists.txt index fb483240136..8cced3a7315 100644 --- a/source/blender/draw/CMakeLists.txt +++ b/source/blender/draw/CMakeLists.txt @@ -153,7 +153,6 @@ set(SRC engines/eevee_next/eevee_material.cc engines/eevee_next/eevee_motion_blur.cc engines/eevee_next/eevee_pipeline.cc - engines/eevee_next/eevee_planar_probes.cc engines/eevee_next/eevee_precompute.cc engines/eevee_next/eevee_raytrace.cc engines/eevee_next/eevee_reflection_probes.cc @@ -228,7 +227,7 @@ set(SRC DRW_engine.h DRW_pbvh.hh - DRW_select_buffer.hh + DRW_select_buffer.h intern/DRW_gpu_wrapper.hh intern/DRW_render.h intern/draw_attributes.hh @@ -290,7 +289,6 @@ set(SRC engines/eevee_next/eevee_material.hh engines/eevee_next/eevee_motion_blur.hh engines/eevee_next/eevee_pipeline.hh - engines/eevee_next/eevee_planar_probes.hh engines/eevee_next/eevee_precompute.hh engines/eevee_next/eevee_raytrace.hh engines/eevee_next/eevee_reflection_probes.hh @@ -336,10 +334,10 @@ set(SRC engines/overlay/overlay_next_prepass.hh engines/overlay/overlay_next_private.hh engines/overlay/overlay_private.hh - engines/select/select_defines.hh - engines/select/select_engine.hh + engines/select/select_defines.h + engines/select/select_engine.h engines/select/select_instance.hh - engines/select/select_private.hh + engines/select/select_private.h engines/workbench/workbench_defines.hh engines/workbench/workbench_engine.h engines/workbench/workbench_enums.hh @@ -473,7 +471,6 @@ set(GLSL_SRC engines/eevee_next/shaders/eevee_deferred_light_frag.glsl engines/eevee_next/shaders/eevee_deferred_capture_frag.glsl engines/eevee_next/shaders/eevee_deferred_combine_frag.glsl - engines/eevee_next/shaders/eevee_deferred_planar_frag.glsl engines/eevee_next/shaders/eevee_depth_of_field_accumulator_lib.glsl engines/eevee_next/shaders/eevee_depth_of_field_bokeh_lut_comp.glsl engines/eevee_next/shaders/eevee_depth_of_field_downsample_comp.glsl @@ -500,7 +497,6 @@ set(GLSL_SRC engines/eevee_next/shaders/eevee_geom_gpencil_vert.glsl engines/eevee_next/shaders/eevee_geom_mesh_vert.glsl engines/eevee_next/shaders/eevee_geom_point_cloud_vert.glsl - engines/eevee_next/shaders/eevee_geom_volume_vert.glsl engines/eevee_next/shaders/eevee_geom_world_vert.glsl engines/eevee_next/shaders/eevee_hiz_debug_frag.glsl engines/eevee_next/shaders/eevee_hiz_update_comp.glsl @@ -525,9 +521,6 @@ set(GLSL_SRC engines/eevee_next/shaders/eevee_motion_blur_gather_comp.glsl engines/eevee_next/shaders/eevee_motion_blur_lib.glsl engines/eevee_next/shaders/eevee_nodetree_lib.glsl - engines/eevee_next/shaders/eevee_occupancy_convert_frag.glsl - engines/eevee_next/shaders/eevee_occupancy_lib.glsl - engines/eevee_next/shaders/eevee_occupancy_test.glsl engines/eevee_next/shaders/eevee_octahedron_lib.glsl engines/eevee_next/shaders/eevee_ray_denoise_bilateral_comp.glsl engines/eevee_next/shaders/eevee_ray_denoise_spatial_comp.glsl @@ -537,14 +530,12 @@ set(GLSL_SRC engines/eevee_next/shaders/eevee_ray_tile_classify_comp.glsl engines/eevee_next/shaders/eevee_ray_tile_compact_comp.glsl engines/eevee_next/shaders/eevee_ray_trace_fallback_comp.glsl - engines/eevee_next/shaders/eevee_ray_trace_planar_comp.glsl engines/eevee_next/shaders/eevee_ray_trace_screen_comp.glsl engines/eevee_next/shaders/eevee_ray_trace_screen_lib.glsl engines/eevee_next/shaders/eevee_ray_types_lib.glsl engines/eevee_next/shaders/eevee_reflection_probe_eval_lib.glsl engines/eevee_next/shaders/eevee_reflection_probe_lib.glsl engines/eevee_next/shaders/eevee_reflection_probe_remap_comp.glsl - engines/eevee_next/shaders/eevee_reflection_probe_select_comp.glsl engines/eevee_next/shaders/eevee_reflection_probe_update_irradiance_comp.glsl engines/eevee_next/shaders/eevee_renderpass_lib.glsl engines/eevee_next/shaders/eevee_sampling_lib.glsl @@ -572,17 +563,13 @@ set(GLSL_SRC engines/eevee_next/shaders/eevee_shadow_tilemap_lib.glsl engines/eevee_next/shaders/eevee_spherical_harmonics_lib.glsl engines/eevee_next/shaders/eevee_subsurface_convolve_comp.glsl - engines/eevee_next/shaders/eevee_subsurface_lib.glsl engines/eevee_next/shaders/eevee_subsurface_setup_comp.glsl engines/eevee_next/shaders/eevee_surf_capture_frag.glsl engines/eevee_next/shaders/eevee_surf_deferred_frag.glsl engines/eevee_next/shaders/eevee_surf_depth_frag.glsl engines/eevee_next/shaders/eevee_surf_forward_frag.glsl engines/eevee_next/shaders/eevee_surf_lib.glsl - engines/eevee_next/shaders/eevee_surf_occupancy_frag.glsl engines/eevee_next/shaders/eevee_surf_shadow_frag.glsl - engines/eevee_next/shaders/eevee_shadow_page_tile_vert.glsl - engines/eevee_next/shaders/eevee_shadow_page_tile_frag.glsl engines/eevee_next/shaders/eevee_surf_world_frag.glsl engines/eevee_next/shaders/eevee_surfel_cluster_build_comp.glsl engines/eevee_next/shaders/eevee_surfel_light_comp.glsl @@ -590,7 +577,6 @@ set(GLSL_SRC engines/eevee_next/shaders/eevee_surfel_list_lib.glsl engines/eevee_next/shaders/eevee_surfel_list_sort_comp.glsl engines/eevee_next/shaders/eevee_surfel_ray_comp.glsl - engines/eevee_next/shaders/eevee_thickness_lib.glsl engines/eevee_next/shaders/eevee_transparency_lib.glsl engines/eevee_next/shaders/eevee_velocity_lib.glsl engines/eevee_next/shaders/eevee_volume_integration_comp.glsl @@ -674,13 +660,8 @@ set(GLSL_SRC intern/shaders/draw_debug_info.hh intern/shaders/draw_debug_print_display_frag.glsl intern/shaders/draw_debug_print_display_vert.glsl - intern/shaders/draw_intersect_lib.glsl - intern/shaders/draw_math_geom_lib.glsl - intern/shaders/draw_model_lib.glsl intern/shaders/draw_resource_finalize_comp.glsl intern/shaders/draw_view_finalize_comp.glsl - intern/shaders/draw_view_lib.glsl - intern/shaders/draw_view_reconstruction_lib.glsl intern/shaders/draw_visibility_comp.glsl intern/draw_command_shared.hh @@ -690,7 +671,6 @@ set(GLSL_SRC intern/draw_shader_shared.h engines/gpencil/shaders/gpencil_frag.glsl - engines/gpencil/shaders/grease_pencil_frag.glsl engines/gpencil/shaders/gpencil_vert.glsl engines/gpencil/shaders/grease_pencil_vert.glsl engines/gpencil/shaders/gpencil_antialiasing_frag.glsl @@ -700,7 +680,6 @@ set(GLSL_SRC engines/gpencil/shaders/gpencil_mask_invert_frag.glsl engines/gpencil/shaders/gpencil_depth_merge_frag.glsl engines/gpencil/shaders/gpencil_depth_merge_vert.glsl - engines/gpencil/shaders/grease_pencil_depth_merge_vert.glsl engines/gpencil/shaders/gpencil_vfx_frag.glsl engines/gpencil/gpencil_defines.h diff --git a/source/blender/draw/DRW_engine.h b/source/blender/draw/DRW_engine.h index bfe59ba267d..bd890e8c562 100644 --- a/source/blender/draw/DRW_engine.h +++ b/source/blender/draw/DRW_engine.h @@ -42,6 +42,7 @@ struct bContext; struct rcti; void DRW_engines_register(void); +void DRW_engines_register_experimental(void); void DRW_engines_free(void); bool DRW_engine_render_support(struct DrawEngineType *draw_engine_type); @@ -141,7 +142,10 @@ void DRW_draw_depth_object(struct Scene *scene, struct View3D *v3d, struct GPUViewport *viewport, struct Object *object); -void DRW_draw_select_id(struct Depsgraph *depsgraph, struct ARegion *region, struct View3D *v3d); +void DRW_draw_select_id(struct Depsgraph *depsgraph, + struct ARegion *region, + struct View3D *v3d, + const struct rcti *rect); /* Grease pencil render. */ diff --git a/source/blender/draw/DRW_pbvh.hh b/source/blender/draw/DRW_pbvh.hh index f96a209457d..bf1c3f7d754 100644 --- a/source/blender/draw/DRW_pbvh.hh +++ b/source/blender/draw/DRW_pbvh.hh @@ -13,12 +13,15 @@ #include "BLI_bitmap.h" #include "BLI_math_vector_types.hh" #include "BLI_offset_indices.hh" -#include "BLI_set.hh" #include "BLI_span.hh" #include "BKE_ccg.h" -class PBVHAttrReq; +#ifdef __cplusplus +extern "C" { +#endif + +struct PBVHAttrReq; struct GPUBatch; struct PBVHNode; struct PBVHBatches; @@ -30,7 +33,6 @@ struct MLoopTri; struct CustomData; struct SubdivCCG; struct BMesh; -struct BMFace; struct PBVH_GPU_Args { int pbvh_type; @@ -70,7 +72,7 @@ struct PBVH_GPU_Args { PBVHNode *node; /* BMesh. */ - const blender::Set *bm_faces; + GSet *bm_unique_vert, *bm_other_verts, *bm_faces; int cd_mask_layer; }; @@ -92,3 +94,7 @@ GPUBatch *DRW_pbvh_lines_get(PBVHBatches *batches, const PBVH_GPU_Args &args, int *r_prim_count, bool do_coarse_grids); + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/draw/DRW_select_buffer.hh b/source/blender/draw/DRW_select_buffer.h similarity index 51% rename from source/blender/draw/DRW_select_buffer.hh rename to source/blender/draw/DRW_select_buffer.h index ab6adf86906..93e3fc29b6a 100644 --- a/source/blender/draw/DRW_select_buffer.hh +++ b/source/blender/draw/DRW_select_buffer.h @@ -8,9 +8,12 @@ #pragma once -#include "BLI_array.hh" #include "BLI_sys_types.h" /* for bool and uint */ +#ifdef __cplusplus +extern "C" { +#endif + struct ARegion; struct Base; struct Depsgraph; @@ -18,17 +21,12 @@ struct Object; struct View3D; struct rcti; -struct SELECTID_ObjectData { +typedef struct SELECTID_ObjectData { DrawData dd; uint drawn_index; - - /* Used to avoid adding to the pass more than once. */ - bool in_pass; - - /* Used to detect and remove objects that are not included in the array. */ bool is_drawn; -}; +} SELECTID_ObjectData; struct ObjectOffsets { /* For convenience only. */ @@ -47,47 +45,63 @@ struct ObjectOffsets { uint vert; }; -struct SELECTID_Context { +typedef struct SELECTID_Context { /* All context objects */ - blender::Array objects; - blender::Array index_offsets; + struct Object **objects; + + /* Array with only drawn objects. When a new object is found within the rect, + * it is added to the end of the list. + * The list is reset to any viewport or context update. */ + struct Object **objects_drawn; + struct ObjectOffsets *index_offsets; + uint objects_len; + uint objects_drawn_len; /** Total number of element indices `index_offsets[object_drawn_len - 1].vert`. */ uint index_drawn_len; short select_mode; + /* rect is used to check which objects whose indexes need to be drawn. */ + rcti last_rect; + /* To check for updates. */ float persmat[4][4]; - bool is_dirty(RegionView3D *rv3d); -}; + bool is_dirty; +} SELECTID_Context; /* `draw_select_buffer.cc` */ bool DRW_select_buffer_elem_get(uint sel_id, uint *r_elem, uint *r_base_index, char *r_elem_type); -uint DRW_select_buffer_context_offset_for_object_elem(Depsgraph *depsgraph, - Object *object, +uint DRW_select_buffer_context_offset_for_object_elem(struct Depsgraph *depsgraph, + struct Object *object, char elem_type); /** * Main function to read a block of pixels from the select frame buffer. */ -uint *DRW_select_buffer_read( - Depsgraph *depsgraph, ARegion *region, View3D *v3d, const rcti *rect, uint *r_buf_len); +uint *DRW_select_buffer_read(struct Depsgraph *depsgraph, + struct ARegion *region, + struct View3D *v3d, + const rcti *rect, + uint *r_buf_len); /** * \param rect: The rectangle to sample indices from (min/max inclusive). * \returns a #BLI_bitmap the length of \a bitmap_len or NULL on failure. */ -uint *DRW_select_buffer_bitmap_from_rect( - Depsgraph *depsgraph, ARegion *region, View3D *v3d, const rcti *rect, uint *r_bitmap_len); +uint *DRW_select_buffer_bitmap_from_rect(struct Depsgraph *depsgraph, + struct ARegion *region, + struct View3D *v3d, + const struct rcti *rect, + uint *r_bitmap_len); /** * \param center: Circle center. * \param radius: Circle radius. * \param r_bitmap_len: Number of indices in the selection id buffer. * \returns a #BLI_bitmap the length of \a r_bitmap_len or NULL on failure. */ -uint *DRW_select_buffer_bitmap_from_circle(Depsgraph *depsgraph, - ARegion *region, - View3D *v3d, +uint *DRW_select_buffer_bitmap_from_circle(struct Depsgraph *depsgraph, + struct ARegion *region, + struct View3D *v3d, const int center[2], int radius, uint *r_bitmap_len); @@ -97,33 +111,34 @@ uint *DRW_select_buffer_bitmap_from_circle(Depsgraph *depsgraph, * \param rect: Polygon boundaries. * \returns a #BLI_bitmap. */ -uint *DRW_select_buffer_bitmap_from_poly(Depsgraph *depsgraph, - ARegion *region, - View3D *v3d, +uint *DRW_select_buffer_bitmap_from_poly(struct Depsgraph *depsgraph, + struct ARegion *region, + struct View3D *v3d, const int poly[][2], int face_len, - const rcti *rect, + const struct rcti *rect, uint *r_bitmap_len); /** * Samples a single pixel. */ -uint DRW_select_buffer_sample_point(Depsgraph *depsgraph, - ARegion *region, - View3D *v3d, +uint DRW_select_buffer_sample_point(struct Depsgraph *depsgraph, + struct ARegion *region, + struct View3D *v3d, const int center[2]); /** * Find the selection id closest to \a center. * \param dist: Use to initialize the distance, * when found, this value is set to the distance of the selection that's returned. */ -uint DRW_select_buffer_find_nearest_to_point(Depsgraph *depsgraph, - ARegion *region, - View3D *v3d, +uint DRW_select_buffer_find_nearest_to_point(struct Depsgraph *depsgraph, + struct ARegion *region, + struct View3D *v3d, const int center[2], uint id_min, uint id_max, uint *dist); -void DRW_select_buffer_context_create(Depsgraph *depsgraph, - Base **bases, - uint bases_len, - short select_mode); +void DRW_select_buffer_context_create(struct Base **bases, uint bases_len, short select_mode); + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/draw/engines/compositor/compositor_engine.cc b/source/blender/draw/engines/compositor/compositor_engine.cc index 45d4a927361..ece9068a291 100644 --- a/source/blender/draw/engines/compositor/compositor_engine.cc +++ b/source/blender/draw/engines/compositor/compositor_engine.cc @@ -153,7 +153,7 @@ class Context : public realtime_compositor::Context { return DRW_viewport_texture_list_get()->color; } - GPUTexture *get_viewer_output_texture(int2 /* size */) override + GPUTexture *get_viewer_output_texture() override { return DRW_viewport_texture_list_get()->color; } diff --git a/source/blender/draw/engines/eevee/eevee_depth_of_field.cc b/source/blender/draw/engines/eevee/eevee_depth_of_field.cc index 1bc9df1e866..66b5efbad68 100644 --- a/source/blender/draw/engines/eevee/eevee_depth_of_field.cc +++ b/source/blender/draw/engines/eevee/eevee_depth_of_field.cc @@ -11,7 +11,7 @@ * - The first uses projection matrix offsetting and sample accumulation to give reference quality * depth of field. But this needs many samples to hide the under-sampling. * - The second one is a post-processing based one. It follows the implementation described in - * the presentation "Life of a Bokeh - SIGGRAPH 2018" from Guillaume Abadie. There are some + * the presentation "Life of a Bokeh - Siggraph 2018" from Guillaume Abadie. There are some * difference with our actual implementation that prioritize quality. */ @@ -24,7 +24,7 @@ #include "BKE_camera.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "DEG_depsgraph.hh" #include "DEG_depsgraph_query.hh" @@ -57,7 +57,7 @@ static float polygon_sides_length(float sides_count) * Start first corners at theta == 0. */ static float circle_to_polygon_radius(float sides_count, float theta) { - /* From Graphics Gems from CryENGINE 3 (SIGGRAPH 2013) by Tiago Sousa (slide 36). */ + /* From Graphics Gems from CryENGINE 3 (Siggraph 2013) by Tiago Sousa (slide 36). */ float side_angle = (2.0f * M_PI) / sides_count; return cosf(side_angle * 0.5f) / cosf(theta - side_angle * floorf((sides_count * theta + M_PI) / (2.0f * M_PI))); diff --git a/source/blender/draw/engines/eevee/eevee_engine.cc b/source/blender/draw/engines/eevee/eevee_engine.cc index 3e80a5d9a56..7fda42fae10 100644 --- a/source/blender/draw/engines/eevee/eevee_engine.cc +++ b/source/blender/draw/engines/eevee/eevee_engine.cc @@ -668,7 +668,7 @@ RenderEngineType DRW_engine_viewport_eevee_type = { /*next*/ nullptr, /*prev*/ nullptr, /*idname*/ EEVEE_ENGINE, - /*name*/ N_("EEVEE (Legacy)"), + /*name*/ N_("EEVEE"), /*flag*/ RE_INTERNAL | RE_USE_PREVIEW | RE_USE_STEREO_VIEWPORT | RE_USE_GPU_CONTEXT, /*update*/ nullptr, /*render*/ &DRW_render_to_image, diff --git a/source/blender/draw/engines/eevee/eevee_lightcache.cc b/source/blender/draw/engines/eevee/eevee_lightcache.cc index 94942f165fa..534b3523089 100644 --- a/source/blender/draw/engines/eevee/eevee_lightcache.cc +++ b/source/blender/draw/engines/eevee/eevee_lightcache.cc @@ -671,12 +671,12 @@ static void eevee_lightbake_count_probes(EEVEE_LightBake *lbake) if (ob->type == OB_LIGHTPROBE) { LightProbe *prb = (LightProbe *)ob->data; - if (prb->type == LIGHTPROBE_TYPE_VOLUME) { + if (prb->type == LIGHTPROBE_TYPE_GRID) { lbake->total_irr_samples += prb->grid_resolution_x * prb->grid_resolution_y * prb->grid_resolution_z; lbake->grid_len++; } - else if (prb->type == LIGHTPROBE_TYPE_SPHERE && lbake->cube_len < EEVEE_PROBE_MAX) { + else if (prb->type == LIGHTPROBE_TYPE_CUBE && lbake->cube_len < EEVEE_PROBE_MAX) { lbake->cube_len++; } } @@ -1334,12 +1334,12 @@ static void eevee_lightbake_gather_probes(EEVEE_LightBake *lbake) if (ob->type == OB_LIGHTPROBE) { LightProbe *prb = (LightProbe *)ob->data; - if (prb->type == LIGHTPROBE_TYPE_VOLUME) { + if (prb->type == LIGHTPROBE_TYPE_GRID) { lbake->grid_prb[grid_len] = prb; EEVEE_LightGrid *egrid = &lcache->grid_data[grid_len++]; EEVEE_lightprobes_grid_data_from_object(ob, egrid, &total_irr_samples); } - else if (prb->type == LIGHTPROBE_TYPE_SPHERE && cube_len < EEVEE_PROBE_MAX) { + else if (prb->type == LIGHTPROBE_TYPE_CUBE && cube_len < EEVEE_PROBE_MAX) { lbake->cube_prb[cube_len] = prb; EEVEE_LightProbe *eprobe = &lcache->cube_data[cube_len++]; EEVEE_lightprobes_cube_data_from_object(ob, eprobe); @@ -1402,7 +1402,7 @@ static bool lightbake_do_sample(EEVEE_LightBake *lbake, return true; } -void EEVEE_lightbake_job(void *custom_data, wmJobWorkerStatus *worker_status) +void EEVEE_lightbake_job(void *custom_data, bool *stop, bool *do_update, float *progress) { EEVEE_LightBake *lbake = (EEVEE_LightBake *)custom_data; Depsgraph *depsgraph = lbake->depsgraph; @@ -1411,9 +1411,9 @@ void EEVEE_lightbake_job(void *custom_data, wmJobWorkerStatus *worker_status) DEG_evaluate_on_framechange(depsgraph, lbake->frame); lbake->view_layer = DEG_get_evaluated_view_layer(depsgraph); - lbake->stop = &worker_status->stop; - lbake->do_update = &worker_status->do_update; - lbake->progress = &worker_status->progress; + lbake->stop = stop; + lbake->do_update = do_update; + lbake->progress = progress; if (G.background) { /* Make sure to init GL capabilities before counting probes. */ diff --git a/source/blender/draw/engines/eevee/eevee_lightcache.h b/source/blender/draw/engines/eevee/eevee_lightcache.h index b6487c9aafc..de9110a3019 100644 --- a/source/blender/draw/engines/eevee/eevee_lightcache.h +++ b/source/blender/draw/engines/eevee/eevee_lightcache.h @@ -18,7 +18,6 @@ struct LightCache; struct Scene; struct SceneEEVEE; struct ViewLayer; -struct wmJobWorkerStatus; #ifdef __cplusplus extern "C" { @@ -44,7 +43,7 @@ void *EEVEE_lightbake_job_data_alloc(struct Main *bmain, int frame); void EEVEE_lightbake_job_data_free(void *custom_data); void EEVEE_lightbake_update(void *custom_data); -void EEVEE_lightbake_job(void *custom_data, wmJobWorkerStatus *worker_status); +void EEVEE_lightbake_job(void *custom_data, bool *stop, bool *do_update, float *progress); /** * This is to update the world irradiance and reflection contribution from diff --git a/source/blender/draw/engines/eevee/eevee_lightprobes.cc b/source/blender/draw/engines/eevee/eevee_lightprobes.cc index ff7c759dfe0..c77fb471e6c 100644 --- a/source/blender/draw/engines/eevee/eevee_lightprobes.cc +++ b/source/blender/draw/engines/eevee/eevee_lightprobes.cc @@ -9,7 +9,7 @@ #include "DRW_render.h" #include "BLI_rand.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_utildefines.h" #include "DNA_image_types.h" @@ -417,7 +417,7 @@ static bool eevee_lightprobes_culling_test(Object *ob) LightProbe *probe = (LightProbe *)ob->data; switch (probe->type) { - case LIGHTPROBE_TYPE_PLANE: { + case LIGHTPROBE_TYPE_PLANAR: { /* See if this planar probe is inside the view frustum. If not, no need to update it. */ /* NOTE: this could be bypassed if we want feedback loop mirrors for rendering. */ BoundBox bbox; @@ -436,9 +436,9 @@ static bool eevee_lightprobes_culling_test(Object *ob) const DRWView *default_view = DRW_view_default_get(); return DRW_culling_box_test(default_view, &bbox); } - case LIGHTPROBE_TYPE_SPHERE: + case LIGHTPROBE_TYPE_CUBE: return true; /* TODO */ - case LIGHTPROBE_TYPE_VOLUME: + case LIGHTPROBE_TYPE_GRID: return true; /* TODO */ } BLI_assert(0); @@ -450,15 +450,15 @@ void EEVEE_lightprobes_cache_add(EEVEE_ViewLayerData *sldata, EEVEE_Data *vedata EEVEE_LightProbesInfo *pinfo = sldata->probes; LightProbe *probe = (LightProbe *)ob->data; - if ((probe->type == LIGHTPROBE_TYPE_SPHERE && pinfo->num_cube >= EEVEE_PROBE_MAX) || - (probe->type == LIGHTPROBE_TYPE_VOLUME && pinfo->num_grid >= EEVEE_PROBE_MAX) || - (probe->type == LIGHTPROBE_TYPE_PLANE && pinfo->num_planar >= MAX_PLANAR)) + if ((probe->type == LIGHTPROBE_TYPE_CUBE && pinfo->num_cube >= EEVEE_PROBE_MAX) || + (probe->type == LIGHTPROBE_TYPE_GRID && pinfo->num_grid >= EEVEE_PROBE_MAX) || + (probe->type == LIGHTPROBE_TYPE_PLANAR && pinfo->num_planar >= MAX_PLANAR)) { printf("Too many probes in the view !!!\n"); return; } - if (probe->type == LIGHTPROBE_TYPE_PLANE) { + if (probe->type == LIGHTPROBE_TYPE_PLANAR) { /* TODO(fclem): Culling should be done after cache generation. * This is needed for future draw cache persistence. */ if (!eevee_lightprobes_culling_test(ob)) { @@ -477,7 +477,7 @@ void EEVEE_lightprobes_cache_add(EEVEE_ViewLayerData *sldata, EEVEE_Data *vedata else { EEVEE_LightProbeEngineData *ped = EEVEE_lightprobe_data_ensure(ob); if (ped->need_update) { - if (probe->type == LIGHTPROBE_TYPE_VOLUME) { + if (probe->type == LIGHTPROBE_TYPE_GRID) { pinfo->do_grid_update = true; } else { diff --git a/source/blender/draw/engines/eevee/eevee_lut_gen.cc b/source/blender/draw/engines/eevee/eevee_lut_gen.cc index 6f1d089afdd..dc2b50ec1de 100644 --- a/source/blender/draw/engines/eevee/eevee_lut_gen.cc +++ b/source/blender/draw/engines/eevee/eevee_lut_gen.cc @@ -15,7 +15,7 @@ #include "BLI_fileops.h" #include "BLI_rand.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "eevee_private.h" diff --git a/source/blender/draw/engines/eevee/eevee_materials.cc b/source/blender/draw/engines/eevee/eevee_materials.cc index 9e5f7a61de5..1c19deb0b9d 100644 --- a/source/blender/draw/engines/eevee/eevee_materials.cc +++ b/source/blender/draw/engines/eevee/eevee_materials.cc @@ -14,7 +14,7 @@ #include "BLI_math_bits.h" #include "BLI_memblock.h" #include "BLI_rand.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BKE_global.h" #include "BKE_paint.hh" diff --git a/source/blender/draw/engines/eevee/eevee_mist.cc b/source/blender/draw/engines/eevee/eevee_mist.cc index 1ed84ef141c..aea25db9b2b 100644 --- a/source/blender/draw/engines/eevee/eevee_mist.cc +++ b/source/blender/draw/engines/eevee/eevee_mist.cc @@ -14,7 +14,7 @@ #include "DNA_world_types.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "eevee_private.h" diff --git a/source/blender/draw/engines/eevee/eevee_motion_blur.cc b/source/blender/draw/engines/eevee/eevee_motion_blur.cc index 846b9381a7b..902debf14a3 100644 --- a/source/blender/draw/engines/eevee/eevee_motion_blur.cc +++ b/source/blender/draw/engines/eevee/eevee_motion_blur.cc @@ -11,7 +11,7 @@ #include "DRW_render.h" #include "BLI_rand.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BKE_animsys.h" #include "BKE_camera.h" diff --git a/source/blender/draw/engines/eevee/eevee_occlusion.cc b/source/blender/draw/engines/eevee/eevee_occlusion.cc index 099e11b2c14..d74401e5b4d 100644 --- a/source/blender/draw/engines/eevee/eevee_occlusion.cc +++ b/source/blender/draw/engines/eevee/eevee_occlusion.cc @@ -10,7 +10,7 @@ #include "DRW_render.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "DEG_depsgraph_query.hh" diff --git a/source/blender/draw/engines/eevee/eevee_renderpasses.cc b/source/blender/draw/engines/eevee/eevee_renderpasses.cc index dd87b77ce6e..79ef7dbcd9b 100644 --- a/source/blender/draw/engines/eevee/eevee_renderpasses.cc +++ b/source/blender/draw/engines/eevee/eevee_renderpasses.cc @@ -14,7 +14,7 @@ #include "BKE_global.h" /* for G.debug_value */ #include "BLI_hash.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "DEG_depsgraph_query.hh" diff --git a/source/blender/draw/engines/eevee/eevee_screen_raytrace.cc b/source/blender/draw/engines/eevee/eevee_screen_raytrace.cc index 66cb71c444a..f37e4807d3b 100644 --- a/source/blender/draw/engines/eevee/eevee_screen_raytrace.cc +++ b/source/blender/draw/engines/eevee/eevee_screen_raytrace.cc @@ -11,7 +11,7 @@ #include "DRW_render.h" #include "BLI_dynstr.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "DEG_depsgraph_query.hh" diff --git a/source/blender/draw/engines/eevee/eevee_shaders.cc b/source/blender/draw/engines/eevee/eevee_shaders.cc index 0df0a11f335..4a2df541a9d 100644 --- a/source/blender/draw/engines/eevee/eevee_shaders.cc +++ b/source/blender/draw/engines/eevee/eevee_shaders.cc @@ -12,7 +12,7 @@ #include "BKE_node.hh" #include "BLI_dynstr.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "DNA_world_types.h" diff --git a/source/blender/draw/engines/eevee/eevee_shadows.cc b/source/blender/draw/engines/eevee/eevee_shadows.cc index 709a2f145b0..3b9422ea80f 100644 --- a/source/blender/draw/engines/eevee/eevee_shadows.cc +++ b/source/blender/draw/engines/eevee/eevee_shadows.cc @@ -6,7 +6,7 @@ * \ingroup EEVEE */ -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_sys_types.h" /* bool */ #include "BKE_object.hh" @@ -155,12 +155,12 @@ void EEVEE_shadows_caster_register(EEVEE_ViewLayerData *sldata, Object *ob) } /* Update World AABB in frontbuffer. */ - const BoundBox bb = *BKE_object_boundbox_get(ob); + const BoundBox *bb = BKE_object_boundbox_get(ob); float min[3], max[3]; INIT_MINMAX(min, max); for (int i = 0; i < 8; i++) { float vec[3]; - copy_v3_v3(vec, bb.vec[i]); + copy_v3_v3(vec, bb->vec[i]); mul_m4_v3(ob->object_to_world, vec); minmax_v3v3_v3(min, max, vec); } diff --git a/source/blender/draw/engines/eevee/eevee_subsurface.cc b/source/blender/draw/engines/eevee/eevee_subsurface.cc index aea6c35eef4..b1878853250 100644 --- a/source/blender/draw/engines/eevee/eevee_subsurface.cc +++ b/source/blender/draw/engines/eevee/eevee_subsurface.cc @@ -10,7 +10,7 @@ #include "DRW_render.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "DEG_depsgraph_query.hh" diff --git a/source/blender/draw/engines/eevee/eevee_volumes.cc b/source/blender/draw/engines/eevee/eevee_volumes.cc index ff4bcc7c852..8165c7faf4e 100644 --- a/source/blender/draw/engines/eevee/eevee_volumes.cc +++ b/source/blender/draw/engines/eevee/eevee_volumes.cc @@ -12,7 +12,7 @@ #include "BLI_listbase.h" #include "BLI_rand.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "DNA_fluid_types.h" #include "DNA_object_force_types.h" diff --git a/source/blender/draw/engines/eevee/shaders/bsdf_common_lib.glsl b/source/blender/draw/engines/eevee/shaders/bsdf_common_lib.glsl index 248cfc0f91c..0a060a02092 100644 --- a/source/blender/draw/engines/eevee/shaders/bsdf_common_lib.glsl +++ b/source/blender/draw/engines/eevee/shaders/bsdf_common_lib.glsl @@ -86,7 +86,7 @@ vec3 F_brdf_multi_scatter(vec3 F0, vec3 F90, vec2 lut) vec3 Favg = F0 + (F90 - F0) / 21.0; /* The original paper uses `FssEss * radiance + Fms*Ems * irradiance`, but - * "A Journey Through Implementing Multi-scattering BRDFs and Area Lights" by Steve McAuley + * "A Journey Through Implementing Multiscattering BRDFs and Area Lights" by Steve McAuley * suggests to use `FssEss * radiance + Fms*Ems * radiance` which results in comparable quality. * We handle `radiance` outside of this function, so the result simplifies to: * `FssEss + Fms*Ems = FssEss * (1 + Ems*Favg / (1 - Ems*Favg)) = FssEss / (1 - Ems*Favg)`. diff --git a/source/blender/draw/engines/eevee/shaders/effect_dof_bokeh_frag.glsl b/source/blender/draw/engines/eevee/shaders/effect_dof_bokeh_frag.glsl index 6ea49aa48c5..d24809b6b45 100644 --- a/source/blender/draw/engines/eevee/shaders/effect_dof_bokeh_frag.glsl +++ b/source/blender/draw/engines/eevee/shaders/effect_dof_bokeh_frag.glsl @@ -21,7 +21,7 @@ float polygon_sides_length(float sides_count) * Start first corners at theta == 0. */ float circle_to_polygon_radius(float sides_count, float theta) { - /* From Graphics Gems from CryENGINE 3 (SIGGRAPH 2013) by Tiago Sousa (slide 36). */ + /* From Graphics Gems from CryENGINE 3 (Siggraph 2013) by Tiago Sousa (slide 36). */ float side_angle = M_2PI / sides_count; float halfside_angle = side_angle * 0.5; return cos(side_angle * 0.5) / diff --git a/source/blender/draw/engines/eevee_next/eevee_ambient_occlusion.cc b/source/blender/draw/engines/eevee_next/eevee_ambient_occlusion.cc index 5ececdd8077..a192e61c40e 100644 --- a/source/blender/draw/engines/eevee_next/eevee_ambient_occlusion.cc +++ b/source/blender/draw/engines/eevee_next/eevee_ambient_occlusion.cc @@ -55,8 +55,8 @@ void AmbientOcclusion::sync() render_pass_ps_.bind_texture(RBUFS_UTILITY_TEX_SLOT, &inst_.pipelines.utility_tx); inst_.bind_uniform_data(&render_pass_ps_); - inst_.sampling.bind_resources(render_pass_ps_); - inst_.hiz_buffer.bind_resources(render_pass_ps_); + inst_.sampling.bind_resources(&render_pass_ps_); + inst_.hiz_buffer.bind_resources(&render_pass_ps_); render_pass_ps_.bind_image("in_normal_img", &inst_.render_buffers.rp_color_tx); render_pass_ps_.push_constant("in_normal_img_layer_index", &inst_.render_buffers.data.normal_id); diff --git a/source/blender/draw/engines/eevee_next/eevee_cryptomatte.hh b/source/blender/draw/engines/eevee_next/eevee_cryptomatte.hh index bbfe2074415..179f4f238e3 100644 --- a/source/blender/draw/engines/eevee_next/eevee_cryptomatte.hh +++ b/source/blender/draw/engines/eevee_next/eevee_cryptomatte.hh @@ -53,9 +53,9 @@ class Cryptomatte { void sync_material(const ::Material *material); void end_sync(); - template void bind_resources(PassType &pass) + template void bind_resources(draw::detail::PassBase *pass) { - pass.bind_ssbo(CRYPTOMATTE_BUF_SLOT, &cryptomatte_object_buf); + pass->bind_ssbo(CRYPTOMATTE_BUF_SLOT, &cryptomatte_object_buf); } /* Register ID to use inside cryptomatte layer and returns associated hash as float. */ diff --git a/source/blender/draw/engines/eevee_next/eevee_defines.hh b/source/blender/draw/engines/eevee_next/eevee_defines.hh index 8b51792a7b2..eaa19b8d8e2 100644 --- a/source/blender/draw/engines/eevee_next/eevee_defines.hh +++ b/source/blender/draw/engines/eevee_next/eevee_defines.hh @@ -17,7 +17,7 @@ #define LUT_WORKGROUP_SIZE 16 /* Hierarchical Z down-sampling. */ -#define HIZ_MIP_COUNT 7 +#define HIZ_MIP_COUNT 8 /* NOTE: The shader is written to update 5 mipmaps using LDS. */ #define HIZ_GROUP_SIZE 32 @@ -30,9 +30,8 @@ #define CULLING_TILE_GROUP_SIZE 256 /* Reflection Probes. */ -#define REFLECTION_PROBES_MAX 128 +#define REFLECTION_PROBES_MAX 256 #define REFLECTION_PROBE_GROUP_SIZE 16 -#define REFLECTION_PROBE_SELECT_GROUP_SIZE 64 /* Number of additional pixels on the border of an octahedral map to reserve for fixing seams. * Border size requires depends on the max number of mipmap levels. */ #define REFLECTION_PROBE_MIPMAP_LEVELS 5 @@ -40,23 +39,14 @@ #define REFLECTION_PROBE_SH_GROUP_SIZE 512 #define REFLECTION_PROBE_SH_SAMPLES_PER_GROUP 64 -#define PLANAR_PROBES_MAX 16 - /** * IMPORTANT: Some data packing are tweaked for these values. * Be sure to update them accordingly. * SHADOW_TILEMAP_RES max is 32 because of the shared bitmaps used for LOD tagging. * It is also limited by the maximum thread group size (1024). */ -#if 0 -/* Useful for debugging the tile-copy version of the shadow rendering without making debugging - * tools unresponsive. */ -# define SHADOW_TILEMAP_RES 4 -# define SHADOW_TILEMAP_LOD 2 /* LOG2(SHADOW_TILEMAP_RES) */ -#else -# define SHADOW_TILEMAP_RES 32 -# define SHADOW_TILEMAP_LOD 5 /* LOG2(SHADOW_TILEMAP_RES) */ -#endif +#define SHADOW_TILEMAP_RES 32 +#define SHADOW_TILEMAP_LOD 5 /* LOG2(SHADOW_TILEMAP_RES) */ #define SHADOW_TILEMAP_LOD0_LEN ((SHADOW_TILEMAP_RES / 1) * (SHADOW_TILEMAP_RES / 1)) #define SHADOW_TILEMAP_LOD1_LEN ((SHADOW_TILEMAP_RES / 2) * (SHADOW_TILEMAP_RES / 2)) #define SHADOW_TILEMAP_LOD2_LEN ((SHADOW_TILEMAP_RES / 4) * (SHADOW_TILEMAP_RES / 4)) @@ -67,19 +57,9 @@ #define SHADOW_TILEDATA_PER_TILEMAP \ (SHADOW_TILEMAP_LOD0_LEN + SHADOW_TILEMAP_LOD1_LEN + SHADOW_TILEMAP_LOD2_LEN + \ SHADOW_TILEMAP_LOD3_LEN + SHADOW_TILEMAP_LOD4_LEN + SHADOW_TILEMAP_LOD5_LEN) -#if 0 -/* Useful for debugging the tile-copy version of the shadow rendering without making debugging - * tools unresponsive. */ -# define SHADOW_PAGE_CLEAR_GROUP_SIZE 8 -# define SHADOW_PAGE_RES 8 -# define SHADOW_PAGE_LOD 3 /* LOG2(SHADOW_PAGE_RES) */ -#else -# define SHADOW_PAGE_CLEAR_GROUP_SIZE 32 -# define SHADOW_PAGE_RES 256 -# define SHADOW_PAGE_LOD 8 /* LOG2(SHADOW_PAGE_RES) */ -#endif -/* For testing only. */ -// #define SHADOW_FORCE_LOD0 +#define SHADOW_PAGE_CLEAR_GROUP_SIZE 32 +#define SHADOW_PAGE_RES 256 +#define SHADOW_PAGE_LOD 8 /* LOG2(SHADOW_PAGE_RES) */ #define SHADOW_MAP_MAX_RES (SHADOW_PAGE_RES * SHADOW_TILEMAP_RES) #define SHADOW_DEPTH_SCAN_GROUP_SIZE 8 #define SHADOW_AABB_TAG_GROUP_SIZE 64 @@ -96,7 +76,6 @@ #define SHADOW_PAGE_PER_LAYER (SHADOW_PAGE_PER_ROW * SHADOW_PAGE_PER_COL) #define SHADOW_MAX_STEP 16 #define SHADOW_MAX_RAY 4 -#define SHADOW_ROG_ID 0 /* Ray-tracing. */ #define RAYTRACE_GROUP_SIZE 8 @@ -154,7 +133,6 @@ /* Volumes. */ #define VOLUME_GROUP_SIZE 4 #define VOLUME_INTEGRATION_GROUP_SIZE 8 -#define VOLUME_HIT_DEPTH_MAX 16 /* Resource bindings. */ @@ -169,9 +147,6 @@ #define REFLECTION_PROBE_TEX_SLOT 7 #define VOLUME_SCATTERING_TEX_SLOT 8 #define VOLUME_TRANSMITTANCE_TEX_SLOT 9 -/* Currently only used by ray-tracing, but might become used by forward too. */ -#define PLANAR_PROBE_DEPTH_TEX_SLOT 10 -#define PLANAR_PROBE_RADIANCE_TEX_SLOT 11 /* Images. */ #define RBUFS_COLOR_SLOT 0 @@ -185,10 +160,6 @@ #define VOLUME_PROP_EXTINCTION_IMG_SLOT 1 #define VOLUME_PROP_EMISSION_IMG_SLOT 2 #define VOLUME_PROP_PHASE_IMG_SLOT 3 -#define VOLUME_OCCUPANCY_SLOT 4 -/* Only during volume pre-pass. */ -#define VOLUME_HIT_DEPTH_SLOT 0 -#define VOLUME_HIT_COUNT_SLOT 1 /* Only during shadow rendering. */ #define SHADOW_ATLAS_IMG_SLOT 4 @@ -198,12 +169,10 @@ /* Only during surface shading (forward and deferred eval). */ #define IRRADIANCE_GRID_BUF_SLOT 2 #define REFLECTION_PROBE_BUF_SLOT 3 -#define PLANAR_PROBE_BUF_SLOT 4 /* Only during pre-pass. */ #define VELOCITY_CAMERA_PREV_BUF 2 #define VELOCITY_CAMERA_CURR_BUF 3 #define VELOCITY_CAMERA_NEXT_BUF 4 -#define CLIP_PLANE_BUF 5 /* Storage Buffers. */ #define LIGHT_CULL_BUF_SLOT 0 diff --git a/source/blender/draw/engines/eevee_next/eevee_depth_of_field.cc b/source/blender/draw/engines/eevee_next/eevee_depth_of_field.cc index a6b7c865760..22e90e4113d 100644 --- a/source/blender/draw/engines/eevee_next/eevee_depth_of_field.cc +++ b/source/blender/draw/engines/eevee_next/eevee_depth_of_field.cc @@ -13,7 +13,7 @@ * under-sampling. * - The second one is a post-processing based one. It follows the * implementation described in the presentation - * "Life of a Bokeh - SIGGRAPH 2018" from Guillaume Abadie. + * "Life of a Bokeh - Siggraph 2018" from Guillaume Abadie. * There are some difference with our actual implementation that prioritize quality. */ @@ -376,7 +376,7 @@ void DepthOfField::gather_pass_sync() DOF_GATHER_FOREGROUND) : (use_bokeh_lut_ ? DOF_GATHER_BACKGROUND_LUT : DOF_GATHER_BACKGROUND); drw_pass.init(); - inst_.sampling.bind_resources(drw_pass); + inst_.sampling.bind_resources(&drw_pass); drw_pass.shader_set(inst_.shaders.static_shader_get(sh_type)); drw_pass.bind_ubo("dof_buf", data_); drw_pass.bind_texture("color_bilinear_tx", reduced_color_tx_, gather_bilinear); @@ -440,7 +440,7 @@ void DepthOfField::hole_fill_pass_sync() const GPUSamplerState gather_nearest = {GPU_SAMPLER_FILTERING_MIPMAP}; hole_fill_ps_.init(); - inst_.sampling.bind_resources(hole_fill_ps_); + inst_.sampling.bind_resources(&hole_fill_ps_); hole_fill_ps_.shader_set(inst_.shaders.static_shader_get(DOF_GATHER_HOLE_FILL)); hole_fill_ps_.bind_ubo("dof_buf", data_); hole_fill_ps_.bind_texture("color_bilinear_tx", reduced_color_tx_, gather_bilinear); @@ -461,7 +461,7 @@ void DepthOfField::resolve_pass_sync() eShaderType sh_type = use_bokeh_lut_ ? DOF_RESOLVE_LUT : DOF_RESOLVE; resolve_ps_.init(); - inst_.sampling.bind_resources(resolve_ps_); + inst_.sampling.bind_resources(&resolve_ps_); resolve_ps_.shader_set(inst_.shaders.static_shader_get(sh_type)); resolve_ps_.bind_ubo("dof_buf", data_); resolve_ps_.bind_texture("depth_tx", &render_buffers.depth_tx, no_filter); diff --git a/source/blender/draw/engines/eevee_next/eevee_depth_of_field.hh b/source/blender/draw/engines/eevee_next/eevee_depth_of_field.hh index 58ccfbb64fa..c5a056009c7 100644 --- a/source/blender/draw/engines/eevee_next/eevee_depth_of_field.hh +++ b/source/blender/draw/engines/eevee_next/eevee_depth_of_field.hh @@ -13,7 +13,7 @@ * under-sampling. * - The second one is a post-processing based one. It follows the * implementation described in the presentation - * "Life of a Bokeh - SIGGRAPH 2018" from Guillaume Abadie. + * "Life of a Bokeh - Siggraph 2018" from Guillaume Abadie. * There are some difference with our actual implementation that prioritize quality. */ diff --git a/source/blender/draw/engines/eevee_next/eevee_engine.cc b/source/blender/draw/engines/eevee_next/eevee_engine.cc index cc700e12ee2..47448c33028 100644 --- a/source/blender/draw/engines/eevee_next/eevee_engine.cc +++ b/source/blender/draw/engines/eevee_next/eevee_engine.cc @@ -186,7 +186,7 @@ RenderEngineType DRW_engine_viewport_eevee_next_type = { /*next*/ nullptr, /*prev*/ nullptr, /*idname*/ "BLENDER_EEVEE_NEXT", - /*name*/ N_("EEVEE"), + /*name*/ N_("EEVEE Next"), /*flag*/ RE_INTERNAL | RE_USE_PREVIEW | RE_USE_STEREO_VIEWPORT | RE_USE_GPU_CONTEXT, /*update*/ nullptr, /*render*/ &DRW_render_to_image, diff --git a/source/blender/draw/engines/eevee_next/eevee_gbuffer.hh b/source/blender/draw/engines/eevee_next/eevee_gbuffer.hh index cdea6350638..b89c1e71060 100644 --- a/source/blender/draw/engines/eevee_next/eevee_gbuffer.hh +++ b/source/blender/draw/engines/eevee_next/eevee_gbuffer.hh @@ -90,11 +90,11 @@ struct GBuffer { // color_tx.release(); } - template void bind_resources(PassType &pass) + template void bind_resources(draw::detail::PassBase *pass) { - pass.bind_texture("gbuf_header_tx", &header_tx); - pass.bind_texture("gbuf_closure_tx", &closure_tx); - pass.bind_texture("gbuf_color_tx", &color_tx); + pass->bind_texture("gbuf_header_tx", &header_tx); + pass->bind_texture("gbuf_closure_tx", &closure_tx); + pass->bind_texture("gbuf_color_tx", &color_tx); } }; diff --git a/source/blender/draw/engines/eevee_next/eevee_hizbuffer.cc b/source/blender/draw/engines/eevee_next/eevee_hizbuffer.cc index 88c0d96636a..c1ebad51ff0 100644 --- a/source/blender/draw/engines/eevee_next/eevee_hizbuffer.cc +++ b/source/blender/draw/engines/eevee_next/eevee_hizbuffer.cc @@ -17,6 +17,8 @@ namespace blender::eevee { void HiZBuffer::sync() { + RenderBuffers &render_buffers = inst_.render_buffers; + int2 render_extent = inst_.film.render_extent_get(); /* Padding to avoid complexity during down-sampling and screen tracing. */ int2 hiz_extent = math::ceil_to_multiple(render_extent, int2(1u << (HIZ_MIP_COUNT - 1))); @@ -31,52 +33,30 @@ void HiZBuffer::sync() data_.uv_scale = float2(render_extent) / float2(hiz_extent); { - PassSimple &pass = hiz_update_ps_; - pass.init(); - pass.shader_set(inst_.shaders.static_shader_get(HIZ_UPDATE)); - pass.bind_ssbo("finished_tile_counter", atomic_tile_counter_); - /* TODO(fclem): Should be a parameter to avoid confusion. */ - pass.bind_texture("depth_tx", &src_tx_, with_filter); - pass.bind_image("out_mip_0", hiz_tx_.mip_view(0)); - pass.bind_image("out_mip_1", hiz_tx_.mip_view(1)); - pass.bind_image("out_mip_2", hiz_tx_.mip_view(2)); - pass.bind_image("out_mip_3", hiz_tx_.mip_view(3)); - pass.bind_image("out_mip_4", hiz_tx_.mip_view(4)); - pass.bind_image("out_mip_5", hiz_tx_.mip_view(5)); - pass.bind_image("out_mip_6", hiz_tx_.mip_view(6)); + hiz_update_ps_.init(); + hiz_update_ps_.shader_set(inst_.shaders.static_shader_get(HIZ_UPDATE)); + hiz_update_ps_.bind_ssbo("finished_tile_counter", atomic_tile_counter_); + hiz_update_ps_.bind_texture("depth_tx", &render_buffers.depth_tx, with_filter); + hiz_update_ps_.bind_image("out_mip_0", hiz_tx_.mip_view(0)); + hiz_update_ps_.bind_image("out_mip_1", hiz_tx_.mip_view(1)); + hiz_update_ps_.bind_image("out_mip_2", hiz_tx_.mip_view(2)); + hiz_update_ps_.bind_image("out_mip_3", hiz_tx_.mip_view(3)); + hiz_update_ps_.bind_image("out_mip_4", hiz_tx_.mip_view(4)); + hiz_update_ps_.bind_image("out_mip_5", hiz_tx_.mip_view(5)); + hiz_update_ps_.bind_image("out_mip_6", hiz_tx_.mip_view(6)); + hiz_update_ps_.bind_image("out_mip_7", hiz_tx_.mip_view(7)); /* TODO(@fclem): There might be occasions where we might not want to * copy mip 0 for performance reasons if there is no need for it. */ - pass.push_constant("update_mip_0", true); - pass.dispatch(int3(dispatch_size, 1)); - pass.barrier(GPU_BARRIER_TEXTURE_FETCH); - } - { - PassSimple &pass = hiz_update_layer_ps_; - pass.init(); - pass.shader_set(inst_.shaders.static_shader_get(HIZ_UPDATE_LAYER)); - pass.bind_ssbo("finished_tile_counter", atomic_tile_counter_); - /* TODO(fclem): Should be a parameter to avoid confusion. */ - pass.bind_texture("depth_layered_tx", &src_tx_, with_filter); - pass.bind_image("out_mip_0", hiz_tx_.mip_view(0)); - pass.bind_image("out_mip_1", hiz_tx_.mip_view(1)); - pass.bind_image("out_mip_2", hiz_tx_.mip_view(2)); - pass.bind_image("out_mip_3", hiz_tx_.mip_view(3)); - pass.bind_image("out_mip_4", hiz_tx_.mip_view(4)); - pass.bind_image("out_mip_5", hiz_tx_.mip_view(5)); - pass.bind_image("out_mip_6", hiz_tx_.mip_view(6)); - /* TODO(@fclem): There might be occasions where we might not want to - * copy mip 0 for performance reasons if there is no need for it. */ - pass.push_constant("update_mip_0", true); - pass.push_constant("layer_id", &layer_id_); - pass.dispatch(int3(dispatch_size, 1)); - pass.barrier(GPU_BARRIER_TEXTURE_FETCH); + hiz_update_ps_.push_constant("update_mip_0", true); + hiz_update_ps_.dispatch(int3(dispatch_size, 1)); + hiz_update_ps_.barrier(GPU_BARRIER_TEXTURE_FETCH); } if (inst_.debug_mode == eDebugMode::DEBUG_HIZ_VALIDATION) { debug_draw_ps_.init(); debug_draw_ps_.state_set(DRW_STATE_WRITE_COLOR | DRW_STATE_BLEND_CUSTOM); debug_draw_ps_.shader_set(inst_.shaders.static_shader_get(HIZ_DEBUG)); - this->bind_resources(debug_draw_ps_); + this->bind_resources(&debug_draw_ps_); debug_draw_ps_.draw_procedural(GPU_PRIM_TRIS, 1, 3); } } @@ -87,13 +67,18 @@ void HiZBuffer::update() return; } - src_tx_ = *src_tx_ptr_; - - if (layer_id_ == -1) { - inst_.manager->submit(hiz_update_ps_); + /* Bind another framebuffer in order to avoid triggering the feedback loop check. + * This is safe because we only use compute shaders in this section of the code. + * Ideally the check should be smarter. */ + GPUFrameBuffer *fb = GPU_framebuffer_active_get(); + if (G.debug & G_DEBUG_GPU) { + GPU_framebuffer_restore(); } - else { - inst_.manager->submit(hiz_update_layer_ps_); + + inst_.manager->submit(hiz_update_ps_); + + if (G.debug & G_DEBUG_GPU) { + GPU_framebuffer_bind(fb); } } diff --git a/source/blender/draw/engines/eevee_next/eevee_hizbuffer.hh b/source/blender/draw/engines/eevee_next/eevee_hizbuffer.hh index f053a96f0fa..e11e423d762 100644 --- a/source/blender/draw/engines/eevee_next/eevee_hizbuffer.hh +++ b/source/blender/draw/engines/eevee_next/eevee_hizbuffer.hh @@ -37,16 +37,10 @@ class HiZBuffer { draw::StorageBuffer atomic_tile_counter_ = {"atomic_tile_counter"}; /** Single pass recursive down-sample. */ PassSimple hiz_update_ps_ = {"HizUpdate"}; - /** Single pass recursive down-sample for layered depth buffer. Only downsample 1 layer. */ - PassSimple hiz_update_layer_ps_ = {"HizUpdate.Layer"}; - int layer_id_ = -1; /** Debug pass. */ PassSimple debug_draw_ps_ = {"HizUpdate.Debug"}; /** Dirty flag to check if the update is necessary. */ bool is_dirty_ = true; - /** Reference to the depth texture to downsample. */ - GPUTexture *src_tx_; - GPUTexture **src_tx_ptr_; HiZData &data_; @@ -58,15 +52,6 @@ class HiZBuffer { void sync(); - /** - * Set source texture for the hiz downsampling. - */ - void set_source(GPUTexture **texture, int layer = -1) - { - src_tx_ptr_ = texture; - layer_id_ = layer; - } - /** * Tag the buffer for update if needed. */ @@ -89,9 +74,9 @@ class HiZBuffer { DRW_shgroup_uniform_texture_ref(grp, "hiz_tx", &hiz_tx_); } - template void bind_resources(PassType &pass) + template void bind_resources(draw::detail::PassBase *pass) { - pass.bind_texture(HIZ_TEX_SLOT, &hiz_tx_); + pass->bind_texture(HIZ_TEX_SLOT, &hiz_tx_); } }; diff --git a/source/blender/draw/engines/eevee_next/eevee_instance.cc b/source/blender/draw/engines/eevee_next/eevee_instance.cc index df7a057be55..fc3df4cecb3 100644 --- a/source/blender/draw/engines/eevee_next/eevee_instance.cc +++ b/source/blender/draw/engines/eevee_next/eevee_instance.cc @@ -76,7 +76,6 @@ void Instance::init(const int2 &output_res, shadows.init(); motion_blur.init(); main_view.init(); - planar_probes.init(); /* Irradiance Cache needs reflection probes to be initialized. */ reflection_probes.init(); irradiance_cache.init(); @@ -109,7 +108,6 @@ void Instance::init_light_bake(Depsgraph *depsgraph, draw::Manager *manager) depth_of_field.init(); shadows.init(); main_view.init(); - planar_probes.init(); /* Irradiance Cache needs reflection probes to be initialized. */ reflection_probes.init(); irradiance_cache.init(); @@ -152,7 +150,6 @@ void Instance::begin_sync() pipelines.begin_sync(); cryptomatte.begin_sync(); reflection_probes.begin_sync(); - planar_probes.begin_sync(); light_probes.begin_sync(); gpencil_engine_enabled = false; @@ -235,7 +232,7 @@ void Instance::object_sync(Object *ob) sync.sync_point_cloud(ob, ob_handle, res_handle, ob_ref); break; case OB_VOLUME: - sync.sync_volume(ob, ob_handle, res_handle); + volume.sync_object(ob, ob_handle, res_handle); break; case OB_CURVES: sync.sync_curves(ob, ob_handle, res_handle); @@ -273,8 +270,7 @@ void Instance::object_sync_render(void *instance_, void Instance::end_sync() { velocity.end_sync(); - volume.end_sync(); /* Needs to be before shadows. */ - shadows.end_sync(); /* Needs to be before lights. */ + shadows.end_sync(); /** \note: Needs to be before lights. */ lights.end_sync(); sampling.end_sync(); subsurface.end_sync(); @@ -283,7 +279,7 @@ void Instance::end_sync() pipelines.end_sync(); light_probes.end_sync(); reflection_probes.end_sync(); - planar_probes.end_sync(); + volume.end_sync(); global_ubo_.push_update(); } @@ -319,25 +315,14 @@ void Instance::render_sync() DRW_curves_update(); } -bool Instance::do_reflection_probe_sync() const +bool Instance::do_probe_sync() const { + if (materials.queued_shaders_count > 0) { + return false; + } if (!reflection_probes.update_probes_this_sample_) { return false; } - if (materials.queued_shaders_count > 0) { - return false; - } - return true; -} - -bool Instance::do_planar_probe_sync() const -{ - if (!planar_probes.update_probes_) { - return false; - } - if (materials.queued_shaders_count > 0) { - return false; - } return true; } @@ -454,7 +439,6 @@ void Instance::render_frame(RenderLayer *render_layer, const char *view_name) * are other light probes in the scene. */ if (DEG_id_type_any_exists(this->depsgraph, ID_LP)) { reflection_probes.update_probes_next_sample_ = true; - planar_probes.update_probes_ = true; } while (!sampling.finished()) { @@ -610,21 +594,12 @@ void Instance::light_bake_irradiance( capture_view.render_world(); irradiance_cache.bake.surfels_create(probe); - - if (irradiance_cache.bake.should_break()) { - return; - } - irradiance_cache.bake.surfels_lights_eval(); irradiance_cache.bake.clusters_build(); irradiance_cache.bake.irradiance_offset(); }); - if (irradiance_cache.bake.should_break()) { - return; - } - sampling.init(probe); while (!sampling.finished()) { context_wrapper([&]() { diff --git a/source/blender/draw/engines/eevee_next/eevee_instance.hh b/source/blender/draw/engines/eevee_next/eevee_instance.hh index 81a629f474f..54fb6532edb 100644 --- a/source/blender/draw/engines/eevee_next/eevee_instance.hh +++ b/source/blender/draw/engines/eevee_next/eevee_instance.hh @@ -29,7 +29,6 @@ #include "eevee_material.hh" #include "eevee_motion_blur.hh" #include "eevee_pipeline.hh" -#include "eevee_planar_probes.hh" #include "eevee_raytrace.hh" #include "eevee_reflection_probes.hh" #include "eevee_renderbuffers.hh" @@ -65,7 +64,6 @@ class Instance { AmbientOcclusion ambient_occlusion; RayTraceModule raytracing; ReflectionProbeModule reflection_probes; - PlanarProbeModule planar_probes; VelocityModule velocity; MotionBlurModule motion_blur; DepthOfField depth_of_field; @@ -123,7 +121,6 @@ class Instance { ambient_occlusion(*this, global_ubo_.ao), raytracing(*this, global_ubo_.raytrace), reflection_probes(*this), - planar_probes(*this), velocity(*this), motion_blur(*this), depth_of_field(*this), @@ -161,8 +158,7 @@ class Instance { /** * Return true when probe pipeline is used during this sample. */ - bool do_reflection_probe_sync() const; - bool do_planar_probe_sync() const; + bool do_probe_sync() const; /* Render. */ diff --git a/source/blender/draw/engines/eevee_next/eevee_irradiance_cache.cc b/source/blender/draw/engines/eevee_next/eevee_irradiance_cache.cc index fb68fd5c3f8..074bad68508 100644 --- a/source/blender/draw/engines/eevee_next/eevee_irradiance_cache.cc +++ b/source/blender/draw/engines/eevee_next/eevee_irradiance_cache.cc @@ -653,8 +653,8 @@ void IrradianceBake::sync() pass.bind_ssbo(SURFEL_BUF_SLOT, &surfels_buf_); pass.bind_ssbo(CAPTURE_BUF_SLOT, &capture_info_buf_); pass.bind_texture(RBUFS_UTILITY_TEX_SLOT, inst_.pipelines.utility_tx); - inst_.lights.bind_resources(pass); - inst_.shadows.bind_resources(pass); + inst_.lights.bind_resources(&pass); + inst_.shadows.bind_resources(&pass); /* Sync with the surfel creation stage. */ pass.barrier(GPU_BARRIER_SHADER_STORAGE); pass.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS); @@ -704,7 +704,7 @@ void IrradianceBake::sync() sub.shader_set(inst_.shaders.static_shader_get(SURFEL_RAY)); sub.bind_ssbo(SURFEL_BUF_SLOT, &surfels_buf_); sub.bind_ssbo(CAPTURE_BUF_SLOT, &capture_info_buf_); - inst_.reflection_probes.bind_resources(sub); + inst_.reflection_probes.bind_resources(&sub); sub.push_constant("radiance_src", &radiance_src_); sub.push_constant("radiance_dst", &radiance_dst_); sub.barrier(GPU_BARRIER_SHADER_STORAGE); @@ -717,7 +717,7 @@ void IrradianceBake::sync() pass.shader_set(inst_.shaders.static_shader_get(LIGHTPROBE_IRRADIANCE_RAY)); pass.bind_ssbo(SURFEL_BUF_SLOT, &surfels_buf_); pass.bind_ssbo(CAPTURE_BUF_SLOT, &capture_info_buf_); - inst_.reflection_probes.bind_resources(pass); + inst_.reflection_probes.bind_resources(&pass); pass.bind_ssbo("list_start_buf", &list_start_buf_); pass.bind_ssbo("list_info_buf", &list_info_buf_); pass.push_constant("radiance_src", &radiance_src_); @@ -828,9 +828,6 @@ void IrradianceBake::surfels_create(const Object &probe_object) capture_info_buf_.capture_indirect = capture_indirect_; capture_info_buf_.capture_emission = capture_emission_; - ReflectionProbeAtlasCoordinate atlas_coord = inst_.reflection_probes.world_atlas_coord_get(); - capture_info_buf_.world_atlas_coord = *reinterpret_cast(&atlas_coord); - dispatch_per_grid_sample_ = math::divide_ceil(grid_resolution, int3(IRRADIANCE_GRID_GROUP_SIZE)); capture_info_buf_.irradiance_grid_size = grid_resolution; capture_info_buf_.irradiance_grid_local_to_world = grid_local_to_world; @@ -863,22 +860,13 @@ void IrradianceBake::surfels_create(const Object &probe_object) irradiance_L1_b_tx_.ensure_3d(GPU_RGBA32F, grid_resolution, texture_usage); irradiance_L1_c_tx_.ensure_3d(GPU_RGBA32F, grid_resolution, texture_usage); validity_tx_.ensure_3d(GPU_R32F, grid_resolution, texture_usage); - virtual_offset_tx_.ensure_3d(GPU_RGBA16F, grid_resolution, texture_usage); - - if (!irradiance_L0_tx_.is_valid() || !irradiance_L1_a_tx_.is_valid() || - !irradiance_L1_b_tx_.is_valid() || !irradiance_L1_c_tx_.is_valid() || - !validity_tx_.is_valid() || !virtual_offset_tx_.is_valid()) - { - inst_.info = "Error: Not enough memory to bake " + std::string(probe_object.id.name) + "."; - do_break_ = true; - return; - } - irradiance_L0_tx_.clear(float4(0.0f)); irradiance_L1_a_tx_.clear(float4(0.0f)); irradiance_L1_b_tx_.clear(float4(0.0f)); irradiance_L1_c_tx_.clear(float4(0.0f)); validity_tx_.clear(float4(0.0f)); + + virtual_offset_tx_.ensure_3d(GPU_RGBA16F, grid_resolution, texture_usage); virtual_offset_tx_.clear(float4(0.0f)); DRW_stats_group_start("IrradianceBake.SceneBounds"); @@ -959,28 +947,10 @@ void IrradianceBake::surfels_create(const Object &probe_object) capture_info_buf_.read(); if (capture_info_buf_.surfel_len == 0) { /* No surfel to allocated. */ - do_break_ = true; return; } - if (capture_info_buf_.surfel_len > surfels_buf_.size()) { - size_t max_size = GPU_max_storage_buffer_size(); - if (GPU_mem_stats_supported()) { - int total_mem_kb, free_mem_kb; - GPU_mem_stats_get(&total_mem_kb, &free_mem_kb); - max_size = min(max_size, size_t(free_mem_kb) * 1024); - } - - size_t required_mem = sizeof(Surfel) * (capture_info_buf_.surfel_len - surfels_buf_.size()); - if (required_mem > max_size) { - capture_info_buf_.surfel_len = 0u; - capture_info_buf_.push_update(); - inst_.info = "Error: Not enough memory to bake " + std::string(probe_object.id.name) + "."; - do_break_ = true; - return; - } - } - + /* TODO(fclem): Check for GL limit and abort if the surfel cache doesn't fit the GPU memory. */ surfels_buf_.resize(capture_info_buf_.surfel_len); surfels_buf_.clear_to_zero(); @@ -1014,9 +984,6 @@ void IrradianceBake::surfels_lights_eval() /* Use the last setup view. This should work since the view is orthographic. */ /* TODO(fclem): Remove this. It is only present to avoid crash inside `shadows.set_view` */ inst_.render_buffers.acquire(int2(1)); - inst_.hiz_buffer.set_source(&inst_.render_buffers.depth_tx); - inst_.hiz_buffer.set_dirty(); - inst_.lights.set_view(view_z_, grid_pixel_extent_.xy()); inst_.shadows.set_view(view_z_); inst_.render_buffers.release(); diff --git a/source/blender/draw/engines/eevee_next/eevee_irradiance_cache.hh b/source/blender/draw/engines/eevee_next/eevee_irradiance_cache.hh index a8cd1a6750f..c129329ce29 100644 --- a/source/blender/draw/engines/eevee_next/eevee_irradiance_cache.hh +++ b/source/blender/draw/engines/eevee_next/eevee_irradiance_cache.hh @@ -130,21 +130,12 @@ class IrradianceBake { /** True if emission is recorded during the light propagation. */ bool capture_emission_ = false; - /** True if the bake job should stop. */ - bool do_break_ = false; - public: IrradianceBake(Instance &inst) : inst_(inst){}; void init(const Object &probe_object); void sync(); - /** True if the bake job should stop. */ - bool should_break() - { - return do_break_; - } - /** Create the views used to rasterize the scene into surfel representation. */ void surfel_raster_views_sync(float3 scene_min, float3 scene_max, float4x4 probe_to_world); /** Create a surfel representation of the scene from the probe using the capture pipeline. */ @@ -232,11 +223,11 @@ class IrradianceCache { Vector bricks_alloc(int brick_len); void bricks_free(Vector &bricks); - template void bind_resources(PassType &pass) + template void bind_resources(draw::detail::PassBase *pass) { - pass.bind_ubo(IRRADIANCE_GRID_BUF_SLOT, &grids_infos_buf_); - pass.bind_ssbo(IRRADIANCE_BRICK_BUF_SLOT, &bricks_infos_buf_); - pass.bind_texture(IRRADIANCE_ATLAS_TEX_SLOT, &irradiance_atlas_tx_); + pass->bind_ubo(IRRADIANCE_GRID_BUF_SLOT, &grids_infos_buf_); + pass->bind_ssbo(IRRADIANCE_BRICK_BUF_SLOT, &bricks_infos_buf_); + pass->bind_texture(IRRADIANCE_ATLAS_TEX_SLOT, &irradiance_atlas_tx_); } private: diff --git a/source/blender/draw/engines/eevee_next/eevee_light.cc b/source/blender/draw/engines/eevee_next/eevee_light.cc index e3cdd6b14a5..39a483a9ac0 100644 --- a/source/blender/draw/engines/eevee_next/eevee_light.cc +++ b/source/blender/draw/engines/eevee_next/eevee_light.cc @@ -71,10 +71,10 @@ void Light::sync(ShadowModule &shadows, const Object *ob, float threshold) float shape_power = shape_radiance_get(la); float point_power = point_radiance_get(la); - this->power[LIGHT_DIFFUSE] = la->diff_fac * shape_power; - this->power[LIGHT_TRANSMIT] = la->diff_fac * point_power; - this->power[LIGHT_SPECULAR] = la->spec_fac * shape_power; - this->power[LIGHT_VOLUME] = la->volume_fac * point_power; + this->diffuse_power = la->diff_fac * shape_power; + this->transmit_power = la->diff_fac * point_power; + this->specular_power = la->spec_fac * shape_power; + this->volume_power = la->volume_fac * point_power; eLightType new_type = to_light_type(la->type, la->area_shape); if (assign_if_different(this->type, new_type)) { @@ -428,7 +428,7 @@ void LightModule::debug_pass_sync() debug_draw_ps_.state_set(DRW_STATE_WRITE_COLOR | DRW_STATE_BLEND_CUSTOM); debug_draw_ps_.shader_set(inst_.shaders.static_shader_get(LIGHT_CULLING_DEBUG)); inst_.bind_uniform_data(&debug_draw_ps_); - inst_.hiz_buffer.bind_resources(debug_draw_ps_); + inst_.hiz_buffer.bind_resources(&debug_draw_ps_); debug_draw_ps_.bind_ssbo("light_buf", &culling_light_buf_); debug_draw_ps_.bind_ssbo("light_cull_buf", &culling_data_buf_); debug_draw_ps_.bind_ssbo("light_zbin_buf", &culling_zbin_buf_); diff --git a/source/blender/draw/engines/eevee_next/eevee_light.hh b/source/blender/draw/engines/eevee_next/eevee_light.hh index 8adda0ff97b..6029732de8c 100644 --- a/source/blender/draw/engines/eevee_next/eevee_light.hh +++ b/source/blender/draw/engines/eevee_next/eevee_light.hh @@ -165,12 +165,12 @@ class LightModule { void debug_draw(View &view, GPUFrameBuffer *view_fb); - template void bind_resources(PassType &pass) + template void bind_resources(draw::detail::PassBase *pass) { - pass.bind_ssbo(LIGHT_CULL_BUF_SLOT, &culling_data_buf_); - pass.bind_ssbo(LIGHT_BUF_SLOT, &culling_light_buf_); - pass.bind_ssbo(LIGHT_ZBIN_BUF_SLOT, &culling_zbin_buf_); - pass.bind_ssbo(LIGHT_TILE_BUF_SLOT, &culling_tile_buf_); + pass->bind_ssbo(LIGHT_CULL_BUF_SLOT, &culling_data_buf_); + pass->bind_ssbo(LIGHT_BUF_SLOT, &culling_light_buf_); + pass->bind_ssbo(LIGHT_ZBIN_BUF_SLOT, &culling_zbin_buf_); + pass->bind_ssbo(LIGHT_TILE_BUF_SLOT, &culling_tile_buf_); } private: diff --git a/source/blender/draw/engines/eevee_next/eevee_lightcache.cc b/source/blender/draw/engines/eevee_next/eevee_lightcache.cc index 30fb2986bf8..75e509da014 100644 --- a/source/blender/draw/engines/eevee_next/eevee_lightcache.cc +++ b/source/blender/draw/engines/eevee_next/eevee_lightcache.cc @@ -179,11 +179,6 @@ class LightBake { } }); - if (instance_->info != "") { - /** TODO: Print to the Status Bar UI. */ - printf("%s\n", instance_->info.c_str()); - } - if ((G.is_break == true) || (stop != nullptr && *stop == true)) { break; } @@ -302,7 +297,7 @@ wmJob *EEVEE_NEXT_lightbake_job_create(wmWindowManager *wm, } /* Stop existing baking job. */ - WM_jobs_stop(wm, nullptr, EEVEE_NEXT_lightbake_job); + WM_jobs_stop(wm, nullptr, (void *)EEVEE_NEXT_lightbake_job); wmJob *wm_job = WM_jobs_get(wm, win, @@ -349,10 +344,9 @@ void EEVEE_NEXT_lightbake_update(void *job_data) static_cast(job_data)->update(); } -void EEVEE_NEXT_lightbake_job(void *job_data, wmJobWorkerStatus *worker_status) +void EEVEE_NEXT_lightbake_job(void *job_data, bool *stop, bool *do_update, float *progress) { - static_cast(job_data)->run( - &worker_status->stop, &worker_status->do_update, &worker_status->progress); + static_cast(job_data)->run(stop, do_update, progress); } /** \} */ diff --git a/source/blender/draw/engines/eevee_next/eevee_lightcache.hh b/source/blender/draw/engines/eevee_next/eevee_lightcache.hh index d470fcff245..6eba53bda26 100644 --- a/source/blender/draw/engines/eevee_next/eevee_lightcache.hh +++ b/source/blender/draw/engines/eevee_next/eevee_lightcache.hh @@ -17,7 +17,6 @@ struct ViewLayer; struct Scene; struct Object; struct wmJob; -struct wmJobWorkerStatus; /** Opaque type hiding eevee::LightBake. */ struct EEVEE_NEXT_LightBake; @@ -73,6 +72,8 @@ void EEVEE_NEXT_lightbake_update(/*EEVEE_NEXT_LightBake*/ void *job_data); * Will call `EEVEE_NEXT_lightbake_update()` on finish. */ void EEVEE_NEXT_lightbake_job(/*EEVEE_NEXT_LightBake*/ void *job_data, - wmJobWorkerStatus *worker_status); + bool *stop, + bool *do_update, + float *progress); /** \} */ diff --git a/source/blender/draw/engines/eevee_next/eevee_lightprobe.cc b/source/blender/draw/engines/eevee_next/eevee_lightprobe.cc index 956616406b8..aefef594cd9 100644 --- a/source/blender/draw/engines/eevee_next/eevee_lightprobe.cc +++ b/source/blender/draw/engines/eevee_next/eevee_lightprobe.cc @@ -67,13 +67,13 @@ void LightProbeModule::sync_probe(const Object *ob, ObjectHandle &handle) { const ::LightProbe *lightprobe = static_cast(ob->data); switch (lightprobe->type) { - case LIGHTPROBE_TYPE_SPHERE: + case LIGHTPROBE_TYPE_CUBE: sync_cube(handle); return; - case LIGHTPROBE_TYPE_PLANE: + case LIGHTPROBE_TYPE_PLANAR: /* TODO(fclem): Remove support? Add support? */ return; - case LIGHTPROBE_TYPE_VOLUME: + case LIGHTPROBE_TYPE_GRID: sync_grid(ob, handle); return; } diff --git a/source/blender/draw/engines/eevee_next/eevee_lookdev.cc b/source/blender/draw/engines/eevee_next/eevee_lookdev.cc index 64aa214b58f..fc7d23f1cc7 100644 --- a/source/blender/draw/engines/eevee_next/eevee_lookdev.cc +++ b/source/blender/draw/engines/eevee_next/eevee_lookdev.cc @@ -120,16 +120,15 @@ bool LookdevModule::sync_world() { /* Check based on the v3d if the world is overridden. */ LookdevParameters new_parameters(inst_.v3d); - bool parameters_changed = parameters_ != new_parameters; - if (parameters_changed) { + if (parameters_ != new_parameters) { if (parameters_.gpu_parameters_changed(new_parameters)) { GPU_material_free(&gpu_materials_); gpu_material_ = nullptr; } parameters_ = new_parameters; + inst_.reflection_probes.do_world_update_set(true); inst_.sampling.reset(); - gpu_status_ = GPU_MAT_CREATED; } if (parameters_.show_scene_world) { @@ -143,17 +142,6 @@ bool LookdevModule::sync_world() MAT_PIPE_DEFERRED, MAT_GEOM_WORLD, true); - - if (assign_if_different(gpu_status_, GPU_material_status(gpu_material_)) && - gpu_status_ == GPU_MAT_SUCCESS) - { - inst_.reflection_probes.sync_world_lookdev(); - } - else if (gpu_status_ == GPU_MAT_QUEUED) { - inst_.sampling.reset(); - DRW_viewport_request_redraw(); - } - inst_.pipelines.world.sync(gpu_material_); inst_.pipelines.background.sync(gpu_material_, parameters_.background_opacity); diff --git a/source/blender/draw/engines/eevee_next/eevee_lookdev.hh b/source/blender/draw/engines/eevee_next/eevee_lookdev.hh index 0d83d4222f9..98c82098187 100644 --- a/source/blender/draw/engines/eevee_next/eevee_lookdev.hh +++ b/source/blender/draw/engines/eevee_next/eevee_lookdev.hh @@ -80,7 +80,6 @@ class LookdevModule { LookdevParameters parameters_; ListBase gpu_materials_ = {nullptr, nullptr}; GPUMaterial *gpu_material_ = nullptr; - eGPUMaterialStatus gpu_status_ = GPU_MAT_CREATED; public: LookdevModule(Instance &inst) : inst_(inst){}; diff --git a/source/blender/draw/engines/eevee_next/eevee_material.cc b/source/blender/draw/engines/eevee_next/eevee_material.cc index 03a8674bcaf..55aa24d99da 100644 --- a/source/blender/draw/engines/eevee_next/eevee_material.cc +++ b/source/blender/draw/engines/eevee_next/eevee_material.cc @@ -76,7 +76,8 @@ MaterialModule::MaterialModule(Instance &inst) : inst_(inst) bNodeTree *ntree = bke::ntreeAddTreeEmbedded( nullptr, &diffuse_mat->id, "Shader Nodetree", ntreeType_Shader->idname); diffuse_mat->use_nodes = true; - diffuse_mat->surface_render_method = MA_SURFACE_METHOD_FORWARD; + /* To use the forward pipeline. */ + diffuse_mat->blend_method = MA_BM_BLEND; bNode *bsdf = nodeAddStaticNode(nullptr, ntree, SH_NODE_BSDF_DIFFUSE); bNodeSocket *base_color = nodeFindSocket(bsdf, SOCK_IN, "Color"); @@ -97,7 +98,8 @@ MaterialModule::MaterialModule(Instance &inst) : inst_(inst) bNodeTree *ntree = bke::ntreeAddTreeEmbedded( nullptr, &glossy_mat->id, "Shader Nodetree", ntreeType_Shader->idname); glossy_mat->use_nodes = true; - glossy_mat->surface_render_method = MA_SURFACE_METHOD_FORWARD; + /* To use the forward pipeline. */ + glossy_mat->blend_method = MA_BM_BLEND; bNode *bsdf = nodeAddStaticNode(nullptr, ntree, SH_NODE_BSDF_GLOSSY); bNodeSocket *base_color = nodeFindSocket(bsdf, SOCK_IN, "Color"); @@ -158,7 +160,7 @@ MaterialPass MaterialModule::material_pass_get(Object *ob, ::Material *blender_mat, eMaterialPipeline pipeline_type, eMaterialGeometry geometry_type, - eMaterialProbe probe_capture) + bool probe_capture) { bNodeTree *ntree = (blender_mat->use_nodes && blender_mat->nodetree != nullptr) ? blender_mat->nodetree : @@ -170,9 +172,6 @@ MaterialPass MaterialModule::material_pass_get(Object *ob, matpass.gpumat = inst_.shaders.material_shader_get( blender_mat, ntree, pipeline_type, geometry_type, use_deferred_compilation); - const bool is_volume = ELEM(pipeline_type, MAT_PIPE_VOLUME_OCCUPANCY, MAT_PIPE_VOLUME_MATERIAL); - const bool is_forward = ELEM(pipeline_type, MAT_PIPE_FORWARD, MAT_PIPE_PREPASS_OVERLAP); - switch (GPU_material_status(matpass.gpumat)) { case GPU_MAT_SUCCESS: { /* Determine optimization status for remaining compilations counter. */ @@ -184,7 +183,8 @@ MaterialPass MaterialModule::material_pass_get(Object *ob, } case GPU_MAT_QUEUED: queued_shaders_count++; - blender_mat = (is_volume) ? BKE_material_default_volume() : BKE_material_default_surface(); + blender_mat = (geometry_type == MAT_GEOM_VOLUME_OBJECT) ? BKE_material_default_volume() : + BKE_material_default_surface(); matpass.gpumat = inst_.shaders.material_shader_get( blender_mat, blender_mat->nodetree, pipeline_type, geometry_type, false); break; @@ -203,13 +203,18 @@ MaterialPass MaterialModule::material_pass_get(Object *ob, inst_.sampling.reset(); } - const bool is_transparent = GPU_material_flag_get(matpass.gpumat, GPU_MATFLAG_TRANSPARENT); - if (is_volume || (is_forward && is_transparent)) { - /* Sub pass is generated later. */ + if (ELEM(pipeline_type, + MAT_PIPE_FORWARD, + MAT_PIPE_FORWARD_PREPASS, + MAT_PIPE_FORWARD_PREPASS_VELOCITY) && + GPU_material_flag_get(matpass.gpumat, GPU_MATFLAG_TRANSPARENT)) + { + /* Transparent pass is generated later. */ matpass.sub_pass = nullptr; } else { - ShaderKey shader_key(matpass.gpumat, probe_capture); + ShaderKey shader_key( + matpass.gpumat, geometry_type, pipeline_type, blender_mat->blend_flag, probe_capture); PassMain::Sub *shader_sub = shader_map_.lookup_or_add_cb(shader_key, [&]() { /* First time encountering this shader. Create a sub that will contain materials using it. */ @@ -235,44 +240,22 @@ Material &MaterialModule::material_sync(Object *ob, eMaterialGeometry geometry_type, bool has_motion) { - if (geometry_type == MAT_GEOM_VOLUME) { - MaterialKey material_key(blender_mat, geometry_type, MAT_PIPE_VOLUME_MATERIAL); - Material &mat = material_map_.lookup_or_add_cb(material_key, [&]() { + if (geometry_type == MAT_GEOM_VOLUME_OBJECT) { + MaterialKey material_key(blender_mat, geometry_type, MAT_PIPE_VOLUME); + return material_map_.lookup_or_add_cb(material_key, [&]() { Material mat = {}; - mat.volume_occupancy = material_pass_get( - ob, blender_mat, MAT_PIPE_VOLUME_OCCUPANCY, MAT_GEOM_VOLUME); - mat.volume_material = material_pass_get( - ob, blender_mat, MAT_PIPE_VOLUME_MATERIAL, MAT_GEOM_VOLUME_OBJECT); + mat.volume = material_pass_get(ob, blender_mat, MAT_PIPE_VOLUME, MAT_GEOM_VOLUME_OBJECT); return mat; }); - - /* Volume needs to use one sub pass per object to support layering. */ - VolumeLayer *layer = inst_.pipelines.volume.register_and_get_layer(ob); - if (layer) { - mat.volume_occupancy.sub_pass = layer->occupancy_add( - ob, blender_mat, mat.volume_occupancy.gpumat); - mat.volume_material.sub_pass = layer->material_add( - ob, blender_mat, mat.volume_material.gpumat); - } - else { - /* Culled volumes. */ - mat.volume_occupancy.sub_pass = nullptr; - mat.volume_material.sub_pass = nullptr; - } - return mat; } - const bool use_forward_pipeline = (blender_mat->surface_render_method == - MA_SURFACE_METHOD_FORWARD); - eMaterialPipeline surface_pipe, prepass_pipe; - if (use_forward_pipeline) { - surface_pipe = MAT_PIPE_FORWARD; - prepass_pipe = has_motion ? MAT_PIPE_PREPASS_FORWARD_VELOCITY : MAT_PIPE_PREPASS_FORWARD; - } - else { - surface_pipe = MAT_PIPE_DEFERRED; - prepass_pipe = has_motion ? MAT_PIPE_PREPASS_DEFERRED_VELOCITY : MAT_PIPE_PREPASS_DEFERRED; - } + eMaterialPipeline surface_pipe = (blender_mat->blend_method == MA_BM_BLEND) ? MAT_PIPE_FORWARD : + MAT_PIPE_DEFERRED; + eMaterialPipeline prepass_pipe = (blender_mat->blend_method == MA_BM_BLEND) ? + (has_motion ? MAT_PIPE_FORWARD_PREPASS_VELOCITY : + MAT_PIPE_FORWARD_PREPASS) : + (has_motion ? MAT_PIPE_DEFERRED_PREPASS_VELOCITY : + MAT_PIPE_DEFERRED_PREPASS); MaterialKey material_key(blender_mat, geometry_type, surface_pipe); @@ -284,93 +267,61 @@ Material &MaterialModule::material_sync(Object *ob, * to avoid this shader compilation in another context. */ mat.shading = material_pass_get(ob, blender_mat, surface_pipe, geometry_type); mat.capture = material_pass_get(ob, blender_mat, MAT_PIPE_CAPTURE, geometry_type); - mat.overlap_masking = MaterialPass(); - mat.reflection_probe_prepass = MaterialPass(); - mat.reflection_probe_shading = MaterialPass(); - mat.planar_probe_prepass = MaterialPass(); - mat.planar_probe_shading = MaterialPass(); - mat.volume_occupancy = MaterialPass(); - mat.volume_material = MaterialPass(); - mat.has_volume = false; /* TODO */ - mat.has_surface = GPU_material_has_surface_output(mat.shading.gpumat); + mat.probe_prepass = MaterialPass(); + mat.probe_shading = MaterialPass(); + mat.volume = MaterialPass(); } else { /* Order is important for transparent. */ mat.prepass = material_pass_get(ob, blender_mat, prepass_pipe, geometry_type); mat.shading = material_pass_get(ob, blender_mat, surface_pipe, geometry_type); - mat.overlap_masking = MaterialPass(); mat.capture = MaterialPass(); + mat.probe_prepass = MaterialPass(); + mat.probe_shading = MaterialPass(); - if (inst_.do_reflection_probe_sync() && !(ob->visibility_flag & OB_HIDE_PROBE_CUBEMAP)) { - mat.reflection_probe_prepass = material_pass_get( - ob, blender_mat, MAT_PIPE_PREPASS_DEFERRED, geometry_type, MAT_PROBE_REFLECTION); - mat.reflection_probe_shading = material_pass_get( - ob, blender_mat, MAT_PIPE_DEFERRED, geometry_type, MAT_PROBE_REFLECTION); - } - else { - mat.reflection_probe_prepass = MaterialPass(); - mat.reflection_probe_shading = MaterialPass(); + if (inst_.do_probe_sync()) { + mat.probe_prepass = material_pass_get( + ob, blender_mat, MAT_PIPE_DEFERRED_PREPASS, geometry_type, true); + mat.probe_shading = material_pass_get( + ob, blender_mat, MAT_PIPE_DEFERRED, geometry_type, true); } - if (inst_.do_planar_probe_sync() && !(ob->visibility_flag & OB_HIDE_PROBE_PLANAR)) { - mat.planar_probe_prepass = material_pass_get( - ob, blender_mat, MAT_PIPE_PREPASS_PLANAR, geometry_type, MAT_PROBE_PLANAR); - mat.planar_probe_shading = material_pass_get( - ob, blender_mat, MAT_PIPE_DEFERRED, geometry_type, MAT_PROBE_PLANAR); + if (GPU_material_has_volume_output(mat.shading.gpumat)) { + mat.volume = material_pass_get(ob, blender_mat, MAT_PIPE_VOLUME, MAT_GEOM_VOLUME_OBJECT); } else { - mat.planar_probe_prepass = MaterialPass(); - mat.planar_probe_shading = MaterialPass(); - } - - mat.has_surface = GPU_material_has_surface_output(mat.shading.gpumat); - mat.has_volume = GPU_material_has_volume_output(mat.shading.gpumat); - if (mat.has_volume) { - mat.volume_occupancy = material_pass_get( - ob, blender_mat, MAT_PIPE_VOLUME_OCCUPANCY, geometry_type); - mat.volume_material = material_pass_get( - ob, blender_mat, MAT_PIPE_VOLUME_MATERIAL, MAT_GEOM_VOLUME_OBJECT); - } - else { - mat.volume_occupancy = MaterialPass(); - mat.volume_material = MaterialPass(); + mat.volume = MaterialPass(); } } - if (true /* TODO: Ray visibility. */) { + if (blender_mat->blend_shadow == MA_BS_NONE) { + mat.shadow = MaterialPass(); + } + else { mat.shadow = material_pass_get(ob, blender_mat, MAT_PIPE_SHADOW, geometry_type); } - mat.is_alpha_blend_transparent = use_forward_pipeline && + mat.is_alpha_blend_transparent = (blender_mat->blend_method == MA_BM_BLEND) && GPU_material_flag_get(mat.shading.gpumat, GPU_MATFLAG_TRANSPARENT); + if (inst_.is_baking()) { + /* WORKAROUND(fclem): This is to request the shadow for the surfels. This will well + * over-request the number of shadow tiles. A better way would be to request from the surfels + * directly. */ + mat.is_alpha_blend_transparent = true; + } return mat; }); if (mat.is_alpha_blend_transparent) { /* Transparent needs to use one sub pass per object to support reordering. * NOTE: Pre-pass needs to be created first in order to be sorted first. */ - mat.overlap_masking.sub_pass = inst_.pipelines.forward.prepass_transparent_add( + mat.prepass.sub_pass = inst_.pipelines.forward.prepass_transparent_add( ob, blender_mat, mat.shading.gpumat); mat.shading.sub_pass = inst_.pipelines.forward.material_transparent_add( ob, blender_mat, mat.shading.gpumat); } - if (mat.has_volume) { - /* Volume needs to use one sub pass per object to support layering. */ - VolumeLayer *layer = inst_.pipelines.volume.register_and_get_layer(ob); - if (layer) { - mat.volume_occupancy.sub_pass = layer->occupancy_add( - ob, blender_mat, mat.volume_occupancy.gpumat); - mat.volume_material.sub_pass = layer->material_add( - ob, blender_mat, mat.volume_material.gpumat); - } - else { - /* Culled volumes. */ - mat.volume_occupancy.sub_pass = nullptr; - mat.volume_material.sub_pass = nullptr; - } - } return mat; } diff --git a/source/blender/draw/engines/eevee_next/eevee_material.hh b/source/blender/draw/engines/eevee_next/eevee_material.hh index 439d8cb31a8..ec434f45426 100644 --- a/source/blender/draw/engines/eevee_next/eevee_material.hh +++ b/source/blender/draw/engines/eevee_next/eevee_material.hh @@ -28,45 +28,25 @@ class Instance; enum eMaterialPipeline { MAT_PIPE_DEFERRED = 0, MAT_PIPE_FORWARD, - /* These all map to the depth shader. */ - MAT_PIPE_PREPASS_DEFERRED, - MAT_PIPE_PREPASS_DEFERRED_VELOCITY, - MAT_PIPE_PREPASS_OVERLAP, - MAT_PIPE_PREPASS_FORWARD, - MAT_PIPE_PREPASS_FORWARD_VELOCITY, - MAT_PIPE_PREPASS_PLANAR, - - MAT_PIPE_VOLUME_MATERIAL, - MAT_PIPE_VOLUME_OCCUPANCY, + MAT_PIPE_DEFERRED_PREPASS, + MAT_PIPE_DEFERRED_PREPASS_VELOCITY, + MAT_PIPE_FORWARD_PREPASS, + MAT_PIPE_FORWARD_PREPASS_VELOCITY, + MAT_PIPE_VOLUME, MAT_PIPE_SHADOW, MAT_PIPE_CAPTURE, }; enum eMaterialGeometry { - /* These maps directly to object types. */ MAT_GEOM_MESH = 0, MAT_GEOM_POINT_CLOUD, MAT_GEOM_CURVES, MAT_GEOM_GPENCIL, - MAT_GEOM_VOLUME, - - /* These maps to special shader. */ MAT_GEOM_VOLUME_OBJECT, MAT_GEOM_VOLUME_WORLD, MAT_GEOM_WORLD, }; -static inline bool geometry_type_has_surface(eMaterialGeometry geometry_type) -{ - return geometry_type < MAT_GEOM_VOLUME; -} - -enum eMaterialProbe { - MAT_PROBE_NONE = 0, - MAT_PROBE_REFLECTION, - MAT_PROBE_PLANAR, -}; - static inline void material_type_from_shader_uuid(uint64_t shader_uuid, eMaterialPipeline &pipeline_type, eMaterialGeometry &geometry_type) @@ -80,7 +60,6 @@ static inline void material_type_from_shader_uuid(uint64_t shader_uuid, static inline uint64_t shader_uuid_from_material_type(eMaterialPipeline pipeline_type, eMaterialGeometry geometry_type) { - BLI_assert(geometry_type < (1 << 4)); return geometry_type | (pipeline_type << 4); } @@ -122,7 +101,7 @@ static inline eMaterialGeometry to_material_geometry(const Object *ob) case OB_CURVES: return MAT_GEOM_CURVES; case OB_VOLUME: - return MAT_GEOM_VOLUME; + return MAT_GEOM_VOLUME_OBJECT; case OB_GPENCIL_LEGACY: return MAT_GEOM_GPENCIL; case OB_POINTCLOUD: @@ -132,17 +111,15 @@ static inline eMaterialGeometry to_material_geometry(const Object *ob) } } -/** - * Unique key to identify each material in the hash-map. - * This is above the shader binning. - */ +/** Unique key to identify each material in the hash-map. */ struct MaterialKey { ::Material *mat; uint64_t options; - MaterialKey(::Material *mat_, eMaterialGeometry geometry, eMaterialPipeline pipeline) : mat(mat_) + MaterialKey(::Material *mat_, eMaterialGeometry geometry, eMaterialPipeline surface_pipeline) + : mat(mat_) { - options = shader_uuid_from_material_type(pipeline, geometry); + options = shader_uuid_from_material_type(surface_pipeline, geometry); } uint64_t hash() const @@ -169,20 +146,21 @@ struct MaterialKey { * * \{ */ -/** - * Key used to find the sub-pass that already renders objects with the same shader. - * This avoids the cost associated with shader switching. - * This is below the material binning. - * Should only include pipeline options that are not baked in the shader itself. - */ struct ShaderKey { GPUShader *shader; uint64_t options; - ShaderKey(GPUMaterial *gpumat, eMaterialProbe probe_capture) + ShaderKey(GPUMaterial *gpumat, + eMaterialGeometry geometry, + eMaterialPipeline pipeline, + char blend_flags, + bool probe_capture) { shader = GPU_material_get_shader(gpumat); - options = uint64_t(probe_capture); + options = blend_flags; + options = (options << 6u) | shader_uuid_from_material_type(pipeline, geometry); + options = (options << 16u) | shader_closure_bits_from_flag(gpumat); + options = (options << 1u) | uint64_t(probe_capture); } uint64_t hash() const @@ -242,19 +220,7 @@ struct MaterialPass { struct Material { bool is_alpha_blend_transparent; - bool has_surface; - bool has_volume; - MaterialPass shadow; - MaterialPass shading; - MaterialPass prepass; - MaterialPass overlap_masking; - MaterialPass capture; - MaterialPass reflection_probe_prepass; - MaterialPass reflection_probe_shading; - MaterialPass planar_probe_prepass; - MaterialPass planar_probe_shading; - MaterialPass volume_occupancy; - MaterialPass volume_material; + MaterialPass shadow, shading, prepass, capture, probe_prepass, probe_shading, volume; }; struct MaterialArray { @@ -310,7 +276,7 @@ class MaterialModule { ::Material *blender_mat, eMaterialPipeline pipeline_type, eMaterialGeometry geometry_type, - eMaterialProbe probe_capture = MAT_PROBE_NONE); + bool probe_capture = false); }; /** \} */ diff --git a/source/blender/draw/engines/eevee_next/eevee_motion_blur.cc b/source/blender/draw/engines/eevee_next/eevee_motion_blur.cc index c00d0e3340b..806e6e5128c 100644 --- a/source/blender/draw/engines/eevee_next/eevee_motion_blur.cc +++ b/source/blender/draw/engines/eevee_next/eevee_motion_blur.cc @@ -139,8 +139,8 @@ void MotionBlurModule::sync() RenderBuffers &render_buffers = inst_.render_buffers; motion_blur_ps_.init(); - inst_.velocity.bind_resources(motion_blur_ps_); - inst_.sampling.bind_resources(motion_blur_ps_); + inst_.velocity.bind_resources(&motion_blur_ps_); + inst_.sampling.bind_resources(&motion_blur_ps_); { /* Create max velocity tiles. */ PassSimple::Sub &sub = motion_blur_ps_.sub("TilesFlatten"); diff --git a/source/blender/draw/engines/eevee_next/eevee_pipeline.cc b/source/blender/draw/engines/eevee_next/eevee_pipeline.cc index 3350e700042..705f288556c 100644 --- a/source/blender/draw/engines/eevee_next/eevee_pipeline.cc +++ b/source/blender/draw/engines/eevee_next/eevee_pipeline.cc @@ -14,8 +14,6 @@ #include "eevee_pipeline.hh" -#include "eevee_shadow.hh" - #include "draw_common.hh" namespace blender::eevee { @@ -43,7 +41,7 @@ void BackgroundPipeline::sync(GPUMaterial *gpumat, const float background_opacit world_ps_.bind_image("rp_value_img", &rbufs.rp_value_tx); world_ps_.bind_image("rp_cryptomatte_img", &rbufs.cryptomatte_tx); /* Required by validation layers. */ - inst_.cryptomatte.bind_resources(world_ps_); + inst_.cryptomatte.bind_resources(&world_ps_); inst_.bind_uniform_data(&world_ps_); @@ -93,7 +91,7 @@ void WorldPipeline::sync(GPUMaterial *gpumat) pass.bind_image("rp_color_img", dummy_aov_color_tx_); pass.bind_image("rp_value_img", dummy_aov_value_tx_); /* Required by validation layers. */ - inst_.cryptomatte.bind_resources(pass); + inst_.cryptomatte.bind_resources(&pass); pass.bind_image("aov_color_img", dummy_aov_color_tx_); pass.bind_image("aov_value_img", dummy_aov_value_tx_); @@ -126,7 +124,7 @@ void WorldVolumePipeline::sync(GPUMaterial *gpumat) world_ps_.state_set(DRW_STATE_WRITE_COLOR); inst_.bind_uniform_data(&world_ps_); inst_.volume.bind_properties_buffers(world_ps_); - inst_.sampling.bind_resources(world_ps_); + inst_.sampling.bind_resources(&world_ps_); world_ps_.material_set(*inst_.manager, gpumat); volume_sub_pass(world_ps_, nullptr, nullptr, gpumat); @@ -155,78 +153,25 @@ void WorldVolumePipeline::render(View &view) void ShadowPipeline::sync() { - render_ps_.init(); - - /* NOTE: TILE_COPY technique perform a three-pass implementation. First performing the clear - * directly on tile, followed by a fast depth-only pass, then storing the on-tile results into - * the shadow atlas during a final storage pass. This takes advantage of TBDR architecture, - * reducing overdraw and additional per-fragment calculations. */ - bool shadow_update_tbdr = (ShadowModule::shadow_technique == ShadowTechnique::TILE_COPY); - if (shadow_update_tbdr) { - draw::PassMain::Sub &pass = render_ps_.sub("Shadow.TilePageClear"); - pass.subpass_transition(GPU_ATTACHEMENT_WRITE, {GPU_ATTACHEMENT_WRITE}); - pass.shader_set(inst_.shaders.static_shader_get(SHADOW_PAGE_TILE_CLEAR)); - /* Only manually clear depth of the updated tiles. - * This is because the depth is initialized to near depth using attachments for fast clear and - * color is cleared to far depth. This way we can save a bit of bandwidth by only clearing - * the updated tiles depth to far depth and not touch the color attachment. */ - pass.state_set(DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_ALWAYS); - pass.bind_ssbo("src_coord_buf", inst_.shadows.src_coord_buf_); - pass.draw_procedural_indirect(GPU_PRIM_TRIS, inst_.shadows.tile_draw_buf_); - } - - { - /* Metal writes depth value in local tile memory, which is considered a color attachment. */ - DRWState state = DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_LESS | DRW_STATE_WRITE_COLOR; - - draw::PassMain::Sub &pass = render_ps_.sub("Shadow.Surface"); - pass.state_set(state); - pass.bind_texture(RBUFS_UTILITY_TEX_SLOT, inst_.pipelines.utility_tx); - pass.bind_ssbo(SHADOW_VIEWPORT_INDEX_BUF_SLOT, &inst_.shadows.viewport_index_buf_); - if (!shadow_update_tbdr) { - /* We do not need all of the shadow information when using the TBDR-optimized approach. */ - pass.bind_image(SHADOW_ATLAS_IMG_SLOT, inst_.shadows.atlas_tx_); - pass.bind_ssbo(SHADOW_RENDER_MAP_BUF_SLOT, &inst_.shadows.render_map_buf_); - pass.bind_ssbo(SHADOW_PAGE_INFO_SLOT, &inst_.shadows.pages_infos_data_); - } - inst_.bind_uniform_data(&pass); - inst_.sampling.bind_resources(pass); - surface_double_sided_ps_ = &pass.sub("Shadow.Surface.Double-Sided"); - surface_single_sided_ps_ = &pass.sub("Shadow.Surface.Single-Sided"); - surface_single_sided_ps_->state_set(state | DRW_STATE_CULL_BACK); - } - - if (shadow_update_tbdr) { - draw::PassMain::Sub &pass = render_ps_.sub("Shadow.TilePageStore"); - pass.shader_set(inst_.shaders.static_shader_get(SHADOW_PAGE_TILE_STORE)); - /* The most optimal way would be to only store pixels that have been rendered to (depth > 0). - * But that requires that the destination pages in the atlas would have been already cleared - * using compute. Experiments showed that it is faster to just copy the whole tiles back. - * - * For relative performance, raster-based clear within tile update adds around 0.1ms vs 0.25ms - * for compute based clear for a simple test case. */ - pass.state_set(DRW_STATE_DEPTH_ALWAYS); - /* Metal have implicit sync with Raster Order Groups. Other backend need to have manual - * sub-pass transition to allow reading the frame-buffer. This is a no-op on Metal. */ - pass.subpass_transition(GPU_ATTACHEMENT_WRITE, {GPU_ATTACHEMENT_READ}); - pass.bind_image(SHADOW_ATLAS_IMG_SLOT, inst_.shadows.atlas_tx_); - pass.bind_ssbo("dst_coord_buf", inst_.shadows.dst_coord_buf_); - pass.bind_ssbo("src_coord_buf", inst_.shadows.src_coord_buf_); - pass.draw_procedural_indirect(GPU_PRIM_TRIS, inst_.shadows.tile_draw_buf_); - } + surface_ps_.init(); + surface_ps_.state_set(DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_LESS); + surface_ps_.bind_texture(RBUFS_UTILITY_TEX_SLOT, inst_.pipelines.utility_tx); + surface_ps_.bind_image(SHADOW_ATLAS_IMG_SLOT, inst_.shadows.atlas_tx_); + surface_ps_.bind_ssbo(SHADOW_RENDER_MAP_BUF_SLOT, &inst_.shadows.render_map_buf_); + surface_ps_.bind_ssbo(SHADOW_VIEWPORT_INDEX_BUF_SLOT, &inst_.shadows.viewport_index_buf_); + surface_ps_.bind_ssbo(SHADOW_PAGE_INFO_SLOT, &inst_.shadows.pages_infos_data_); + inst_.bind_uniform_data(&surface_ps_); + inst_.sampling.bind_resources(&surface_ps_); } -PassMain::Sub *ShadowPipeline::surface_material_add(::Material *material, GPUMaterial *gpumat) +PassMain::Sub *ShadowPipeline::surface_material_add(GPUMaterial *gpumat) { - PassMain::Sub *pass = (material->blend_flag & MA_BL_CULL_BACKFACE_SHADOW) ? - surface_single_sided_ps_ : - surface_double_sided_ps_; - return &pass->sub(GPU_material_get_name(gpumat)); + return &surface_ps_.sub(GPU_material_get_name(gpumat)); } void ShadowPipeline::render(View &view) { - inst_.manager->submit(render_ps_, view); + inst_.manager->submit(surface_ps_, view); } /** \} */ @@ -254,8 +199,8 @@ void ForwardPipeline::sync() prepass_ps_.bind_texture(RBUFS_UTILITY_TEX_SLOT, inst_.pipelines.utility_tx); inst_.bind_uniform_data(&prepass_ps_); - inst_.velocity.bind_resources(prepass_ps_); - inst_.sampling.bind_resources(prepass_ps_); + inst_.velocity.bind_resources(&prepass_ps_); + inst_.sampling.bind_resources(&prepass_ps_); } prepass_double_sided_static_ps_ = &prepass_ps_.sub("DoubleSided.Static"); @@ -284,13 +229,10 @@ void ForwardPipeline::sync() opaque_ps_.bind_texture(RBUFS_UTILITY_TEX_SLOT, inst_.pipelines.utility_tx); inst_.bind_uniform_data(&opaque_ps_); - inst_.lights.bind_resources(opaque_ps_); - inst_.shadows.bind_resources(opaque_ps_); - inst_.volume.bind_resources(opaque_ps_); - inst_.sampling.bind_resources(opaque_ps_); - inst_.hiz_buffer.bind_resources(opaque_ps_); - inst_.irradiance_cache.bind_resources(opaque_ps_); - inst_.reflection_probes.bind_resources(opaque_ps_); + inst_.lights.bind_resources(&opaque_ps_); + inst_.shadows.bind_resources(&opaque_ps_); + inst_.sampling.bind_resources(&opaque_ps_); + inst_.hiz_buffer.bind_resources(&opaque_ps_); } opaque_single_sided_ps_ = &opaque_ps_.sub("SingleSided"); @@ -312,13 +254,11 @@ void ForwardPipeline::sync() sub.bind_texture(RBUFS_UTILITY_TEX_SLOT, inst_.pipelines.utility_tx); inst_.bind_uniform_data(&sub); - inst_.lights.bind_resources(sub); - inst_.shadows.bind_resources(sub); + inst_.lights.bind_resources(&sub); + inst_.shadows.bind_resources(&sub); inst_.volume.bind_resources(sub); - inst_.sampling.bind_resources(sub); - inst_.hiz_buffer.bind_resources(sub); - inst_.irradiance_cache.bind_resources(sub); - inst_.reflection_probes.bind_resources(sub); + inst_.sampling.bind_resources(&sub); + inst_.hiz_buffer.bind_resources(&sub); } } @@ -331,19 +271,11 @@ PassMain::Sub *ForwardPipeline::prepass_opaque_add(::Material *blender_mat, prepass_single_sided_static_ps_) : (has_motion ? prepass_double_sided_moving_ps_ : prepass_double_sided_static_ps_); - - /* If material is fully additive or transparent, we can skip the opaque prepass. */ - /* TODO(fclem): To skip it, we need to know if the transparent BSDF is fully white AND if there - * is no mix shader (could do better constant folding but that's expensive). */ - return &pass->sub(GPU_material_get_name(gpumat)); } PassMain::Sub *ForwardPipeline::material_opaque_add(::Material *blender_mat, GPUMaterial *gpumat) { - BLI_assert_msg(GPU_material_flag_get(gpumat, GPU_MATFLAG_TRANSPARENT) == false, - "Forward Transparent should be registered directly without calling " - "PipelineModule::material_add()"); PassMain::Sub *pass = (blender_mat->blend_flag & MA_BL_CULL_BACKFACE) ? opaque_single_sided_ps_ : opaque_double_sided_ps_; return &pass->sub(GPU_material_get_name(gpumat)); @@ -435,8 +367,8 @@ void DeferredLayer::begin_sync() prepass_ps_.bind_texture(RBUFS_UTILITY_TEX_SLOT, inst_.pipelines.utility_tx); inst_.bind_uniform_data(&prepass_ps_); - inst_.velocity.bind_resources(prepass_ps_); - inst_.sampling.bind_resources(prepass_ps_); + inst_.velocity.bind_resources(&prepass_ps_); + inst_.sampling.bind_resources(&prepass_ps_); } DRWState state_depth_only = DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_LESS; @@ -477,9 +409,9 @@ void DeferredLayer::begin_sync() gbuffer_ps_.bind_texture(RBUFS_UTILITY_TEX_SLOT, inst_.pipelines.utility_tx); inst_.bind_uniform_data(&gbuffer_ps_); - inst_.sampling.bind_resources(gbuffer_ps_); - inst_.hiz_buffer.bind_resources(gbuffer_ps_); - inst_.cryptomatte.bind_resources(gbuffer_ps_); + inst_.sampling.bind_resources(&gbuffer_ps_); + inst_.hiz_buffer.bind_resources(&gbuffer_ps_); + inst_.cryptomatte.bind_resources(&gbuffer_ps_); } DRWState state = DRW_STATE_WRITE_COLOR | DRW_STATE_BLEND_CUSTOM | DRW_STATE_DEPTH_EQUAL | @@ -513,11 +445,11 @@ void DeferredLayer::end_sync() pass.bind_image(RBUFS_COLOR_SLOT, &inst_.render_buffers.rp_color_tx); pass.bind_image(RBUFS_VALUE_SLOT, &inst_.render_buffers.rp_value_tx); inst_.bind_uniform_data(&pass); - inst_.gbuffer.bind_resources(pass); - inst_.lights.bind_resources(pass); - inst_.shadows.bind_resources(pass); - inst_.sampling.bind_resources(pass); - inst_.hiz_buffer.bind_resources(pass); + inst_.gbuffer.bind_resources(&pass); + inst_.lights.bind_resources(&pass); + inst_.shadows.bind_resources(&pass); + inst_.sampling.bind_resources(&pass); + inst_.hiz_buffer.bind_resources(&pass); pass.barrier(GPU_BARRIER_TEXTURE_FETCH | GPU_BARRIER_SHADER_IMAGE_ACCESS); pass.draw_procedural(GPU_PRIM_TRIS, 1, 3); } @@ -536,7 +468,7 @@ void DeferredLayer::end_sync() pass.bind_image("indirect_refract_img", &indirect_refract_tx_); pass.bind_image(RBUFS_COLOR_SLOT, &inst_.render_buffers.rp_color_tx); pass.bind_image(RBUFS_VALUE_SLOT, &inst_.render_buffers.rp_value_tx); - inst_.gbuffer.bind_resources(pass); + inst_.gbuffer.bind_resources(&pass); inst_.bind_uniform_data(&pass); pass.barrier(GPU_BARRIER_TEXTURE_FETCH | GPU_BARRIER_SHADER_IMAGE_ACCESS); pass.draw_procedural(GPU_PRIM_TRIS, 1, 3); @@ -767,213 +699,28 @@ void DeferredPipeline::render(View &main_view, /** \} */ -/* -------------------------------------------------------------------- */ -/** \name Volume Layer - * - * \{ */ - -void VolumeLayer::sync() -{ - object_bounds_.clear(); - use_hit_list = false; - is_empty = true; - finalized = false; - - draw::PassMain &layer_pass = volume_layer_ps_; - layer_pass.init(); - { - PassMain::Sub &pass = layer_pass.sub("occupancy_ps"); - /* Double sided without depth test. */ - pass.state_set(DRW_STATE_WRITE_DEPTH); - inst_.bind_uniform_data(&pass); - inst_.volume.bind_occupancy_buffers(pass); - inst_.sampling.bind_resources(pass); - occupancy_ps_ = &pass; - } - { - PassMain::Sub &pass = layer_pass.sub("material_ps"); - pass.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS); - pass.bind_texture(RBUFS_UTILITY_TEX_SLOT, inst_.pipelines.utility_tx); - inst_.bind_uniform_data(&pass); - inst_.volume.bind_properties_buffers(pass); - inst_.sampling.bind_resources(pass); - material_ps_ = &pass; - } -} - -PassMain::Sub *VolumeLayer::occupancy_add(const Object *ob, - const ::Material *blender_mat, - GPUMaterial *gpumat) -{ - BLI_assert_msg(GPU_material_has_volume_output(gpumat) == true, - "Only volume material should be added here"); - bool use_fast_occupancy = (ob->type == OB_VOLUME) || - (blender_mat->volume_intersection_method == MA_VOLUME_ISECT_FAST); - use_hit_list |= !use_fast_occupancy; - is_empty = false; - - PassMain::Sub *pass = &occupancy_ps_->sub(GPU_material_get_name(gpumat)); - pass->material_set(*inst_.manager, gpumat); - pass->push_constant("use_fast_method", use_fast_occupancy); - return pass; -} - -PassMain::Sub *VolumeLayer::material_add(const Object * /*ob*/, - const ::Material * /*blender_mat*/, - GPUMaterial *gpumat) -{ - BLI_assert_msg(GPU_material_has_volume_output(gpumat) == true, - "Only volume material should be added here"); - PassMain::Sub *pass = &material_ps_->sub(GPU_material_get_name(gpumat)); - pass->material_set(*inst_.manager, gpumat); - return pass; -} - -void VolumeLayer::render(View &view, Texture &occupancy_tx) -{ - if (is_empty) { - return; - } - if (finalized == false) { - finalized = true; - if (use_hit_list) { - /* Add resolve pass only when needed. Insert after occupancy, before material pass. */ - occupancy_ps_->shader_set(inst_.shaders.static_shader_get(VOLUME_OCCUPANCY_CONVERT)); - occupancy_ps_->barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS); - occupancy_ps_->draw_procedural(GPU_PRIM_TRIS, 1, 3); - } - } - /* TODO(fclem): Move this clear inside the render pass. */ - occupancy_tx.clear(uint4(0u)); - inst_.manager->submit(volume_layer_ps_, view); -} - -/** \} */ - /* -------------------------------------------------------------------- */ /** \name Volume Pipeline + * * \{ */ void VolumePipeline::sync() { - enabled_ = false; - for (auto &layer : layers_) { - layer->sync(); - } + volume_ps_.init(); + volume_ps_.bind_texture(RBUFS_UTILITY_TEX_SLOT, inst_.pipelines.utility_tx); + inst_.bind_uniform_data(&volume_ps_); + inst_.volume.bind_properties_buffers(volume_ps_); + inst_.sampling.bind_resources(&volume_ps_); } -void VolumePipeline::render(View &view, Texture &occupancy_tx) +PassMain::Sub *VolumePipeline::volume_material_add(GPUMaterial *gpumat) { - BLI_assert_msg(enabled_, "Trying to run the volume object pipeline with no actual volume calls"); - - for (auto &layer : layers_) { - layer->render(view, occupancy_tx); - } + return &volume_ps_.sub(GPU_material_get_name(gpumat)); } -GridAABB VolumePipeline::grid_aabb_from_object(Object *ob) +void VolumePipeline::render(View &view) { - const Camera &camera = inst_.camera; - const VolumesInfoData &data = inst_.volume.data_; - /* Returns the unified volume grid cell corner of a world space coordinate. */ - auto to_global_grid_coords = [&](float3 wP) -> int3 { - /* TODO(fclem): Should we use the render view winmat and not the camera one? */ - const float4x4 &view_matrix = camera.data_get().viewmat; - const float4x4 &projection_matrix = camera.data_get().winmat; - - float3 ndc_coords = math::project_point(projection_matrix * view_matrix, wP); - ndc_coords = (ndc_coords * 0.5f) + float3(0.5f); - - float3 grid_coords = screen_to_volume(projection_matrix, - data.depth_near, - data.depth_far, - data.depth_distribution, - data.coord_scale, - ndc_coords); - /* Round to nearest grid corner. */ - return int3(grid_coords * float3(data.tex_size) + 0.5); - }; - - const BoundBox bbox = *BKE_object_boundbox_get(ob); - int3 min = int3(INT32_MAX); - int3 max = int3(INT32_MIN); - - for (float3 l_corner : bbox.vec) { - float3 w_corner = math::transform_point(float4x4(ob->object_to_world), l_corner); - /* Note that this returns the nearest cell corner coordinate. - * So sub-froxel AABB will effectively return the same coordinate - * for each corner (making it empty and skipped) unless it - * cover the center of the froxel. */ - math::min_max(to_global_grid_coords(w_corner), min, max); - } - return {min, max}; -} - -GridAABB VolumePipeline::grid_aabb_from_view() -{ - return {int3(0), inst_.volume.data_.tex_size}; -} - -VolumeLayer *VolumePipeline::register_and_get_layer(Object *ob) -{ - GridAABB object_aabb = grid_aabb_from_object(ob); - GridAABB view_aabb = grid_aabb_from_view(); - if (object_aabb.intersection(view_aabb).is_empty()) { - /* Skip invisible object with respect to raster grid and bounds density. */ - return nullptr; - } - /* Do linear search in all layers in order. This can be optimized. */ - for (auto &layer : layers_) { - if (!layer->bounds_overlaps(object_aabb)) { - layer->add_object_bound(object_aabb); - return layer.get(); - } - } - /* No non-overlapping layer found. Create new one. */ - int64_t index = layers_.append_and_get_index(std::make_unique(inst_)); - (*layers_[index]).add_object_bound(object_aabb); - return layers_[index].get(); -} - -void VolumePipeline::material_call(MaterialPass &volume_material_pass, - Object *ob, - ResourceHandle res_handle) -{ - if (volume_material_pass.sub_pass == nullptr) { - /* Can happen if shader is not compiled, or if object has been culled. */ - return; - } - - /* TODO(fclem): This should be revisited, `volume_sub_pass()` should not decide on the volume - * visibility. Instead, we should query visibility upstream and not try to even compile the - * shader. */ - PassMain::Sub *object_pass = volume_sub_pass( - *volume_material_pass.sub_pass, inst_.scene, ob, volume_material_pass.gpumat); - if (object_pass) { - /* Possible double work here. Should be relatively insignificant in practice. */ - GridAABB object_aabb = grid_aabb_from_object(ob); - GridAABB view_aabb = grid_aabb_from_view(); - GridAABB visible_aabb = object_aabb.intersection(view_aabb); - /* Invisible volumes should already have been clipped. */ - BLI_assert(visible_aabb.is_empty() == false); - /* TODO(fclem): Use graphic pipeline instead of compute so we can leverage GPU culling, - * resource indexing and other further optimizations. */ - object_pass->push_constant("drw_ResourceID", int(res_handle.resource_index())); - object_pass->push_constant("grid_coords_min", visible_aabb.min); - object_pass->dispatch(math::divide_ceil(visible_aabb.extent(), int3(VOLUME_GROUP_SIZE))); - /* Notify the volume module to enable itself. */ - enabled_ = true; - } -} - -bool VolumePipeline::use_hit_list() const -{ - for (auto &layer : layers_) { - if (layer->use_hit_list) { - return true; - } - } - return false; + inst_.manager->submit(volume_ps_, view); } /** \} */ @@ -993,16 +740,17 @@ void DeferredProbeLayer::begin_sync() prepass_ps_.bind_texture(RBUFS_UTILITY_TEX_SLOT, inst_.pipelines.utility_tx); inst_.bind_uniform_data(&prepass_ps_); - inst_.velocity.bind_resources(prepass_ps_); - inst_.sampling.bind_resources(prepass_ps_); + inst_.velocity.bind_resources(&prepass_ps_); + inst_.sampling.bind_resources(&prepass_ps_); } DRWState state_depth_only = DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_LESS; - /* Only setting up static pass because we don't use motion vectors for light-probes. */ - prepass_double_sided_static_ps_ = &prepass_ps_.sub("DoubleSided"); - prepass_double_sided_static_ps_->state_set(state_depth_only); - prepass_single_sided_static_ps_ = &prepass_ps_.sub("SingleSided"); - prepass_single_sided_static_ps_->state_set(state_depth_only | DRW_STATE_CULL_BACK); + + prepass_double_sided_ps_ = &prepass_ps_.sub("DoubleSided"); + prepass_double_sided_ps_->state_set(state_depth_only); + + prepass_single_sided_ps_ = &prepass_ps_.sub("SingleSided"); + prepass_single_sided_ps_->state_set(state_depth_only | DRW_STATE_CULL_BACK); } { gbuffer_ps_.init(); @@ -1026,9 +774,9 @@ void DeferredProbeLayer::begin_sync() gbuffer_ps_.bind_texture(RBUFS_UTILITY_TEX_SLOT, inst_.pipelines.utility_tx); inst_.bind_uniform_data(&gbuffer_ps_); - inst_.sampling.bind_resources(gbuffer_ps_); - inst_.hiz_buffer.bind_resources(gbuffer_ps_); - inst_.cryptomatte.bind_resources(gbuffer_ps_); + inst_.sampling.bind_resources(&gbuffer_ps_); + inst_.hiz_buffer.bind_resources(&gbuffer_ps_); + inst_.cryptomatte.bind_resources(&gbuffer_ps_); } DRWState state = DRW_STATE_WRITE_COLOR | DRW_STATE_BLEND_CUSTOM | DRW_STATE_DEPTH_EQUAL | @@ -1040,6 +788,12 @@ void DeferredProbeLayer::begin_sync() gbuffer_single_sided_ps_ = &gbuffer_ps_.sub("SingleSided"); gbuffer_single_sided_ps_->state_set(state | DRW_STATE_CULL_BACK); } + + /* Light evaluate resources. */ + { + eGPUTextureUsage usage = GPU_TEXTURE_USAGE_SHADER_READ | GPU_TEXTURE_USAGE_SHADER_WRITE; + dummy_light_tx_.ensure_2d(GPU_RGBA16F, int2(1), usage); + } } void DeferredProbeLayer::end_sync() @@ -1055,12 +809,13 @@ void DeferredProbeLayer::end_sync() pass.bind_image(RBUFS_VALUE_SLOT, &inst_.render_buffers.rp_value_tx); pass.bind_texture(RBUFS_UTILITY_TEX_SLOT, inst_.pipelines.utility_tx); inst_.bind_uniform_data(&pass); - inst_.gbuffer.bind_resources(pass); - inst_.lights.bind_resources(pass); - inst_.shadows.bind_resources(pass); - inst_.sampling.bind_resources(pass); - inst_.hiz_buffer.bind_resources(pass); - inst_.irradiance_cache.bind_resources(pass); + inst_.gbuffer.bind_resources(&pass); + inst_.lights.bind_resources(&pass); + inst_.shadows.bind_resources(&pass); + inst_.sampling.bind_resources(&pass); + inst_.hiz_buffer.bind_resources(&pass); + inst_.reflection_probes.bind_resources(&pass); + inst_.irradiance_cache.bind_resources(&pass); pass.barrier(GPU_BARRIER_TEXTURE_FETCH | GPU_BARRIER_SHADER_IMAGE_ACCESS); pass.draw_procedural(GPU_PRIM_TRIS, 1, 3); } @@ -1069,8 +824,8 @@ void DeferredProbeLayer::end_sync() PassMain::Sub *DeferredProbeLayer::prepass_add(::Material *blender_mat, GPUMaterial *gpumat) { PassMain::Sub *pass = (blender_mat->blend_flag & MA_BL_CULL_BACKFACE) ? - prepass_single_sided_static_ps_ : - prepass_double_sided_static_ps_; + prepass_single_sided_ps_ : + prepass_double_sided_ps_; return &pass->sub(GPU_material_get_name(gpumat)); } @@ -1096,7 +851,6 @@ void DeferredProbeLayer::render(View &view, GPU_framebuffer_bind(prepass_fb); inst_.manager->submit(prepass_ps_, view); - inst_.hiz_buffer.set_source(&inst_.render_buffers.depth_tx); inst_.hiz_buffer.set_dirty(); inst_.lights.set_view(view, extent); inst_.shadows.set_view(view); @@ -1152,127 +906,6 @@ void DeferredProbePipeline::render(View &view, /** \} */ -/* -------------------------------------------------------------------- */ -/** \name Deferred Planar Probe Pipeline - * - * \{ */ - -void PlanarProbePipeline::begin_sync() -{ - { - prepass_ps_.init(); - prepass_ps_.bind_texture(RBUFS_UTILITY_TEX_SLOT, inst_.pipelines.utility_tx); - prepass_ps_.bind_ubo(CLIP_PLANE_BUF, inst_.planar_probes.world_clip_buf_); - inst_.bind_uniform_data(&prepass_ps_); - inst_.sampling.bind_resources(prepass_ps_); - - DRWState state_depth_only = DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_LESS; - - prepass_double_sided_static_ps_ = &prepass_ps_.sub("DoubleSided.Static"); - prepass_double_sided_static_ps_->state_set(state_depth_only); - - prepass_single_sided_static_ps_ = &prepass_ps_.sub("SingleSided.Static"); - prepass_single_sided_static_ps_->state_set(state_depth_only | DRW_STATE_CULL_BACK); - } - { - gbuffer_ps_.init(); - gbuffer_ps_.bind_image(GBUF_CLOSURE_SLOT, &inst_.gbuffer.closure_tx); - gbuffer_ps_.bind_image(GBUF_COLOR_SLOT, &inst_.gbuffer.color_tx); - gbuffer_ps_.bind_image(GBUF_HEADER_SLOT, &inst_.gbuffer.header_tx); - gbuffer_ps_.bind_texture(RBUFS_UTILITY_TEX_SLOT, inst_.pipelines.utility_tx); - inst_.bind_uniform_data(&gbuffer_ps_); - inst_.sampling.bind_resources(gbuffer_ps_); - inst_.hiz_buffer.bind_resources(gbuffer_ps_); - /* Cryptomatte. */ - gbuffer_ps_.bind_image(RBUFS_CRYPTOMATTE_SLOT, &inst_.render_buffers.cryptomatte_tx); - /* RenderPasses & AOVs. */ - gbuffer_ps_.bind_image(RBUFS_COLOR_SLOT, &inst_.render_buffers.rp_color_tx); - gbuffer_ps_.bind_image(RBUFS_VALUE_SLOT, &inst_.render_buffers.rp_value_tx); - inst_.cryptomatte.bind_resources(gbuffer_ps_); - - DRWState state = DRW_STATE_WRITE_COLOR | DRW_STATE_BLEND_CUSTOM | DRW_STATE_DEPTH_EQUAL; - - gbuffer_double_sided_ps_ = &gbuffer_ps_.sub("DoubleSided"); - gbuffer_double_sided_ps_->state_set(state); - - gbuffer_single_sided_ps_ = &gbuffer_ps_.sub("SingleSided"); - gbuffer_single_sided_ps_->state_set(state | DRW_STATE_CULL_BACK); - } - { - PassSimple &pass = eval_light_ps_; - pass.init(); - pass.state_set(DRW_STATE_WRITE_COLOR | DRW_STATE_BLEND_ADD_FULL); - pass.shader_set(inst_.shaders.static_shader_get(DEFERRED_PLANAR_EVAL)); - pass.bind_texture(RBUFS_UTILITY_TEX_SLOT, inst_.pipelines.utility_tx); - inst_.bind_uniform_data(&pass); - inst_.gbuffer.bind_resources(pass); - inst_.lights.bind_resources(pass); - inst_.shadows.bind_resources(pass); - inst_.sampling.bind_resources(pass); - inst_.hiz_buffer.bind_resources(pass); - inst_.reflection_probes.bind_resources(pass); - inst_.irradiance_cache.bind_resources(pass); - pass.barrier(GPU_BARRIER_TEXTURE_FETCH | GPU_BARRIER_SHADER_IMAGE_ACCESS); - pass.draw_procedural(GPU_PRIM_TRIS, 1, 3); - } - - closure_bits_ = CLOSURE_NONE; -} - -void PlanarProbePipeline::end_sync() -{ - /* No-op for now. */ -} - -PassMain::Sub *PlanarProbePipeline::prepass_add(::Material *blender_mat, GPUMaterial *gpumat) -{ - PassMain::Sub *pass = (blender_mat->blend_flag & MA_BL_CULL_BACKFACE) ? - prepass_single_sided_static_ps_ : - prepass_double_sided_static_ps_; - return &pass->sub(GPU_material_get_name(gpumat)); -} - -PassMain::Sub *PlanarProbePipeline::material_add(::Material *blender_mat, GPUMaterial *gpumat) -{ - eClosureBits closure_bits = shader_closure_bits_from_flag(gpumat); - closure_bits_ |= closure_bits; - - PassMain::Sub *pass = (blender_mat->blend_flag & MA_BL_CULL_BACKFACE) ? - gbuffer_single_sided_ps_ : - gbuffer_double_sided_ps_; - return &pass->sub(GPU_material_get_name(gpumat)); -} - -void PlanarProbePipeline::render(View &view, Framebuffer &combined_fb, int layer_id, int2 extent) -{ - GPU_debug_group_begin("Planar.Capture"); - - inst_.hiz_buffer.set_source(&inst_.planar_probes.depth_tx_, layer_id); - inst_.hiz_buffer.set_dirty(); - - GPU_framebuffer_bind(combined_fb); - GPU_framebuffer_clear_depth(combined_fb, 1.0f); - inst_.manager->submit(prepass_ps_, view); - - inst_.lights.set_view(view, extent); - inst_.shadows.set_view(view); - inst_.irradiance_cache.set_view(view); - - inst_.gbuffer.acquire(extent, closure_bits_); - - inst_.hiz_buffer.update(); - GPU_framebuffer_bind(combined_fb); - GPU_framebuffer_clear_color(combined_fb, float4(0.0f, 0.0f, 0.0f, 1.0f)); - inst_.manager->submit(gbuffer_ps_, view); - inst_.manager->submit(eval_light_ps_, view); - - inst_.gbuffer.release(); - - GPU_debug_group_end(); -} - -/** \} */ - /* -------------------------------------------------------------------- */ /** \name Capture Pipeline * @@ -1295,14 +928,9 @@ void CapturePipeline::sync() inst_.bind_uniform_data(&surface_ps_); } -PassMain::Sub *CapturePipeline::surface_material_add(::Material *blender_mat, GPUMaterial *gpumat) +PassMain::Sub *CapturePipeline::surface_material_add(GPUMaterial *gpumat) { - PassMain::Sub &sub_pass = surface_ps_.sub(GPU_material_get_name(gpumat)); - GPUPass *gpupass = GPU_material_get_pass(gpumat); - sub_pass.shader_set(GPU_pass_shader_get(gpupass)); - sub_pass.push_constant("is_double_sided", - !(blender_mat->blend_flag & MA_BL_LIGHTPROBE_VOLUME_DOUBLE_SIDED)); - return &sub_pass; + return &surface_ps_.sub(GPU_material_get_name(gpumat)); } void CapturePipeline::render(View &view) diff --git a/source/blender/draw/engines/eevee_next/eevee_pipeline.hh b/source/blender/draw/engines/eevee_next/eevee_pipeline.hh index eed7380fcac..65bb066759d 100644 --- a/source/blender/draw/engines/eevee_next/eevee_pipeline.hh +++ b/source/blender/draw/engines/eevee_next/eevee_pipeline.hh @@ -107,19 +107,14 @@ class ShadowPipeline { private: Instance &inst_; - /* Shadow update pass. */ - PassMain render_ps_ = {"Shadow.Surface"}; - /* Shadow surface render sub-passes. */ - PassMain::Sub *surface_double_sided_ps_ = nullptr; - PassMain::Sub *surface_single_sided_ps_ = nullptr; + PassMain surface_ps_ = {"Shadow.Surface"}; public: ShadowPipeline(Instance &inst) : inst_(inst){}; - PassMain::Sub *surface_material_add(::Material *material, GPUMaterial *gpumat); + PassMain::Sub *surface_material_add(GPUMaterial *gpumat); void sync(); - void render(View &view); }; @@ -177,7 +172,10 @@ class ForwardPipeline { /** \name Deferred lighting. * \{ */ -struct DeferredLayerBase { +class DeferredLayer { + private: + Instance &inst_; + PassMain prepass_ps_ = {"Prepass"}; PassMain::Sub *prepass_single_sided_static_ps_ = nullptr; PassMain::Sub *prepass_single_sided_moving_ps_ = nullptr; @@ -188,19 +186,14 @@ struct DeferredLayerBase { PassMain::Sub *gbuffer_single_sided_ps_ = nullptr; PassMain::Sub *gbuffer_double_sided_ps_ = nullptr; - /* Closures bits from the materials in this pass. */ - eClosureBits closure_bits_ = CLOSURE_NONE; -}; - -class DeferredLayer : private DeferredLayerBase { - private: - Instance &inst_; - /* Evaluate all light objects contribution. */ PassSimple eval_light_ps_ = {"EvalLights"}; /* Combine direct and indirect light contributions and apply BSDF color. */ PassSimple combine_ps_ = {"Combine"}; + /* Closures bits from the materials in this pass. */ + eClosureBits closure_bits_ = CLOSURE_NONE; + /** * Accumulation textures for all stages of lighting evaluation (Light, SSR, SSSS, SSGI ...). * These are split and separate from the main radiance buffer in order to accumulate light for @@ -217,9 +210,7 @@ class DeferredLayer : private DeferredLayerBase { GPUTexture *indirect_reflect_tx_ = nullptr; GPUTexture *indirect_refract_tx_ = nullptr; - /* TODO(fclem): This should be a TextureFromPool. */ Texture radiance_behind_tx_ = {"radiance_behind_tx"}; - /* TODO(fclem): This shouldn't be part of the pipeline but of the view. */ Texture radiance_feedback_tx_ = {"radiance_feedback_tx"}; float4x4 radiance_feedback_persmat_; @@ -275,133 +266,19 @@ class DeferredPipeline { * * \{ */ -struct GridAABB { - int3 min, max; - - GridAABB(int3 min_, int3 max_) : min(min_), max(max_){}; - - /** Returns the intersection between this AABB and the \a other AABB. */ - GridAABB intersection(const GridAABB &other) const - { - return {math::max(this->min, other.min), math::min(this->max, other.max)}; - } - - /** Returns the extent of the volume. Undefined if AABB is empty. */ - int3 extent() const - { - return max - min; - } - - /** Returns true if volume covers nothing or is negative. */ - bool is_empty() const - { - return math::reduce_min(max - min) <= 0; - } -}; - -/** - * A volume layer contains a list of non-overlapping volume objects. - */ -class VolumeLayer { - public: - bool use_hit_list = false; - bool is_empty = true; - bool finalized = false; - - private: - Instance &inst_; - - PassMain volume_layer_ps_ = {"Volume.Layer"}; - /* Sub-passes of volume_layer_ps. */ - PassMain::Sub *occupancy_ps_; - PassMain::Sub *material_ps_; - /* List of bounds from all objects contained inside this pass. */ - Vector object_bounds_; - - public: - VolumeLayer(Instance &inst) : inst_(inst) - { - this->sync(); - } - - PassMain::Sub *occupancy_add(const Object *ob, - const ::Material *blender_mat, - GPUMaterial *gpumat); - PassMain::Sub *material_add(const Object *ob, - const ::Material *blender_mat, - GPUMaterial *gpumat); - - /* Return true if the given bounds overlaps any of the contained object in this layer. */ - bool bounds_overlaps(const GridAABB &object_aabb) const - { - for (const GridAABB &other_aabb : object_bounds_) { - if (object_aabb.intersection(other_aabb).is_empty() == false) { - return true; - } - } - return false; - } - - void add_object_bound(const GridAABB &object_aabb) - { - object_bounds_.append(object_aabb); - } - - void sync(); - void render(View &view, Texture &occupancy_tx); -}; - class VolumePipeline { private: Instance &inst_; - Vector> layers_; - - /* True if any volume (any object type) creates a volume draw-call. Enables the volume module. */ - bool enabled_ = false; + PassMain volume_ps_ = {"Volume.Objects"}; public: VolumePipeline(Instance &inst) : inst_(inst){}; + PassMain::Sub *volume_material_add(GPUMaterial *gpumat); + void sync(); - void render(View &view, Texture &occupancy_tx); - - /** - * Returns correct volume layer for a given object and add the object to the layer. - * Returns nullptr if the object is not visible at all. - */ - VolumeLayer *register_and_get_layer(Object *ob); - - /** - * Creates a volume material call. - * If any call to this function result in a valid draw-call, then the volume module will be - * enabled. - */ - void material_call(MaterialPass &volume_material_pass, Object *ob, ResourceHandle res_handle); - - bool is_enabled() const - { - return enabled_; - } - - /* Returns true if any volume layer uses the hist list. */ - bool use_hit_list() const; - - private: - /** - * Returns Axis aligned bounding box in the volume grid. - * Used for frustum culling and volumes overlapping detection. - * Represents min and max grid corners covered by a volume. - * So a volume covering the first froxel will have min={0,0,0} and max={1,1,1}. - * A volume with min={0,0,0} and max={0,0,0} covers nothing. - */ - GridAABB grid_aabb_from_object(Object *ob); - - /** - * Returns the view entire AABB. Used for clipping object bounds. - * Remember that these are cells corners, so this extents to `tex_size`. - */ - GridAABB grid_aabb_from_view(); + void render(View &view); }; /** \} */ @@ -409,12 +286,25 @@ class VolumePipeline { /* -------------------------------------------------------------------- */ /** \name Deferred Probe Capture. * \{ */ -class DeferredProbeLayer : DeferredLayerBase { +class DeferredProbeLayer { private: Instance &inst_; + PassMain prepass_ps_ = {"Prepass"}; + PassMain::Sub *prepass_single_sided_ps_ = nullptr; + PassMain::Sub *prepass_double_sided_ps_ = nullptr; + + PassMain gbuffer_ps_ = {"Shading"}; + PassMain::Sub *gbuffer_single_sided_ps_ = nullptr; + PassMain::Sub *gbuffer_double_sided_ps_ = nullptr; + PassSimple eval_light_ps_ = {"EvalLights"}; + /* Closures bits from the materials in this pass. */ + eClosureBits closure_bits_; + + Texture dummy_light_tx_ = {"dummy_light_accum_tx"}; + public: DeferredProbeLayer(Instance &inst) : inst_(inst){}; @@ -445,33 +335,6 @@ class DeferredProbePipeline { /** \} */ -/* -------------------------------------------------------------------- */ -/** \name Deferred Planar Probe Capture. - * \{ */ - -class PlanarProbePipeline : DeferredLayerBase { - private: - Instance &inst_; - - PassSimple eval_light_ps_ = {"EvalLights"}; - - /* Closures bits from the materials in this pass. */ - eClosureBits closure_bits_ = CLOSURE_NONE; - - public: - PlanarProbePipeline(Instance &inst) : inst_(inst){}; - - void begin_sync(); - void end_sync(); - - PassMain::Sub *prepass_add(::Material *material, GPUMaterial *gpumat); - PassMain::Sub *material_add(::Material *material, GPUMaterial *gpumat); - - void render(View &view, Framebuffer &combined_fb, int layer_id, int2 extent); -}; - -/** \} */ - /* -------------------------------------------------------------------- */ /** \name Capture Pipeline * @@ -486,7 +349,7 @@ class CapturePipeline { public: CapturePipeline(Instance &inst) : inst_(inst){}; - PassMain::Sub *surface_material_add(::Material *blender_mat, GPUMaterial *gpumat); + PassMain::Sub *surface_material_add(GPUMaterial *gpumat); void sync(); void render(View &view); @@ -584,7 +447,6 @@ class PipelineModule { WorldPipeline world; WorldVolumePipeline world_volume; DeferredProbePipeline probe; - PlanarProbePipeline planar; DeferredPipeline deferred; ForwardPipeline forward; ShadowPipeline shadow; @@ -599,7 +461,6 @@ class PipelineModule { world(inst), world_volume(inst), probe(inst), - planar(inst), deferred(inst), forward(inst), shadow(inst), @@ -609,7 +470,6 @@ class PipelineModule { void begin_sync() { probe.begin_sync(); - planar.begin_sync(); deferred.begin_sync(); forward.sync(); shadow.sync(); @@ -620,71 +480,56 @@ class PipelineModule { void end_sync() { probe.end_sync(); - planar.end_sync(); deferred.end_sync(); } - PassMain::Sub *material_add(Object * /*ob*/ /* TODO remove. */, + PassMain::Sub *material_add(Object *ob, ::Material *blender_mat, GPUMaterial *gpumat, eMaterialPipeline pipeline_type, - eMaterialProbe probe_capture) + bool probe_capture) { - if (probe_capture == MAT_PROBE_REFLECTION) { + if (probe_capture) { switch (pipeline_type) { - case MAT_PIPE_PREPASS_DEFERRED: + case MAT_PIPE_DEFERRED_PREPASS: return probe.prepass_add(blender_mat, gpumat); case MAT_PIPE_DEFERRED: return probe.material_add(blender_mat, gpumat); default: - BLI_assert_unreachable(); - break; - } - } - if (probe_capture == MAT_PROBE_PLANAR) { - switch (pipeline_type) { - case MAT_PIPE_PREPASS_PLANAR: - return planar.prepass_add(blender_mat, gpumat); - case MAT_PIPE_DEFERRED: - return planar.material_add(blender_mat, gpumat); - default: - BLI_assert_unreachable(); break; } } switch (pipeline_type) { - case MAT_PIPE_PREPASS_DEFERRED: + case MAT_PIPE_DEFERRED_PREPASS: return deferred.prepass_add(blender_mat, gpumat, false); - case MAT_PIPE_PREPASS_FORWARD: + case MAT_PIPE_FORWARD_PREPASS: + if (GPU_material_flag_get(gpumat, GPU_MATFLAG_TRANSPARENT)) { + return forward.prepass_transparent_add(ob, blender_mat, gpumat); + } return forward.prepass_opaque_add(blender_mat, gpumat, false); - case MAT_PIPE_PREPASS_OVERLAP: - BLI_assert_msg(0, "Overlap prepass should register to the forward pipeline directly."); - return nullptr; - case MAT_PIPE_PREPASS_DEFERRED_VELOCITY: + case MAT_PIPE_DEFERRED_PREPASS_VELOCITY: return deferred.prepass_add(blender_mat, gpumat, true); - case MAT_PIPE_PREPASS_FORWARD_VELOCITY: + case MAT_PIPE_FORWARD_PREPASS_VELOCITY: + if (GPU_material_flag_get(gpumat, GPU_MATFLAG_TRANSPARENT)) { + return forward.prepass_transparent_add(ob, blender_mat, gpumat); + } return forward.prepass_opaque_add(blender_mat, gpumat, true); case MAT_PIPE_DEFERRED: return deferred.material_add(blender_mat, gpumat); case MAT_PIPE_FORWARD: + if (GPU_material_flag_get(gpumat, GPU_MATFLAG_TRANSPARENT)) { + return forward.material_transparent_add(ob, blender_mat, gpumat); + } return forward.material_opaque_add(blender_mat, gpumat); + case MAT_PIPE_VOLUME: + return volume.volume_material_add(gpumat); case MAT_PIPE_SHADOW: - return shadow.surface_material_add(blender_mat, gpumat); + return shadow.surface_material_add(gpumat); case MAT_PIPE_CAPTURE: - return capture.surface_material_add(blender_mat, gpumat); - - case MAT_PIPE_VOLUME_OCCUPANCY: - case MAT_PIPE_VOLUME_MATERIAL: - BLI_assert_msg(0, "Volume shaders must register to the volume pipeline directly."); - return nullptr; - - case MAT_PIPE_PREPASS_PLANAR: - /* Should be handled by the `probe_capture == MAT_PROBE_PLANAR` case. */ - BLI_assert_unreachable(); - return nullptr; + return capture.surface_material_add(gpumat); } return nullptr; } diff --git a/source/blender/draw/engines/eevee_next/eevee_planar_probes.cc b/source/blender/draw/engines/eevee_next/eevee_planar_probes.cc deleted file mode 100644 index f1d39c8c14d..00000000000 --- a/source/blender/draw/engines/eevee_next/eevee_planar_probes.cc +++ /dev/null @@ -1,152 +0,0 @@ -/* SPDX-FileCopyrightText: 2023 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -#include "eevee_planar_probes.hh" -#include "eevee_instance.hh" - -namespace blender::eevee { - -using namespace blender::math; - -/* -------------------------------------------------------------------- */ -/** \name Planar Probe - * \{ */ - -void PlanarProbe::sync(const float4x4 &world_to_object, - float clipping_offset, - float influence_distance) -{ - this->plane_to_world = float4x4(world_to_object); - this->plane_to_world.z_axis() = normalize(this->plane_to_world.z_axis()) * influence_distance; - this->world_to_plane = invert(this->plane_to_world); - this->clipping_offset = clipping_offset; -} - -void PlanarProbe::set_view(const draw::View &view, int layer_id) -{ - this->viewmat = view.viewmat() * reflection_matrix_get(); - this->winmat = view.winmat(); - this->world_to_object_transposed = float3x4(transpose(world_to_plane)); - this->normal = normalize(plane_to_world.z_axis()); - - bool view_is_below_plane = dot(view.location() - plane_to_world.location(), - plane_to_world.z_axis()) < 0.0; - if (view_is_below_plane) { - this->normal = -this->normal; - } - this->layer_id = layer_id; -} - -float4x4 PlanarProbe::reflection_matrix_get() -{ - return plane_to_world * from_scale(float3(1, 1, -1)) * world_to_plane; -} - -float4 PlanarProbe::reflection_clip_plane_get() -{ - return float4(-normal, dot(normal, plane_to_world.location()) - clipping_offset); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Planar Probe Module - * \{ */ - -void PlanarProbeModule::init() -{ - if (assign_if_different(update_probes_, !probes_.is_empty())) { - instance_.sampling.reset(); - } -} - -void PlanarProbeModule::begin_sync() -{ - for (PlanarProbe &probe : probes_.values()) { - probe.is_probe_used = false; - } -} - -void PlanarProbeModule::sync_object(Object *ob, ObjectHandle &ob_handle) -{ - const ::LightProbe *light_probe = (::LightProbe *)ob->data; - if (light_probe->type != LIGHTPROBE_TYPE_PLANE) { - return; - } - - PlanarProbe &probe = find_or_insert(ob_handle); - probe.sync(float4x4(ob->object_to_world), light_probe->clipsta, light_probe->distinf); - probe.is_probe_used = true; -} - -void PlanarProbeModule::end_sync() -{ - probes_.remove_if([](const PlanarProbes::Item &item) { return !item.value.is_probe_used; }); - - /* When first planar probes are enabled it can happen that the first sample is off. */ - if (!update_probes_ && !probes_.is_empty()) { - DRW_viewport_request_redraw(); - } -} - -void PlanarProbeModule::set_view(const draw::View &main_view, int2 main_view_extent) -{ - const int64_t num_probes = probes_.size(); - - /* TODO resolution percentage. */ - int2 extent = main_view_extent; - int layer_count = num_probes; - - if (num_probes == 0) { - /* Create valid dummy texture. */ - extent = int2(1); - layer_count = 1; - } - - eGPUTextureUsage usage = GPU_TEXTURE_USAGE_ATTACHMENT | GPU_TEXTURE_USAGE_SHADER_READ; - radiance_tx_.ensure_2d_array(GPU_R11F_G11F_B10F, extent, layer_count, usage); - depth_tx_.ensure_2d_array(GPU_DEPTH_COMPONENT32F, extent, layer_count, usage); - - int resource_index = 0; - for (PlanarProbe &probe : probes_.values()) { - if (resource_index == PLANAR_PROBES_MAX) { - break; - } - - PlanarProbeResources &res = resources_[resource_index]; - - /* TODO Cull out of view planars. */ - - probe.set_view(main_view, resource_index); - probe_planar_buf_[resource_index] = probe; - - res.view.sync(probe.viewmat, probe.winmat); - - world_clip_buf_.plane = probe.reflection_clip_plane_get(); - world_clip_buf_.push_update(); - - res.combined_fb.ensure(GPU_ATTACHMENT_TEXTURE_LAYER(depth_tx_, resource_index), - GPU_ATTACHMENT_TEXTURE_LAYER(radiance_tx_, resource_index)); - - instance_.pipelines.planar.render(res.view, res.combined_fb, resource_index, extent); - - resource_index++; - } - - if (resource_index < PLANAR_PROBES_MAX) { - /* Tag the end of the array. */ - probe_planar_buf_[resource_index].layer_id = -1; - } - probe_planar_buf_.push_update(); -} - -PlanarProbe &PlanarProbeModule::find_or_insert(ObjectHandle &ob_handle) -{ - PlanarProbe &planar_probe = probes_.lookup_or_add_default(ob_handle.object_key.hash()); - return planar_probe; -} - -/** \} */ - -} // namespace blender::eevee diff --git a/source/blender/draw/engines/eevee_next/eevee_planar_probes.hh b/source/blender/draw/engines/eevee_next/eevee_planar_probes.hh deleted file mode 100644 index a4412f85c3e..00000000000 --- a/source/blender/draw/engines/eevee_next/eevee_planar_probes.hh +++ /dev/null @@ -1,127 +0,0 @@ -/* SPDX-FileCopyrightText: 2023 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -/** \file - * \ingroup eevee - */ - -#pragma once - -#include "eevee_shader_shared.hh" - -#include "BKE_cryptomatte.hh" - -extern "C" { -struct Material; -} - -namespace blender::eevee { - -class Instance; -class HiZBuffer; -struct ObjectHandle; - -/* -------------------------------------------------------------------- */ -/** \name Planar Probe - * \{ */ - -struct PlanarProbe : ProbePlanarData { - /* Copy of object matrices. */ - float4x4 plane_to_world; - float4x4 world_to_plane; - /* Offset to the clipping plane in the normal direction. */ - float clipping_offset; - /* Index in the resource array. */ - int resource_index; - /* Pruning flag. */ - bool is_probe_used = false; - - public: - void sync(const float4x4 &world_to_object, float clipping_offset, float influence_distance); - - /** - * Update the ProbePlanarData part of the struct. - * `view` is the view we want to render this probe with. - */ - void set_view(const draw::View &view, int layer_id); - - /** - * Create the reflection clip plane equation that clips along the XY plane of the given - * transform. The `clip_offset` will push the clip plane a bit further to avoid missing pixels in - * reflections. The transform does not need to be normalized but is expected to be orthogonal. - * \note Only works after `set_view` was called. - */ - float4 reflection_clip_plane_get(); - - private: - /** - * Create the reflection matrix that reflect along the XY plane of the given transform. - * The transform does not need to be normalized but is expected to be orthogonal. - */ - float4x4 reflection_matrix_get(); -}; - -struct PlanarProbeResources : NonCopyable { - Framebuffer combined_fb = {"planar.combined_fb"}; - draw::View view = {"planar.view"}; -}; - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Planar Probe Module - * \{ */ - -class PlanarProbeModule { - using PlanarProbes = Map; - - private: - Instance &instance_; - - PlanarProbes probes_; - std::array resources_; - - Texture radiance_tx_ = {"planar.radiance_tx"}; - Texture depth_tx_ = {"planar.depth_tx"}; - - ClipPlaneBuf world_clip_buf_ = {"world_clip_buf"}; - ProbePlanarDataBuf probe_planar_buf_ = {"probe_planar_buf"}; - - bool update_probes_ = false; - - public: - PlanarProbeModule(Instance &instance) : instance_(instance) {} - - void init(); - void begin_sync(); - void sync_object(Object *ob, ObjectHandle &ob_handle); - void end_sync(); - - void set_view(const draw::View &main_view, int2 main_view_extent); - - template void bind_resources(PassType &pass) - { - /* Disable filter to avoid interpolation with missing background. */ - GPUSamplerState no_filter = GPUSamplerState::default_sampler(); - pass.bind_ubo(PLANAR_PROBE_BUF_SLOT, &probe_planar_buf_); - pass.bind_texture(PLANAR_PROBE_RADIANCE_TEX_SLOT, &radiance_tx_, no_filter); - pass.bind_texture(PLANAR_PROBE_DEPTH_TEX_SLOT, &depth_tx_); - } - - bool enabled() const - { - return update_probes_; - } - - private: - PlanarProbe &find_or_insert(ObjectHandle &ob_handle); - - friend class Instance; - friend class HiZBuffer; - friend class PlanarProbePipeline; -}; - -/** \} */ - -} // namespace blender::eevee diff --git a/source/blender/draw/engines/eevee_next/eevee_raytrace.cc b/source/blender/draw/engines/eevee_next/eevee_raytrace.cc index f0f6ee554dd..da96ed84779 100644 --- a/source/blender/draw/engines/eevee_next/eevee_raytrace.cc +++ b/source/blender/draw/engines/eevee_next/eevee_raytrace.cc @@ -62,7 +62,7 @@ void RayTraceModule::sync() pass.bind_ssbo("ray_dispatch_buf", &ray_dispatch_buf_); pass.bind_ssbo("denoise_dispatch_buf", &denoise_dispatch_buf_); inst_.bind_uniform_data(&pass); - inst_.gbuffer.bind_resources(pass); + inst_.gbuffer.bind_resources(&pass); pass.dispatch(&tile_classify_dispatch_size_); pass.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS | GPU_BARRIER_SHADER_STORAGE); } @@ -86,8 +86,8 @@ void RayTraceModule::sync() pass.bind_texture(RBUFS_UTILITY_TEX_SLOT, inst_.pipelines.utility_tx); pass.bind_image("out_ray_data_img", &ray_data_tx_); pass.bind_ssbo("tiles_coord_buf", &ray_tiles_buf_); - inst_.sampling.bind_resources(pass); - inst_.gbuffer.bind_resources(pass); + inst_.sampling.bind_resources(&pass); + inst_.gbuffer.bind_resources(&pass); pass.dispatch(ray_dispatch_buf_); pass.barrier(GPU_BARRIER_SHADER_STORAGE | GPU_BARRIER_TEXTURE_FETCH | GPU_BARRIER_SHADER_IMAGE_ACCESS); @@ -96,23 +96,6 @@ void RayTraceModule::sync() for (auto type : IndexRange(3)) { PassSimple &pass = PASS_VARIATION(trace_, type, _ps_); pass.init(); - if (inst_.planar_probes.enabled() && (&pass == &trace_reflect_ps_)) { - /* Inject planar tracing in the same pass as reflection tracing. */ - PassSimple::Sub &sub = pass.sub("Trace.Planar"); - sub.shader_set(inst_.shaders.static_shader_get(RAY_TRACE_PLANAR)); - sub.bind_ssbo("tiles_coord_buf", &ray_tiles_buf_); - sub.bind_image("ray_data_img", &ray_data_tx_); - sub.bind_image("ray_time_img", &ray_time_tx_); - sub.bind_image("ray_radiance_img", &ray_radiance_tx_); - sub.bind_texture("depth_tx", &depth_tx); - inst_.bind_uniform_data(&sub); - inst_.planar_probes.bind_resources(sub); - inst_.irradiance_cache.bind_resources(sub); - inst_.reflection_probes.bind_resources(sub); - /* TODO(@fclem): Use another dispatch with only tiles that touches planar captures. */ - sub.dispatch(ray_dispatch_buf_); - sub.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS); - } pass.shader_set(inst_.shaders.static_shader_get(SHADER_VARIATION(RAY_TRACE_SCREEN_, type))); pass.bind_ssbo("tiles_coord_buf", &ray_tiles_buf_); pass.bind_image("ray_data_img", &ray_data_tx_); @@ -121,10 +104,9 @@ void RayTraceModule::sync() pass.bind_texture("depth_tx", &depth_tx); pass.bind_image("ray_radiance_img", &ray_radiance_tx_); inst_.bind_uniform_data(&pass); - inst_.hiz_buffer.bind_resources(pass); - inst_.sampling.bind_resources(pass); - inst_.irradiance_cache.bind_resources(pass); - inst_.reflection_probes.bind_resources(pass); + inst_.hiz_buffer.bind_resources(&pass); + inst_.sampling.bind_resources(&pass); + inst_.reflection_probes.bind_resources(&pass); pass.dispatch(ray_dispatch_buf_); pass.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS); } @@ -138,8 +120,7 @@ void RayTraceModule::sync() pass.bind_image("ray_radiance_img", &ray_radiance_tx_); pass.bind_texture("depth_tx", &depth_tx); inst_.bind_uniform_data(&pass); - inst_.irradiance_cache.bind_resources(pass); - inst_.reflection_probes.bind_resources(pass); + inst_.reflection_probes.bind_resources(&pass); pass.dispatch(ray_dispatch_buf_); pass.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS); } @@ -159,8 +140,8 @@ void RayTraceModule::sync() pass.bind_image("out_hit_depth_img", &hit_depth_tx_); pass.bind_image("tile_mask_img", &tile_mask_tx_); inst_.bind_uniform_data(&pass); - inst_.sampling.bind_resources(pass); - inst_.gbuffer.bind_resources(pass); + inst_.sampling.bind_resources(&pass); + inst_.gbuffer.bind_resources(&pass); pass.dispatch(denoise_dispatch_buf_); pass.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS); } @@ -179,7 +160,7 @@ void RayTraceModule::sync() pass.bind_image("in_variance_img", &hit_variance_tx_); pass.bind_image("out_variance_img", &denoise_variance_tx_); pass.bind_ssbo("tiles_coord_buf", &denoise_tiles_buf_); - inst_.sampling.bind_resources(pass); + inst_.sampling.bind_resources(&pass); pass.dispatch(denoise_dispatch_buf_); pass.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS); } @@ -195,8 +176,8 @@ void RayTraceModule::sync() pass.bind_image("tile_mask_img", &tile_mask_tx_); pass.bind_ssbo("tiles_coord_buf", &denoise_tiles_buf_); inst_.bind_uniform_data(&pass); - inst_.sampling.bind_resources(pass); - inst_.gbuffer.bind_resources(pass); + inst_.sampling.bind_resources(&pass); + inst_.gbuffer.bind_resources(&pass); pass.dispatch(denoise_dispatch_buf_); pass.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS); } @@ -393,13 +374,10 @@ RayTraceResult RayTraceModule::trace(RayTraceBuffer &rt_buffer, if (use_bilateral_denoise) { denoise_buf->denoised_bilateral_tx.acquire(extent, RAYTRACE_RADIANCE_FORMAT); denoised_bilateral_tx_ = denoise_buf->denoised_bilateral_tx; - /* Swap back for one last use. */ - TextureFromPool::swap(tile_mask_tx_, denoise_buf->tilemask_history_tx); inst_.manager->submit(*denoise_bilateral_ps, render_view); /* Swap after last use. */ - TextureFromPool::swap(tile_mask_tx_, denoise_buf->tilemask_history_tx); TextureFromPool::swap(denoise_buf->denoised_temporal_tx, denoise_buf->radiance_history_tx); TextureFromPool::swap(denoise_variance_tx_, denoise_buf->variance_history_tx); diff --git a/source/blender/draw/engines/eevee_next/eevee_reflection_probes.cc b/source/blender/draw/engines/eevee_next/eevee_reflection_probes.cc index 52fee08161c..b0490ab4aaf 100644 --- a/source/blender/draw/engines/eevee_next/eevee_reflection_probes.cc +++ b/source/blender/draw/engines/eevee_next/eevee_reflection_probes.cc @@ -11,9 +11,176 @@ namespace blender::eevee { -/* -------------------------------------------------------------------- */ -/** \name ProbeLocationFinder - * \{ */ +void ReflectionProbeModule::init() +{ + if (probes_.is_empty()) { + ReflectionProbeData init_probe_data = {}; + init_probe_data.layer = -1; + for (int i : IndexRange(REFLECTION_PROBES_MAX)) { + data_buf_[i] = init_probe_data; + } + + /* Initialize the world probe. */ + ReflectionProbeData world_probe_data{}; + world_probe_data.layer = 0; + world_probe_data.layer_subdivision = 0; + world_probe_data.area_index = 0; + world_probe_data.pos = float4(0.0f); + data_buf_[0] = world_probe_data; + + ReflectionProbe world_probe; + world_probe.type = ReflectionProbe::Type::World; + world_probe.do_update_data = true; + world_probe.do_render = true; + world_probe.index = 0; + world_probe.clipping_distances = float2(1.0f, 10.0f); + probes_.add(world_object_key_, world_probe); + + probes_tx_.ensure_2d_array(GPU_RGBA16F, + int2(max_resolution_), + init_num_probes_, + GPU_TEXTURE_USAGE_SHADER_WRITE | GPU_TEXTURE_USAGE_SHADER_READ, + nullptr, + 9999); + GPU_texture_mipmap_mode(probes_tx_, true, true); + probes_tx_.clear(float4(0.0f)); + + recalc_lod_factors(); + data_buf_.push_update(); + } + + { + PassSimple &pass = remap_ps_; + pass.init(); + pass.shader_set(instance_.shaders.static_shader_get(REFLECTION_PROBE_REMAP)); + pass.bind_texture("cubemap_tx", &cubemap_tx_); + pass.bind_image("octahedral_img", &probes_tx_); + pass.bind_ubo(REFLECTION_PROBE_BUF_SLOT, data_buf_); + pass.push_constant("reflection_probe_index", &reflection_probe_index_); + pass.dispatch(&dispatch_probe_pack_); + } + + { + PassSimple &pass = update_irradiance_ps_; + pass.init(); + pass.shader_set(instance_.shaders.static_shader_get(REFLECTION_PROBE_UPDATE_IRRADIANCE)); + pass.push_constant("reflection_probe_index", &reflection_probe_index_); + pass.bind_image("irradiance_atlas_img", &instance_.irradiance_cache.irradiance_atlas_tx_); + bind_resources(&pass); + pass.dispatch(int2(1, 1)); + } +} +void ReflectionProbeModule::begin_sync() +{ + for (ReflectionProbe &reflection_probe : probes_.values()) { + if (reflection_probe.type == ReflectionProbe::Type::Probe) { + reflection_probe.is_probe_used = false; + } + } + + update_probes_this_sample_ = false; + if (update_probes_next_sample_) { + update_probes_this_sample_ = true; + instance_.sampling.reset(); + } +} + +int ReflectionProbeModule::needed_layers_get() const +{ + int max_layer = 0; + for (const ReflectionProbe &probe : probes_.values()) { + const ReflectionProbeData &probe_data = data_buf_[probe.index]; + max_layer = max_ii(max_layer, probe_data.layer); + } + return max_layer + 1; +} + +static int layer_subdivision_for(const int max_resolution, + const eLightProbeResolution probe_resolution) +{ + int i_probe_resolution = int(probe_resolution); + return max_ii(int(log2(max_resolution)) - i_probe_resolution, 0); +} + +void ReflectionProbeModule::sync_world(::World *world, WorldHandle & /*ob_handle*/) +{ + const ReflectionProbe &probe = probes_.lookup(world_object_key_); + ReflectionProbeData &probe_data = data_buf_[probe.index]; + int requested_layer_subdivision = layer_subdivision_for( + max_resolution_, static_cast(world->probe_resolution)); + if (requested_layer_subdivision != probe_data.layer_subdivision) { + ReflectionProbeData new_probe_data = find_empty_reflection_probe_data( + requested_layer_subdivision); + probe_data.layer = new_probe_data.layer; + probe_data.layer_subdivision = new_probe_data.layer_subdivision; + probe_data.area_index = new_probe_data.area_index; + do_world_update_set(true); + } +} + +void ReflectionProbeModule::sync_object(Object *ob, ObjectHandle &ob_handle) +{ + const ::LightProbe *light_probe = (::LightProbe *)ob->data; + if (light_probe->type != LIGHTPROBE_TYPE_CUBE) { + return; + } + const bool is_dirty = ob_handle.recalc != 0; + int subdivision = layer_subdivision_for( + max_resolution_, static_cast(light_probe->resolution)); + ReflectionProbe &probe = find_or_insert(ob_handle, subdivision); + probe.do_render |= is_dirty; + probe.is_probe_used = true; + + /* Only update data when rerendering the probes to reduce flickering. */ + if (!instance_.do_probe_sync()) { + update_probes_next_sample_ = true; + return; + } + + probe.do_update_data |= is_dirty; + probe.clipping_distances = float2(light_probe->clipsta, light_probe->clipend); + + ReflectionProbeData &probe_data = data_buf_[probe.index]; + if (probe_data.layer_subdivision != subdivision) { + ReflectionProbeData new_probe_data = find_empty_reflection_probe_data(subdivision); + probe_data.layer = new_probe_data.layer; + probe_data.layer_subdivision = new_probe_data.layer_subdivision; + probe_data.area_index = new_probe_data.area_index; + } + + probe_data.pos = float4x4(ob->object_to_world) * float4(0.0, 0.0, 0.0, 1.0); +} + +ReflectionProbe &ReflectionProbeModule::find_or_insert(ObjectHandle &ob_handle, + int subdivision_level) +{ + ReflectionProbe &reflection_probe = probes_.lookup_or_add_cb( + ob_handle.object_key.hash_value, [this, subdivision_level]() { + ReflectionProbe probe; + ReflectionProbeData probe_data = find_empty_reflection_probe_data(subdivision_level); + + probe.do_update_data = true; + probe.do_render = true; + probe.type = ReflectionProbe::Type::Probe; + probe.index = reflection_probe_data_index_max() + 1; + + data_buf_[probe.index] = probe_data; + return probe; + }); + + return reflection_probe; +} + +int ReflectionProbeModule::reflection_probe_data_index_max() const +{ + int result = -1; + for (const ReflectionProbe &probe : probes_.values()) { + if (probe.type != ReflectionProbe::Type::Unused) { + result = max_ii(result, probe.index); + } + } + return result; +} /** * Utility class to find a location in the probes_tx_ that can be used to store a new probe in @@ -70,17 +237,18 @@ class ProbeLocationFinder { * The input probe data can be stored in a different subdivision level and should be converted to * the subdivision level what we are looking for. */ - void mark_space_used(const ReflectionProbeAtlasCoordinate &coord) + void mark_space_used(const ReflectionProbeData &probe_data) { - const int shift_right = max_ii(coord.layer_subdivision - subdivision_level_, 0); - const int shift_left = max_ii(subdivision_level_ - coord.layer_subdivision, 0); + const int shift_right = max_ii(probe_data.layer_subdivision - subdivision_level_, 0); + const int shift_left = max_ii(subdivision_level_ - probe_data.layer_subdivision, 0); const int spots_per_dimension = 1 << shift_left; - const int probes_per_dimension_in_probe_data = 1 << coord.layer_subdivision; - const int2 pos_in_probe_data = int2(coord.area_index % probes_per_dimension_in_probe_data, - coord.area_index / probes_per_dimension_in_probe_data); + const int probes_per_dimension_in_probe_data = 1 << probe_data.layer_subdivision; + const int2 pos_in_probe_data = int2(probe_data.area_index % probes_per_dimension_in_probe_data, + probe_data.area_index / + probes_per_dimension_in_probe_data); const int2 pos_in_location_finder = int2((pos_in_probe_data.x >> shift_right) << shift_left, (pos_in_probe_data.y >> shift_right) << shift_left); - const int layer_offset = coord.layer * probes_per_layer_; + const int layer_offset = probe_data.layer * probes_per_layer_; for (const int y : IndexRange(spots_per_dimension)) { for (const int x : IndexRange(spots_per_dimension)) { const int2 pos = pos_in_location_finder + int2(x, y); @@ -99,268 +267,51 @@ class ProbeLocationFinder { * Asserts when no free spot is found. ProbeLocationFinder should always be initialized with an * additional layer to make sure that there is always a free spot. */ - ReflectionProbeAtlasCoordinate first_free_spot() const + ReflectionProbeData first_free_spot() const { - ReflectionProbeAtlasCoordinate result; + ReflectionProbeData result = {}; result.layer_subdivision = subdivision_level_; for (int index : taken_spots_.index_range()) { if (!taken_spots_[index]) { - result.layer = index / probes_per_layer_; - result.area_index = index % probes_per_layer_; + int layer = index / probes_per_layer_; + int area_index = index % probes_per_layer_; + result.layer = layer; + result.area_index = area_index; return result; } } + BLI_assert_unreachable(); return result; } }; -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Reflection Probe Module - * \{ */ - -eLightProbeResolution ReflectionProbeModule::reflection_probe_resolution() const -{ - switch (instance_.scene->eevee.gi_cubemap_resolution) { - case 64: - return LIGHT_PROBE_RESOLUTION_64; - case 128: - return LIGHT_PROBE_RESOLUTION_128; - case 256: - return LIGHT_PROBE_RESOLUTION_256; - case 512: - return LIGHT_PROBE_RESOLUTION_512; - case 1024: - return LIGHT_PROBE_RESOLUTION_1024; - default: - return LIGHT_PROBE_RESOLUTION_2048; - } - return LIGHT_PROBE_RESOLUTION_2048; -} - -void ReflectionProbeModule::init() -{ - if (!is_initialized) { - is_initialized = true; - - /* Initialize the world probe. */ - - ReflectionProbe world_probe = {}; - world_probe.type = ReflectionProbe::Type::WORLD; - world_probe.is_probe_used = true; - world_probe.do_render = true; - world_probe.clipping_distances = float2(1.0f, 10.0f); - world_probe.world_to_probe_transposed = float3x4::identity(); - world_probe.influence_shape = SHAPE_ELIPSOID; - world_probe.parallax_shape = SHAPE_ELIPSOID; - /* Full influence. */ - world_probe.influence_scale = 0.0f; - world_probe.influence_bias = 1.0f; - world_probe.parallax_distance = 1e10f; - - probes_.add(world_object_key_, world_probe); - - probes_tx_.ensure_2d_array(GPU_RGBA16F, - int2(max_resolution_), - 1, - GPU_TEXTURE_USAGE_SHADER_WRITE | GPU_TEXTURE_USAGE_SHADER_READ, - nullptr, - 9999); - GPU_texture_mipmap_mode(probes_tx_, true, true); - probes_tx_.clear(float4(0.0f)); - } - - { - PassSimple &pass = remap_ps_; - pass.init(); - pass.shader_set(instance_.shaders.static_shader_get(REFLECTION_PROBE_REMAP)); - pass.bind_texture("cubemap_tx", &cubemap_tx_); - pass.bind_image("octahedral_img", &probes_tx_); - pass.push_constant("probe_coord_packed", reinterpret_cast(&reflection_probe_coord_)); - pass.push_constant("world_coord_packed", reinterpret_cast(&world_probe_coord_)); - pass.dispatch(&dispatch_probe_pack_); - } - - { - PassSimple &pass = update_irradiance_ps_; - pass.init(); - pass.shader_set(instance_.shaders.static_shader_get(REFLECTION_PROBE_UPDATE_IRRADIANCE)); - pass.push_constant("world_coord_packed", reinterpret_cast(&world_probe_coord_)); - pass.bind_image("irradiance_atlas_img", &instance_.irradiance_cache.irradiance_atlas_tx_); - pass.bind_texture("reflection_probes_tx", &probes_tx_); - pass.dispatch(int2(1, 1)); - } -} - -void ReflectionProbeModule::begin_sync() -{ - for (ReflectionProbe &reflection_probe : probes_.values()) { - if (reflection_probe.type == ReflectionProbe::Type::PROBE) { - reflection_probe.is_probe_used = false; - } - } - - update_probes_this_sample_ = false; - if (update_probes_next_sample_) { - update_probes_this_sample_ = true; - instance_.sampling.reset(); - } - - { - PassSimple &pass = select_ps_; - pass.init(); - pass.shader_set(instance_.shaders.static_shader_get(REFLECTION_PROBE_SELECT)); - pass.push_constant("reflection_probe_count", &reflection_probe_count_); - pass.bind_ssbo("reflection_probe_buf", &data_buf_); - instance_.irradiance_cache.bind_resources(pass); - pass.dispatch(&dispatch_probe_select_); - pass.barrier(GPU_BARRIER_UNIFORM); - } -} - -int ReflectionProbeModule::needed_layers_get() const -{ - int max_layer = 0; - for (const ReflectionProbe &probe : probes_.values()) { - max_layer = max_ii(max_layer, probe.atlas_coord.layer); - } - return max_layer + 1; -} - -static int layer_subdivision_for(const int max_resolution, - const eLightProbeResolution probe_resolution) -{ - int i_probe_resolution = int(probe_resolution); - return max_ii(int(log2(max_resolution)) - i_probe_resolution, 0); -} - -void ReflectionProbeModule::sync_world(::World *world, WorldHandle & /*ob_handle*/) -{ - ReflectionProbe &probe = probes_.lookup(world_object_key_); - - eLightProbeResolution resolution = static_cast(world->probe_resolution); - int layer_subdivision = layer_subdivision_for(max_resolution_, resolution); - if (layer_subdivision != probe.atlas_coord.layer_subdivision) { - probe.atlas_coord = find_empty_atlas_region(layer_subdivision); - do_world_update_set(true); - } - world_probe_coord_ = probe.atlas_coord; -} - -void ReflectionProbeModule::sync_world_lookdev() -{ - ReflectionProbe &probe = probes_.lookup(world_object_key_); - const eLightProbeResolution resolution = reflection_probe_resolution(); - int layer_subdivision = layer_subdivision_for(max_resolution_, resolution); - if (layer_subdivision != probe.atlas_coord.layer_subdivision) { - probe.atlas_coord = find_empty_atlas_region(layer_subdivision); - } - world_probe_coord_ = probe.atlas_coord; - - do_world_update_set(true); -} - -void ReflectionProbeModule::sync_object(Object *ob, ObjectHandle &ob_handle) -{ - const ::LightProbe &light_probe = *(::LightProbe *)ob->data; - if (light_probe.type != LIGHTPROBE_TYPE_SPHERE) { - return; - } - - ReflectionProbe &probe = probes_.lookup_or_add_cb(ob_handle.object_key.hash(), []() { - ReflectionProbe probe = {}; - probe.do_render = true; - probe.type = ReflectionProbe::Type::PROBE; - return probe; - }); - - probe.do_render |= (ob_handle.recalc != 0); - probe.is_probe_used = true; - - const bool probe_sync_active = instance_.do_reflection_probe_sync(); - if (!probe_sync_active && probe.do_render) { - update_probes_next_sample_ = true; - } - - /* Only update data when rerendering the probes to reduce flickering. */ - if (!probe_sync_active) { - return; - } - - probe.clipping_distances = float2(light_probe.clipsta, light_probe.clipend); - - int subdivision = layer_subdivision_for(max_resolution_, reflection_probe_resolution()); - if (probe.atlas_coord.layer_subdivision != subdivision) { - probe.atlas_coord = find_empty_atlas_region(subdivision); - } - - bool use_custom_parallax = (light_probe.flag & LIGHTPROBE_FLAG_CUSTOM_PARALLAX) != 0; - float parallax_distance = use_custom_parallax ? - max_ff(light_probe.distpar, light_probe.distinf) : - light_probe.distinf; - float influence_distance = light_probe.distinf; - float influence_falloff = light_probe.falloff; - probe.influence_shape = (light_probe.attenuation_type == LIGHTPROBE_SHAPE_BOX) ? SHAPE_CUBOID : - SHAPE_ELIPSOID; - probe.parallax_shape = (light_probe.parallax_type == LIGHTPROBE_SHAPE_BOX) ? SHAPE_CUBOID : - SHAPE_ELIPSOID; - - float4x4 object_to_world = math::scale(float4x4(ob->object_to_world), - float3(influence_distance)); - probe.location = object_to_world.location(); - probe.volume = math::abs(math::determinant(object_to_world)); - probe.world_to_probe_transposed = float3x4(math::transpose(math::invert(object_to_world))); - probe.influence_scale = 1.0 / max_ff(1e-8f, influence_falloff); - probe.influence_bias = probe.influence_scale; - probe.parallax_distance = parallax_distance / influence_distance; -} - -ReflectionProbeAtlasCoordinate ReflectionProbeModule::find_empty_atlas_region( +ReflectionProbeData ReflectionProbeModule::find_empty_reflection_probe_data( int subdivision_level) const { ProbeLocationFinder location_finder(needed_layers_get() + 1, subdivision_level); - for (const ReflectionProbe &probe : probes_.values()) { - if (probe.atlas_coord.layer != -1) { - location_finder.mark_space_used(probe.atlas_coord); - } + for (const ReflectionProbeData &data : + Span(data_buf_.data(), reflection_probe_data_index_max() + 1)) + { + location_finder.mark_space_used(data); } return location_finder.first_free_spot(); } void ReflectionProbeModule::end_sync() { - const bool probes_removed = remove_unused_probes(); - const bool world_updated = do_world_update_get(); - const bool only_world = has_only_world_probe(); - const int number_layers_needed = needed_layers_get(); - const int current_layers = probes_tx_.depth(); - const bool resize_layers = current_layers < number_layers_needed; + remove_unused_probes(); - const bool rerender_all_probes = resize_layers || world_updated; - if (rerender_all_probes) { - for (ReflectionProbe &probe : probes_.values()) { - probe.do_render = true; - } - } - - const bool do_update = instance_.do_reflection_probe_sync() || (only_world && world_updated); + const bool do_update = instance_.do_probe_sync() || + (has_only_world_probe() && do_world_update_get()); if (!do_update) { - /* World has changed this sample, but probe update isn't initialized this sample. */ - if (world_updated && !only_world) { - update_probes_next_sample_ = true; - } - if (update_probes_next_sample_ && !update_probes_this_sample_) { - DRW_viewport_request_redraw(); - } - - if (!update_probes_next_sample_ && probes_removed) { - data_buf_.push_update(); - } return; } + int number_layers_needed = needed_layers_get(); + int current_layers = probes_tx_.depth(); + bool resize_layers = current_layers < number_layers_needed; + if (resize_layers) { probes_tx_.ensure_2d_array(GPU_RGBA16F, int2(max_resolution_), @@ -370,26 +321,73 @@ void ReflectionProbeModule::end_sync() 9999); GPU_texture_mipmap_mode(probes_tx_, true, true); probes_tx_.clear(float4(0.0f)); + + for (ReflectionProbe &probe : probes_.values()) { + probe.do_update_data = true; + probe.do_render = true; + } } - /* Check reset probe updating as we will rendering probes. */ - if (update_probes_this_sample_ || only_world) { - update_probes_next_sample_ = false; - } + recalc_lod_factors(); data_buf_.push_update(); } -bool ReflectionProbeModule::remove_unused_probes() +void ReflectionProbeModule::remove_unused_probes() { - const int64_t removed_count = probes_.remove_if( - [](const ReflectionProbes::Item &item) { return !item.value.is_probe_used; }); - - if (removed_count > 0) { - instance_.sampling.reset(); - } - return removed_count > 0; + bool found = false; + do { + found = false; + uint64_t key_to_remove = 0; + for (const Map::Item &item : probes_.items()) { + const ReflectionProbe &probe = item.value; + if (probe.type == ReflectionProbe::Type::Probe && !probe.is_probe_used) { + key_to_remove = item.key; + found = true; + break; + } + } + if (found) { + probes_.remove(key_to_remove); + } + } while (found); } +void ReflectionProbeModule::remove_reflection_probe_data(int reflection_probe_data_index) +{ + int max_index = reflection_probe_data_index_max(); + BLI_assert_msg(reflection_probe_data_index <= max_index, + "Trying to remove reflection probes when it isn't part of the reflection probe " + "data. This can also happens when the state is set to " + "ReflectionProbe::Type::UNUSED, before removing the data."); + for (int index = reflection_probe_data_index; index < max_index; index++) { + data_buf_[index] = data_buf_[index + 1]; + } + for (ReflectionProbe &probe : probes_.values()) { + if (probe.index == reflection_probe_data_index) { + probe.index = -1; + } + if (probe.index > reflection_probe_data_index) { + probe.index--; + } + } + data_buf_[max_index].layer = -1; + BLI_assert(reflection_probe_data_index_max() == max_index - 1); +} + +void ReflectionProbeModule::recalc_lod_factors() +{ + for (ReflectionProbeData &probe_data : data_buf_) { + if (probe_data.layer == -1) { + return; + } + + const float bias = 0.0; + const float lod_factor = + bias + + 0.5 * log(float(square_i(probes_tx_.width() >> probe_data.layer_subdivision))) / log(2.0); + probe_data.lod_factor = lod_factor; + } +} bool ReflectionProbeModule::do_world_update_get() const { const ReflectionProbe &world_probe = probes_.lookup(world_object_key_); @@ -400,7 +398,8 @@ void ReflectionProbeModule::do_world_update_set(bool value) { ReflectionProbe &world_probe = probes_.lookup(world_object_key_); world_probe.do_render = value; - do_world_update_irradiance_set(value); + world_probe.do_world_irradiance_update = value; + instance_.irradiance_cache.do_update_world_ = true; } void ReflectionProbeModule::do_world_update_irradiance_set(bool value) @@ -414,36 +413,93 @@ bool ReflectionProbeModule::has_only_world_probe() const return probes_.size() == 1; } +/* -------------------------------------------------------------------- */ +/** \name Debugging + * + * \{ */ + +void ReflectionProbeModule::debug_print() const +{ + std::ostream &os = std::cout; + for (const ReflectionProbe &probe : probes_.values()) { + os << probe; + + if (probe.index != -1) { + os << data_buf_[probe.index]; + } + } +} + +std::ostream &operator<<(std::ostream &os, const ReflectionProbeData &probe_data) +{ + os << " - layer: " << probe_data.layer; + os << " subdivision: " << probe_data.layer_subdivision; + os << " area: " << probe_data.area_index; + os << "\n"; + + return os; +} + +std::ostream &operator<<(std::ostream &os, const ReflectionProbe &probe) +{ + switch (probe.type) { + case ReflectionProbe::Type::Unused: { + os << "UNUSED\n"; + + break; + } + case ReflectionProbe::Type::World: { + os << "WORLD"; + os << " is_dirty: " << probe.do_update_data; + os << " index: " << probe.index; + os << "\n"; + break; + } + case ReflectionProbe::Type::Probe: { + os << "PROBE"; + os << " is_dirty: " << probe.do_update_data; + os << " is_used: " << probe.is_probe_used; + os << " index: " << probe.index; + os << "\n"; + break; + } + } + return os; +} + +/** \} */ + std::optional ReflectionProbeModule::update_info_pop( const ReflectionProbe::Type probe_type) { - const bool do_probe_sync = instance_.do_reflection_probe_sync(); - const bool only_world = has_only_world_probe(); + const bool do_probe_sync = instance_.do_probe_sync(); const int max_shift = int(log2(max_resolution_)); - for (ReflectionProbe &probe : probes_.values()) { - if (!probe.do_render && !probe.do_world_irradiance_update) { + for (const Map::Item &item : probes_.items()) { + if (!item.value.do_render && !item.value.do_world_irradiance_update) { continue; } - if (probe.type != probe_type) { + if (probe_type == ReflectionProbe::Type::World && item.value.type != probe_type) { + return std::nullopt; + } + if (probe_type == ReflectionProbe::Type::Probe && item.value.type != probe_type) { continue; } /* Do not update this probe during this sample. */ - if (probe.type == ReflectionProbe::Type::WORLD && !only_world && !do_probe_sync) { - continue; - } - if (probe.type == ReflectionProbe::Type::PROBE && !do_probe_sync) { + if (item.value.type == ReflectionProbe::Type::Probe && !do_probe_sync) { continue; } + ReflectionProbeData &probe_data = data_buf_[item.value.index]; ReflectionProbeUpdateInfo info = {}; - info.probe_type = probe.type; - info.atlas_coord = probe.atlas_coord; - info.resolution = 1 << (max_shift - probe.atlas_coord.layer_subdivision - 1); - info.clipping_distances = probe.clipping_distances; - info.probe_pos = probe.location; - info.do_render = probe.do_render; - info.do_world_irradiance_update = probe.do_world_irradiance_update; + info.probe_type = item.value.type; + info.object_key = item.key; + info.resolution = 1 << (max_shift - probe_data.layer_subdivision - 1); + info.clipping_distances = item.value.clipping_distances; + info.probe_pos = float3(probe_data.pos); + info.do_render = item.value.do_render; + info.do_world_irradiance_update = item.value.do_world_irradiance_update; + ReflectionProbe &probe = probes_.lookup(item.key); probe.do_render = false; probe.do_world_irradiance_update = false; @@ -457,22 +513,37 @@ std::optional ReflectionProbeModule::update_info_pop( return info; } + /* Check reset probe updating as we completed rendering all Probes. */ + if (probe_type == ReflectionProbe::Type::Probe && update_probes_this_sample_ && + update_probes_next_sample_) + { + update_probes_next_sample_ = false; + } + return std::nullopt; } -void ReflectionProbeModule::remap_to_octahedral_projection( - const ReflectionProbeAtlasCoordinate &atlas_coord) +void ReflectionProbeModule::remap_to_octahedral_projection(uint64_t object_key) { - int resolution = max_resolution_ >> atlas_coord.layer_subdivision; + const ReflectionProbe &probe = probes_.lookup(object_key); + const ReflectionProbeData &probe_data = data_buf_[probe.index]; + /* Update shader parameters that change per dispatch. */ - reflection_probe_coord_ = atlas_coord; - dispatch_probe_pack_ = int3(int2(ceil_division(resolution, REFLECTION_PROBE_GROUP_SIZE)), 1); + reflection_probe_index_ = probe.index; + dispatch_probe_pack_ = int3(int2(ceil_division(max_resolution_ >> probe_data.layer_subdivision, + REFLECTION_PROBE_GROUP_SIZE)), + 1); instance_.manager->submit(remap_ps_); } void ReflectionProbeModule::update_world_irradiance() { + const ReflectionProbe &probe = probes_.lookup(world_object_key_); + + /* Update shader parameters that change per dispatch. */ + reflection_probe_index_ = probe.index; + instance_.manager->submit(update_irradiance_ps_); } @@ -481,109 +552,4 @@ void ReflectionProbeModule::update_probes_texture_mipmaps() GPU_texture_update_mipmap_chain(probes_tx_); } -void ReflectionProbeModule::set_view(View & /*view*/) -{ - Vector probe_active; - for (auto &probe : probes_.values()) { - /* Last slot is reserved for the world probe. */ - if (reflection_probe_count_ >= REFLECTION_PROBES_MAX - 1) { - break; - } - probe.recalc_lod_factors(probes_tx_.width()); - /* World is always considered active and added last. */ - if (probe.type == ReflectionProbe::Type::WORLD) { - continue; - } - /* TODO(fclem): Culling. */ - probe_active.append(&probe); - } - - /* Stable sorting of probes. */ - std::sort(probe_active.begin(), - probe_active.end(), - [](const ReflectionProbe *a, const ReflectionProbe *b) { - if (a->volume != b->volume) { - /* Smallest first. */ - return a->volume < b->volume; - } - /* Volumes are identical. Any arbitrary criteria can be used to sort them. - * Use position to avoid unstable result caused by depsgraph non deterministic eval - * order. This could also become a priority parameter. */ - float3 _a = a->location; - float3 _b = b->location; - if (_a.x != _b.x) { - return _a.x < _b.x; - } - else if (_a.y != _b.y) { - return _a.y < _b.y; - } - else if (_a.z != _b.z) { - return _a.z < _b.z; - } - else { - /* Fallback to memory address, since there's no good alternative.*/ - return a < b; - } - }); - - /* Push all sorted data to the UBO. */ - int probe_id = 0; - for (auto &probe : probe_active) { - data_buf_[probe_id++] = *probe; - } - /* Add world probe at the end. */ - data_buf_[probe_id++] = probes_.lookup(world_object_key_); - /* Tag the end of the array. */ - if (probe_id < REFLECTION_PROBES_MAX) { - data_buf_[probe_id].atlas_coord.layer = -1; - } - data_buf_.push_update(); - - /* Add one for world probe. */ - reflection_probe_count_ = probe_active.size() + 1; - dispatch_probe_select_.x = divide_ceil_u(reflection_probe_count_, - REFLECTION_PROBE_SELECT_GROUP_SIZE); - instance_.manager->submit(select_ps_); -} - -ReflectionProbeAtlasCoordinate ReflectionProbeModule::world_atlas_coord_get() const -{ - return probes_.lookup(world_object_key_).atlas_coord; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Debugging - * - * \{ */ - -void ReflectionProbeModule::debug_print() const -{ - std::ostream &os = std::cout; - for (const ReflectionProbe &probe : probes_.values()) { - switch (probe.type) { - case ReflectionProbe::Type::WORLD: { - os << "WORLD"; - os << " do_render: " << probe.do_render; - os << "\n"; - break; - } - case ReflectionProbe::Type::PROBE: { - os << "PROBE"; - os << " do_render: " << probe.do_render; - os << " is_used: " << probe.is_probe_used; - os << "\n"; - break; - } - } - os << " - layer: " << probe.atlas_coord.layer; - os << " subdivision: " << probe.atlas_coord.layer_subdivision; - os << " area: " << probe.atlas_coord.area_index; - os << "\n"; - } -} - -/** \} */ - } // namespace blender::eevee diff --git a/source/blender/draw/engines/eevee_next/eevee_reflection_probes.hh b/source/blender/draw/engines/eevee_next/eevee_reflection_probes.hh index 9a4641bfab4..d1134dc95f9 100644 --- a/source/blender/draw/engines/eevee_next/eevee_reflection_probes.hh +++ b/source/blender/draw/engines/eevee_next/eevee_reflection_probes.hh @@ -28,15 +28,14 @@ struct ReflectionProbeUpdateInfo; /** \name Reflection Probe * \{ */ -struct ReflectionProbe : ReflectionProbeData { - enum class Type { - WORLD, - PROBE, - } type; +struct ReflectionProbe { + enum Type { Unused, World, Probe }; - /* Used to sort the probes by priority. */ - float volume; + Type type = Type::Unused; + /* Probe data needs to be updated. + * TODO: Remove this flag? */ + bool do_update_data = false; /* Should the area in the probes_tx_ be updated? */ bool do_render = false; bool do_world_irradiance_update = false; @@ -48,24 +47,31 @@ struct ReflectionProbe : ReflectionProbeData { */ bool is_probe_used = false; + /** + * Index into ReflectionProbeDataBuf. + * -1 = not added yet + */ + int index = -1; + /** * Far and near clipping distances for rendering */ float2 clipping_distances; - ReflectionProbe() + /** + * Check if the probe needs to be updated during this sample. + */ + bool needs_update() const { - this->atlas_coord.layer_subdivision = -1; - this->atlas_coord.layer = -1; - this->atlas_coord.area_index = -1; - } - - void recalc_lod_factors(int atlas_resolution) - { - const float probe_resolution = atlas_resolution >> atlas_coord.layer_subdivision; - const float bias = 0.0; - const float lod_factor = bias + 0.5 * log(float(square_i(probe_resolution))) / log(2.0); - this->lod_factor = lod_factor; + switch (type) { + case Type::Unused: + return false; + case Type::World: + return do_update_data || do_render; + case Type::Probe: + return (do_update_data || do_render) && is_probe_used; + } + return false; } }; @@ -76,9 +82,10 @@ struct ReflectionProbe : ReflectionProbeData { * \{ */ class ReflectionProbeModule { - using ReflectionProbes = Map; - private: + /** The max number of probes to initially allocate. */ + static constexpr int init_num_probes_ = 1; + /** * The maximum resolution of a cube-map side. * @@ -88,22 +95,17 @@ class ReflectionProbeModule { static constexpr uint64_t world_object_key_ = 0; - bool is_initialized = false; Instance &instance_; ReflectionProbeDataBuf data_buf_; - ReflectionProbes probes_; - - ReflectionProbe world_probe_data; + Map probes_; /** Probes texture stored in octahedral mapping. */ Texture probes_tx_ = {"Probes"}; PassSimple remap_ps_ = {"Probe.CubemapToOctahedral"}; PassSimple update_irradiance_ps_ = {"Probe.UpdateIrradiance"}; - PassSimple select_ps_ = {"Probe.Select"}; - int3 dispatch_probe_pack_ = int3(1); - int3 dispatch_probe_select_ = int3(1); + int3 dispatch_probe_pack_ = int3(0); /** * Texture containing a cube-map where the probe should be rendering to. @@ -113,12 +115,6 @@ class ReflectionProbeModule { Texture cubemap_tx_ = {"Probe.Cubemap"}; /** Index of the probe being updated. */ int reflection_probe_index_ = 0; - /** Updated Probe coordinates in the atlas. */ - ReflectionProbeAtlasCoordinate reflection_probe_coord_; - /** World coordinates in the atlas. */ - ReflectionProbeAtlasCoordinate world_probe_coord_; - /** Number of the probe to process in the select phase. */ - int reflection_probe_count_ = 0; bool update_probes_next_sample_ = false; bool update_probes_this_sample_ = false; @@ -129,57 +125,66 @@ class ReflectionProbeModule { void init(); void begin_sync(); void sync_world(::World *world, WorldHandle &ob_handle); - void sync_world_lookdev(); void sync_object(Object *ob, ObjectHandle &ob_handle); void end_sync(); - template void bind_resources(PassType &pass) + template void bind_resources(draw::detail::PassBase *pass) { - pass.bind_texture(REFLECTION_PROBE_TEX_SLOT, &probes_tx_); - pass.bind_ubo(REFLECTION_PROBE_BUF_SLOT, &data_buf_); + pass->bind_texture(REFLECTION_PROBE_TEX_SLOT, &probes_tx_); + pass->bind_ubo(REFLECTION_PROBE_BUF_SLOT, &data_buf_); } bool do_world_update_get() const; void do_world_update_set(bool value); void do_world_update_irradiance_set(bool value); - void set_view(View &view); - void debug_print() const; - ReflectionProbeAtlasCoordinate world_atlas_coord_get() const; - private: + ReflectionProbe &find_or_insert(ObjectHandle &ob_handle, int subdivision_level); + /** Get the number of layers that is needed to store probes. */ int needed_layers_get() const; - bool remove_unused_probes(); + void remove_unused_probes(); + void recalc_lod_factors(); + + /* TODO: also add _len() which is a max + 1. */ + /* Get the number of reflection probe data elements. */ + int reflection_probe_data_index_max() const; + + /** + * Remove reflection probe data from the module. + * Ensures that data_buf is sequential and cube-maps are relinked to its corresponding data. + */ + void remove_reflection_probe_data(int reflection_probe_data_index); /** * Create a reflection probe data element that points to an empty spot in the cubemap that can * hold a texture with the given subdivision_level. */ - ReflectionProbeAtlasCoordinate find_empty_atlas_region(int subdivision_level) const; + ReflectionProbeData find_empty_reflection_probe_data(int subdivision_level) const; /** * Pop the next reflection probe that requires to be updated. */ std::optional update_info_pop(ReflectionProbe::Type probe_type); - - void remap_to_octahedral_projection(const ReflectionProbeAtlasCoordinate &atlas_coord); + void remap_to_octahedral_projection(uint64_t object_key); void update_probes_texture_mipmaps(); void update_world_irradiance(); bool has_only_world_probe() const; - eLightProbeResolution reflection_probe_resolution() const; - /* Capture View requires access to the cube-maps texture for frame-buffer configuration. */ friend class CaptureView; /* Instance requires access to #update_probes_this_sample_ */ friend class Instance; }; +std::ostream &operator<<(std::ostream &os, const ReflectionProbeModule &module); +std::ostream &operator<<(std::ostream &os, const ReflectionProbeData &probe_data); +std::ostream &operator<<(std::ostream &os, const ReflectionProbe &probe); + /** \} */ /* -------------------------------------------------------------------- */ @@ -195,8 +200,7 @@ struct ReflectionProbeUpdateInfo { int resolution; float2 clipping_distances; - - ReflectionProbeAtlasCoordinate atlas_coord; + uint64_t object_key; bool do_render; bool do_world_irradiance_update; diff --git a/source/blender/draw/engines/eevee_next/eevee_sampling.hh b/source/blender/draw/engines/eevee_next/eevee_sampling.hh index 4c15dec532e..aa7f67d7740 100644 --- a/source/blender/draw/engines/eevee_next/eevee_sampling.hh +++ b/source/blender/draw/engines/eevee_next/eevee_sampling.hh @@ -86,9 +86,9 @@ class Sampling { return reset_; } - template void bind_resources(PassType &pass) + template void bind_resources(draw::detail::PassBase *pass) { - pass.bind_ssbo(SAMPLING_BUF_SLOT, &data_); + pass->bind_ssbo(SAMPLING_BUF_SLOT, &data_); } /* Returns a pseudo random number in [0..1] range. Each dimension are de-correlated. diff --git a/source/blender/draw/engines/eevee_next/eevee_shader.cc b/source/blender/draw/engines/eevee_next/eevee_shader.cc index 09046d4f12e..43c7d32eb81 100644 --- a/source/blender/draw/engines/eevee_next/eevee_shader.cc +++ b/source/blender/draw/engines/eevee_next/eevee_shader.cc @@ -15,10 +15,6 @@ #include "eevee_shader.hh" -#include "eevee_shadow.hh" - -#include "BLI_assert.h" - namespace blender::eevee { /* -------------------------------------------------------------------- */ @@ -98,14 +94,10 @@ const char *ShaderModule::static_shader_create_info_name_get(eShaderType shader_ return "eevee_deferred_light"; case DEFERRED_CAPTURE_EVAL: return "eevee_deferred_capture_eval"; - case DEFERRED_PLANAR_EVAL: - return "eevee_deferred_planar_eval"; case HIZ_DEBUG: return "eevee_hiz_debug"; case HIZ_UPDATE: return "eevee_hiz_update"; - case HIZ_UPDATE_LAYER: - return "eevee_hiz_update_layer"; case MOTION_BLUR_GATHER: return "eevee_motion_blur_gather"; case MOTION_BLUR_TILE_DILATE: @@ -186,8 +178,6 @@ const char *ShaderModule::static_shader_create_info_name_get(eShaderType shader_ return "eevee_ray_generate_refract"; case RAY_TRACE_FALLBACK: return "eevee_ray_trace_fallback"; - case RAY_TRACE_PLANAR: - return "eevee_ray_trace_planar"; case RAY_TRACE_SCREEN_DIFFUSE: return "eevee_ray_trace_screen_diffuse"; case RAY_TRACE_SCREEN_REFLECT: @@ -210,8 +200,6 @@ const char *ShaderModule::static_shader_create_info_name_get(eShaderType shader_ return "eevee_reflection_probe_remap"; case REFLECTION_PROBE_UPDATE_IRRADIANCE: return "eevee_reflection_probe_update_irradiance"; - case REFLECTION_PROBE_SELECT: - return "eevee_reflection_probe_select"; case SHADOW_CLIPMAP_CLEAR: return "eevee_shadow_clipmap_clear"; case SHADOW_DEBUG: @@ -240,10 +228,6 @@ const char *ShaderModule::static_shader_create_info_name_get(eShaderType shader_ return "eevee_shadow_tag_usage_surfels"; case SHADOW_TILEMAP_TAG_USAGE_TRANSPARENT: return "eevee_shadow_tag_usage_transparent"; - case SHADOW_PAGE_TILE_CLEAR: - return "eevee_shadow_page_tile_clear"; - case SHADOW_PAGE_TILE_STORE: - return "eevee_shadow_page_tile_store"; case SHADOW_TILEMAP_TAG_USAGE_VOLUME: return "eevee_shadow_tag_usage_volume"; case SUBSURFACE_CONVOLVE: @@ -262,8 +246,6 @@ const char *ShaderModule::static_shader_create_info_name_get(eShaderType shader_ return "eevee_surfel_ray"; case VOLUME_INTEGRATION: return "eevee_volume_integration"; - case VOLUME_OCCUPANCY_CONVERT: - return "eevee_volume_occupancy_convert"; case VOLUME_RESOLVE: return "eevee_volume_resolve"; case VOLUME_SCATTER: @@ -344,9 +326,14 @@ void ShaderModule::material_create_info_ammend(GPUMaterial *gpumat, GPUCodegenOu } } + /* WORKAROUND: Needed because node_tree isn't present in test shaders. */ + if (pipeline_type == MAT_PIPE_DEFERRED) { + info.additional_info("eevee_render_pass_out"); + } + if (GPU_material_flag_get(gpumat, GPU_MATFLAG_AO) && ELEM(pipeline_type, MAT_PIPE_FORWARD, MAT_PIPE_DEFERRED) && - geometry_type_has_surface(geometry_type)) + ELEM(geometry_type, MAT_GEOM_MESH, MAT_GEOM_CURVES)) { info.define("MAT_AMBIENT_OCCLUSION"); } @@ -354,23 +341,15 @@ void ShaderModule::material_create_info_ammend(GPUMaterial *gpumat, GPUCodegenOu if (GPU_material_flag_get(gpumat, GPU_MATFLAG_TRANSPARENT)) { info.define("MAT_TRANSPARENT"); /* Transparent material do not have any velocity specific pipeline. */ - if (pipeline_type == MAT_PIPE_PREPASS_FORWARD_VELOCITY) { - pipeline_type = MAT_PIPE_PREPASS_FORWARD; + if (pipeline_type == MAT_PIPE_FORWARD_PREPASS_VELOCITY) { + pipeline_type = MAT_PIPE_FORWARD_PREPASS; } } - if (pipeline_type == MAT_PIPE_PREPASS_FORWARD) { - info.define("MAT_FORWARD"); - } - - bool supports_render_passes = (pipeline_type == MAT_PIPE_DEFERRED); - /* Opaque forward do support AOVs and render pass if not using transparency. */ - if (!GPU_material_flag_get(gpumat, GPU_MATFLAG_TRANSPARENT) && - (pipeline_type == MAT_PIPE_FORWARD)) { - supports_render_passes = true; - } - - if (supports_render_passes) { + if (GPU_material_flag_get(gpumat, GPU_MATFLAG_TRANSPARENT) == false && + pipeline_type == MAT_PIPE_FORWARD) + { + /* Opaque forward do support AOVs and render pass if not using transparency. */ info.additional_info("eevee_render_pass_out"); info.additional_info("eevee_cryptomatte_out"); } @@ -432,7 +411,6 @@ void ShaderModule::material_create_info_ammend(GPUMaterial *gpumat, GPUCodegenOu } info.vertex_inputs_.clear(); break; - case MAT_GEOM_VOLUME: case MAT_GEOM_VOLUME_OBJECT: case MAT_GEOM_VOLUME_WORLD: /** Volume grid attributes come from 3D textures. Transfer attributes to samplers. */ @@ -443,11 +421,8 @@ void ShaderModule::material_create_info_ammend(GPUMaterial *gpumat, GPUCodegenOu break; } - const bool do_vertex_attrib_load = !ELEM(geometry_type, - MAT_GEOM_WORLD, - MAT_GEOM_VOLUME_WORLD, - MAT_GEOM_VOLUME_OBJECT, - MAT_GEOM_VOLUME); + const bool do_vertex_attrib_load = !ELEM( + geometry_type, MAT_GEOM_WORLD, MAT_GEOM_VOLUME_WORLD, MAT_GEOM_VOLUME_OBJECT); if (!do_vertex_attrib_load && !info.vertex_out_interfaces_.is_empty()) { /* Codegen outputs only one interface. */ @@ -471,7 +446,7 @@ void ShaderModule::material_create_info_ammend(GPUMaterial *gpumat, GPUCodegenOu std::stringstream vert_gen, frag_gen, comp_gen; - bool is_compute = pipeline_type == MAT_PIPE_VOLUME_MATERIAL; + bool is_compute = pipeline_type == MAT_PIPE_VOLUME; if (do_vertex_attrib_load) { vert_gen << global_vars.str() << attr_load.str(); @@ -484,12 +459,7 @@ void ShaderModule::material_create_info_ammend(GPUMaterial *gpumat, GPUCodegenOu } if (!is_compute) { - if (!ELEM(geometry_type, - MAT_GEOM_WORLD, - MAT_GEOM_VOLUME_WORLD, - MAT_GEOM_VOLUME_OBJECT, - MAT_GEOM_VOLUME)) - { + if (!ELEM(geometry_type, MAT_GEOM_WORLD, MAT_GEOM_VOLUME_WORLD, MAT_GEOM_VOLUME_OBJECT)) { vert_gen << "vec3 nodetree_displacement()\n"; vert_gen << "{\n"; vert_gen << ((codegen.displacement) ? codegen.displacement : "return vec3(0);\n"); @@ -562,9 +532,6 @@ void ShaderModule::material_create_info_ammend(GPUMaterial *gpumat, GPUCodegenOu case MAT_GEOM_POINT_CLOUD: info.additional_info("eevee_geom_point_cloud"); break; - case MAT_GEOM_VOLUME: - info.additional_info("eevee_geom_volume"); - break; } /* Pipeline Info. */ switch (geometry_type) { @@ -576,34 +543,16 @@ void ShaderModule::material_create_info_ammend(GPUMaterial *gpumat, GPUCodegenOu break; default: switch (pipeline_type) { - case MAT_PIPE_PREPASS_FORWARD_VELOCITY: - case MAT_PIPE_PREPASS_DEFERRED_VELOCITY: + case MAT_PIPE_FORWARD_PREPASS_VELOCITY: + case MAT_PIPE_DEFERRED_PREPASS_VELOCITY: info.additional_info("eevee_surf_depth", "eevee_velocity_geom"); break; - case MAT_PIPE_PREPASS_OVERLAP: - case MAT_PIPE_PREPASS_FORWARD: - case MAT_PIPE_PREPASS_DEFERRED: + case MAT_PIPE_FORWARD_PREPASS: + case MAT_PIPE_DEFERRED_PREPASS: info.additional_info("eevee_surf_depth"); break; - case MAT_PIPE_PREPASS_PLANAR: - info.additional_info("eevee_surf_depth", "eevee_clip_plane"); - break; case MAT_PIPE_SHADOW: - /* Determine surface shadow shader depending on used update technique. */ - switch (ShadowModule::shadow_technique) { - case ShadowTechnique::ATOMIC_RASTER: { - info.additional_info("eevee_surf_shadow_atomic"); - } break; - case ShadowTechnique::TILE_COPY: { - info.additional_info("eevee_surf_shadow_tbdr"); - } break; - default: { - BLI_assert_unreachable(); - } break; - } - break; - case MAT_PIPE_VOLUME_OCCUPANCY: - info.additional_info("eevee_surf_occupancy"); + info.additional_info("eevee_surf_shadow"); break; case MAT_PIPE_CAPTURE: info.additional_info("eevee_surf_capture"); @@ -635,7 +584,7 @@ GPUMaterial *ShaderModule::material_shader_get(::Material *blender_mat, eMaterialGeometry geometry_type, bool deferred_compilation) { - bool is_volume = ELEM(pipeline_type, MAT_PIPE_VOLUME_MATERIAL, MAT_PIPE_VOLUME_OCCUPANCY); + bool is_volume = (pipeline_type == MAT_PIPE_VOLUME); uint64_t shader_uuid = shader_uuid_from_material_type(pipeline_type, geometry_type); @@ -647,7 +596,7 @@ GPUMaterial *ShaderModule::world_shader_get(::World *blender_world, bNodeTree *nodetree, eMaterialPipeline pipeline_type) { - bool is_volume = (pipeline_type == MAT_PIPE_VOLUME_MATERIAL); + bool is_volume = (pipeline_type == MAT_PIPE_VOLUME); bool defer_compilation = is_volume; eMaterialGeometry geometry_type = is_volume ? MAT_GEOM_VOLUME_WORLD : MAT_GEOM_WORLD; @@ -669,7 +618,7 @@ GPUMaterial *ShaderModule::material_shader_get(const char *name, { uint64_t shader_uuid = shader_uuid_from_material_type(pipeline_type, geometry_type); - bool is_volume = ELEM(pipeline_type, MAT_PIPE_VOLUME_MATERIAL, MAT_PIPE_VOLUME_OCCUPANCY); + bool is_volume = (pipeline_type == MAT_PIPE_VOLUME); GPUMaterial *gpumat = GPU_material_from_nodetree(nullptr, nullptr, diff --git a/source/blender/draw/engines/eevee_next/eevee_shader.hh b/source/blender/draw/engines/eevee_next/eevee_shader.hh index 530e8701db7..ada6df35d38 100644 --- a/source/blender/draw/engines/eevee_next/eevee_shader.hh +++ b/source/blender/draw/engines/eevee_next/eevee_shader.hh @@ -35,7 +35,6 @@ enum eShaderType { DEFERRED_COMBINE, DEFERRED_LIGHT, DEFERRED_CAPTURE_EVAL, - DEFERRED_PLANAR_EVAL, DEBUG_SURFELS, DEBUG_IRRADIANCE_GRID, @@ -61,7 +60,6 @@ enum eShaderType { DOF_TILES_FLATTEN, HIZ_UPDATE, - HIZ_UPDATE_LAYER, HIZ_DEBUG, LIGHT_CULLING_DEBUG, @@ -93,14 +91,12 @@ enum eShaderType { RAY_TILE_CLASSIFY, RAY_TILE_COMPACT, RAY_TRACE_FALLBACK, - RAY_TRACE_PLANAR, RAY_TRACE_SCREEN_DIFFUSE, RAY_TRACE_SCREEN_REFLECT, RAY_TRACE_SCREEN_REFRACT, REFLECTION_PROBE_REMAP, REFLECTION_PROBE_UPDATE_IRRADIANCE, - REFLECTION_PROBE_SELECT, SHADOW_CLIPMAP_CLEAR, SHADOW_DEBUG, @@ -109,8 +105,6 @@ enum eShaderType { SHADOW_PAGE_DEFRAG, SHADOW_PAGE_FREE, SHADOW_PAGE_MASK, - SHADOW_PAGE_TILE_CLEAR, - SHADOW_PAGE_TILE_STORE, SHADOW_TILEMAP_BOUNDS, SHADOW_TILEMAP_FINALIZE, SHADOW_TILEMAP_INIT, @@ -130,7 +124,6 @@ enum eShaderType { SURFEL_RAY, VOLUME_INTEGRATION, - VOLUME_OCCUPANCY_CONVERT, VOLUME_RESOLVE, VOLUME_SCATTER, VOLUME_SCATTER_WITH_LIGHTS, diff --git a/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh b/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh index 82b67313e58..a993abcc272 100644 --- a/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh +++ b/source/blender/draw/engines/eevee_next/eevee_shader_shared.hh @@ -331,7 +331,7 @@ static inline float film_filter_weight(float filter_radius, float sample_distanc float weight = expf(fac * r); #else /* Blackman-Harris filter. */ - float r = M_TAU * saturate(0.5 + sqrtf(sample_distance_sqr) / (2.0 * filter_radius)); + float r = M_2PI * saturate(0.5 + sqrtf(sample_distance_sqr) / (2.0 * filter_radius)); float weight = 0.35875 - 0.48829 * cosf(r) + 0.14128 * cosf(2.0 * r) - 0.01168 * cosf(3.0 * r); #endif return weight; @@ -520,12 +520,12 @@ static inline float view_z_to_volume_z( } } -static inline float3 screen_to_volume(float4x4 projection_matrix, - float near, - float far, - float distribution, - float2 coord_scale, - float3 coord) +static inline float3 ndc_to_volume(float4x4 projection_matrix, + float near, + float far, + float distribution, + float2 coord_scale, + float3 coord) { bool is_persp = projection_matrix[3][3] == 0.0; @@ -621,7 +621,7 @@ static inline float regular_polygon_side_length(float sides_count) * Start first corners at theta == 0. */ static inline float circle_to_polygon_radius(float sides_count, float theta) { - /* From Graphics Gems from CryENGINE 3 (SIGGRAPH 2013) by Tiago Sousa (slide 36). */ + /* From Graphics Gems from CryENGINE 3 (Siggraph 2013) by Tiago Sousa (slide 36). */ float side_angle = (2.0f * M_PI) / sides_count; return cosf(side_angle * 0.5f) / cosf(theta - side_angle * floorf((sides_count * theta + M_PI) / (2.0f * M_PI))); @@ -708,13 +708,6 @@ enum eLightType : uint32_t { LIGHT_ELLIPSE = 21u }; -enum LightingType : uint32_t { - LIGHT_DIFFUSE = 0u, - LIGHT_SPECULAR = 1u, - LIGHT_TRANSMIT = 2u, - LIGHT_VOLUME = 3u, -}; - static inline bool is_area_light(eLightType type) { return type >= LIGHT_RECT; @@ -749,14 +742,6 @@ struct LightData { # define _back object_mat[2].xyz # define _position object_mat[3].xyz #endif - /** Power depending on shader type. Referenced by LightingType. */ - float4 power; - /** Light Color. */ - packed_float3 color; - /** Light Type. */ - eLightType type; - /** Inverse spot size (in X and Y axes). Aligned to size of float2. */ - float2 spot_size_inv; /** Punctual : Influence radius (inverted and squared) adjusted for Surface / Volume power. */ float influence_radius_invsqr_surface; float influence_radius_invsqr_volume; @@ -764,10 +749,22 @@ struct LightData { float influence_radius_max; /** Special radius factor for point lighting. */ float radius_squared; + /** NOTE: It is ok to use float3 here. A float is declared right after it. + * float3 is also aligned to 16 bytes. */ + packed_float3 color; + /** Light Type. */ + eLightType type; + /** Spot size. Aligned to size of float2. */ + float2 spot_size_inv; /** Spot angle tangent. */ float spot_tan; /** Reuse for directional LOD bias. */ #define _clipmap_lod_bias spot_tan + /** Power depending on shader type. */ + float diffuse_power; + float specular_power; + float volume_power; + float transmit_power; /** --- Shadow Data --- */ /** Near clip distances. Float stored as int for atomic operations. */ @@ -937,33 +934,34 @@ enum eShadowFlag : uint32_t { SHADOW_IS_USED = (1u << 31u) }; -/* NOTE: Trust the input to be in valid range (max is [3,3,255]). - * If it is in valid range, it should pack to 12bits so that `shadow_tile_pack()` can use it. - * But sometime this is used to encode invalid pages uint3(-1) and it needs to output uint(-1). */ static inline uint shadow_page_pack(uint3 page) { + /* NOTE: Trust the input to be in valid range. + * But sometime this is used to encode invalid pages uint3(-1) and it needs to output uint(-1). + */ return (page.x << 0u) | (page.y << 2u) | (page.z << 4u); } + static inline uint3 shadow_page_unpack(uint data) { uint3 page; - BLI_STATIC_ASSERT(SHADOW_PAGE_PER_ROW <= 4 && SHADOW_PAGE_PER_COL <= 4, "Update page packing") - page.x = (data >> 0u) & 3u; - page.y = (data >> 2u) & 3u; - BLI_STATIC_ASSERT(SHADOW_MAX_PAGE <= 4096, "Update page packing") - page.z = (data >> 4u) & 255u; + /* Tweaked for SHADOW_PAGE_PER_ROW = 4. */ + page.x = data & uint(SHADOW_PAGE_PER_ROW - 1); + page.y = (data >> 2u) & uint(SHADOW_PAGE_PER_COL - 1); + page.z = (data >> 4u); return page; } static inline ShadowTileData shadow_tile_unpack(ShadowTileDataPacked data) { ShadowTileData tile; - tile.page = shadow_page_unpack(data); + /* Tweaked for SHADOW_MAX_PAGE = 4096. */ + tile.page = shadow_page_unpack(data & uint(SHADOW_MAX_PAGE - 1)); /* -- 12 bits -- */ - BLI_STATIC_ASSERT(SHADOW_TILEMAP_LOD < 8, "Update page packing") + /* Tweaked for SHADOW_TILEMAP_LOD < 8. */ tile.lod = (data >> 12u) & 7u; /* -- 15 bits -- */ - BLI_STATIC_ASSERT(SHADOW_MAX_PAGE <= 4096, "Update page packing") + /* Tweaked for SHADOW_MAX_TILEMAP = 4096. */ tile.cache_index = (data >> 15u) & 4095u; /* -- 27 bits -- */ tile.is_used = (data & SHADOW_IS_USED) != 0; @@ -976,10 +974,7 @@ static inline ShadowTileData shadow_tile_unpack(ShadowTileDataPacked data) static inline ShadowTileDataPacked shadow_tile_pack(ShadowTileData tile) { - uint data; - /* NOTE: Page might be set to invalid values for tracking invalid usages. - * So we have to mask the result. */ - data = shadow_page_pack(tile.page) & uint(SHADOW_MAX_PAGE - 1); + uint data = shadow_page_pack(tile.page) & uint(SHADOW_MAX_PAGE - 1); data |= (tile.lod & 7u) << 12u; data |= (tile.cache_index & 4095u) << 15u; data |= (tile.is_used ? uint(SHADOW_IS_USED) : 0); @@ -1036,11 +1031,6 @@ struct Surfel { packed_float3 albedo_back; /** Cluster this surfel is assigned to. */ int cluster_id; - /** True if the light can bounce or be emitted by the surfel back face. */ - bool1 double_sided; - int _pad0; - int _pad1; - int _pad2; /** Surface radiance: Emission + Direct Lighting. */ SurfelRadiance radiance_direct; /** Surface radiance: Indirect Lighting. Double buffered to avoid race conditions. */ @@ -1094,9 +1084,6 @@ struct CaptureInfoData { bool1 capture_indirect; bool1 capture_emission; int _pad0; - /* World light probe atlas coordinate. */ - /* TODO(fclem): Remove this silly aliasing. */ - int4 world_atlas_coord; }; BLI_STATIC_ASSERT_ALIGN(CaptureInfoData, 16) @@ -1289,84 +1276,39 @@ static inline float3 burley_eval(float3 d, float r) /** \name Reflection Probes * \{ */ -struct ReflectionProbeLowFreqLight { - packed_float3 direction; - float ambient; -}; -BLI_STATIC_ASSERT_ALIGN(ReflectionProbeLowFreqLight, 16) +/** Mapping data to locate a reflection probe in texture. */ +struct ReflectionProbeData { + /** + * Position of the light probe in world space. + * World probe uses origin. + * + * 4th component is not used. + */ + float4 pos; -enum LightProbeShape : uint32_t { - SHAPE_ELIPSOID = 0u, - SHAPE_CUBOID = 1u, -}; - -struct ReflectionProbeAtlasCoordinate { /** On which layer of the texture array is this reflection probe stored. */ int layer; + /** * Subdivision of the layer. 0 = no subdivision and resolution would be * ReflectionProbeModule::MAX_RESOLUTION. */ int layer_subdivision; + /** * Which area of the subdivided layer is the reflection probe located. * * A layer has (2^layer_subdivision)^2 areas. */ int area_index; - int _pad1; -}; -BLI_STATIC_ASSERT_ALIGN(ReflectionProbeAtlasCoordinate, 16) - -/** Mapping data to locate a reflection probe in texture. */ -struct ReflectionProbeData { - /** Transform to probe local position with non-uniform scaling. */ - float3x4 world_to_probe_transposed; - - packed_float3 location; - float _pad2; - - /** Shape of the parallax projection. */ - LightProbeShape parallax_shape; - LightProbeShape influence_shape; - float parallax_distance; - /** Influence factor based on the distance to the parallax shape. */ - float influence_scale; - float influence_bias; - /** LOD factor for mipmap selection. */ - float lod_factor; - float _pad0; - float _pad1; - - ReflectionProbeAtlasCoordinate atlas_coord; /** - * Irradiance at the probe location encoded as spherical harmonics. - * Only contain the average luminance. Used for cube-map normalization. + * LOD factor for mipmap selection. */ - ReflectionProbeLowFreqLight low_freq_light; + float lod_factor; }; BLI_STATIC_ASSERT_ALIGN(ReflectionProbeData, 16) -struct ProbePlanarData { - /** Matrices used to render the planar capture. */ - float4x4 viewmat; - float4x4 winmat; - /** Transform world to local position with influence distance as Z scale. */ - float3x4 world_to_object_transposed; - /** World space plane normal. */ - packed_float3 normal; - /** Layer in the planar capture textures used by this probe. */ - int layer_id; -}; -BLI_STATIC_ASSERT_ALIGN(ProbePlanarData, 16) - -struct ClipPlaneData { - /** World space clip plane equation. Used to render planar light-probes. */ - float4 plane; -}; -BLI_STATIC_ASSERT_ALIGN(ClipPlaneData, 16) - /** \} */ /* -------------------------------------------------------------------- */ @@ -1484,7 +1426,6 @@ using RayTraceTileBuf = draw::StorageArrayBuffer; using SubsurfaceTileBuf = RayTraceTileBuf; using ReflectionProbeDataBuf = draw::UniformArrayBuffer; -using ProbePlanarDataBuf = draw::UniformArrayBuffer; using SamplingDataBuf = draw::StorageBuffer; using ShadowStatisticsBuf = draw::StorageBuffer; using ShadowPagesInfoDataBuf = draw::StorageBuffer; @@ -1501,7 +1442,6 @@ using VelocityGeometryBuf = draw::StorageArrayBuffer; using VelocityIndexBuf = draw::StorageArrayBuffer; using VelocityObjectBuf = draw::StorageArrayBuffer; using CryptomatteObjectBuf = draw::StorageArrayBuffer; -using ClipPlaneBuf = draw::UniformBuffer; } // namespace blender::eevee #endif diff --git a/source/blender/draw/engines/eevee_next/eevee_shadow.cc b/source/blender/draw/engines/eevee_next/eevee_shadow.cc index 66df398a22b..c53483ee882 100644 --- a/source/blender/draw/engines/eevee_next/eevee_shadow.cc +++ b/source/blender/draw/engines/eevee_next/eevee_shadow.cc @@ -19,8 +19,6 @@ namespace blender::eevee { -ShadowTechnique ShadowModule::shadow_technique = ShadowTechnique::ATOMIC_RASTER; - /* -------------------------------------------------------------------- */ /** \name Tile map * @@ -721,17 +719,6 @@ ShadowModule::ShadowModule(Instance &inst, ShadowSceneData &data) : inst_(inst), void ShadowModule::init() { - /* Determine shadow update technique and atlas format. - * NOTE(Metal): Metal utilizes a tile-optimized approach for Apple Silicon's architecture. */ - const bool is_metal_backend = (GPU_backend_get_type() == GPU_BACKEND_METAL); - const bool is_tile_based_arch = (GPU_platform_architecture() == GPU_ARCHITECTURE_TBDR); - if (is_metal_backend && is_tile_based_arch) { - ShadowModule::shadow_technique = ShadowTechnique::TILE_COPY; - } - else { - ShadowModule::shadow_technique = ShadowTechnique::ATOMIC_RASTER; - } - ::Scene &scene = *inst_.scene; bool enabled = (scene.eevee.flag & SCE_EEVEE_SHADOW_ENABLED) != 0; if (assign_if_different(enabled_, enabled)) { @@ -771,7 +758,7 @@ void ShadowModule::init() /* Make allocation safe. Avoids crash later on. */ if (!atlas_tx_.is_valid()) { - atlas_tx_.ensure_2d_array(ShadowModule::atlas_type, int2(1), 1); + atlas_tx_.ensure_2d_array(atlas_type, int2(1), 1); inst_.info = "Error: Could not allocate shadow atlas. Most likely out of GPU memory."; } @@ -807,8 +794,6 @@ void ShadowModule::init() /* Create different viewport to support different update region size. The most fitting viewport * is then selected during the tilemap finalize stage in `viewport_select`. */ for (int i = 0; i < multi_viewports_.size(); i++) { - /** IMPORTANT: Reflect changes in TBDR tile vertex shader which assumes viewport index 15 - * covers the whole framebuffer. */ int size_in_tile = min_ii(1 << i, SHADOW_TILEMAP_RES); multi_viewports_[i][0] = 0; multi_viewports_[i][1] = 0; @@ -849,7 +834,7 @@ void ShadowModule::begin_sync() sub.bind_ssbo("capture_info_buf", &capture_info_buf); sub.push_constant("directional_level", directional_level); sub.push_constant("tilemap_projection_ratio", projection_ratio); - inst_.lights.bind_resources(sub); + inst_.lights.bind_resources(&sub); sub.dispatch(&inst_.irradiance_cache.bake.dispatch_per_surfel_); /* Skip opaque and transparent tagging for light baking. */ @@ -857,18 +842,18 @@ void ShadowModule::begin_sync() } { - /* Use depth buffer to tag needed shadow pages for opaque geometry. */ + /** Use depth buffer to tag needed shadow pages for opaque geometry. */ PassMain::Sub &sub = pass.sub("Opaque"); sub.shader_set(inst_.shaders.static_shader_get(SHADOW_TILEMAP_TAG_USAGE_OPAQUE)); sub.bind_ssbo("tilemaps_buf", &tilemap_pool.tilemaps_data); sub.bind_ssbo("tiles_buf", &tilemap_pool.tiles_data); sub.bind_texture("depth_tx", &render_buffers.depth_tx); sub.push_constant("tilemap_projection_ratio", &tilemap_projection_ratio_); - inst_.lights.bind_resources(sub); + inst_.lights.bind_resources(&sub); sub.dispatch(&dispatch_depth_scan_size_); } { - /* Use bounding boxes for transparent geometry. */ + /** Use bounding boxes for transparent geometry. */ PassMain::Sub &sub = pass.sub("Transparent"); /* WORKAROUND: The DRW_STATE_WRITE_STENCIL is here only to avoid enabling the rasterizer * discard inside draw manager. */ @@ -884,8 +869,8 @@ void ShadowModule::begin_sync() sub.push_constant("fb_resolution", &usage_tag_fb_resolution_); sub.push_constant("fb_lod", &usage_tag_fb_lod_); inst_.bind_uniform_data(&sub); - inst_.hiz_buffer.bind_resources(sub); - inst_.lights.bind_resources(sub); + inst_.hiz_buffer.bind_resources(&sub); + inst_.lights.bind_resources(&sub); box_batch_ = DRW_cache_cube_get(); tilemap_usage_transparent_ps_ = ⊂ @@ -1012,7 +997,7 @@ void ShadowModule::end_sync() pass.init(); { - /* Clear tile-map clip buffer. */ + /** Clear tile-map clip buffer. */ PassSimple::Sub &sub = pass.sub("ClearClipmap"); sub.shader_set(inst_.shaders.static_shader_get(SHADOW_CLIPMAP_CLEAR)); sub.bind_ssbo("tilemaps_clip_buf", tilemap_pool.tilemaps_clip); @@ -1023,7 +1008,7 @@ void ShadowModule::end_sync() } { - /* Compute near/far clip distances for directional shadows based on casters bounds. */ + /** Compute near/far clip distances for directional shadows based on casters bounds. */ PassSimple::Sub &sub = pass.sub("DirectionalBounds"); sub.shader_set(inst_.shaders.static_shader_get(SHADOW_TILEMAP_BOUNDS)); sub.bind_ssbo("tilemaps_buf", tilemap_pool.tilemaps_data); @@ -1031,7 +1016,7 @@ void ShadowModule::end_sync() sub.bind_ssbo("casters_id_buf", curr_casters_); sub.bind_ssbo("bounds_buf", &manager.bounds_buf.current()); sub.push_constant("resource_len", int(curr_casters_.size())); - inst_.lights.bind_resources(sub); + inst_.lights.bind_resources(&sub); sub.dispatch(int3( divide_ceil_u(std::max(curr_casters_.size(), int64_t(1)), SHADOW_BOUNDS_GROUP_SIZE), 1, @@ -1039,7 +1024,7 @@ void ShadowModule::end_sync() sub.barrier(GPU_BARRIER_SHADER_STORAGE); } { - /* Clear usage bits. Tag update from the tile-map for sun shadow clip-maps shifting. */ + /** Clear usage bits. Tag update from the tile-map for sun shadow clip-maps shifting. */ PassSimple::Sub &sub = pass.sub("Init"); sub.shader_set(inst_.shaders.static_shader_get(SHADOW_TILEMAP_INIT)); sub.bind_ssbo("tilemaps_buf", tilemap_pool.tilemaps_data); @@ -1047,7 +1032,7 @@ void ShadowModule::end_sync() sub.bind_ssbo("tiles_buf", tilemap_pool.tiles_data); sub.bind_ssbo("pages_cached_buf", pages_cached_data_); sub.dispatch(int3(1, 1, tilemap_pool.tilemaps_data.size())); - /* Free unused tiles from tile-maps not used by any shadow. */ + /** Free unused tiles from tile-maps not used by any shadow. */ if (tilemap_pool.tilemaps_unused.size() > 0) { sub.bind_ssbo("tilemaps_buf", tilemap_pool.tilemaps_unused); sub.dispatch(int3(1, 1, tilemap_pool.tilemaps_unused.size())); @@ -1055,7 +1040,7 @@ void ShadowModule::end_sync() sub.barrier(GPU_BARRIER_SHADER_STORAGE); } { - /* Mark for update all shadow pages touching an updated shadow caster. */ + /** Mark for update all shadow pages touching an updated shadow caster. */ PassSimple::Sub &sub = pass.sub("CasterUpdate"); sub.shader_set(inst_.shaders.static_shader_get(SHADOW_TILEMAP_TAG_UPDATE)); sub.bind_ssbo("tilemaps_buf", tilemap_pool.tilemaps_data); @@ -1086,9 +1071,9 @@ void ShadowModule::end_sync() sub.bind_ssbo("tiles_buf", &tilemap_pool.tiles_data); sub.push_constant("tilemap_projection_ratio", &tilemap_projection_ratio_); inst_.bind_uniform_data(&sub); - inst_.hiz_buffer.bind_resources(sub); - inst_.sampling.bind_resources(sub); - inst_.lights.bind_resources(sub); + inst_.hiz_buffer.bind_resources(&sub); + inst_.sampling.bind_resources(&sub); + inst_.lights.bind_resources(&sub); inst_.volume.bind_resources(sub); inst_.volume.bind_properties_buffers(sub); sub.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS); @@ -1099,7 +1084,7 @@ void ShadowModule::end_sync() PassSimple &pass = tilemap_update_ps_; pass.init(); { - /* Mark tiles that are redundant in the mipmap chain as unused. */ + /** Mark tiles that are redundant in the mipmap chain as unused. */ PassSimple::Sub &sub = pass.sub("MaskLod"); sub.shader_set(inst_.shaders.static_shader_get(SHADOW_PAGE_MASK)); sub.bind_ssbo("tilemaps_buf", tilemap_pool.tilemaps_data); @@ -1108,7 +1093,7 @@ void ShadowModule::end_sync() sub.barrier(GPU_BARRIER_SHADER_STORAGE); } { - /* Free unused pages & Reclaim cached pages. */ + /** Free unused pages & Reclaim cached pages. */ PassSimple::Sub &sub = pass.sub("Free"); sub.shader_set(inst_.shaders.static_shader_get(SHADOW_PAGE_FREE)); sub.bind_ssbo("tilemaps_buf", tilemap_pool.tilemaps_data); @@ -1117,7 +1102,7 @@ void ShadowModule::end_sync() sub.bind_ssbo("pages_free_buf", pages_free_data_); sub.bind_ssbo("pages_cached_buf", pages_cached_data_); sub.dispatch(int3(1, 1, tilemap_pool.tilemaps_data.size())); - /* Free unused tiles from tile-maps not used by any shadow. */ + /** Free unused tiles from tile-maps not used by any shadow. */ if (tilemap_pool.tilemaps_unused.size() > 0) { sub.bind_ssbo("tilemaps_buf", tilemap_pool.tilemaps_unused); sub.dispatch(int3(1, 1, tilemap_pool.tilemaps_unused.size())); @@ -1125,7 +1110,7 @@ void ShadowModule::end_sync() sub.barrier(GPU_BARRIER_SHADER_STORAGE); } { - /* De-fragment the free page heap after cache reuse phase which can leave hole. */ + /** De-fragment the free page heap after cache reuse phase which can leave hole. */ PassSimple::Sub &sub = pass.sub("Defrag"); sub.shader_set(inst_.shaders.static_shader_get(SHADOW_PAGE_DEFRAG)); sub.bind_ssbo("pages_infos_buf", pages_infos_data_); @@ -1133,12 +1118,11 @@ void ShadowModule::end_sync() sub.bind_ssbo("pages_cached_buf", pages_cached_data_); sub.bind_ssbo("statistics_buf", statistics_buf_.current()); sub.bind_ssbo("clear_dispatch_buf", clear_dispatch_buf_); - sub.bind_ssbo("tile_draw_buf", tile_draw_buf_); sub.dispatch(int3(1, 1, 1)); sub.barrier(GPU_BARRIER_SHADER_STORAGE); } { - /* Assign pages to tiles that have been marked as used but possess no page. */ + /** Assign pages to tiles that have been marked as used but possess no page. */ PassSimple::Sub &sub = pass.sub("AllocatePages"); sub.shader_set(inst_.shaders.static_shader_get(SHADOW_PAGE_ALLOCATE)); sub.bind_ssbo("tilemaps_buf", tilemap_pool.tilemaps_data); @@ -1151,7 +1135,7 @@ void ShadowModule::end_sync() sub.barrier(GPU_BARRIER_SHADER_STORAGE); } { - /* Convert the unordered tiles into a texture used during shading. Creates views. */ + /** Convert the unordered tiles into a texture used during shading. Creates views. */ PassSimple::Sub &sub = pass.sub("Finalize"); sub.shader_set(inst_.shaders.static_shader_get(SHADOW_TILEMAP_FINALIZE)); sub.bind_ssbo("tilemaps_buf", tilemap_pool.tilemaps_data); @@ -1160,9 +1144,7 @@ void ShadowModule::end_sync() sub.bind_ssbo("view_infos_buf", &shadow_multi_view_.matrices_ubo_get()); sub.bind_ssbo("statistics_buf", statistics_buf_.current()); sub.bind_ssbo("clear_dispatch_buf", clear_dispatch_buf_); - sub.bind_ssbo("tile_draw_buf", tile_draw_buf_); - sub.bind_ssbo("dst_coord_buf", dst_coord_buf_); - sub.bind_ssbo("src_coord_buf", src_coord_buf_); + sub.bind_ssbo("clear_list_buf", clear_list_buf_); sub.bind_ssbo("render_map_buf", render_map_buf_); sub.bind_ssbo("viewport_index_buf", viewport_index_buf_); sub.bind_ssbo("pages_infos_buf", pages_infos_data_); @@ -1171,17 +1153,14 @@ void ShadowModule::end_sync() sub.barrier(GPU_BARRIER_SHADER_STORAGE | GPU_BARRIER_UNIFORM | GPU_BARRIER_TEXTURE_FETCH | GPU_BARRIER_SHADER_IMAGE_ACCESS); } - - /* NOTE: We do not need to run the clear pass when using the TBDR update variant, as tiles - * will be fully cleared as part of the shadow raster step. */ - if (ShadowModule::shadow_technique != ShadowTechnique::TILE_COPY) { + { /** Clear pages that need to be rendered. */ PassSimple::Sub &sub = pass.sub("RenderClear"); sub.framebuffer_set(&render_fb_); sub.state_set(DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_ALWAYS); sub.shader_set(inst_.shaders.static_shader_get(SHADOW_PAGE_CLEAR)); sub.bind_ssbo("pages_infos_buf", pages_infos_data_); - sub.bind_ssbo("dst_coord_buf", dst_coord_buf_); + sub.bind_ssbo("clear_list_buf", clear_list_buf_); sub.bind_image("shadow_atlas_img", atlas_tx_); sub.dispatch(clear_dispatch_buf_); sub.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS); @@ -1233,9 +1212,9 @@ void ShadowModule::debug_end_sync() debug_draw_ps_.bind_ssbo("tilemaps_buf", &tilemap_pool.tilemaps_data); debug_draw_ps_.bind_ssbo("tiles_buf", &tilemap_pool.tiles_data); inst_.bind_uniform_data(&debug_draw_ps_); - inst_.hiz_buffer.bind_resources(debug_draw_ps_); - inst_.lights.bind_resources(debug_draw_ps_); - inst_.shadows.bind_resources(debug_draw_ps_); + inst_.hiz_buffer.bind_resources(&debug_draw_ps_); + inst_.lights.bind_resources(&debug_draw_ps_); + inst_.shadows.bind_resources(&debug_draw_ps_); debug_draw_ps_.draw_procedural(GPU_PRIM_TRIS, 1, 3); } @@ -1284,35 +1263,10 @@ void ShadowModule::set_view(View &view) int2(std::exp2(usage_tag_fb_lod_))); usage_tag_fb.ensure(usage_tag_fb_resolution_); - eGPUTextureUsage usage = GPU_TEXTURE_USAGE_ATTACHMENT | GPU_TEXTURE_USAGE_MEMORYLESS; - int2 fb_size = int2(SHADOW_TILEMAP_RES * shadow_page_size_); - int fb_layers = SHADOW_VIEW_MAX; - - if (shadow_technique == ShadowTechnique::ATOMIC_RASTER) { - if (GPU_backend_get_type() == GPU_BACKEND_METAL) { - /* Metal requires a memoryless attachment to create an empty framebuffer. - * Might as well make use of it. */ - shadow_depth_fb_tx_.ensure_2d_array(GPU_DEPTH_COMPONENT32F, fb_size, fb_layers, usage); - shadow_depth_accum_tx_.free(); - render_fb_.ensure(GPU_ATTACHMENT_TEXTURE(shadow_depth_fb_tx_)); - } - else { - /* Create attachment-less framebuffer. */ - shadow_depth_fb_tx_.free(); - shadow_depth_accum_tx_.free(); - render_fb_.ensure(fb_size); - } - } - else if (shadow_technique == ShadowTechnique::TILE_COPY) { - /* Create memoryless depth attachment for on-tile surface depth accumulation.*/ - shadow_depth_fb_tx_.ensure_2d_array(GPU_DEPTH_COMPONENT32F, fb_size, fb_layers, usage); - shadow_depth_accum_tx_.ensure_2d_array(GPU_R32F, fb_size, fb_layers, usage); - render_fb_.ensure(GPU_ATTACHMENT_TEXTURE(shadow_depth_fb_tx_), - GPU_ATTACHMENT_TEXTURE(shadow_depth_accum_tx_)); - } - else { - BLI_assert_unreachable(); - } + render_fb_.ensure(int2(SHADOW_TILEMAP_RES * shadow_page_size_)); + GPU_framebuffer_bind(render_fb_); + GPU_framebuffer_multi_viewports_set(render_fb_, + reinterpret_cast(multi_viewports_.data())); inst_.hiz_buffer.update(); @@ -1323,52 +1277,15 @@ void ShadowModule::set_view(View &view) GPU_uniformbuf_clear_to_zero(shadow_multi_view_.matrices_ubo_get()); inst_.manager->submit(tilemap_setup_ps_, view); + inst_.manager->submit(tilemap_usage_ps_, view); + inst_.manager->submit(tilemap_update_ps_, view); shadow_multi_view_.compute_procedural_bounds(); - statistics_buf_.current().async_flush_to_host(); - - /* Isolate shadow update into own command buffer. - * If parameter buffer exceeds limits, then other work will not be impacted. */ - bool use_flush = (shadow_technique == ShadowTechnique::TILE_COPY) && - (GPU_backend_get_type() == GPU_BACKEND_METAL); - - if (use_flush) { - GPU_flush(); - } - - /* TODO(fclem): Move all of this to the draw::PassMain. */ - if (shadow_depth_fb_tx_.is_valid() && shadow_depth_accum_tx_.is_valid()) { - GPU_framebuffer_bind_ex( - render_fb_, - { - /* Depth is cleared to 0 for TBDR optimization. */ - {GPU_LOADACTION_CLEAR, GPU_STOREACTION_DONT_CARE, {0.0f, 0.0f, 0.0f, 0.0f}}, - {GPU_LOADACTION_CLEAR, GPU_STOREACTION_DONT_CARE, {1.0f, 1.0f, 1.0f, 1.0f}}, - }); - } - else if (shadow_depth_fb_tx_.is_valid()) { - GPU_framebuffer_bind_ex( - render_fb_, - { - {GPU_LOADACTION_CLEAR, GPU_STOREACTION_DONT_CARE, {1.0f, 1.0f, 1.0f, 1.0f}}, - }); - } - else { - GPU_framebuffer_bind(render_fb_); - } - - GPU_framebuffer_multi_viewports_set(render_fb_, - reinterpret_cast(multi_viewports_.data())); - inst_.pipelines.shadow.render(shadow_multi_view_); - if (use_flush) { - GPU_flush(); - } - GPU_memory_barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS | GPU_BARRIER_TEXTURE_FETCH); } DRW_stats_group_end(); @@ -1379,7 +1296,8 @@ void ShadowModule::set_view(View &view) else { /* This provoke a GPU/CPU sync. Avoid it if we are sure that all tile-maps will be rendered * in a single iteration. */ - bool enough_tilemap_for_single_iteration = tilemap_pool.tilemaps_data.size() <= fb_layers; + bool enough_tilemap_for_single_iteration = tilemap_pool.tilemaps_data.size() <= + SHADOW_VIEW_MAX; if (enough_tilemap_for_single_iteration) { tile_update_remains = false; } diff --git a/source/blender/draw/engines/eevee_next/eevee_shadow.hh b/source/blender/draw/engines/eevee_next/eevee_shadow.hh index 81a88e54975..f095bc6a390 100644 --- a/source/blender/draw/engines/eevee_next/eevee_shadow.hh +++ b/source/blender/draw/engines/eevee_next/eevee_shadow.hh @@ -15,11 +15,9 @@ #include "GPU_batch.h" -#include "eevee_camera.hh" #include "eevee_material.hh" #include "eevee_shader.hh" #include "eevee_shader_shared.hh" -#include "eevee_sync.hh" namespace blender::eevee { @@ -54,20 +52,6 @@ constexpr static const float shadow_clipmap_scale_mat[4][4] = {{SHADOW_TILEMAP_R {0, 0, 0.5, 0}, {0, 0, 0.5, 1}}; -/* Technique used for updating the virtual shadow map contents. */ -enum class ShadowTechnique { - /* Default virtual shadow map update using large virtual framebuffer to rasterize geometry with - * per-fragment textureAtomicMin to perform depth-test and indirectly store nearest depth value - * in the shadow atlas. */ - ATOMIC_RASTER = 0, - - /* Tile-architecture optimized virtual shadow map update, leveraging on-tile memory for clearing - * and depth-testing during geometry rasterization to avoid atomic operations, simplify mesh - * depth shader and only perform a single storage operation per pixel. This technique performs - * a 3-pass solution, first clearing tiles, updating depth and storing final results. */ - TILE_COPY = 1, -}; - /* -------------------------------------------------------------------- */ /** \name Tile-Map * @@ -196,9 +180,6 @@ class ShadowModule { friend ShadowTileMapPool; public: - /* Shadowing technique. */ - static ShadowTechnique shadow_technique; - /** Need to be first because of destructor order. */ ShadowTileMapPool tilemap_pool; @@ -233,14 +214,10 @@ class ShadowModule { StorageVectorBuffer curr_casters_ = {"CurrCasters"}; /** Indirect arguments for page clearing. */ - DispatchIndirectBuf clear_dispatch_buf_ = {"clear_dispatch_buf"}; - /** Indirect arguments for TBDR Tile Page passes. */ - DrawIndirectBuf tile_draw_buf_ = {"tile_draw_buf"}; - /** A compact stream of rendered tile coordinates in the shadow atlas. */ - StorageArrayBuffer dst_coord_buf_ = {"dst_coord_buf"}; - /** A compact stream of rendered tile coordinates in the framebuffer. */ - StorageArrayBuffer src_coord_buf_ = {"src_coord_buf"}; - /** Same as dst_coord_buf_ but is not compact. More like a linear texture. */ + DispatchIndirectBuf clear_dispatch_buf_; + /** Array containing a compact stream of tiles to clear. */ + StorageArrayBuffer clear_list_buf_ = {"clear_list_buf"}; + /** Tile to pages mapping. */ StorageArrayBuffer render_map_buf_ = {"render_map_buf"}; /** View to viewport index mapping. */ StorageArrayBuffer viewport_index_buf_ = {"viewport_index_buf"}; @@ -287,14 +264,7 @@ class ShadowModule { /** Multi-View containing a maximum of 64 view to be rendered with the shadow pipeline. */ View shadow_multi_view_ = {"ShadowMultiView", SHADOW_VIEW_MAX, true}; /** Framebuffer with the atlas_tx attached. */ - Framebuffer render_fb_ = {"shadow_write_framebuffer"}; - - /* NOTE(Metal): Metal requires memoryless textures to be created which represent attachments in - * the shadow write framebuffer. These textures do not occupy any physical memory, but require a - * Texture object containing its parameters.*/ - Texture shadow_depth_fb_tx_ = {"shadow_depth_fb_tx_"}; - Texture shadow_depth_accum_tx_ = {"shadow_depth_accum_tx_"}; - + Framebuffer render_fb_; /** Arrays of viewports to rendering each tile to. */ std::array multi_viewports_; @@ -341,10 +311,10 @@ class ShadowModule { void debug_end_sync(); void debug_draw(View &view, GPUFrameBuffer *view_fb); - template void bind_resources(PassType &pass) + template void bind_resources(draw::detail::PassBase *pass) { - pass.bind_texture(SHADOW_ATLAS_TEX_SLOT, &atlas_tx_); - pass.bind_texture(SHADOW_TILEMAPS_TEX_SLOT, &tilemap_pool.tilemap_tx); + pass->bind_texture(SHADOW_ATLAS_TEX_SLOT, &atlas_tx_); + pass->bind_texture(SHADOW_TILEMAPS_TEX_SLOT, &tilemap_pool.tilemap_tx); } private: diff --git a/source/blender/draw/engines/eevee_next/eevee_subsurface.cc b/source/blender/draw/engines/eevee_next/eevee_subsurface.cc index 085bff39146..85a08d8d0a1 100644 --- a/source/blender/draw/engines/eevee_next/eevee_subsurface.cc +++ b/source/blender/draw/engines/eevee_next/eevee_subsurface.cc @@ -34,7 +34,7 @@ void SubsurfaceModule::end_sync() pass.init(); pass.state_set(DRW_STATE_NO_DRAW); pass.shader_set(inst_.shaders.static_shader_get(SUBSURFACE_SETUP)); - inst_.gbuffer.bind_resources(pass); + inst_.gbuffer.bind_resources(&pass); pass.bind_texture("depth_tx", &inst_.render_buffers.depth_tx); pass.bind_image("direct_light_img", &direct_light_tx_); pass.bind_image("indirect_light_img", &indirect_light_tx_); @@ -60,7 +60,7 @@ void SubsurfaceModule::end_sync() pass.state_set(DRW_STATE_NO_DRAW); pass.shader_set(inst_.shaders.static_shader_get(SUBSURFACE_CONVOLVE)); inst_.bind_uniform_data(&pass); - inst_.gbuffer.bind_resources(pass); + inst_.gbuffer.bind_resources(&pass); pass.bind_texture("radiance_tx", &radiance_tx_, sampler); pass.bind_texture("depth_tx", &inst_.render_buffers.depth_tx, sampler); pass.bind_texture("object_id_tx", &object_id_tx_, sampler); diff --git a/source/blender/draw/engines/eevee_next/eevee_subsurface.hh b/source/blender/draw/engines/eevee_next/eevee_subsurface.hh index 6d5262303f5..59471035683 100644 --- a/source/blender/draw/engines/eevee_next/eevee_subsurface.hh +++ b/source/blender/draw/engines/eevee_next/eevee_subsurface.hh @@ -8,8 +8,8 @@ * Postprocess diffuse radiance output from the diffuse evaluation pass to mimic subsurface * transmission. * - * This implementation follows the technique described in the SIGGRAPH presentation: - * "Efficient screen space subsurface scattering SIGGRAPH 2018" + * This implementation follows the technique described in the siggraph presentation: + * "Efficient screen space subsurface scattering Siggraph 2018" * by Evgenii Golubev * * But, instead of having all the precomputed weights for all three color primaries, diff --git a/source/blender/draw/engines/eevee_next/eevee_sync.cc b/source/blender/draw/engines/eevee_next/eevee_sync.cc index 5b179c963c4..3db26844f5b 100644 --- a/source/blender/draw/engines/eevee_next/eevee_sync.cc +++ b/source/blender/draw/engines/eevee_next/eevee_sync.cc @@ -19,8 +19,6 @@ #include "DNA_gpencil_legacy_types.h" #include "DNA_modifier_types.h" #include "DNA_particle_types.h" -#include "DNA_pointcloud_types.h" -#include "DNA_volume_types.h" #include "draw_common.hh" #include "draw_sculpt.hh" @@ -42,29 +40,25 @@ static void draw_data_init_cb(DrawData *dd) ObjectHandle &SyncModule::sync_object(Object *ob) { - ObjectKey key(ob); - - ObjectHandle &handle = ob_handles.lookup_or_add_cb(key, [&]() { - ObjectHandle new_handle; - new_handle.object_key = key; - new_handle.recalc = ID_RECALC_ALL; - return new_handle; - }); - - /** TODO(Miguel Pozo): DrawData is the only way of retrieving the correct recalc flags. - * We should find a more optimal way to handle this. */ DrawEngineType *owner = (DrawEngineType *)&DRW_engine_viewport_eevee_next_type; - DrawData *dd = DRW_drawdata_ensure((ID *)ob, owner, sizeof(DrawData), nullptr, nullptr); - handle.recalc |= dd->recalc; - dd->recalc = 0; + DrawData *dd = DRW_drawdata_ensure( + (ID *)ob, owner, sizeof(eevee::ObjectHandle), draw_data_init_cb, nullptr); + ObjectHandle &eevee_dd = *reinterpret_cast(dd); + + if (eevee_dd.object_key.ob == nullptr) { + ob = DEG_get_original_object(ob); + eevee_dd.object_key = ObjectKey(ob); + } const int recalc_flags = ID_RECALC_COPY_ON_WRITE | ID_RECALC_TRANSFORM | ID_RECALC_SHADING | ID_RECALC_GEOMETRY; - if ((handle.recalc & recalc_flags) != 0) { + if ((eevee_dd.recalc & recalc_flags) != 0) { + /** WARNING: Some objects are always created "on the fly" (ie. Geometry Nodes volumes), + * so this causes to redraw the sample 1 forever. */ inst_.sampling.reset(); } - return handle; + return eevee_dd; } WorldHandle &SyncModule::sync_world(::World *world) @@ -142,6 +136,7 @@ void SyncModule::sync_mesh(Object *ob, bool is_shadow_caster = false; bool is_alpha_blend = false; + bool do_probe_sync = inst_.do_probe_sync() && !(ob->visibility_flag & OB_HIDE_PROBE_CUBEMAP); for (auto i : material_array.gpu_materials.index_range()) { GPUBatch *geom = mat_geom[i]; if (geom == nullptr) { @@ -151,10 +146,9 @@ void SyncModule::sync_mesh(Object *ob, Material &material = material_array.materials[i]; GPUMaterial *gpu_material = material_array.gpu_materials[i]; - if (material.has_volume && (i == 0)) { + if (material.volume.gpumat && i == 0) { /* Only support single volume material for now. */ - geometry_call(material.volume_occupancy.sub_pass, geom, res_handle); - inst_.pipelines.volume.material_call(material.volume_material, ob, res_handle); + inst_.volume.sync_object(ob, ob_handle, res_handle, &material.volume); /* Do not render surface if we are rendering a volume object * and do not have a surface closure. */ if (gpu_material && !GPU_material_has_surface_output(gpu_material)) { @@ -162,16 +156,14 @@ void SyncModule::sync_mesh(Object *ob, } } - geometry_call(material.capture.sub_pass, geom, res_handle); - geometry_call(material.overlap_masking.sub_pass, geom, res_handle); - geometry_call(material.prepass.sub_pass, geom, res_handle); geometry_call(material.shading.sub_pass, geom, res_handle); + geometry_call(material.prepass.sub_pass, geom, res_handle); geometry_call(material.shadow.sub_pass, geom, res_handle); - - geometry_call(material.planar_probe_prepass.sub_pass, geom, res_handle); - geometry_call(material.planar_probe_shading.sub_pass, geom, res_handle); - geometry_call(material.reflection_probe_prepass.sub_pass, geom, res_handle); - geometry_call(material.reflection_probe_shading.sub_pass, geom, res_handle); + geometry_call(material.capture.sub_pass, geom, res_handle); + if (do_probe_sync) { + geometry_call(material.probe_prepass.sub_pass, geom, res_handle); + geometry_call(material.probe_shading.sub_pass, geom, res_handle); + } is_shadow_caster = is_shadow_caster || material.shadow.sub_pass != nullptr; is_alpha_blend = is_alpha_blend || material.is_alpha_blend_transparent; @@ -217,6 +209,7 @@ bool SyncModule::sync_sculpt(Object *ob, bool is_shadow_caster = false; bool is_alpha_blend = false; + bool do_probe_sync = inst_.do_probe_sync() && !(ob->visibility_flag & OB_HIDE_PROBE_CUBEMAP); for (SculptBatch &batch : sculpt_batches_per_material_get(ob_ref.object, material_array.gpu_materials)) { @@ -227,27 +220,16 @@ bool SyncModule::sync_sculpt(Object *ob, Material &material = material_array.materials[batch.material_slot]; - if (material.has_volume && (batch.material_slot == 0)) { - /* Only support single volume material for now. */ - geometry_call(material.volume_occupancy.sub_pass, geom, res_handle); - inst_.pipelines.volume.material_call(material.volume_material, ob, res_handle); - /* Do not render surface if we are rendering a volume object - * and do not have a surface closure. */ - if (material.has_surface == false) { - continue; - } - } - - geometry_call(material.capture.sub_pass, geom, res_handle); - geometry_call(material.overlap_masking.sub_pass, geom, res_handle); - geometry_call(material.prepass.sub_pass, geom, res_handle); geometry_call(material.shading.sub_pass, geom, res_handle); + geometry_call(material.prepass.sub_pass, geom, res_handle); geometry_call(material.shadow.sub_pass, geom, res_handle); - geometry_call(material.planar_probe_prepass.sub_pass, geom, res_handle); - geometry_call(material.planar_probe_shading.sub_pass, geom, res_handle); - geometry_call(material.reflection_probe_prepass.sub_pass, geom, res_handle); - geometry_call(material.reflection_probe_shading.sub_pass, geom, res_handle); + /* TODO(Miguel Pozo): Is this needed ? */ + geometry_call(material.capture.sub_pass, geom, res_handle); + if (do_probe_sync) { + geometry_call(material.probe_prepass.sub_pass, geom, res_handle); + geometry_call(material.probe_shading.sub_pass, geom, res_handle); + } is_shadow_caster = is_shadow_caster || material.shadow.sub_pass != nullptr; is_alpha_blend = is_alpha_blend || material.is_alpha_blend_transparent; @@ -276,7 +258,7 @@ void SyncModule::sync_point_cloud(Object *ob, ResourceHandle res_handle, const ObjectRef & /*ob_ref*/) { - const int material_slot = POINTCLOUD_MATERIAL_NR; + int material_slot = 1; bool has_motion = inst_.velocity.step_object_sync( ob, ob_handle.object_key, res_handle, ob_handle.recalc); @@ -293,29 +275,10 @@ void SyncModule::sync_point_cloud(Object *ob, object_pass.draw(geometry, res_handle); }; - if (material.has_volume) { - /* Only support single volume material for now. */ - drawcall_add(material.volume_occupancy); - inst_.pipelines.volume.material_call(material.volume_material, ob, res_handle); - - /* Do not render surface if we are rendering a volume object - * and do not have a surface closure. */ - if (material.has_surface == false) { - return; - } - } - - drawcall_add(material.capture); - drawcall_add(material.overlap_masking); - drawcall_add(material.prepass); drawcall_add(material.shading); + drawcall_add(material.prepass); drawcall_add(material.shadow); - drawcall_add(material.planar_probe_prepass); - drawcall_add(material.planar_probe_shading); - drawcall_add(material.reflection_probe_prepass); - drawcall_add(material.reflection_probe_shading); - inst_.cryptomatte.sync_object(ob, res_handle); GPUMaterial *gpu_material = inst_.materials.material_array_get(ob, has_motion).gpu_materials[material_slot - 1]; @@ -329,30 +292,6 @@ void SyncModule::sync_point_cloud(Object *ob, /** \} */ -/* -------------------------------------------------------------------- */ -/** \name Volume Objects - * \{ */ - -void SyncModule::sync_volume(Object *ob, ObjectHandle & /*ob_handle*/, ResourceHandle res_handle) -{ - const int material_slot = VOLUME_MATERIAL_NR; - - /* Motion is not supported on volumes yet. */ - const bool has_motion = false; - - Material &material = inst_.materials.material_get( - ob, has_motion, material_slot - 1, MAT_GEOM_VOLUME); - - /* Use bounding box tag empty spaces. */ - GPUBatch *geom = DRW_cache_cube_get(); - - geometry_call(material.volume_occupancy.sub_pass, geom, res_handle); - - inst_.pipelines.volume.material_call(material.volume_material, ob, res_handle); -} - -/** \} */ - /* -------------------------------------------------------------------- */ /** \name GPencil * \{ */ @@ -529,28 +468,10 @@ void SyncModule::sync_curves(Object *ob, } }; - if (material.has_volume) { - /* Only support single volume material for now. */ - drawcall_add(material.volume_occupancy); - inst_.pipelines.volume.material_call(material.volume_material, ob, res_handle); - /* Do not render surface if we are rendering a volume object - * and do not have a surface closure. */ - if (material.has_surface == false) { - return; - } - } - - drawcall_add(material.capture); - drawcall_add(material.overlap_masking); - drawcall_add(material.prepass); drawcall_add(material.shading); + drawcall_add(material.prepass); drawcall_add(material.shadow); - drawcall_add(material.planar_probe_prepass); - drawcall_add(material.planar_probe_shading); - drawcall_add(material.reflection_probe_prepass); - drawcall_add(material.reflection_probe_shading); - inst_.cryptomatte.sync_object(ob, res_handle); GPUMaterial *gpu_material = inst_.materials.material_array_get(ob, has_motion).gpu_materials[mat_nr - 1]; @@ -572,7 +493,6 @@ void SyncModule::sync_light_probe(Object *ob, ObjectHandle &ob_handle) { inst_.light_probes.sync_probe(ob, ob_handle); inst_.reflection_probes.sync_object(ob, ob_handle); - inst_.planar_probes.sync_object(ob, ob_handle); } /** \} */ @@ -592,8 +512,8 @@ void foreach_hair_particle_handle(Object *ob, ObjectHandle ob_handle, HairHandle continue; } - ObjectHandle particle_sys_handle = ob_handle; - particle_sys_handle.object_key = ObjectKey(ob, sub_key++); + ObjectHandle particle_sys_handle = {{nullptr}}; + particle_sys_handle.object_key = ObjectKey(ob_handle.object_key.ob, sub_key++); particle_sys_handle.recalc = particle_sys->recalc; callback(particle_sys_handle, *md, *particle_sys); diff --git a/source/blender/draw/engines/eevee_next/eevee_sync.hh b/source/blender/draw/engines/eevee_next/eevee_sync.hh index e455b6b50fc..c813f5bc2ff 100644 --- a/source/blender/draw/engines/eevee_next/eevee_sync.hh +++ b/source/blender/draw/engines/eevee_next/eevee_sync.hh @@ -14,7 +14,6 @@ #include "BKE_duplilist.h" #include "BLI_ghash.h" #include "BLI_map.hh" -#include "DEG_depsgraph_query.hh" #include "DNA_object_types.h" #include "DRW_render.h" #include "GPU_material.h" @@ -32,61 +31,87 @@ class Instance; * Note that we get a unique key for each object component. * \{ */ -class ObjectKey { +struct ObjectKey { /** Hash value of the key. */ - uint64_t hash_value_; + uint64_t hash_value; /** Original Object or source object for duplis. */ - Object *ob_; + Object *ob; /** Original Parent object for duplis. */ - Object *parent_; + Object *parent; /** Dupli objects recursive unique identifier */ - int id_[MAX_DUPLI_RECUR]; + int id[MAX_DUPLI_RECUR]; /** Used for particle system hair. */ int sub_key_; +#ifdef DEBUG + char name[64]; +#endif + ObjectKey() : ob(nullptr), parent(nullptr){}; - public: - ObjectKey() = default; - - ObjectKey(Object *ob, int sub_key = 0) + ObjectKey(Object *ob_, Object *parent_, int id_[MAX_DUPLI_RECUR], int sub_key_ = 0) + : ob(ob_), parent(parent_), sub_key_(sub_key_) { - /* Since we use `memcmp` for comparison, - * we have to ensure the padding bytes are initialized as well. */ - memset(this, 0, sizeof(*this)); - - ob_ = DEG_get_original_object(ob); - hash_value_ = BLI_ghashutil_ptrhash(ob_); - - if (DupliObject *dupli = DRW_object_get_dupli(ob)) { - parent_ = DRW_object_get_dupli_parent(ob); - hash_value_ = BLI_ghashutil_combine_hash(hash_value_, BLI_ghashutil_ptrhash(parent_)); - for (int i : IndexRange(MAX_DUPLI_RECUR)) { - id_[i] = dupli->persistent_id[i]; - if (id_[i] == INT_MAX) { - break; - } - hash_value_ = BLI_ghashutil_combine_hash(hash_value_, BLI_ghashutil_inthash(id_[i])); + if (id_) { + memcpy(id, id_, sizeof(id)); + } + else { + memset(id, 0, sizeof(id)); + } + /* Compute hash on creation so we avoid the cost of it for every sync. */ + hash_value = BLI_ghashutil_ptrhash(ob); + hash_value = BLI_ghashutil_combine_hash(hash_value, BLI_ghashutil_ptrhash(parent)); + for (int i = 0; i < MAX_DUPLI_RECUR; i++) { + if (id[i] != 0) { + hash_value = BLI_ghashutil_combine_hash(hash_value, BLI_ghashutil_inthash(id[i])); + } + else { + break; } } - - if (sub_key != 0) { - sub_key_ = sub_key; - hash_value_ = BLI_ghashutil_combine_hash(hash_value_, BLI_ghashutil_inthash(sub_key_)); + if (sub_key_ != 0) { + hash_value = BLI_ghashutil_combine_hash(hash_value, sub_key_); } +#ifdef DEBUG + STRNCPY(name, ob->id.name); +#endif } + ObjectKey(Object *ob, DupliObject *dupli, Object *parent, int sub_key_ = 0) + : ObjectKey(ob, parent, dupli ? dupli->persistent_id : nullptr, sub_key_){}; + + ObjectKey(Object *ob, int sub_key_ = 0) + : ObjectKey(ob, DRW_object_get_dupli(ob), DRW_object_get_dupli_parent(ob), sub_key_){}; + uint64_t hash() const { - return hash_value_; + return hash_value; } bool operator<(const ObjectKey &k) const { - return memcmp(this, &k, sizeof(*this)) < 0; + if (ob != k.ob) { + return (ob < k.ob); + } + if (parent != k.parent) { + return (parent < k.parent); + } + if (sub_key_ != k.sub_key_) { + return (sub_key_ < k.sub_key_); + } + return memcmp(id, k.id, sizeof(id)) < 0; } bool operator==(const ObjectKey &k) const { - return memcmp(this, &k, sizeof(*this)) == 0; + if (ob != k.ob) { + return false; + } + if (parent != k.parent) { + return false; + } + if (sub_key_ != k.sub_key_) { + return false; + } + return memcmp(id, k.id, sizeof(id)) == 0; } }; @@ -97,9 +122,9 @@ class ObjectKey { * * \{ */ -struct BaseHandle { - /* Accumulated recalc flags, which corresponds to ID->recalc flags. */ - unsigned int recalc; +struct ObjectHandle : public DrawData { + ObjectKey object_key; + void reset_recalc_flag() { if (recalc != 0) { @@ -108,10 +133,6 @@ struct BaseHandle { } }; -struct ObjectHandle : public BaseHandle { - ObjectKey object_key; -}; - struct WorldHandle : public DrawData { void reset_recalc_flag() { @@ -134,8 +155,6 @@ class SyncModule { private: Instance &inst_; - Map ob_handles = {}; - public: SyncModule(Instance &inst) : inst_(inst){}; ~SyncModule(){}; @@ -156,7 +175,6 @@ class SyncModule { ObjectHandle &ob_handle, ResourceHandle res_handle, const ObjectRef &ob_ref); - void sync_volume(Object *ob, ObjectHandle &ob_handle, ResourceHandle res_handle); void sync_gpencil(Object *ob, ObjectHandle &ob_handle, ResourceHandle res_handle); void sync_curves(Object *ob, ObjectHandle &ob_handle, diff --git a/source/blender/draw/engines/eevee_next/eevee_velocity.cc b/source/blender/draw/engines/eevee_next/eevee_velocity.cc index 6339ddb2bb5..ddea38ce41e 100644 --- a/source/blender/draw/engines/eevee_next/eevee_velocity.cc +++ b/source/blender/draw/engines/eevee_next/eevee_velocity.cc @@ -154,7 +154,7 @@ bool VelocityModule::step_object_sync(Object *ob, VelocityObjectData &vel = velocity_map.lookup_or_add_default(object_key); vel.obj.ofs[step_] = object_steps_usage[step_]++; vel.obj.resource_id = resource_handle.resource_index(); - vel.id = object_key.hash(); + vel.id = object_key.hash_value; object_steps[step_]->get_or_resize(vel.obj.ofs[step_]) = float4x4_view(ob->object_to_world); if (step_ == STEP_CURRENT) { /* Replace invalid steps. Can happen if object was hidden in one of those steps. */ @@ -327,7 +327,7 @@ void VelocityModule::end_sync() { Vector deleted_obj; - uint32_t max_resource_id_ = 0u; + uint32_t max_resource_id_ = 1u; for (MapItem item : velocity_map.items()) { if (item.value.obj.resource_id == uint32_t(-1)) { @@ -350,7 +350,7 @@ void VelocityModule::end_sync() velocity_map.remove(key); } - indirection_buf.resize(ceil_to_multiple_u(max_resource_id_ + 1, 128)); + indirection_buf.resize(ceil_to_multiple_u(max_resource_id_, 128)); /* Avoid uploading more data to the GPU as well as an extra level of * indirection on the GPU by copying back offsets the to VelocityIndex. */ diff --git a/source/blender/draw/engines/eevee_next/eevee_velocity.hh b/source/blender/draw/engines/eevee_next/eevee_velocity.hh index 4d408018a63..27dca23cc5a 100644 --- a/source/blender/draw/engines/eevee_next/eevee_velocity.hh +++ b/source/blender/draw/engines/eevee_next/eevee_velocity.hh @@ -120,18 +120,18 @@ class VelocityModule { void bind_resources(DRWShadingGroup *grp); - template void bind_resources(PassType &pass) + template void bind_resources(draw::detail::Pass *pass) { /* Storage Buffer. */ - pass.bind_ssbo(VELOCITY_OBJ_PREV_BUF_SLOT, &(*object_steps[STEP_PREVIOUS])); - pass.bind_ssbo(VELOCITY_OBJ_NEXT_BUF_SLOT, &(*object_steps[next_step_])); - pass.bind_ssbo(VELOCITY_GEO_PREV_BUF_SLOT, &(*geometry_steps[STEP_PREVIOUS])); - pass.bind_ssbo(VELOCITY_GEO_NEXT_BUF_SLOT, &(*geometry_steps[next_step_])); - pass.bind_ssbo(VELOCITY_INDIRECTION_BUF_SLOT, &indirection_buf); + pass->bind_ssbo(VELOCITY_OBJ_PREV_BUF_SLOT, &(*object_steps[STEP_PREVIOUS])); + pass->bind_ssbo(VELOCITY_OBJ_NEXT_BUF_SLOT, &(*object_steps[next_step_])); + pass->bind_ssbo(VELOCITY_GEO_PREV_BUF_SLOT, &(*geometry_steps[STEP_PREVIOUS])); + pass->bind_ssbo(VELOCITY_GEO_NEXT_BUF_SLOT, &(*geometry_steps[next_step_])); + pass->bind_ssbo(VELOCITY_INDIRECTION_BUF_SLOT, &indirection_buf); /* Uniform Buffer. */ - pass.bind_ubo(VELOCITY_CAMERA_PREV_BUF, &(*camera_steps[STEP_PREVIOUS])); - pass.bind_ubo(VELOCITY_CAMERA_CURR_BUF, &(*camera_steps[STEP_CURRENT])); - pass.bind_ubo(VELOCITY_CAMERA_NEXT_BUF, &(*camera_steps[next_step_])); + pass->bind_ubo(VELOCITY_CAMERA_PREV_BUF, &(*camera_steps[STEP_PREVIOUS])); + pass->bind_ubo(VELOCITY_CAMERA_CURR_BUF, &(*camera_steps[STEP_CURRENT])); + pass->bind_ubo(VELOCITY_CAMERA_NEXT_BUF, &(*camera_steps[next_step_])); } bool camera_has_motion() const; diff --git a/source/blender/draw/engines/eevee_next/eevee_view.cc b/source/blender/draw/engines/eevee_next/eevee_view.cc index a175190d00d..e613283ccf9 100644 --- a/source/blender/draw/engines/eevee_next/eevee_view.cc +++ b/source/blender/draw/engines/eevee_next/eevee_view.cc @@ -102,11 +102,11 @@ void ShadingView::render() update_view(); + inst_.hiz_buffer.set_dirty(); + DRW_stats_group_start(name_); DRW_view_set_active(render_view_); - inst_.planar_probes.set_view(render_view_new_, extent_); - /* If camera has any motion, compute motion vector in the film pass. Otherwise, we avoid float * precision issue by setting the motion of all static geometry to 0. */ float4 clear_velocity = float4(inst_.velocity.camera_has_motion() ? VELOCITY_INVALID : 0.0f); @@ -118,14 +118,12 @@ void ShadingView::render() GPU_framebuffer_bind(combined_fb_); GPU_framebuffer_clear_color_depth(combined_fb_, clear_color, 1.0f); - inst_.hiz_buffer.set_source(&inst_.render_buffers.depth_tx); inst_.hiz_buffer.set_dirty(); inst_.pipelines.background.render(render_view_new_); /* TODO(fclem): Move it after the first prepass (and hiz update) once pipeline is stabilized. */ inst_.lights.set_view(render_view_new_, extent_); - inst_.reflection_probes.set_view(render_view_new_); inst_.volume.draw_prepass(render_view_new_); @@ -214,7 +212,7 @@ void ShadingView::update_view() void CaptureView::render_world() { const std::optional update_info = - inst_.reflection_probes.update_info_pop(ReflectionProbe::Type::WORLD); + inst_.reflection_probes.update_info_pop(ReflectionProbe::Type::World); if (!update_info.has_value()) { return; } @@ -240,7 +238,7 @@ void CaptureView::render_world() inst_.pipelines.world.render(view); } - inst_.reflection_probes.remap_to_octahedral_projection(update_info->atlas_coord); + inst_.reflection_probes.remap_to_octahedral_projection(update_info->object_key); inst_.reflection_probes.update_probes_texture_mipmaps(); } @@ -257,7 +255,7 @@ void CaptureView::render_probes() View view = {"Capture.View"}; bool do_update_mipmap_chain = false; while (const std::optional update_info = - inst_.reflection_probes.update_info_pop(ReflectionProbe::Type::PROBE)) + inst_.reflection_probes.update_info_pop(ReflectionProbe::Type::Probe)) { GPU_debug_group_begin("Probe.Capture"); do_update_mipmap_chain = true; @@ -291,11 +289,12 @@ void CaptureView::render_probes() inst_.render_buffers.release(); GPU_debug_group_end(); - inst_.reflection_probes.remap_to_octahedral_projection(update_info->atlas_coord); + inst_.reflection_probes.remap_to_octahedral_projection(update_info->object_key); } if (do_update_mipmap_chain) { /* TODO: only update the regions that have been updated. */ + /* TODO: Composite world into the probes. */ inst_.reflection_probes.update_probes_texture_mipmaps(); } } diff --git a/source/blender/draw/engines/eevee_next/eevee_view.hh b/source/blender/draw/engines/eevee_next/eevee_view.hh index 277cee6bfde..ee0855807c6 100644 --- a/source/blender/draw/engines/eevee_next/eevee_view.hh +++ b/source/blender/draw/engines/eevee_next/eevee_view.hh @@ -161,22 +161,4 @@ class CaptureView { /** \} */ -/* -------------------------------------------------------------------- */ -/** \name Capture Planar View - * - * View for capturing planar probes outside a ShadingView. - * \{ */ - -class CapturePlanarView { - private: - Instance &inst_; - Framebuffer capture_fb_ = {"Planar.Capture"}; - - public: - CapturePlanarView(Instance &inst) : inst_(inst) {} - void render_probes(); -}; - -/** \} */ - } // namespace blender::eevee diff --git a/source/blender/draw/engines/eevee_next/eevee_volume.cc b/source/blender/draw/engines/eevee_next/eevee_volume.cc index c73fc8c273d..f0bc38ba7ab 100644 --- a/source/blender/draw/engines/eevee_next/eevee_volume.cc +++ b/source/blender/draw/engines/eevee_next/eevee_volume.cc @@ -22,9 +22,63 @@ namespace blender::eevee { +bool VolumeModule::GridAABB::init(Object *ob, const Camera &camera, const VolumesInfoData &data) +{ + /* Returns the unified volume grid cell index of a world space coordinate. */ + auto to_global_grid_coords = [&](float3 wP) -> int3 { + const float4x4 &view_matrix = camera.data_get().viewmat; + const float4x4 &projection_matrix = camera.data_get().winmat; + + float3 ndc_coords = math::project_point(projection_matrix * view_matrix, wP); + ndc_coords = (ndc_coords * 0.5f) + float3(0.5f); + + float3 grid_coords = ndc_to_volume(projection_matrix, + data.depth_near, + data.depth_far, + data.depth_distribution, + data.coord_scale, + ndc_coords); + + return int3(grid_coords * float3(data.tex_size)); + }; + + const BoundBox &bbox = *BKE_object_boundbox_get(ob); + min = int3(INT32_MAX); + max = int3(INT32_MIN); + + for (float3 corner : bbox.vec) { + corner = math::transform_point(float4x4(ob->object_to_world), corner); + int3 grid_coord = to_global_grid_coords(corner); + min = math::min(min, grid_coord); + max = math::max(max, grid_coord); + } + + bool is_visible = false; + for (int i : IndexRange(3)) { + is_visible = is_visible || (min[i] >= 0 && min[i] < data.tex_size[i]); + is_visible = is_visible || (max[i] >= 0 && max[i] < data.tex_size[i]); + } + + min = math::clamp(min, int3(0), data.tex_size); + max = math::clamp(max, int3(0), data.tex_size); + + return is_visible; +} + +bool VolumeModule::GridAABB::overlaps(const GridAABB &aabb) +{ + for (int i : IndexRange(3)) { + if (min[i] > aabb.max[i] || max[i] < aabb.min[i]) { + return false; + } + } + return true; +} + void VolumeModule::init() { enabled_ = false; + subpass_aabbs_.clear(); const Scene *scene_eval = inst_.scene; @@ -98,12 +152,72 @@ void VolumeModule::begin_sync() enabled_ = inst_.world.has_volume(); } +void VolumeModule::sync_object(Object *ob, + ObjectHandle & /*ob_handle*/, + ResourceHandle res_handle, + MaterialPass *material_pass /*=nullptr*/) +{ + float3 size = math::to_scale(float4x4(ob->object_to_world)); + /* Check if any of the axes have 0 length. (see #69070) */ + const float epsilon = 1e-8f; + if (size.x < epsilon || size.y < epsilon || size.z < epsilon) { + return; + } + + if (material_pass == nullptr) { + Material material = inst_.materials.material_get( + ob, false, VOLUME_MATERIAL_NR, MAT_GEOM_VOLUME_OBJECT); + material_pass = &material.volume; + } + + /* If shader failed to compile or is currently compiling. */ + if (material_pass->gpumat == nullptr) { + return; + } + + GPUShader *shader = GPU_material_get_shader(material_pass->gpumat); + if (shader == nullptr) { + return; + } + + GridAABB aabb; + if (!aabb.init(ob, inst_.camera, data_)) { + return; + } + + PassMain::Sub *object_pass = volume_sub_pass( + *material_pass->sub_pass, inst_.scene, ob, material_pass->gpumat); + if (object_pass) { + enabled_ = true; + + /* Add a barrier at the start of a subpass or when 2 volumes overlaps. */ + if (!subpass_aabbs_.contains_as(shader)) { + object_pass->barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS); + subpass_aabbs_.add(shader, {aabb}); + } + else { + Vector &aabbs = subpass_aabbs_.lookup(shader); + for (GridAABB &_aabb : aabbs) { + if (aabb.overlaps(_aabb)) { + object_pass->barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS); + aabbs.clear(); + break; + } + } + aabbs.append(aabb); + } + + int3 grid_size = aabb.max - aabb.min + int3(1); + + object_pass->push_constant("drw_ResourceID", int(res_handle.resource_index())); + object_pass->push_constant("grid_coords_min", aabb.min); + object_pass->dispatch(math::divide_ceil(grid_size, int3(VOLUME_GROUP_SIZE))); + } +} + void VolumeModule::end_sync() { - enabled_ = enabled_ || inst_.pipelines.volume.is_enabled(); - if (!enabled_) { - occupancy_tx_.free(); prop_scattering_tx_.free(); prop_extinction_tx_.free(); prop_emission_tx_.free(); @@ -127,38 +241,6 @@ void VolumeModule::end_sync() prop_emission_tx_.ensure_3d(GPU_R11F_G11F_B10F, data_.tex_size, usage); prop_phase_tx_.ensure_3d(GPU_RG16F, data_.tex_size, usage); - int occupancy_layers = divide_ceil_u(data_.tex_size.z, 32u); - eGPUTextureUsage occupancy_usage = GPU_TEXTURE_USAGE_SHADER_READ | - GPU_TEXTURE_USAGE_SHADER_WRITE | GPU_TEXTURE_USAGE_ATOMIC; - occupancy_tx_.ensure_3d(GPU_R32UI, int3(data_.tex_size.xy(), occupancy_layers), occupancy_usage); - - { - eGPUTextureUsage hit_count_usage = GPU_TEXTURE_USAGE_SHADER_READ | - GPU_TEXTURE_USAGE_SHADER_WRITE | GPU_TEXTURE_USAGE_ATOMIC; - eGPUTextureUsage hit_depth_usage = GPU_TEXTURE_USAGE_SHADER_READ | - GPU_TEXTURE_USAGE_SHADER_WRITE; - int2 hit_list_size = int2(1); - int hit_list_layer = 1; - if (inst_.pipelines.volume.use_hit_list()) { - hit_list_layer = clamp_i(inst_.scene->eevee.volumetric_ray_depth, 1, 16); - hit_list_size = data_.tex_size.xy(); - } - hit_depth_tx_.ensure_3d(GPU_R32F, int3(hit_list_size, hit_list_layer), hit_depth_usage); - if (hit_count_tx_.ensure_2d(GPU_R32UI, hit_list_size, hit_count_usage)) { - hit_count_tx_.clear(uint4(0u)); - } - } - - if (GPU_backend_get_type() == GPU_BACKEND_METAL) { - /* Metal requires a dummy attachment. */ - occupancy_fb_.ensure(GPU_ATTACHMENT_NONE, - GPU_ATTACHMENT_TEXTURE_LAYER(prop_extinction_tx_, 0)); - } - else { - /* Empty frame-buffer. */ - occupancy_fb_.ensure(data_.tex_size.xy()); - } - if (!inst_.pipelines.world_volume.is_valid()) { prop_scattering_tx_.clear(float4(0.0f)); prop_extinction_tx_.clear(float4(0.0f)); @@ -178,11 +260,10 @@ void VolumeModule::end_sync() scatter_ps_.init(); scatter_ps_.shader_set(inst_.shaders.static_shader_get( data_.use_lights ? VOLUME_SCATTER_WITH_LIGHTS : VOLUME_SCATTER)); - inst_.lights.bind_resources(scatter_ps_); - inst_.reflection_probes.bind_resources(scatter_ps_); - inst_.irradiance_cache.bind_resources(scatter_ps_); - inst_.shadows.bind_resources(scatter_ps_); - inst_.sampling.bind_resources(scatter_ps_); + inst_.lights.bind_resources(&scatter_ps_); + inst_.irradiance_cache.bind_resources(&scatter_ps_); + inst_.shadows.bind_resources(&scatter_ps_); + inst_.sampling.bind_resources(&scatter_ps_); scatter_ps_.bind_image("in_scattering_img", &prop_scattering_tx_); scatter_ps_.bind_image("in_extinction_img", &prop_extinction_tx_); scatter_ps_.bind_texture("extinction_tx", &prop_extinction_tx_); @@ -190,7 +271,6 @@ void VolumeModule::end_sync() scatter_ps_.bind_image("in_phase_img", &prop_phase_tx_); scatter_ps_.bind_image("out_scattering_img", &scatter_tx_); scatter_ps_.bind_image("out_extinction_img", &extinction_tx_); - scatter_ps_.bind_texture(RBUFS_UTILITY_TEX_SLOT, inst_.pipelines.utility_tx); /* Sync with the property pass. */ scatter_ps_.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS | GPU_BARRIER_TEXTURE_FETCH); scatter_ps_.dispatch(math::divide_ceil(data_.tex_size, int3(VOLUME_GROUP_SIZE))); @@ -214,7 +294,6 @@ void VolumeModule::end_sync() bind_resources(resolve_ps_); resolve_ps_.bind_texture("depth_tx", &inst_.render_buffers.depth_tx); resolve_ps_.bind_image(RBUFS_COLOR_SLOT, &inst_.render_buffers.rp_color_tx); - resolve_ps_.bind_image(RBUFS_VALUE_SLOT, &inst_.render_buffers.rp_value_tx); /* Sync with the integration pass. */ resolve_ps_.barrier(GPU_BARRIER_TEXTURE_FETCH); resolve_ps_.draw_procedural(GPU_PRIM_TRIS, 1, 3); @@ -226,26 +305,8 @@ void VolumeModule::draw_prepass(View &view) return; } - DRW_stats_group_start("Volumes"); inst_.pipelines.world_volume.render(view); - - float left, right, bottom, top, near, far; - float4x4 winmat = view.winmat(); - projmat_dimensions(winmat.ptr(), &left, &right, &bottom, &top, &near, &far); - - float4x4 winmat_infinite = view.is_persp() ? - math::projection::perspective_infinite( - left, right, bottom, top, near) : - math::projection::orthographic_infinite(left, right, bottom, top); - - View volume_view = {"Volume View"}; - volume_view.sync(view.viewmat(), winmat_infinite); - - if (inst_.pipelines.volume.is_enabled()) { - occupancy_fb_.bind(); - inst_.pipelines.volume.render(volume_view, occupancy_tx_); - } - DRW_stats_group_end(); + inst_.pipelines.volume.render(view); } void VolumeModule::draw_compute(View &view) diff --git a/source/blender/draw/engines/eevee_next/eevee_volume.hh b/source/blender/draw/engines/eevee_next/eevee_volume.hh index 3cd98603e19..98ba84afc5c 100644 --- a/source/blender/draw/engines/eevee_next/eevee_volume.hh +++ b/source/blender/draw/engines/eevee_next/eevee_volume.hh @@ -40,11 +40,8 @@ namespace blender::eevee { class Instance; -class VolumePipeline; class VolumeModule { - friend VolumePipeline; - private: Instance &inst_; @@ -52,22 +49,6 @@ class VolumeModule { VolumesInfoData &data_; - /** - * Occupancy map that allows to fill froxels that are inside the geometry. - * It is filled during a pre-pass using atomic operations. - * Using a 3D bit-field, we only allocate one bit per froxel. - */ - Texture occupancy_tx_ = {"occupancy_tx"}; - /** - * List of surface hit for correct occupancy determination. - * One texture holds the number of hit count and the other the depth and - * the facing of each hit. - */ - Texture hit_count_tx_ = {"hit_count_tx"}; - Texture hit_depth_tx_ = {"hit_depth_tx"}; - /** Empty frame-buffer for occupancy pass. */ - Framebuffer occupancy_fb_ = {"occupancy_fb"}; - /* Material Parameters */ Texture prop_scattering_tx_; Texture prop_extinction_tx_; @@ -95,6 +76,21 @@ class VolumeModule { Texture dummy_scatter_tx_; Texture dummy_transmit_tx_; + /* Axis aligned bounding box in the volume grid. + * Used for frustum culling and volumes overlapping detection. */ + struct GridAABB { + int3 min, max; + + /* Returns true if visible. */ + bool init(Object *ob, const Camera &camera, const VolumesInfoData &data); + + bool overlaps(const GridAABB &aabb); + }; + /* Stores a vector of volume AABBs for each material pass, + * so we can detect overlapping volumes and place GPU barriers where needed + * (Only stores the AABBs for the volumes rendered since the last barrier). */ + Map> subpass_aabbs_; + public: VolumeModule(Instance &inst, VolumesInfoData &data) : inst_(inst), data_(data) { @@ -105,27 +101,19 @@ class VolumeModule { ~VolumeModule(){}; /* Bind resources needed by external passes to perform their own resolve. */ - template void bind_resources(PassType &pass) + template void bind_resources(PassType &ps) { - pass.bind_texture(VOLUME_SCATTERING_TEX_SLOT, &transparent_pass_scatter_tx_); - pass.bind_texture(VOLUME_TRANSMITTANCE_TEX_SLOT, &transparent_pass_transmit_tx_); + ps.bind_texture(VOLUME_SCATTERING_TEX_SLOT, &transparent_pass_scatter_tx_); + ps.bind_texture(VOLUME_TRANSMITTANCE_TEX_SLOT, &transparent_pass_transmit_tx_); } /* Bind the common resources needed by all volumetric passes. */ - template void bind_properties_buffers(PassType &pass) + template void bind_properties_buffers(PassType &ps) { - pass.bind_image(VOLUME_PROP_SCATTERING_IMG_SLOT, &prop_scattering_tx_); - pass.bind_image(VOLUME_PROP_EXTINCTION_IMG_SLOT, &prop_extinction_tx_); - pass.bind_image(VOLUME_PROP_EMISSION_IMG_SLOT, &prop_emission_tx_); - pass.bind_image(VOLUME_PROP_PHASE_IMG_SLOT, &prop_phase_tx_); - pass.bind_image(VOLUME_OCCUPANCY_SLOT, &occupancy_tx_); - } - - template void bind_occupancy_buffers(PassType &pass) - { - pass.bind_image(VOLUME_OCCUPANCY_SLOT, &occupancy_tx_); - pass.bind_image(VOLUME_HIT_DEPTH_SLOT, &hit_depth_tx_); - pass.bind_image(VOLUME_HIT_COUNT_SLOT, &hit_count_tx_); + ps.bind_image(VOLUME_PROP_SCATTERING_IMG_SLOT, &prop_scattering_tx_); + ps.bind_image(VOLUME_PROP_EXTINCTION_IMG_SLOT, &prop_extinction_tx_); + ps.bind_image(VOLUME_PROP_EMISSION_IMG_SLOT, &prop_emission_tx_); + ps.bind_image(VOLUME_PROP_PHASE_IMG_SLOT, &prop_phase_tx_); } bool needs_shadow_tagging() @@ -143,8 +131,10 @@ class VolumeModule { void begin_sync(); void sync_world(); - - void material_call(MaterialPass &material_pass, Object *ob, ResourceHandle res_handle); + void sync_object(Object *ob, + ObjectHandle &ob_handle, + ResourceHandle res_handle, + MaterialPass *material_pass = nullptr); void end_sync(); diff --git a/source/blender/draw/engines/eevee_next/eevee_world.cc b/source/blender/draw/engines/eevee_next/eevee_world.cc index bb686d45c09..3c92047213b 100644 --- a/source/blender/draw/engines/eevee_next/eevee_world.cc +++ b/source/blender/draw/engines/eevee_next/eevee_world.cc @@ -94,8 +94,7 @@ void World::sync() bNodeTree *ntree; world_and_ntree_get(bl_world, ntree); - GPUMaterial *volume_gpumat = inst_.shaders.world_shader_get( - bl_world, ntree, MAT_PIPE_VOLUME_MATERIAL); + GPUMaterial *volume_gpumat = inst_.shaders.world_shader_get(bl_world, ntree, MAT_PIPE_VOLUME); inst_.pipelines.world_volume.sync(volume_gpumat); if (inst_.lookdev.sync_world()) { @@ -129,7 +128,7 @@ bool World::has_volume() bNodeTree *ntree; world_and_ntree_get(bl_world, ntree); - GPUMaterial *gpumat = inst_.shaders.world_shader_get(bl_world, ntree, MAT_PIPE_VOLUME_MATERIAL); + GPUMaterial *gpumat = inst_.shaders.world_shader_get(bl_world, ntree, MAT_PIPE_VOLUME); return GPU_material_has_volume_output(gpumat); } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_ambient_occlusion_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_ambient_occlusion_lib.glsl index aed8476a60c..60e2ad503c6 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_ambient_occlusion_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_ambient_occlusion_lib.glsl @@ -2,11 +2,8 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ -#pragma BLENDER_REQUIRE(draw_view_lib.glsl) -#pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl) -#pragma BLENDER_REQUIRE(gpu_shader_math_base_lib.glsl) -#pragma BLENDER_REQUIRE(gpu_shader_math_fast_lib.glsl) -#pragma BLENDER_REQUIRE(draw_math_geom_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_geom_lib.glsl) #pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl) #pragma BLENDER_REQUIRE(eevee_ray_types_lib.glsl) @@ -96,14 +93,14 @@ float ambient_ambient_occlusion_search_horizon(vec3 vI, if (ssray.max_time <= 2.0) { /* Produces self shadowing under this threshold. */ - return acos_fast(h); + return fast_acos(h); } float prev_time, time = 0.0; for (float iter = 0.0; time < ssray.max_time && iter < sample_count; iter++) { prev_time = time; /* Gives us good precision at center and ensure we cross at least one pixel per iteration. */ - time = 1.0 + iter + square((iter + noise) / sample_count) * ssray.max_time; + time = 1.0 + iter + sqr((iter + noise) / sample_count) * ssray.max_time; float stride = time - prev_time; float lod = (log2(stride) - noise) / (1.0 + uniform_buf.ao.quality); @@ -119,7 +116,7 @@ float ambient_ambient_occlusion_search_horizon(vec3 vI, const float bias = 2.0 * 2.4e-7; depth += (inverted != 0.0) ? -bias : bias; - vec3 s = drw_point_screen_to_view(vec3(uv, depth)); + vec3 s = get_view_space_from_depth(uv, depth); vec3 omega_s = s - vP; float len = length(omega_s); /* Sample's horizon angle cosine. */ @@ -127,7 +124,7 @@ float ambient_ambient_occlusion_search_horizon(vec3 vI, /* Blend weight to fade artifacts. */ float dist_ratio = abs(len) / radius; /* Sphere falloff. */ - float dist_fac = square(saturate(dist_ratio)); + float dist_fac = sqr(saturate(dist_ratio)); /* Unbiased, gives too much hard cut behind objects */ // float dist_fac = step(0.999, dist_ratio); @@ -138,7 +135,7 @@ float ambient_ambient_occlusion_search_horizon(vec3 vI, h = mix(max(h, s_h), h, dist_fac); } } - return acos_fast(h); + return fast_acos(h); } OcclusionData ambient_occlusion_search(vec3 vP, @@ -150,8 +147,8 @@ OcclusionData ambient_occlusion_search(vec3 vP, { vec2 noise = ambient_occlusion_get_noise(texel); vec2 dir = ambient_occlusion_get_dir(noise.x); - vec2 uv = drw_point_view_to_screen(vP).xy; - vec3 vI = (drw_view_is_perspective() ? normalize(-vP) : vec3(0.0, 0.0, 1.0)); + vec2 uv = get_uvs_from_view(vP); + vec3 vI = ((ProjectionMatrix[3][3] == 0.0) ? normalize(-vP) : vec3(0.0, 0.0, 1.0)); vec3 avg_dir = vec3(0.0); float avg_apperture = 0.0; @@ -214,8 +211,8 @@ void ambient_occlusion_eval(OcclusionData data, /* No error by default. */ visibility_error = 1.0; - bool early_out = (inverted != 0.0) ? (reduce_max(abs(data.horizons)) == 0.0) : - (reduce_min(abs(data.horizons)) == M_PI); + bool early_out = (inverted != 0.0) ? (max_v4(abs(data.horizons)) == 0.0) : + (min_v4(abs(data.horizons)) == M_PI); if (early_out) { visibility = saturate(dot(N, Ng) * 0.5 + 0.5); visibility = min(visibility, data.custom_occlusion); @@ -237,13 +234,13 @@ void ambient_occlusion_eval(OcclusionData data, bent_normal = N * 0.001; for (int i = 0; i < 2; i++) { - vec3 T = drw_normal_view_to_world(vec3(dir, 0.0)); + vec3 T = transform_direction(ViewMatrixInverse, vec3(dir, 0.0)); /* Setup integration domain around V. */ vec3 B = normalize(cross(V, T)); T = normalize(cross(B, V)); float proj_N_len; - vec3 proj_N = normalize_and_get_length(N - B * dot(N, B), proj_N_len); + vec3 proj_N = normalize_len(N - B * dot(N, B), proj_N_len); vec3 proj_Ng = normalize(Ng - B * dot(Ng, B)); vec2 h = (i == 0) ? data.horizons.xy : data.horizons.zw; @@ -253,8 +250,8 @@ void ambient_occlusion_eval(OcclusionData data, float N_cos = saturate(dot(proj_N, V)); float Ng_cos = saturate(dot(proj_Ng, V)); /* Gamma, angle between normalized projected normal and view vector. */ - float angle_Ng = sign(Ng_sin) * acos_fast(Ng_cos); - float angle_N = sign(N_sin) * acos_fast(N_cos); + float angle_Ng = sign(Ng_sin) * fast_acos(Ng_cos); + float angle_N = sign(N_sin) * fast_acos(N_cos); /* Clamp horizons to hemisphere around shading normal. */ h = ambient_occlusion_clamp_horizons_to_hemisphere(h, angle_N, inverted); @@ -288,7 +285,7 @@ void ambient_occlusion_eval(OcclusionData data, if (AO_BENT_NORMALS) { /* NOTE: using pow(visibility, 6.0) produces NaN (see #87369). */ - float tmp = saturate(pow6f(visibility)); + float tmp = saturate(pow6(visibility)); bent_normal = normalize(mix(bent_normal, N, tmp)); } else { @@ -382,14 +379,14 @@ float ambient_occlusion_specular( specular_dir = normalize(mix(specular_dir, visibility_dir, roughness * (1.0 - visibility))); /* Visibility to cone angle (eq. 18). */ - float vis_angle = acos_fast(sqrt(1 - visibility)); + float vis_angle = fast_acos(sqrt(1 - visibility)); /* Roughness to cone angle (eq. 26). */ /* A 0.001 min_angle can generate NaNs on Intel GPUs. See D12508. */ const float min_angle = 0.00990998744964599609375; - float spec_angle = max(min_angle, acos_fast(ambient_occlusion_cone_cosine(roughness))); + float spec_angle = max(min_angle, fast_acos(ambient_occlusion_cone_cosine(roughness))); /* Angle between cone axes. */ - float cone_cone_dist = acos_fast(saturate(dot(visibility_dir, specular_dir))); - float cone_nor_dist = acos_fast(saturate(dot(N, specular_dir))); + float cone_cone_dist = fast_acos(saturate(dot(visibility_dir, specular_dir))); + float cone_nor_dist = fast_acos(saturate(dot(N, specular_dir))); float isect_solid_angle = ambient_occlusion_spherical_cap_intersection( vis_angle, spec_angle, cone_cone_dist); @@ -397,7 +394,7 @@ float ambient_occlusion_specular( M_PI_2, spec_angle, cone_nor_dist); float specular_occlusion = isect_solid_angle / specular_solid_angle; /* Mix because it is unstable in unoccluded areas. */ - float tmp = saturate(pow8f(visibility)); + float tmp = saturate(pow8(visibility)); visibility = mix(specular_occlusion, 1.0, tmp); return saturate(visibility); diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_ambient_occlusion_pass_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_ambient_occlusion_pass_comp.glsl index 9b33cd57f21..456be0f8107 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_ambient_occlusion_pass_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_ambient_occlusion_pass_comp.glsl @@ -2,10 +2,64 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ -#pragma BLENDER_REQUIRE(gpu_shader_math_vector_lib.glsl) -#pragma BLENDER_REQUIRE(draw_view_reconstruction_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_geom_lib.glsl) #pragma BLENDER_REQUIRE(eevee_ambient_occlusion_lib.glsl) +/* Similar to https://atyuwen.github.io/posts/normal-reconstruction/. + * This samples the depth buffer 4 time for each direction to get the most correct + * implicit normal reconstruction out of the depth buffer. */ +vec3 view_position_derivative_from_depth( + sampler2D depth_tx, ivec2 extent, vec2 uv, ivec2 offset, vec3 vP, float depth_center) +{ + vec4 H; + H.x = texelFetch(depth_tx, ivec2(uv * vec2(extent)) - offset * 2, 0).r; + H.y = texelFetch(depth_tx, ivec2(uv * vec2(extent)) - offset, 0).r; + H.z = texelFetch(depth_tx, ivec2(uv * vec2(extent)) + offset, 0).r; + H.w = texelFetch(depth_tx, ivec2(uv * vec2(extent)) + offset * 2, 0).r; + + vec2 uv_offset = vec2(offset) / vec2(extent); + vec2 uv1 = uv - uv_offset * 2.0; + vec2 uv2 = uv - uv_offset; + vec2 uv3 = uv + uv_offset; + vec2 uv4 = uv + uv_offset * 2.0; + + /* Fix issue with depth precision. Take even larger diff. */ + vec4 diff = abs(vec4(depth_center, H.yzw) - H.x); + if (max_v4(diff) < 2.4e-7 && all(lessThan(diff.xyz, diff.www))) { + return 0.25 * (get_view_space_from_depth(uv3, H.w) - get_view_space_from_depth(uv1, H.x)); + } + /* Simplified (H.xw + 2.0 * (H.yz - H.xw)) - depth_center */ + vec2 deltas = abs((2.0 * H.yz - H.xw) - depth_center); + if (deltas.x < deltas.y) { + return vP - get_view_space_from_depth(uv2, H.y); + } + else { + return get_view_space_from_depth(uv3, H.z) - vP; + } +} + +/* TODO(Miguel Pozo): This should be in common_view_lib, + * but moving it there results in dependency hell. */ +bool reconstruct_view_position_and_normal_from_depth( + sampler2D depth_tx, ivec2 extent, vec2 uv, out vec3 vP, out vec3 vNg) +{ + float depth_center = texelFetch(depth_tx, ivec2(uv * vec2(extent)), 0).r; + + vP = get_view_space_from_depth(uv, depth_center); + + vec3 dPdx = view_position_derivative_from_depth( + depth_tx, extent, uv, ivec2(1, 0), vP, depth_center); + vec3 dPdy = view_position_derivative_from_depth( + depth_tx, extent, uv, ivec2(0, 1), vP, depth_center); + + vNg = safe_normalize(cross(dPdx, dPdy)); + + /* Background case. */ + return depth_center != 1.0; +} + void main() { ivec2 texel = ivec2(gl_GlobalInvocationID.xy); @@ -14,26 +68,29 @@ void main() return; } - SurfaceReconstructResult surf = view_reconstruct_from_depth(hiz_tx, extent, texel); - if (surf.is_background) { + vec2 uv = (vec2(texel) + vec2(0.5)) / vec2(extent); + vec3 vP, vNg; + if (!reconstruct_view_position_and_normal_from_depth(hiz_tx, extent, uv, vP, vNg)) { /* Do not trace for background */ imageStore(out_ao_img, ivec3(texel, out_ao_img_layer_index), vec4(0.0)); return; } - vec3 P = drw_point_view_to_world(surf.vP); - vec3 V = drw_world_incident_vector(P); - vec3 Ng = drw_normal_view_to_world(surf.vNg); + vec3 P = transform_point(ViewMatrixInverse, vP); + vec3 V = cameraVec(P); + vec3 Ng = transform_direction(ViewMatrixInverse, vNg); vec3 N = imageLoad(in_normal_img, ivec3(texel, in_normal_img_layer_index)).xyz; OcclusionData data = ambient_occlusion_search( - surf.vP, hiz_tx, texel, uniform_buf.ao.distance, 0.0, 8.0); + vP, hiz_tx, texel, uniform_buf.ao.distance, 0.0, 8.0); float visibility; - float unused_visibility_error_out; - vec3 unused_bent_normal_out; + float visibility_error_out; + vec3 bent_normal_out; ambient_occlusion_eval( - data, texel, V, N, Ng, 0.0, visibility, unused_visibility_error_out, unused_bent_normal_out); + data, texel, V, N, Ng, 0.0, visibility, visibility_error_out, bent_normal_out); + /* Scale by user factor */ + visibility = saturate(visibility); - imageStore(out_ao_img, ivec3(texel, out_ao_img_layer_index), vec4(saturate(visibility))); + imageStore(out_ao_img, ivec3(texel, out_ao_img_layer_index), vec4(visibility)); } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_attributes_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_attributes_lib.glsl index 0d617ae3974..f5b824aa843 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_attributes_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_attributes_lib.glsl @@ -2,8 +2,8 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ -#pragma BLENDER_REQUIRE(draw_model_lib.glsl) -#pragma BLENDER_REQUIRE(gpu_shader_math_vector_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_lib.glsl) #pragma BLENDER_REQUIRE(gpu_shader_codegen_lib.glsl) #define EEVEE_ATTRIBUTE_LIB @@ -33,7 +33,7 @@ vec3 attr_load_orco(vec4 orco) # endif vec4 attr_load_tangent(vec4 tangent) { - tangent.xyz = safe_normalize(drw_normal_object_to_world(tangent.xyz)); + tangent.xyz = safe_normalize(normal_object_to_world(tangent.xyz)); return tangent; } vec4 attr_load_vec4(vec4 attr) @@ -187,7 +187,7 @@ int g_curves_attr_id = 0; * based on the attribute scope (point or spline). */ int curves_attribute_element_id() { - int id = curve_interp_flat.strand_id; + int id = interp_flat.curves_strand_id; if (drw_curves.is_point_attribute[g_curves_attr_id][0] != 0u) { # ifdef COMMON_HAIR_LIB id = hair_get_base_id(); @@ -205,11 +205,11 @@ vec4 attr_load_tangent(samplerBuffer cd_buf) } vec3 attr_load_uv(samplerBuffer cd_buf) { - return texelFetch(cd_buf, curve_interp_flat.strand_id).rgb; + return texelFetch(cd_buf, interp_flat.curves_strand_id).rgb; } vec4 attr_load_color(samplerBuffer cd_buf) { - return texelFetch(cd_buf, curve_interp_flat.strand_id).rgba; + return texelFetch(cd_buf, interp_flat.curves_strand_id).rgba; } vec4 attr_load_vec4(samplerBuffer cd_buf) { @@ -230,7 +230,7 @@ float attr_load_float(samplerBuffer cd_buf) /** \} */ -#elif defined(MAT_GEOM_VOLUME) || defined(MAT_GEOM_VOLUME_OBJECT) || defined(MAT_GEOM_VOLUME_WORLD) +#elif defined(MAT_GEOM_VOLUME_OBJECT) || defined(MAT_GEOM_VOLUME_WORLD) /* -------------------------------------------------------------------- */ /** \name Volume diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_bxdf_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_bxdf_lib.glsl index 448958d6c35..30cfff63f78 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_bxdf_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_bxdf_lib.glsl @@ -6,7 +6,6 @@ * BxDF evaluation functions. */ -#pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl) #pragma BLENDER_REQUIRE(gpu_shader_math_base_lib.glsl) /* -------------------------------------------------------------------- */ diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_bxdf_sampling_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_bxdf_sampling_lib.glsl index d8af14d26e1..a241470e2fe 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_bxdf_sampling_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_bxdf_sampling_lib.glsl @@ -7,7 +7,7 @@ */ #pragma BLENDER_REQUIRE(eevee_bxdf_lib.glsl) -#pragma BLENDER_REQUIRE(draw_math_geom_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_geom_lib.glsl) /* -------------------------------------------------------------------- */ /** \name Microfacet GGX distribution @@ -17,7 +17,7 @@ float sample_pdf_ggx_reflect(float NH, float NV, float VH, float G1_V, float alpha) { - float a2 = square(alpha); + float a2 = sqr(alpha); #if GGX_USE_VISIBLE_NORMAL return 0.25 * bxdf_ggx_D(NH, a2) * G1_V / NV; #else @@ -28,10 +28,10 @@ float sample_pdf_ggx_reflect(float NH, float NV, float VH, float G1_V, float alp float sample_pdf_ggx_refract( float NH, float NV, float VH, float LH, float G1_V, float alpha, float eta) { - float a2 = square(alpha); + float a2 = sqr(alpha); float D = bxdf_ggx_D(NH, a2); - float Ht2 = square(eta * LH + VH); - return (D * G1_V * abs(VH * LH) * square(eta)) / (NV * Ht2); + float Ht2 = sqr(eta * LH + VH); + return (D * G1_V * abs(VH * LH) * sqr(eta)) / (NV * Ht2); } /** @@ -70,8 +70,8 @@ vec3 sample_ggx(vec3 rand, float alpha, vec3 Vt, out float G1_V) return Ht; #else /* Theta is the cone angle. */ - float z = sqrt((1.0 - rand.x) / (1.0 + square(alpha) * rand.x - rand.x)); /* cos theta */ - float r = sqrt(max(0.0, 1.0 - z * z)); /* sin theta */ + float z = sqrt((1.0 - rand.x) / (1.0 + sqr(alpha) * rand.x - rand.x)); /* cos theta */ + float r = sqrt(max(0.0, 1.0 - z * z)); /* sin theta */ float x = r * rand.y; float y = r * rand.z; /* Microfacet Normal */ diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_camera_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_camera_lib.glsl index 9940949c65c..60324da6b7a 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_camera_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_camera_lib.glsl @@ -6,7 +6,7 @@ * Camera projection / uv functions and utils. */ -#pragma BLENDER_REQUIRE(gpu_shader_math_base_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_lib.glsl) /* -------------------------------------------------------------------- */ /** \name Panoramic Projections @@ -68,10 +68,10 @@ vec3 camera_mirror_ball_to_direction(CameraData cam, vec2 uv) uv = uv * cam.uv_scale + cam.uv_bias; vec3 dir; dir.xy = uv * 2.0 - 1.0; - if (length_squared(dir.xy) > 1.0) { + if (len_squared(dir.xy) > 1.0) { return vec3(0.0); } - dir.z = -safe_sqrt(1.0 - square(dir.x) - square(dir.y)); + dir.z = -safe_sqrt(1.0 - sqr(dir.x) - sqr(dir.y)); const vec3 I = vec3(0.0, 0.0, 1.0); return reflect(I, dir); } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_colorspace_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_colorspace_lib.glsl index f2c94b52796..9683bc968e3 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_colorspace_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_colorspace_lib.glsl @@ -38,16 +38,3 @@ vec4 colorspace_scene_linear_from_YCoCg(vec4 ycocg_color) } /** \} */ - -/** - * Clamp components to avoid black square artifacts if a pixel goes NaN or negative. - * Threshold is arbitrary. - */ -vec4 colorspace_safe_color(vec4 c) -{ - return clamp(c, vec4(0.0), vec4(1e20)); -} -vec3 colorspace_safe_color(vec3 c) -{ - return clamp(c, vec3(0.0), vec3(1e20)); -} diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_debug_irradiance_grid_vert.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_debug_irradiance_grid_vert.glsl index 3754855fe2b..d7ecd7c6511 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_debug_irradiance_grid_vert.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_debug_irradiance_grid_vert.glsl @@ -2,8 +2,8 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ -#pragma BLENDER_REQUIRE(draw_view_lib.glsl) #pragma BLENDER_REQUIRE(eevee_lightprobe_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_lib.glsl) void main() { @@ -49,7 +49,7 @@ void main() } } - gl_Position = drw_point_world_to_homogenous(P); + gl_Position = point_world_to_ndc(P); gl_Position.z -= 2.5e-5; gl_PointSize = 3.0; } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_debug_surfels_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_debug_surfels_frag.glsl index b8a9d3ce928..0812f3b7262 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_debug_surfels_frag.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_debug_surfels_frag.glsl @@ -2,8 +2,8 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ -#pragma BLENDER_REQUIRE(gpu_shader_debug_gradients_lib.glsl) #pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_lib.glsl) vec3 debug_random_color(int v) { diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_debug_surfels_vert.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_debug_surfels_vert.glsl index 25292d13a50..bbe5830ff0e 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_debug_surfels_vert.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_debug_surfels_vert.glsl @@ -2,8 +2,8 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ -#pragma BLENDER_REQUIRE(draw_view_lib.glsl) -#pragma BLENDER_REQUIRE(gpu_shader_math_matrix_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_geom_lib.glsl) void main() { @@ -37,15 +37,17 @@ void main() break; } - mat3x3 TBN = from_up_axis(surfel.normal); + vec3 N = surfel.normal; + vec3 T, B; + make_orthonormal_basis(N, T, B); - mat4 model_matrix = mat4(vec4(TBN[0] * debug_surfel_radius, 0), - vec4(TBN[1] * debug_surfel_radius, 0), - vec4(TBN[2] * debug_surfel_radius, 0), + mat4 model_matrix = mat4(vec4(T * debug_surfel_radius, 0), + vec4(B * debug_surfel_radius, 0), + vec4(N * debug_surfel_radius, 0), vec4(surfel.position, 1)); P = (model_matrix * vec4(lP, 1)).xyz; - gl_Position = drw_point_world_to_homogenous(P); + gl_Position = point_world_to_ndc(P); gl_Position.z -= 2.5e-5; } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_deferred_capture_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_deferred_capture_frag.glsl index 23c8a72b384..813d2f13dcc 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_deferred_capture_frag.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_deferred_capture_frag.glsl @@ -6,8 +6,8 @@ * Compute light objects lighting contribution using captured Gbuffer data. */ -#pragma BLENDER_REQUIRE(draw_view_lib.glsl) #pragma BLENDER_REQUIRE(eevee_gbuffer_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_lib.glsl) #pragma BLENDER_REQUIRE(eevee_light_eval_lib.glsl) #pragma BLENDER_REQUIRE(eevee_lightprobe_eval_lib.glsl) @@ -16,28 +16,32 @@ void main() ivec2 texel = ivec2(gl_FragCoord.xy); float depth = texelFetch(hiz_tx, texel, 0).r; + vec3 P = get_world_space_from_depth(uvcoordsvar.xy, depth); + + vec3 V = cameraVec(P); + float vP_z = dot(cameraForward, P) - dot(cameraForward, cameraPos); GBufferData gbuf = gbuffer_read(gbuf_header_tx, gbuf_closure_tx, gbuf_color_tx, texel); - ClosureLightStack stack; - stack.cl[0].N = gbuf.has_diffuse ? gbuf.diffuse.N : gbuf.reflection.N; - stack.cl[0].ltc_mat = LTC_LAMBERT_MAT; - stack.cl[0].type = LIGHT_DIFFUSE; + vec3 Ng = gbuf.diffuse.N; - vec3 P = drw_point_screen_to_world(vec3(uvcoordsvar.xy, depth)); - vec3 Ng = stack.cl[0].N; - vec3 V = drw_world_incident_vector(P); - float vPz = dot(drw_view_forward(), P) - dot(drw_view_forward(), drw_view_position()); + vec3 diffuse_light = vec3(0.0); + vec3 unused_reflection_light = vec3(0.0); + vec3 unused_refraction_light = vec3(0.0); + float unused_shadow = 1.0; - /* Direct light. */ - light_eval(stack, P, Ng, V, vPz, gbuf.thickness); - /* Indirect light. */ - /* Can only load irradiance to avoid dependency loop with the reflection probe. */ - SphericalHarmonicL1 sh = lightprobe_irradiance_sample(P, V, Ng); - - vec3 radiance = stack.cl[0].light_shadowed + spherical_harmonics_evaluate_lambert(Ng, sh); + light_eval(gbuf.diffuse, + gbuf.reflection, + P, + Ng, + V, + vP_z, + gbuf.thickness, + diffuse_light, + unused_reflection_light, + unused_shadow); vec3 albedo = gbuf.diffuse.color + gbuf.reflection.color + gbuf.refraction.color; - out_radiance = vec4(radiance * albedo, 0.0); + out_radiance = vec4(diffuse_light * albedo, 0.0); } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_deferred_combine_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_deferred_combine_frag.glsl index 362c861fdca..aa61e55c541 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_deferred_combine_frag.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_deferred_combine_frag.glsl @@ -31,7 +31,7 @@ void main() } if (gbuf.has_refraction) { - refract_light = /* imageLoad(direct_refract_img, texel).rgb + */ /* TODO: Not implemented. */ + refract_light = imageLoad(direct_refract_img, texel).rgb + imageLoad(indirect_refract_img, texel).rgb; } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_deferred_light_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_deferred_light_frag.glsl index e3716d5c3e8..987200c8fe6 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_deferred_light_frag.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_deferred_light_frag.glsl @@ -4,95 +4,52 @@ /** * Compute light objects lighting contribution using Gbuffer data. + * + * Output light . */ -#pragma BLENDER_REQUIRE(draw_view_lib.glsl) #pragma BLENDER_REQUIRE(eevee_gbuffer_lib.glsl) #pragma BLENDER_REQUIRE(eevee_renderpass_lib.glsl) #pragma BLENDER_REQUIRE(eevee_light_eval_lib.glsl) -#pragma BLENDER_REQUIRE(eevee_thickness_lib.glsl) -#pragma BLENDER_REQUIRE(eevee_subsurface_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_lib.glsl) void main() { ivec2 texel = ivec2(gl_FragCoord.xy); float depth = texelFetch(hiz_tx, texel, 0).r; + vec3 P = get_world_space_from_depth(uvcoordsvar.xy, depth); + + vec3 V = cameraVec(P); + float vP_z = dot(cameraForward, P) - dot(cameraForward, cameraPos); + GBufferData gbuf = gbuffer_read(gbuf_header_tx, gbuf_closure_tx, gbuf_color_tx, texel); - vec3 P = drw_point_screen_to_world(vec3(uvcoordsvar.xy, depth)); + vec3 diffuse_light = vec3(0.0); + vec3 reflection_light = vec3(0.0); + vec3 refraction_light = vec3(0.0); + float shadow = 1.0; + /* Assume reflection closure normal is always somewhat representative of the geometric normal. * Ng is only used for shadow biases and subsurface check in this case. */ vec3 Ng = gbuf.has_reflection ? gbuf.reflection.N : gbuf.diffuse.N; - vec3 V = drw_world_incident_vector(P); - float vPz = dot(drw_view_forward(), P) - dot(drw_view_forward(), drw_view_position()); - ClosureLightStack stack; + light_eval(gbuf.diffuse, + gbuf.reflection, + P, + Ng, + V, + vP_z, + gbuf.thickness, + diffuse_light, + reflection_light, + /* TODO(fclem): Implement refraction light. */ + // refraction_light, + shadow); - ClosureLight cl_diff; - cl_diff.N = gbuf.diffuse.N; - cl_diff.ltc_mat = LTC_LAMBERT_MAT; - cl_diff.type = LIGHT_DIFFUSE; - stack.cl[0] = cl_diff; + output_renderpass_value(uniform_buf.render_pass.shadow_id, shadow); - ClosureLight cl_refl; - cl_refl.N = gbuf.reflection.N; - cl_refl.ltc_mat = LTC_GGX_MAT(dot(gbuf.reflection.N, V), gbuf.reflection.roughness); - cl_refl.type = LIGHT_SPECULAR; - stack.cl[1] = cl_refl; - -#ifdef SSS_TRANSMITTANCE - ClosureLight cl_sss; - cl_sss.N = -gbuf.diffuse.N; - cl_sss.ltc_mat = LTC_LAMBERT_MAT; - cl_sss.type = LIGHT_DIFFUSE; - stack.cl[2] = cl_sss; -#endif - -#ifdef SSS_TRANSMITTANCE - float shadow_thickness = thickness_from_shadow(P, Ng, vPz); - float thickness = (shadow_thickness != THICKNESS_NO_VALUE) ? - max(shadow_thickness, gbuf.thickness) : - gbuf.thickness; -#else - float thickness = 0.0; -#endif - - light_eval(stack, P, Ng, V, vPz, thickness); - -#ifdef SSS_TRANSMITTANCE - if (gbuf.diffuse.sss_id != 0u) { - vec3 sss_profile = subsurface_transmission(gbuf.diffuse.sss_radius, thickness); - stack.cl[2].light_shadowed *= sss_profile; - stack.cl[2].light_unshadowed *= sss_profile; - } - else { - stack.cl[2].light_shadowed = vec3(0.0); - stack.cl[2].light_unshadowed = vec3(0.0); - } -#endif - - vec3 radiance_diffuse = stack.cl[0].light_shadowed; - vec3 radiance_specular = stack.cl[1].light_shadowed; -#ifdef SSS_TRANSMITTANCE - radiance_diffuse += stack.cl[2].light_shadowed; -#endif - - vec3 radiance_shadowed = stack.cl[0].light_shadowed; - vec3 radiance_unshadowed = stack.cl[0].light_unshadowed; - radiance_shadowed += stack.cl[1].light_shadowed; - radiance_unshadowed += stack.cl[1].light_unshadowed; -#ifdef SSS_TRANSMITTANCE - radiance_shadowed += stack.cl[2].light_shadowed; - radiance_unshadowed += stack.cl[2].light_unshadowed; -#endif - - /* TODO(fclem): Change shadow pass to be colored. */ - vec3 shadows = radiance_shadowed * safe_rcp(radiance_unshadowed); - output_renderpass_value(uniform_buf.render_pass.shadow_id, average(shadows)); - - imageStore(direct_diffuse_img, texel, vec4(radiance_diffuse, 1.0)); - imageStore(direct_reflect_img, texel, vec4(radiance_specular, 1.0)); - /* TODO(fclem): Support LTC for refraction. */ - // imageStore(direct_refract_img, texel, vec4(cl_refr.light_shadowed, 1.0)); + imageStore(direct_diffuse_img, texel, vec4(diffuse_light, 1.0)); + imageStore(direct_reflect_img, texel, vec4(reflection_light, 1.0)); + imageStore(direct_refract_img, texel, vec4(refraction_light, 1.0)); } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_deferred_planar_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_deferred_planar_frag.glsl deleted file mode 100644 index 7303c4c828a..00000000000 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_deferred_planar_frag.glsl +++ /dev/null @@ -1,49 +0,0 @@ -/* SPDX-FileCopyrightText: 2023 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -/** - * Compute light objects lighting contribution using captured Gbuffer data. - */ - -#pragma BLENDER_REQUIRE(draw_view_lib.glsl) -#pragma BLENDER_REQUIRE(eevee_gbuffer_lib.glsl) -#pragma BLENDER_REQUIRE(eevee_light_eval_lib.glsl) -#pragma BLENDER_REQUIRE(eevee_lightprobe_eval_lib.glsl) - -void main() -{ - ivec2 texel = ivec2(gl_FragCoord.xy); - - float depth = texelFetch(hiz_tx, texel, 0).r; - - GBufferData gbuf = gbuffer_read(gbuf_header_tx, gbuf_closure_tx, gbuf_color_tx, texel); - - vec3 P = drw_point_screen_to_world(vec3(uvcoordsvar.xy, depth)); - vec3 Ng = gbuf.diffuse.N; - vec3 V = drw_world_incident_vector(P); - float vPz = dot(drw_view_forward(), P) - dot(drw_view_forward(), drw_view_position()); - - ClosureLightStack stack; - stack.cl[0].N = gbuf.diffuse.N; - stack.cl[0].ltc_mat = LTC_LAMBERT_MAT; - stack.cl[0].type = LIGHT_DIFFUSE; - - stack.cl[1].N = gbuf.reflection.N; - stack.cl[1].ltc_mat = LTC_GGX_MAT(dot(gbuf.reflection.N, V), gbuf.reflection.roughness); - stack.cl[1].type = LIGHT_SPECULAR; - - /* Direct light. */ - light_eval(stack, P, Ng, V, vPz, gbuf.thickness); - /* Indirect light. */ - LightProbeSample samp = lightprobe_load(P, Ng, V); - - vec3 radiance = vec3(0.0); - radiance += (stack.cl[0].light_shadowed + lightprobe_eval(samp, gbuf.diffuse, P, V, vec2(0.0))) * - gbuf.diffuse.color; - radiance += (stack.cl[1].light_shadowed + - lightprobe_eval(samp, gbuf.reflection, P, V, vec2(0.0))) * - gbuf.reflection.color; - - out_radiance = vec4(radiance, 0.0); -} diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_accumulator_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_accumulator_lib.glsl index 1e13bc72ebf..f297710b459 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_accumulator_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_accumulator_lib.glsl @@ -8,12 +8,10 @@ * One is for the half-resolution gather passes and the other one for slight in focus regions. */ -#pragma BLENDER_REQUIRE(gpu_shader_debug_gradients_lib.glsl) -#pragma BLENDER_REQUIRE(draw_view_lib.glsl) -#pragma BLENDER_REQUIRE(gpu_shader_math_matrix_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_lib.glsl) #pragma BLENDER_REQUIRE(eevee_colorspace_lib.glsl) -#pragma BLENDER_REQUIRE(eevee_depth_of_field_lib.glsl) #pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl) +#pragma BLENDER_REQUIRE(eevee_depth_of_field_lib.glsl) /* -------------------------------------------------------------------- */ /** \name Options. @@ -318,7 +316,7 @@ void dof_gather_accumulate_center_sample(DofGatherData center_data, if (is_foreground && !is_resolve) { /* Reduce issue with closer foreground over distant foreground. */ - float ring_area = square(bordering_radius); + float ring_area = sqr(bordering_radius); dof_gather_ammend_weight(center_data, ring_area); } @@ -470,7 +468,7 @@ void dof_gather_accumulator(sampler2D color_tx, int sample_pair_count = gather_ring_density * ring; float step_rot = M_PI / float(sample_pair_count); - mat2 step_rot_mat = from_rotation(Angle(step_rot)); + mat2 step_rot_mat = rot2_from_angle(step_rot); float angle_offset = noise.y * step_rot; vec2 offset = vec2(cos(angle_offset), sin(angle_offset)); @@ -518,9 +516,9 @@ void dof_gather_accumulator(sampler2D color_tx, if (is_foreground) { /* Reduce issue with closer foreground over distant foreground. */ /* TODO(fclem) this seems to not be completely correct as the issue remains. */ - float ring_area = (square(float(ring) + 0.5 + coc_radius_error) - - square(float(ring) - 0.5 + coc_radius_error)) * - square(base_radius * unit_sample_radius); + float ring_area = (sqr(float(ring) + 0.5 + coc_radius_error) - + sqr(float(ring) - 0.5 + coc_radius_error)) * + sqr(base_radius * unit_sample_radius); dof_gather_ammend_weight(ring_data, ring_area); } @@ -576,13 +574,13 @@ void dof_gather_accumulator(sampler2D color_tx, if (debug_gather_perf && density_change > 0) { float fac = saturate(float(density_change) / float(10.0)); - out_color.rgb = average(out_color.rgb) * neon_gradient(fac); + out_color.rgb = avg(out_color.rgb) * neon_gradient(fac); } if (debug_gather_perf && do_fast_gather) { - out_color.rgb = average(out_color.rgb) * vec3(0.0, 1.0, 0.0); + out_color.rgb = avg(out_color.rgb) * vec3(0.0, 1.0, 0.0); } if (debug_scatter_perf) { - out_color.rgb = average(out_color.rgb) * vec3(0.0, 1.0, 0.0); + out_color.rgb = avg(out_color.rgb) * vec3(0.0, 1.0, 0.0); } /* Output premultiplied color so we can use bilinear sampler in resolve pass. */ @@ -618,7 +616,7 @@ void dof_slight_focus_gather(depth2D depth_tx, const float sample_count_max = float(DOF_SLIGHT_FOCUS_SAMPLE_MAX); /* Scale by search area. */ - float sample_count = sample_count_max * saturate(square(radius) / square(dof_layer_threshold)); + float sample_count = sample_count_max * saturate(sqr(radius) / sqr(dof_layer_threshold)); bool first_ring = true; @@ -634,7 +632,7 @@ void dof_slight_focus_gather(depth2D depth_tx, vec2 sample_uv = (frag_coord + sample_offset) / vec2(textureSize(depth_tx, 0)); float depth = textureLod(depth_tx, sample_uv, 0.0).r; pair_data[i].coc = dof_coc_from_depth(dof_buf, sample_uv, depth); - pair_data[i].color = colorspace_safe_color(textureLod(color_tx, sample_uv, 0.0)); + pair_data[i].color = safe_color(textureLod(color_tx, sample_uv, 0.0)); pair_data[i].dist = ring_dist; if (DOF_BOKEH_TEXTURE) { /* Contains sub-pixel distance to bokeh shape. */ @@ -670,7 +668,7 @@ void dof_slight_focus_gather(depth2D depth_tx, /* Center sample. */ vec2 sample_uv = frag_coord / vec2(textureSize(depth_tx, 0)); DofGatherData center_data; - center_data.color = colorspace_safe_color(textureLod(color_tx, sample_uv, 0.0)); + center_data.color = safe_color(textureLod(color_tx, sample_uv, 0.0)); center_data.coc = dof_coc_from_depth(dof_buf, sample_uv, textureLod(depth_tx, sample_uv, 0.0).r); center_data.coc = clamp(center_data.coc, -dof_buf.coc_abs_max, dof_buf.coc_abs_max); center_data.dist = 0.0; diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_bokeh_lut_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_bokeh_lut_comp.glsl index 97b8cca1b42..64ae6c28037 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_bokeh_lut_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_bokeh_lut_comp.glsl @@ -24,7 +24,7 @@ void main() if (dof_buf.bokeh_blades > 0.0) { /* NOTE: atan(y,x) has output range [-M_PI..M_PI], so add 2pi to avoid negative angles. */ - float theta = atan(gather_uv.y, gather_uv.x) + M_TAU; + float theta = atan(gather_uv.y, gather_uv.x) + M_2PI; float r = length(gather_uv); radius /= circle_to_polygon_radius(dof_buf.bokeh_blades, theta - dof_buf.bokeh_rotation); @@ -39,7 +39,7 @@ void main() { /* Slight focus distance */ slight_focus_texel *= dof_buf.bokeh_anisotropic_scale_inv; - float theta = atan(slight_focus_texel.y, -slight_focus_texel.x) + M_TAU; + float theta = atan(slight_focus_texel.y, -slight_focus_texel.x) + M_2PI; slight_focus_texel /= circle_to_polygon_radius(dof_buf.bokeh_blades, theta + dof_buf.bokeh_rotation); } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_downsample_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_downsample_comp.glsl index b265abcf64d..f291a4703d7 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_downsample_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_downsample_comp.glsl @@ -9,7 +9,6 @@ * Also does not weight luma for the bilateral weights. */ -#pragma BLENDER_REQUIRE(gpu_shader_math_vector_lib.glsl) #pragma BLENDER_REQUIRE(eevee_depth_of_field_lib.glsl) void main() @@ -28,7 +27,7 @@ void main() vec4 weights = dof_bilateral_coc_weights(cocs); /* Normalize so that the sum is 1. */ - weights *= safe_rcp(reduce_add(weights)); + weights *= safe_rcp(sum(weights)); vec4 out_color = weighted_sum_array(colors, weights); diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_lib.glsl index 1f5bde10164..7b6abd2b829 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_lib.glsl @@ -6,9 +6,8 @@ * Depth of Field utils. */ -#pragma BLENDER_REQUIRE(draw_view_lib.glsl) -#pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl) -#pragma BLENDER_REQUIRE(gpu_shader_math_base_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_lib.glsl) /* -------------------------------------------------------------------- */ /** \name Constants. @@ -115,10 +114,10 @@ float dof_coc_from_depth(DepthOfFieldData dof_data, vec2 uv, float depth) { if (is_panoramic(dof_data.camera_type)) { /* Use radial depth. */ - depth = -length(drw_point_screen_to_view(vec3(uv, depth))); + depth = -length(get_view_space_from_depth(uv, depth)); } else { - depth = drw_depth_screen_to_view(depth); + depth = get_view_z_from_depth(depth); } return coc_radius_from_camera_depth(dof_data, depth); } @@ -154,7 +153,7 @@ vec4 dof_layer_weight(vec4 coc) float dof_sample_weight(float coc) { #if 1 /* Optimized */ - return min(1.0, 1.0 / square(coc)); + return min(1.0, 1.0 / sqr(coc)); #else /* Full intensity if CoC radius is below the pixel footprint. */ const float min_coc = 1.0; @@ -165,7 +164,7 @@ float dof_sample_weight(float coc) vec4 dof_sample_weight(vec4 coc) { #if 1 /* Optimized */ - return min(vec4(1.0), 1.0 / square(coc)); + return min(vec4(1.0), 1.0 / sqr(coc)); #else /* Full intensity if CoC radius is below the pixel footprint. */ const float min_coc = 1.0; diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_reduce_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_reduce_comp.glsl index c37ee2ba599..2ad9f1ee895 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_reduce_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_reduce_comp.glsl @@ -16,7 +16,6 @@ */ #pragma BLENDER_REQUIRE(eevee_depth_of_field_lib.glsl) -#pragma BLENDER_REQUIRE(gpu_shader_math_vector_lib.glsl) /* NOTE: Do not compare alpha as it is not scattered by the scatter pass. */ float dof_scatter_neighborhood_rejection(vec3 color) @@ -35,7 +34,7 @@ float dof_scatter_neighborhood_rejection(vec3 color) vec3 ref = textureLod(downsample_tx, sample_uv, 0.0).rgb; ref = min(vec3(dof_buf.scatter_neighbor_max_color), ref); - float diff = reduce_max(max(vec3(0.0), abs(ref - color))); + float diff = max_v3(max(vec3(0.0), abs(ref - color))); const float rejection_threshold = 0.7; diff = saturate(diff / rejection_threshold - 1.0); @@ -52,7 +51,7 @@ float dof_scatter_screen_border_rejection(float coc, ivec2 texel) vec2 screen_size = vec2(imageSize(inout_color_lod0_img)); vec2 uv = (vec2(texel) + 0.5) / screen_size; vec2 screen_pos = uv * screen_size; - float min_screen_border_distance = reduce_min(min(screen_pos, screen_size - screen_pos)); + float min_screen_border_distance = min_v2(min(screen_pos, screen_size - screen_pos)); /* Full-resolution to half-resolution CoC. */ coc *= 0.5; /* Allow 10px transition. */ @@ -63,7 +62,7 @@ float dof_scatter_screen_border_rejection(float coc, ivec2 texel) float dof_scatter_luminosity_rejection(vec3 color) { const float rejection_hardness = 1.0; - return saturate(reduce_max(color - dof_buf.scatter_color_threshold) * rejection_hardness); + return saturate(max_v3(color - dof_buf.scatter_color_threshold) * rejection_hardness); } float dof_scatter_coc_radius_rejection(float coc) @@ -119,7 +118,7 @@ void main() do_scatter4.w = do_scatter[LOCAL_OFFSET(0, 0)]; if (any(greaterThan(do_scatter4, vec4(0.0)))) { /* Apply energy conservation to anamorphic scattered bokeh. */ - do_scatter4 *= reduce_max(dof_buf.bokeh_anisotropic_scale_inv); + do_scatter4 *= max_v2(dof_buf.bokeh_anisotropic_scale_inv); /* Circle of Confusion. */ vec4 coc4; @@ -133,7 +132,7 @@ void main() vec2 offset = vec2(gl_GlobalInvocationID.xy) + 1; /* Add 2.5 to max_coc because the max_coc may not be centered on the sprite origin * and because we smooth the bokeh shape a bit in the pixel shader. */ - vec2 half_extent = reduce_max(abs(coc4)) * dof_buf.bokeh_anisotropic_scale + 2.5; + vec2 half_extent = max_v4(abs(coc4)) * dof_buf.bokeh_anisotropic_scale + 2.5; /* Issue a sprite for each field if any CoC matches. */ if (any(lessThan(do_scatter4 * sign(coc4), vec4(0.0)))) { /* Same value for all threads. Not an issue if we don't sync access to it. */ @@ -227,7 +226,7 @@ void main() vec4 weights = dof_bilateral_coc_weights(coc4); weights *= dof_bilateral_color_weights(colors); /* Normalize so that the sum is 1. */ - weights *= safe_rcp(reduce_add(weights)); + weights *= safe_rcp(sum(weights)); color_cache[LOCAL_INDEX] = weighted_sum_array(colors, weights); coc_cache[LOCAL_INDEX] = dot(coc4, weights); diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_resolve_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_resolve_comp.glsl index ed58a9abd7e..8f6df511376 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_resolve_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_resolve_comp.glsl @@ -72,11 +72,11 @@ vec3 dof_neighborhood_clamp(vec2 frag_coord, vec3 color, float center_coc, float neighbor_max = (i == 0) ? ycocg_sample : max(neighbor_max, ycocg_sample); } /* Pad the bounds in the near in focus region to get back a bit of detail. */ - float padding = 0.125 * saturate(1.0 - square(center_coc) / square(8.0)); + float padding = 0.125 * saturate(1.0 - sqr(center_coc) / sqr(8.0)); neighbor_max += abs(neighbor_min) * padding; neighbor_min -= abs(neighbor_min) * padding; /* Progressively apply the clamp to avoid harsh transition. Also mask by weight. */ - float fac = saturate(square(center_coc) * 4.0) * weight; + float fac = saturate(sqr(center_coc) * 4.0) * weight; /* Clamp in YCoCg space to avoid too much color drift. */ color = colorspace_YCoCg_from_scene_linear(color); color = mix(color, clamp(color, neighbor_min, neighbor_max), fac); @@ -154,7 +154,7 @@ void main() } if (!no_focus_pass && prediction.do_focus) { - layer_color = colorspace_safe_color(textureLod(color_tx, uv, 0.0)); + layer_color = safe_color(textureLod(color_tx, uv, 0.0)); layer_weight = 1.0; /* Composite in focus. */ out_color = out_color * (1.0 - layer_weight) + layer_color; diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_scatter_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_scatter_frag.glsl index 74b1440b6d2..03bd4c6119c 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_scatter_frag.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_scatter_frag.glsl @@ -10,7 +10,6 @@ */ #pragma BLENDER_REQUIRE(eevee_depth_of_field_lib.glsl) -#pragma BLENDER_REQUIRE(gpu_shader_math_vector_lib.glsl) #define linearstep(p0, p1, v) (clamp(((v) - (p0)) / abs((p1) - (p0)), 0.0, 1.0)) @@ -39,7 +38,7 @@ void main() /* Smooth the edges a bit to fade out the undersampling artifacts. */ shapes = saturate(1.0 - linearstep(-0.8, 0.8, shapes)); /* Outside of bokeh shape. Try to avoid overloading ROPs. */ - if (reduce_max(shapes) == 0.0) { + if (max_v4(shapes) == 0.0) { discard; return; } @@ -53,7 +52,7 @@ void main() /* Occlude the sprite with geometry from the same field using a chebychev test (slide 85). */ float mean = occlusion_data.x; float variance = occlusion_data.y; - shapes *= variance * safe_rcp(variance + square(max(coc4 * correction_fac - mean, 0.0))); + shapes *= variance * safe_rcp(variance + sqr(max(coc4 * correction_fac - mean, 0.0))); } out_color = (interp_flat.color_and_coc1 * shapes[0] + interp_flat.color_and_coc2 * shapes[1] + @@ -62,6 +61,6 @@ void main() out_color.a = 0.0; if (debug_scatter_perf) { - out_color.rgb = average(out_color.rgb) * vec3(1.0, 0.0, 0.0); + out_color.rgb = avg(out_color.rgb) * vec3(1.0, 0.0, 0.0); } } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_scatter_vert.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_scatter_vert.glsl index 3d7480f7f1b..eaccb8e5fbf 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_scatter_vert.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_scatter_vert.glsl @@ -10,7 +10,6 @@ */ #pragma BLENDER_REQUIRE(eevee_depth_of_field_lib.glsl) -#pragma BLENDER_REQUIRE(gpu_shader_math_vector_lib.glsl) void main() { @@ -37,7 +36,7 @@ void main() interp_noperspective.rect_uv3 = ((uv + quad_offsets[2]) * uv_div) * 0.5 + 0.5; interp_noperspective.rect_uv4 = ((uv + quad_offsets[3]) * uv_div) * 0.5 + 0.5; /* Only for sampling. */ - interp_flat.distance_scale *= reduce_max(abs(rect.half_extent)); + interp_flat.distance_scale *= max_v2(abs(rect.half_extent)); } else { interp_flat.distance_scale = 1.0; diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_setup_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_setup_comp.glsl index 71854ed5889..ab7dfa1a305 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_setup_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_setup_comp.glsl @@ -14,10 +14,9 @@ * Half-resolution Color, signed CoC (out_coc.x), and max slight focus abs CoC (out_coc.y). */ -#pragma BLENDER_REQUIRE(draw_view_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_lib.glsl) #pragma BLENDER_REQUIRE(eevee_depth_of_field_lib.glsl) -#pragma BLENDER_REQUIRE(eevee_colorspace_lib.glsl) -#pragma BLENDER_REQUIRE(gpu_shader_math_vector_lib.glsl) void main() { @@ -30,7 +29,7 @@ void main() for (int i = 0; i < 4; i++) { vec2 sample_uv = quad_center + quad_offsets[i] * fullres_texel_size; /* NOTE: We use samplers without filtering. */ - colors[i] = colorspace_safe_color(textureLod(color_tx, sample_uv, 0.0)); + colors[i] = safe_color(textureLod(color_tx, sample_uv, 0.0)); cocs[i] = dof_coc_from_depth(dof_buf, sample_uv, textureLod(depth_tx, sample_uv, 0.0).r); } @@ -39,7 +38,7 @@ void main() vec4 weights = dof_bilateral_coc_weights(cocs); weights *= dof_bilateral_color_weights(colors); /* Normalize so that the sum is 1. */ - weights *= safe_rcp(reduce_add(weights)); + weights *= safe_rcp(sum(weights)); ivec2 out_texel = ivec2(gl_GlobalInvocationID.xy); vec4 out_color = weighted_sum_array(colors, weights); diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_stabilize_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_stabilize_comp.glsl index dc219266359..b31a22c3316 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_stabilize_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_depth_of_field_stabilize_comp.glsl @@ -17,6 +17,7 @@ * - Stabilized Color and CoC (half-resolution). */ +#pragma BLENDER_REQUIRE(common_math_geom_lib.glsl) #pragma BLENDER_REQUIRE(eevee_colorspace_lib.glsl) #pragma BLENDER_REQUIRE(eevee_depth_of_field_lib.glsl) #pragma BLENDER_REQUIRE(eevee_velocity_lib.glsl) @@ -107,7 +108,7 @@ float dof_fetch_half_depth(ivec2 offset) float dof_luma_weight(float luma) { - /* Slide 20 of "High Quality Temporal Supersampling" by Brian Karis at SIGGRAPH 2014. */ + /* Slide 20 of "High Quality Temporal Supersampling" by Brian Karis at Siggraph 2014. */ /* To preserve more details in dark areas, we use a bigger bias. */ const float exposure_scale = 1.0; /* TODO. */ return 1.0 / (4.0 + luma * exposure_scale); @@ -168,7 +169,7 @@ DofNeighborhoodMinMax dof_neighbor_boundbox() const ivec2 plus_offsets[4] = ivec2[4](ivec2(-1, 0), ivec2(0, -1), ivec2(1, 0), ivec2(0, 1)); /** * Simple bounding box calculation in YCoCg as described in: - * "High Quality Temporal Supersampling" by Brian Karis at SIGGRAPH 2014 + * "High Quality Temporal Supersampling" by Brian Karis at Siggraph 2014 */ DofSample min_c = dof_fetch_input_sample(ivec2(0)); DofSample max_c = min_c; @@ -204,7 +205,7 @@ vec2 dof_pixel_history_motion_vector(ivec2 texel_sample) { /** * Dilate velocity by using the nearest pixel in a cross pattern. - * "High Quality Temporal Supersampling" by Brian Karis at SIGGRAPH 2014 (Slide 27) + * "High Quality Temporal Supersampling" by Brian Karis at Siggraph 2014 (Slide 27) */ const ivec2 corners[4] = ivec2[4](ivec2(-2, -2), ivec2(2, -2), ivec2(-2, 2), ivec2(2, 2)); float min_depth = dof_fetch_half_depth(ivec2(0)); @@ -241,7 +242,7 @@ DofSample dof_sample_history(vec2 input_texel) /** * Use optimized version by leveraging bilinear filtering from hardware sampler and by removing * corner taps. - * From "Filmic SMAA" by Jorge Jimenez at SIGGRAPH 2016 + * From "Filmic SMAA" by Jorge Jimenez at Siggraph 2016 * http://advances.realtimerendering.com/s2016/Filmic%20SMAA%20v7.pptx */ center_texel += 0.5; @@ -262,7 +263,7 @@ DofSample dof_sample_history(vec2 input_texel) color += textureLod(in_history_tx, vec2(uv_3.x, uv_12.y), 0.0) * weight_cross.z; color += textureLod(in_history_tx, vec2(uv_12.x, uv_3.y), 0.0) * weight_cross.w; /* Re-normalize for the removed corners. */ - color /= (weight_center + reduce_add(weight_cross)); + color /= (weight_center + sum(weight_cross)); #endif /* NOTE(fclem): Opacity is wrong on purpose. Final Opacity does not rely on history. */ return DofSample(color.xyzz, color.w); @@ -302,10 +303,10 @@ float dof_history_blend_factor( /* NOTE(fclem): velocity threshold multiplied by 2 because of half resolution. */ blend = mix(blend, 0.20, saturate(velocity * 0.02 * 2.0)); /** - * "High Quality Temporal Supersampling" by Brian Karis at SIGGRAPH 2014 (Slide 43) + * "High Quality Temporal Supersampling" by Brian Karis at Siggraph 2014 (Slide 43) * Bias towards history if incoming pixel is near clamping. Reduces flicker. */ - float distance_to_luma_clip = reduce_min(vec2(luma_history - luma_min, luma_max - luma_history)); + float distance_to_luma_clip = min_v2(vec2(luma_history - luma_min, luma_max - luma_history)); /* Divide by bbox size to get a factor. 2 factor to compensate the line above. */ distance_to_luma_clip *= 2.0 * safe_rcp(luma_max - luma_min); /* Linearly blend when history gets below to 25% of the bbox size. */ diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_display_probe_grid_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_display_probe_grid_frag.glsl index 66c52964810..528940e20b8 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_display_probe_grid_frag.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_display_probe_grid_frag.glsl @@ -3,8 +3,8 @@ * SPDX-License-Identifier: GPL-2.0-or-later */ #pragma BLENDER_REQUIRE(eevee_spherical_harmonics_lib.glsl) -#pragma BLENDER_REQUIRE(gpu_shader_math_matrix_lib.glsl) -#pragma BLENDER_REQUIRE(draw_view_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_lib.glsl) void main() { @@ -24,7 +24,7 @@ void main() float validity = texelFetch(validity_tx, cell, 0).r; vec3 vN = vec3(lP, sqrt(max(0.0, 1.0 - dist_sqr))); - vec3 N = drw_normal_view_to_world(vN); + vec3 N = normal_view_to_world(vN); vec3 lN = transform_direction(world_to_grid, N); vec3 irradiance = spherical_harmonics_evaluate_lambert(lN, sh); diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_display_probe_grid_vert.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_display_probe_grid_vert.glsl index 968310d660a..3d2977b1615 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_display_probe_grid_vert.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_display_probe_grid_vert.glsl @@ -2,7 +2,7 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ -#pragma BLENDER_REQUIRE(draw_view_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_lib.glsl) #pragma BLENDER_REQUIRE(eevee_lightprobe_lib.glsl) void main() @@ -35,9 +35,9 @@ void main() } vec3 vs_offset = vec3(lP, 0.0) * sphere_radius_final; - vec3 vP = drw_point_world_to_view(ws_cell_pos) + vs_offset; + vec3 vP = (ViewMatrix * vec4(ws_cell_pos, 1.0)).xyz + vs_offset; - gl_Position = drw_point_view_to_homogenous(vP); + gl_Position = ProjectionMatrix * vec4(vP, 1.0); /* Small bias to let the icon draw without Z-fighting. */ gl_Position.z += 0.0001; } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_film_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_film_comp.glsl index 28b246036b1..912d6276f7c 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_film_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_film_comp.glsl @@ -2,6 +2,7 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ +#pragma BLENDER_REQUIRE(common_view_lib.glsl) #pragma BLENDER_REQUIRE(eevee_film_lib.glsl) void main() diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_film_cryptomatte_post_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_film_cryptomatte_post_comp.glsl index 5a77f525e3f..804bfa6a426 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_film_cryptomatte_post_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_film_cryptomatte_post_comp.glsl @@ -2,7 +2,7 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ -#pragma BLENDER_REQUIRE(gpu_shader_math_base_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_lib.glsl) #define CRYPTOMATTE_LEVELS_MAX 16 diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_film_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_film_frag.glsl index 7f265f207ff..650eea6180d 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_film_frag.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_film_frag.glsl @@ -2,6 +2,7 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ +#pragma BLENDER_REQUIRE(common_view_lib.glsl) #pragma BLENDER_REQUIRE(eevee_film_lib.glsl) void main() @@ -32,7 +33,7 @@ void main() film_process_data(texel_film, out_color, out_depth); } - gl_FragDepth = drw_depth_view_to_screen(-out_depth); + gl_FragDepth = get_depth_from_view_z(-out_depth); gl_FragDepth = film_display_depth_ammend(texel_film, gl_FragDepth); } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_film_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_film_lib.glsl index 151bf8c9b88..73d2b27cdd3 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_film_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_film_lib.glsl @@ -6,12 +6,12 @@ * Film accumulation utils functions. */ -#pragma BLENDER_REQUIRE(draw_view_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_geom_lib.glsl) +#pragma BLENDER_REQUIRE(eevee_camera_lib.glsl) +#pragma BLENDER_REQUIRE(eevee_velocity_lib.glsl) #pragma BLENDER_REQUIRE(eevee_colorspace_lib.glsl) #pragma BLENDER_REQUIRE(eevee_cryptomatte_lib.glsl) -#pragma BLENDER_REQUIRE(gpu_shader_math_vector_lib.glsl) -#pragma BLENDER_REQUIRE(draw_math_geom_lib.glsl) -#pragma BLENDER_REQUIRE(eevee_velocity_lib.glsl) /* Return scene linear Z depth from the camera or radial depth for panoramic cameras. */ float film_depth_convert_to_scene(float depth) @@ -20,7 +20,7 @@ float film_depth_convert_to_scene(float depth) /* TODO */ return 1.0; } - return abs(drw_depth_screen_to_view(depth)); + return abs(get_view_z_from_depth(depth)); } /* Load a texture sample in a specific format. Combined pass needs to use this. */ @@ -37,7 +37,7 @@ vec4 film_texelfetch_as_YCoCg_opacity(sampler2D tx, ivec2 texel) /* Returns a weight based on Luma to reduce the flickering introduced by high energy pixels. */ float film_luma_weight(float luma) { - /* Slide 20 of "High Quality Temporal Supersampling" by Brian Karis at SIGGRAPH 2014. */ + /* Slide 20 of "High Quality Temporal Supersampling" by Brian Karis at Siggraph 2014. */ /* To preserve more details in dark areas, we use a bigger bias. */ return 1.0 / (4.0 + luma * uniform_buf.film.exposure_scale); } @@ -70,7 +70,7 @@ FilmSample film_sample_get(int sample_n, ivec2 texel_film) * can be used by many final pixel. */ vec2 offset = uniform_buf.film.subpixel_offset - vec2(texel_film % uniform_buf.film.scaling_factor); - film_sample.weight = film_filter_weight(uniform_buf.film.filter_size, length_squared(offset)); + film_sample.weight = film_filter_weight(uniform_buf.film.filter_size, len_squared(offset)); # endif #endif /* PANORAMIC */ @@ -119,7 +119,7 @@ void film_sample_accum_mist(FilmSample samp, inout float accum) } float depth = texelFetch(depth_tx, samp.texel, 0).x; vec2 uv = (vec2(samp.texel) + 0.5) / vec2(textureSize(depth_tx, 0).xy); - vec3 vP = drw_point_screen_to_view(vec3(uv, depth)); + vec3 vP = get_view_space_from_depth(uv, depth); bool is_persp = ProjectionMatrix[3][3] == 0.0; float mist = (is_persp) ? length(vP) : abs(vP.z); /* Remap to 0..1 range. */ @@ -223,7 +223,7 @@ vec2 film_pixel_history_motion_vector(ivec2 texel_sample) { /** * Dilate velocity by using the nearest pixel in a cross pattern. - * "High Quality Temporal Supersampling" by Brian Karis at SIGGRAPH 2014 (Slide 27) + * "High Quality Temporal Supersampling" by Brian Karis at Siggraph 2014 (Slide 27) */ const ivec2 corners[4] = ivec2[4](ivec2(-2, -2), ivec2(2, -2), ivec2(-2, 2), ivec2(2, 2)); float min_depth = texelFetch(depth_tx, texel_sample, 0).x; @@ -291,7 +291,7 @@ vec4 film_sample_catmull_rom(sampler2D color_tx, vec2 input_texel) /** * Use optimized version by leveraging bilinear filtering from hardware sampler and by removing * corner taps. - * From "Filmic SMAA" by Jorge Jimenez at SIGGRAPH 2016 + * From "Filmic SMAA" by Jorge Jimenez at Siggraph 2016 * http://advances.realtimerendering.com/s2016/Filmic%20SMAA%20v7.pptx */ center_texel += 0.5; @@ -312,7 +312,7 @@ vec4 film_sample_catmull_rom(sampler2D color_tx, vec2 input_texel) color += textureLod(color_tx, vec2(uv_3.x, uv_12.y), 0.0) * weight_cross.z; color += textureLod(color_tx, vec2(uv_12.x, uv_3.y), 0.0) * weight_cross.w; /* Re-normalize for the removed corners. */ - return color / (weight_center + reduce_add(weight_cross)); + return color / (weight_center + sum(weight_cross)); #else /* Nearest interpolation for debugging. 1 Tap. */ ivec2 texel = ivec2(center_texel) + ivec2(greaterThan(inter_texel, vec2(0.5))); @@ -343,7 +343,7 @@ void film_combined_neighbor_boundbox(ivec2 texel, out vec4 min_c, out vec4 max_c for (int i = 0; i < 5; i++) { vec4 color = film_texelfetch_as_YCoCg_opacity(combined_tx, texel + plus_offsets[i]); mu1 += color; - mu2 += square(color); + mu2 += sqr(color); } mu1 *= (1.0 / 5.0); mu2 *= (1.0 / 5.0); @@ -352,14 +352,14 @@ void film_combined_neighbor_boundbox(ivec2 texel, out vec4 min_c, out vec4 max_c * Balance between more flickering (0.75) or more ghosting (1.25). */ const float gamma = 1.25; /* Standard deviation. */ - vec4 sigma = sqrt(abs(mu2 - square(mu1))); + vec4 sigma = sqrt(abs(mu2 - sqr(mu1))); /* eq. 6 in "A Survey of Temporal Anti-aliasing Techniques". */ min_c = mu1 - gamma * sigma; max_c = mu1 + gamma * sigma; #else /** * Simple bounding box calculation in YCoCg as described in: - * "High Quality Temporal Supersampling" by Brian Karis at SIGGRAPH 2014 + * "High Quality Temporal Supersampling" by Brian Karis at Siggraph 2014 */ min_c = vec4(1e16); max_c = vec4(-1e16); @@ -422,10 +422,10 @@ float film_history_blend_factor(float velocity, /* Blend less history if the pixel has substantial velocity. */ blend = mix(blend, 0.20, saturate(velocity * 0.02)); /** - * "High Quality Temporal Supersampling" by Brian Karis at SIGGRAPH 2014 (Slide 43) + * "High Quality Temporal Supersampling" by Brian Karis at Siggraph 2014 (Slide 43) * Bias towards history if incoming pixel is near clamping. Reduces flicker. */ - float distance_to_luma_clip = reduce_min(vec2(luma_history - luma_min, luma_max - luma_history)); + float distance_to_luma_clip = min_v2(vec2(luma_history - luma_min, luma_max - luma_history)); /* Divide by bbox size to get a factor. 2 factor to compensate the line above. */ distance_to_luma_clip *= 2.0 * safe_rcp(luma_max - luma_min); /* Linearly blend when history gets below to 25% of the bbox size. */ @@ -747,15 +747,12 @@ void film_process_data(ivec2 texel_film, out vec4 out_color, out float out_depth ao_accum); film_sample_accum_mist(src, mist_accum); } - /* Monochrome render passes that have colored outputs. Set alpha to 1. */ - vec4 shadow_accum_color = vec4(vec3(shadow_accum), weight_accum); - vec4 ao_accum_color = vec4(vec3(ao_accum), weight_accum); - film_store_color(dst, uniform_buf.film.diffuse_color_id, diffuse_color_accum, out_color); film_store_color(dst, uniform_buf.film.specular_color_id, specular_color_accum, out_color); film_store_color(dst, uniform_buf.film.environment_id, environment_accum, out_color); - film_store_color(dst, uniform_buf.film.shadow_id, shadow_accum_color, out_color); - film_store_color(dst, uniform_buf.film.ambient_occlusion_id, ao_accum_color, out_color); + film_store_color(dst, uniform_buf.film.shadow_id, vec4(vec3(shadow_accum), 1.0), out_color); + film_store_color( + dst, uniform_buf.film.ambient_occlusion_id, vec4(vec3(ao_accum), 1.0), out_color); film_store_value(dst, uniform_buf.film.mist_id, mist_accum, out_color); } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_geom_curves_vert.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_geom_curves_vert.glsl index c8df870040e..57d8d1b5a7e 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_geom_curves_vert.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_geom_curves_vert.glsl @@ -2,12 +2,13 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ -#pragma BLENDER_REQUIRE(draw_model_lib.glsl) +#pragma BLENDER_REQUIRE(common_hair_lib.glsl) /* TODO rename to curve. */ +#pragma BLENDER_REQUIRE(common_math_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_lib.glsl) #pragma BLENDER_REQUIRE(eevee_attributes_lib.glsl) #pragma BLENDER_REQUIRE(eevee_nodetree_lib.glsl) #pragma BLENDER_REQUIRE(eevee_surf_lib.glsl) #pragma BLENDER_REQUIRE(eevee_velocity_lib.glsl) -#pragma BLENDER_REQUIRE(common_hair_lib.glsl) /* TODO rename to curve. */ void main() { @@ -24,15 +25,15 @@ void main() ViewMatrixInverse[3].xyz, ViewMatrixInverse[2].xyz, interp.P, - curve_interp.tangent, - curve_interp.binormal, - curve_interp.time, - curve_interp.thickness, - curve_interp.time_width); + interp.curves_tangent, + interp.curves_binormal, + interp.curves_time, + interp.curves_thickness, + interp.curves_time_width); - interp.N = cross(curve_interp.tangent, curve_interp.binormal); - curve_interp_flat.strand_id = hair_get_strand_id(); - curve_interp.barycentric_coords = hair_get_barycentric(); + interp.N = cross(interp.curves_tangent, interp.curves_binormal); + interp_flat.curves_strand_id = hair_get_strand_id(); + interp.barycentric_coords = hair_get_barycentric(); #ifdef MAT_VELOCITY /* Due to the screen space nature of the vertex positioning, we compute only the motion of curve * strand, not its cylinder. Otherwise we would add the rotation velocity. */ @@ -53,9 +54,5 @@ void main() interp.P += nodetree_displacement(); -#ifdef MAT_CLIP_PLANE - clip_interp.clip_distance = dot(clip_plane.plane, vec4(interp.P, 1.0)); -#endif - - gl_Position = drw_point_world_to_homogenous(interp.P); + gl_Position = point_world_to_ndc(interp.P); } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_geom_gpencil_vert.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_geom_gpencil_vert.glsl index 8d6aac80718..3ecb6d8d730 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_geom_gpencil_vert.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_geom_gpencil_vert.glsl @@ -2,9 +2,8 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ -#pragma BLENDER_REQUIRE(draw_model_lib.glsl) -/* Grease pencil includes commmon_view_lib. */ -// #pragma BLENDER_REQUIRE(common_gpencil_lib.glsl) +#pragma BLENDER_REQUIRE(common_gpencil_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_lib.glsl) #pragma BLENDER_REQUIRE(eevee_attributes_lib.glsl) #pragma BLENDER_REQUIRE(eevee_surf_lib.glsl) #pragma BLENDER_REQUIRE(eevee_velocity_lib.glsl) @@ -39,7 +38,7 @@ void main() hardness); #ifdef MAT_VELOCITY /* GPencil do not support deformation motion blur. */ - vec3 lP_curr = drw_point_world_to_object(interp.P); + vec3 lP_curr = transform_point(ModelMatrixInverse, interp.P); /* FIXME(fclem): Evaluating before displacement avoid displacement being treated as motion but * ignores motion from animated displacement. Supporting animated displacement motion vectors * would require evaluating the node-tree multiple time with different node-tree UBOs evaluated @@ -52,8 +51,4 @@ void main() attrib_load(); interp.P += nodetree_displacement(); - -#ifdef MAT_CLIP_PLANE - clip_interp.clip_distance = dot(clip_plane.plane, vec4(interp.P, 1.0)); -#endif } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_geom_mesh_vert.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_geom_mesh_vert.glsl index 5f23d9e6be1..09875e3ba5b 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_geom_mesh_vert.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_geom_mesh_vert.glsl @@ -2,7 +2,7 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ -#pragma BLENDER_REQUIRE(draw_model_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_lib.glsl) #pragma BLENDER_REQUIRE(eevee_attributes_lib.glsl) #pragma BLENDER_REQUIRE(eevee_nodetree_lib.glsl) #pragma BLENDER_REQUIRE(eevee_surf_lib.glsl) @@ -17,8 +17,8 @@ void main() init_interface(); - interp.P = drw_point_object_to_world(pos); - interp.N = drw_normal_object_to_world(nor); + interp.P = point_object_to_world(pos); + interp.N = normal_object_to_world(nor); #ifdef MAT_VELOCITY vec3 prv, nxt; velocity_local_pos_get(pos, gl_VertexID, prv, nxt); @@ -35,9 +35,5 @@ void main() interp.P += nodetree_displacement(); -#ifdef MAT_CLIP_PLANE - clip_interp.clip_distance = dot(clip_plane.plane, vec4(interp.P, 1.0)); -#endif - - gl_Position = drw_point_world_to_homogenous(interp.P); + gl_Position = point_world_to_ndc(interp.P); } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_geom_point_cloud_vert.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_geom_point_cloud_vert.glsl index ff6d0135f9f..f80d19c7de5 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_geom_point_cloud_vert.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_geom_point_cloud_vert.glsl @@ -2,13 +2,13 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ -#pragma BLENDER_REQUIRE(draw_model_lib.glsl) #pragma BLENDER_REQUIRE(gpu_shader_math_rotation_lib.glsl) +#pragma BLENDER_REQUIRE(common_pointcloud_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_lib.glsl) #pragma BLENDER_REQUIRE(eevee_attributes_lib.glsl) #pragma BLENDER_REQUIRE(eevee_nodetree_lib.glsl) #pragma BLENDER_REQUIRE(eevee_surf_lib.glsl) #pragma BLENDER_REQUIRE(eevee_velocity_lib.glsl) -#pragma BLENDER_REQUIRE(common_pointcloud_lib.glsl) void main() { @@ -27,11 +27,11 @@ void main() * Apply a bias to avoid self-shadow issues. */ /* TODO(fclem): remove multiplication here. Here only for keeping the size correct for now. */ float actual_radius = point_cloud_interp.radius * 0.01; - interp.P -= drw_world_incident_vector(interp.P) * actual_radius; + interp.P -= cameraVec(interp.P) * actual_radius; #endif #ifdef MAT_VELOCITY - vec3 lP = drw_point_world_to_object(point_cloud_interp.position); + vec3 lP = point_world_to_object(point_cloud_interp.position); vec3 prv, nxt; velocity_local_pos_get(lP, point_cloud_interp_flat.id, prv, nxt); /* FIXME(fclem): Evaluating before displacement avoid displacement being treated as motion but @@ -47,9 +47,5 @@ void main() interp.P += nodetree_displacement(); -#ifdef MAT_CLIP_PLANE - clip_interp.clip_distance = dot(clip_plane.plane, vec4(interp.P, 1.0)); -#endif - - gl_Position = drw_point_world_to_homogenous(interp.P); + gl_Position = point_world_to_ndc(interp.P); } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_geom_volume_vert.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_geom_volume_vert.glsl deleted file mode 100644 index d9ccedec578..00000000000 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_geom_volume_vert.glsl +++ /dev/null @@ -1,24 +0,0 @@ -/* SPDX-FileCopyrightText: 2022-2023 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -#pragma BLENDER_REQUIRE(draw_model_lib.glsl) -#pragma BLENDER_REQUIRE(eevee_surf_lib.glsl) -#pragma BLENDER_REQUIRE(common_pointcloud_lib.glsl) - -void main() -{ - DRW_VIEW_FROM_RESOURCE_ID; - - init_interface(); - - /* TODO(fclem): Find a better way? This is reverting what draw_resource_finalize does. */ - vec3 size = safe_rcp(OrcoTexCoFactors[1].xyz * 2.0); /* Box half-extent. */ - vec3 loc = size + (OrcoTexCoFactors[0].xyz / -OrcoTexCoFactors[1].xyz); /* Box center. */ - - /* Use bounding box geometry for now. */ - vec3 lP = loc + pos * size; - interp.P = drw_point_object_to_world(lP); - - gl_Position = drw_point_world_to_homogenous(interp.P); -} diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_geom_world_vert.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_geom_world_vert.glsl index ea59fbe9631..220afe1b2d0 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_geom_world_vert.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_geom_world_vert.glsl @@ -6,7 +6,7 @@ * Custom full-screen triangle with placeholders varyings. */ -#pragma BLENDER_REQUIRE(draw_view_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_lib.glsl) #pragma BLENDER_REQUIRE(eevee_nodetree_lib.glsl) #pragma BLENDER_REQUIRE(eevee_surf_lib.glsl) @@ -19,6 +19,6 @@ void main() gl_Position = vec4(x, y, 1.0, 1.0); /* Pass view position to keep accuracy. */ - interp.P = drw_point_ndc_to_view(gl_Position.xyz); + interp.P = project_point(ProjectionMatrixInverse, gl_Position.xyz); interp.N = vec3(1); } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_hiz_update_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_hiz_update_comp.glsl index 33ecb45f4f2..70bf6ff7108 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_hiz_update_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_hiz_update_comp.glsl @@ -17,7 +17,7 @@ * downsample to max level. */ -#pragma BLENDER_REQUIRE(gpu_shader_math_vector_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_lib.glsl) shared float local_depths[gl_WorkGroupSize.y][gl_WorkGroupSize.x]; @@ -45,11 +45,7 @@ void main() /* Copy level 0. */ ivec2 src_px = ivec2(kernel_origin + local_px) * 2; vec2 samp_co = (vec2(src_px) + 0.5) / vec2(textureSize(depth_tx, 0)); -#ifdef HIZ_LAYER - vec4 samp = textureGather(depth_layered_tx, vec3(samp_co, float(layer_id))); -#else vec4 samp = textureGather(depth_tx, samp_co); -#endif if (update_mip_0) { imageStore(out_mip_0, src_px + ivec2(0, 1), samp.xxxx); @@ -59,7 +55,7 @@ void main() } /* Level 1. (No load) */ - float max_depth = reduce_max(samp); + float max_depth = max_v4(samp); ivec2 dst_px = ivec2(kernel_origin + local_px); imageStore(out_mip_1, dst_px, vec4(max_depth)); store_local_depth(local_px, max_depth); @@ -72,7 +68,7 @@ void main() active_thread = all(lessThan(uvec2(local_px), gl_WorkGroupSize.xy >> uint(mask_shift))); \ barrier(); /* Wait for previous writes to finish. */ \ if (active_thread) { \ - max_depth = reduce_max(load_local_depths(local_px)); \ + max_depth = max_v4(load_local_depths(local_px)); \ dst_px = ivec2((kernel_origin >> mask_shift) + local_px); \ imageStore(out_mip__, dst_px, vec4(max_depth)); \ } \ @@ -109,15 +105,15 @@ void main() samp.z = imageLoad(out_mip_5, min(src_px + ivec2(1, 0), image_border)).x; samp.w = imageLoad(out_mip_5, min(src_px + ivec2(0, 0), image_border)).x; /* Level 6. */ - float max_depth = reduce_max(samp); + float max_depth = max_v4(samp); ivec2 dst_px = ivec2(kernel_origin + local_px); imageStore(out_mip_6, dst_px, vec4(max_depth)); store_local_depth(local_px, max_depth); mask_shift = 1; - /* Level 7 requires barriers inside a non-uniform control flow. */ - // downsample_level(out_mip_7, 7); + /* Level 7. */ + downsample_level(out_mip_7, 7); /* Limited by OpenGL maximum of 8 image slot. */ // downsample_level(out_mip_8, 8); @@ -125,4 +121,4 @@ void main() // downsample_level(out_mip_10, 10); } } -} \ No newline at end of file +} diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_light_culling_debug_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_light_culling_debug_frag.glsl index 344dbc76635..a4ff24de484 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_light_culling_debug_frag.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_light_culling_debug_frag.glsl @@ -8,8 +8,8 @@ * pass is not conservative enough). */ -#pragma BLENDER_REQUIRE(gpu_shader_debug_gradients_lib.glsl) -#pragma BLENDER_REQUIRE(draw_view_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_lib.glsl) #pragma BLENDER_REQUIRE(eevee_light_lib.glsl) #pragma BLENDER_REQUIRE(eevee_light_iter_lib.glsl) @@ -18,8 +18,8 @@ void main() ivec2 texel = ivec2(gl_FragCoord.xy); float depth = texelFetch(hiz_tx, texel, 0).r; - float vP_z = drw_depth_screen_to_view(depth); - vec3 P = drw_point_screen_to_world(vec3(uvcoordsvar.xy, depth)); + float vP_z = get_view_z_from_depth(depth); + vec3 P = get_world_space_from_depth(uvcoordsvar.xy, depth); float light_count = 0.0; uint light_cull = 0u; @@ -35,9 +35,10 @@ void main() LIGHT_FOREACH_BEGIN_LOCAL_NO_CULL(light_cull_buf, l_idx) { LightData light = light_buf[l_idx]; - LightVector lv = light_vector_get(light, false, P); - /* Use light vector as Ng to never cull based on angle to light. */ - if (light_attenuation_surface(light, false, lv.L, false, lv) > LIGHT_ATTENUATION_THRESHOLD) { + vec3 L; + float dist; + light_vector_get(light, P, L, dist); + if (light_attenuation(light, L, dist) > 0.0) { light_nocull |= 1u << l_idx; } } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_light_culling_select_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_light_culling_select_comp.glsl index 15e8f576098..20ce3c19f70 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_light_culling_select_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_light_culling_select_comp.glsl @@ -6,9 +6,9 @@ * Select the visible items inside the active view and put them inside the sorting buffer. */ -#pragma BLENDER_REQUIRE(draw_view_lib.glsl) -#pragma BLENDER_REQUIRE(draw_math_geom_lib.glsl) -#pragma BLENDER_REQUIRE(draw_intersect_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_geom_lib.glsl) +#pragma BLENDER_REQUIRE(common_intersect_lib.glsl) void main() { @@ -62,9 +62,7 @@ void main() if (intersect_view(sphere)) { uint index = atomicAdd(light_cull_buf.visible_count, 1u); - float z_dist = dot(drw_view_forward(), light._position) - - dot(drw_view_forward(), drw_view_position()); - out_zdist_buf[index] = z_dist; + out_zdist_buf[index] = dot(cameraForward, light._position) - dot(cameraForward, cameraPos); out_key_buf[index] = l_idx; } } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_light_culling_sort_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_light_culling_sort_comp.glsl index 5565bccabb0..46897820813 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_light_culling_sort_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_light_culling_sort_comp.glsl @@ -8,7 +8,7 @@ * One thread processes one Light entity. */ -#pragma BLENDER_REQUIRE(gpu_shader_math_base_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_lib.glsl) shared float zdists_cache[gl_WorkGroupSize.x]; diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_light_culling_tile_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_light_culling_tile_comp.glsl index 7533d4891a6..a574c654bf3 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_light_culling_tile_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_light_culling_tile_comp.glsl @@ -8,7 +8,7 @@ * Dispatch one thread per word. */ -#pragma BLENDER_REQUIRE(draw_view_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_lib.glsl) #pragma BLENDER_REQUIRE(common_intersect_lib.glsl) #pragma BLENDER_REQUIRE(eevee_light_iter_lib.glsl) @@ -148,10 +148,10 @@ void main() LightData light = light_buf[l_idx]; /* Culling in view space for precision and simplicity. */ - vec3 vP = drw_point_world_to_view(light._position); - vec3 v_right = drw_normal_world_to_view(light._right); - vec3 v_up = drw_normal_world_to_view(light._up); - vec3 v_back = drw_normal_world_to_view(light._back); + vec3 vP = transform_point(ViewMatrix, light._position); + vec3 v_right = transform_direction(ViewMatrix, light._right); + vec3 v_up = transform_direction(ViewMatrix, light._up); + vec3 v_back = transform_direction(ViewMatrix, light._back); float radius = light.influence_radius_max; Sphere sphere = shape_sphere(vP, radius); diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_light_culling_zbin_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_light_culling_zbin_comp.glsl index d947d73fc72..a5eabc4ea03 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_light_culling_zbin_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_light_culling_zbin_comp.glsl @@ -8,8 +8,7 @@ * For this reason, we only dispatch 1 thread group. */ -#pragma BLENDER_REQUIRE(draw_view_lib.glsl) -#pragma BLENDER_REQUIRE(gpu_shader_math_base_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_lib.glsl) #pragma BLENDER_REQUIRE(eevee_light_iter_lib.glsl) /* Fits the limit of 32KB. */ @@ -38,7 +37,7 @@ void main() vec3 P = light_buf[index]._position; /* TODO(fclem): Could have better bounds for spot and area lights. */ float radius = light_buf[index].influence_radius_max; - float z_dist = dot(drw_view_forward(), P) - dot(drw_view_forward(), drw_view_position()); + float z_dist = dot(cameraForward, P) - dot(cameraForward, cameraPos); int z_min = culling_z_to_zbin( light_cull_buf.zbin_scale, light_cull_buf.zbin_bias, z_dist + radius); int z_max = culling_z_to_zbin( diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_light_eval_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_light_eval_lib.glsl index 8a0f6f1b302..174da95590d 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_light_eval_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_light_eval_lib.glsl @@ -18,185 +18,118 @@ #pragma BLENDER_REQUIRE(eevee_shadow_lib.glsl) #pragma BLENDER_REQUIRE(gpu_shader_codegen_lib.glsl) -/* If using compute, the shader should define it's own pixel. */ -#if !defined(PIXEL) && defined(GPU_FRAGMENT_SHADER) -# define PIXEL gl_FragCoord.xy -#endif - -#if !defined(LIGHT_CLOSURE_EVAL_COUNT) -# define LIGHT_CLOSURE_EVAL_COUNT 1 -#endif - -uint shadow_pack(float visibility, uint bit_depth, uint shift) -{ - return uint(visibility * float((1u << bit_depth) - 1u)) << shift; -} - -float shadow_unpack(uint shadow_bits, uint bit_depth, uint shift) -{ - return float((shadow_bits >> shift) & ~(~0u << bit_depth)) / float((1u << bit_depth) - 1u); -} - -void light_shadow_single(uint l_idx, - const bool is_directional, - vec3 P, - vec3 Ng, - float thickness, - inout uint shadow_bits, - inout uint shift) +void light_eval_ex(ClosureDiffuse diffuse, + ClosureReflection reflection, + const bool is_directional, + vec3 P, + vec3 Ng, + vec3 V, + float vP_z, /* TODO(fclem): Remove, is unused. */ + float thickness, + vec4 ltc_mat, + uint l_idx, + inout vec3 out_diffuse, + inout vec3 out_specular, + inout float out_shadow) { LightData light = light_buf[l_idx]; + vec3 L; + float dist; + light_vector_get(light, P, L, dist); - if (light.tilemap_index == LIGHT_NO_SHADOW) { - return; - } + float visibility = is_directional ? 1.0 : light_attenuation(light, L, dist); - bool use_subsurface = thickness > 0.0; - LightVector lv = light_vector_get(light, is_directional, P); - float attenuation = light_attenuation_surface(light, is_directional, Ng, use_subsurface, lv); - if (attenuation < LIGHT_ATTENUATION_THRESHOLD) { - return; - } - int ray_count = uniform_buf.shadow.ray_count; - int ray_step_count = uniform_buf.shadow.step_count; - - ShadowEvalResult result = shadow_eval( - light, is_directional, P, Ng, thickness, ray_count, ray_step_count); - - shadow_bits |= shadow_pack(result.light_visibilty, ray_count, shift); - shift += ray_count; -} - -void light_shadow_mask(vec3 P, vec3 Ng, float vPz, float thickness, out uint shadow_bits) -{ - int ray_count = uniform_buf.shadow.ray_count; - int ray_step_count = uniform_buf.shadow.step_count; - - uint shift = 0u; - shadow_bits = 0u; - LIGHT_FOREACH_BEGIN_DIRECTIONAL (light_cull_buf, l_idx) { - light_shadow_single(l_idx, true, P, Ng, thickness, shadow_bits, shift); - } - LIGHT_FOREACH_END - - LIGHT_FOREACH_BEGIN_LOCAL (light_cull_buf, light_zbin_buf, light_tile_buf, PIXEL, vPz, l_idx) { - light_shadow_single(l_idx, false, P, Ng, thickness, shadow_bits, shift); - } - LIGHT_FOREACH_END -} - -struct ClosureLight { - /* Shading normal. */ - vec3 N; - /* LTC matrix. */ - vec4 ltc_mat; - /* Enum (used as index) telling how to treat the lighting. */ - LightingType type; - /* Output both shadowed and unshadowed for shadow denoising. */ - vec3 light_shadowed; - vec3 light_unshadowed; -}; - -struct ClosureLightStack { - /* NOTE: This is wrapped into a struct to avoid array shenanigans on MSL. */ - ClosureLight cl[LIGHT_CLOSURE_EVAL_COUNT]; -}; - -void light_eval_single_closure(LightData light, - LightVector lv, - inout ClosureLight cl, - vec3 P, - vec3 V, - float thickness, - float attenuation, - float visibility) -{ - if (light.power[cl.type] > 0.0) { - float ltc_result = light_ltc(utility_tx, light, cl.N, V, lv, cl.ltc_mat); - vec3 out_radiance = light.color * light.power[cl.type] * ltc_result; - cl.light_shadowed += visibility * out_radiance; - cl.light_unshadowed += attenuation * out_radiance; - } -} - -void light_eval_single(uint l_idx, - const bool is_directional, - inout ClosureLightStack stack, - vec3 P, - vec3 Ng, - vec3 V, - float thickness, - uint packed_shadows, - inout uint shift) -{ - LightData light = light_buf[l_idx]; - int ray_count = uniform_buf.shadow.ray_count; - int ray_step_count = uniform_buf.shadow.step_count; - - bool use_subsurface = thickness > 0.0; - LightVector lv = light_vector_get(light, is_directional, P); - float attenuation = light_attenuation_surface(light, is_directional, Ng, use_subsurface, lv); - if (attenuation < LIGHT_ATTENUATION_THRESHOLD) { - return; - } - float shadow = 1.0; - if (light.tilemap_index != LIGHT_NO_SHADOW) { -#ifdef SHADOW_DEFERRED - shadow = shadow_unpack(packed_shadows, ray_count, shift); - shift += ray_count; + if (light.tilemap_index != LIGHT_NO_SHADOW && (visibility > 0.0)) { +#ifdef SURFEL_LIGHT + ShadowEvalResult shadow = shadow_eval(light, is_directional, P, Ng, 16, 8); #else - ShadowEvalResult result = shadow_eval( - light, is_directional, P, Ng, thickness, ray_count, ray_step_count); - shadow = result.light_visibilty; + ShadowEvalResult shadow = shadow_eval( + light, is_directional, P, Ng, uniform_buf.shadow.ray_count, uniform_buf.shadow.step_count); #endif - } - float visibility = attenuation * shadow; - /* WATCH(@fclem): Might have to manually unroll for best performance. */ - for (int i = 0; i < LIGHT_CLOSURE_EVAL_COUNT; i++) { - light_eval_single_closure(light, lv, stack.cl[i], P, V, thickness, attenuation, visibility); +#ifdef SSS_TRANSMITTANCE + /* Transmittance evaluation first to use initial visibility without shadow. */ + if (diffuse.sss_id != 0u && light.diffuse_power > 0.0) { + float delta = max(thickness, shadow.subsurface_occluder_distance); + + vec3 intensity = visibility * light.transmit_power * + light_translucent( + is_directional, light, diffuse.N, L, dist, diffuse.sss_radius, delta); + out_diffuse += light.color * intensity; + } +#endif + visibility *= shadow.surface_light_visibilty; + out_shadow *= shadow.surface_light_visibilty; + } + + if (visibility < 1e-6) { + return; + } + + if (light.diffuse_power > 0.0) { + float intensity = visibility * light.diffuse_power * + light_diffuse(utility_tx, is_directional, light, diffuse.N, V, L, dist); + out_diffuse += light.color * intensity; + } + + if (light.specular_power > 0.0) { + float intensity = visibility * light.specular_power * + light_ltc( + utility_tx, is_directional, light, reflection.N, V, L, dist, ltc_mat); + out_specular += light.color * intensity; } } -void light_eval(inout ClosureLightStack stack, +void light_eval(ClosureDiffuse diffuse, + ClosureReflection reflection, vec3 P, vec3 Ng, vec3 V, - float vPz, + float vP_z, float thickness, - uint packed_shadows) + inout vec3 out_diffuse, + inout vec3 out_specular, + inout float out_shadow) { - for (int i = 0; i < LIGHT_CLOSURE_EVAL_COUNT; i++) { - stack.cl[i].light_shadowed = vec3(0.0); - stack.cl[i].light_unshadowed = vec3(0.0); - } - - uint shift = 0u; + vec4 ltc_mat = utility_tx_sample_lut( + utility_tx, dot(reflection.N, V), reflection.roughness, UTIL_LTC_MAT_LAYER); LIGHT_FOREACH_BEGIN_DIRECTIONAL (light_cull_buf, l_idx) { - light_eval_single(l_idx, true, stack, P, Ng, V, thickness, packed_shadows, shift); + light_eval_ex(diffuse, + reflection, + true, + P, + Ng, + V, + vP_z, + thickness, + ltc_mat, + l_idx, + out_diffuse, + out_specular, + out_shadow); } LIGHT_FOREACH_END - LIGHT_FOREACH_BEGIN_LOCAL (light_cull_buf, light_zbin_buf, light_tile_buf, PIXEL, vPz, l_idx) { - light_eval_single(l_idx, false, stack, P, Ng, V, thickness, packed_shadows, shift); - } - LIGHT_FOREACH_END -} - -/* Variations that have less arguments. */ - -#if !defined(SHADOW_DEFERRED) -void light_eval(inout ClosureLightStack stack, vec3 P, vec3 Ng, vec3 V, float vPz, float thickness) -{ - light_eval(stack, P, Ng, V, vPz, thickness, 0u); -} - -# if !defined(SSS_TRANSMITTANCE) && defined(LIGHT_ITER_FORCE_NO_CULLING) -void light_eval(inout ClosureLightStack stack, vec3 P, vec3 Ng, vec3 V) -{ - light_eval(stack, P, Ng, V, 0.0, 0.0, 0u); -} -# endif - +#ifdef GPU_FRAGMENT_SHADER + vec2 px = gl_FragCoord.xy; +#else + vec2 px = vec2(0.0); #endif + LIGHT_FOREACH_BEGIN_LOCAL (light_cull_buf, light_zbin_buf, light_tile_buf, px, vP_z, l_idx) { + light_eval_ex(diffuse, + reflection, + false, + P, + Ng, + V, + vP_z, + thickness, + ltc_mat, + l_idx, + out_diffuse, + out_specular, + out_shadow); + } + LIGHT_FOREACH_END +} diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_light_iter_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_light_iter_lib.glsl index dcb12fa11a9..3d83334a65a 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_light_iter_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_light_iter_lib.glsl @@ -2,12 +2,7 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ -uint bitfield_mask(uint bit_width, uint bit_min) -{ - /* Cannot bit shift more than 31 positions. */ - uint mask = (bit_width > 31u) ? 0x0u : (0xFFFFFFFFu << bit_width); - return ~mask << bit_min; -} +#pragma BLENDER_REQUIRE(common_math_lib.glsl) uint zbin_mask(uint word_index, uint zbin_min, uint zbin_max) { @@ -16,7 +11,7 @@ uint zbin_mask(uint word_index, uint zbin_min, uint zbin_max) uint local_min = max(zbin_min, word_start); uint local_max = min(zbin_max, word_end); uint mask_width = local_max - local_min + 1; - return bitfield_mask(mask_width, local_min); + return bit_field_mask(mask_width, local_min); } int culling_z_to_zbin(float scale, float bias, float z) @@ -42,49 +37,38 @@ int culling_z_to_zbin(float scale, float bias, float z) { \ for (uint _index = _culling.local_lights_len; _index < _culling.items_count; _index++) { +#define LIGHT_FOREACH_BEGIN_LOCAL(_culling, _zbins, _words, _pixel, _linearz, _item_index) \ + { \ + uvec2 tile_co = uvec2(_pixel / _culling.tile_size); \ + uint tile_word_offset = (tile_co.x + tile_co.y * _culling.tile_x_len) * \ + _culling.tile_word_len; \ + int zbin_index = culling_z_to_zbin(_culling.zbin_scale, _culling.zbin_bias, _linearz); \ + zbin_index = clamp(zbin_index, 0, CULLING_ZBIN_COUNT - 1); \ + uint zbin_data = _zbins[zbin_index]; \ + uint min_index = zbin_data & 0xFFFFu; \ + uint max_index = zbin_data >> 16u; \ + /* Ensure all threads inside a subgroup get the same value to reduce VGPR usage. */ \ + min_index = subgroupBroadcastFirst(subgroupMin(min_index)); \ + max_index = subgroupBroadcastFirst(subgroupMax(max_index)); \ + /* Same as divide by 32 but avoid integer division. */ \ + uint word_min = min_index >> 5u; \ + uint word_max = max_index >> 5u; \ + for (uint word_idx = word_min; word_idx <= word_max; word_idx++) { \ + uint word = _words[tile_word_offset + word_idx]; \ + word &= zbin_mask(word_idx, min_index, max_index); \ + /* Ensure all threads inside a subgroup get the same value to reduce VGPR usage. */ \ + word = subgroupBroadcastFirst(subgroupOr(word)); \ + int bit_index; \ + while ((bit_index = findLSB(word)) != -1) { \ + word &= ~1u << uint(bit_index); \ + uint _item_index = word_idx * 32u + bit_index; + /* No culling. Iterate over all items. */ #define LIGHT_FOREACH_BEGIN_LOCAL_NO_CULL(_culling, _item_index) \ { \ { \ for (uint _item_index = 0; _item_index < _culling.visible_count; _item_index++) { -#ifdef LIGHT_ITER_FORCE_NO_CULLING - -/* Skip the optimization structure traversal for cases where the acceleration structure - * doesn't match. */ -# define LIGHT_FOREACH_BEGIN_LOCAL(_culling, _zbins, _words, _pixel, _linearz, _item_index) \ - LIGHT_FOREACH_BEGIN_LOCAL_NO_CULL(_culling, _item_index) - -#else - -# define LIGHT_FOREACH_BEGIN_LOCAL(_culling, _zbins, _words, _pixel, _linearz, _item_index) \ - { \ - uvec2 tile_co = uvec2(_pixel / _culling.tile_size); \ - uint tile_word_offset = (tile_co.x + tile_co.y * _culling.tile_x_len) * \ - _culling.tile_word_len; \ - int zbin_index = culling_z_to_zbin(_culling.zbin_scale, _culling.zbin_bias, _linearz); \ - zbin_index = clamp(zbin_index, 0, CULLING_ZBIN_COUNT - 1); \ - uint zbin_data = _zbins[zbin_index]; \ - uint min_index = zbin_data & 0xFFFFu; \ - uint max_index = zbin_data >> 16u; \ - /* Ensure all threads inside a subgroup get the same value to reduce VGPR usage. */ \ - min_index = subgroupBroadcastFirst(subgroupMin(min_index)); \ - max_index = subgroupBroadcastFirst(subgroupMax(max_index)); \ - /* Same as divide by 32 but avoid integer division. */ \ - uint word_min = min_index >> 5u; \ - uint word_max = max_index >> 5u; \ - for (uint word_idx = word_min; word_idx <= word_max; word_idx++) { \ - uint word = _words[tile_word_offset + word_idx]; \ - word &= zbin_mask(word_idx, min_index, max_index); \ - /* Ensure all threads inside a subgroup get the same value to reduce VGPR usage. */ \ - word = subgroupBroadcastFirst(subgroupOr(word)); \ - int bit_index; \ - while ((bit_index = findLSB(word)) != -1) { \ - word &= ~1u << uint(bit_index); \ - uint _item_index = word_idx * 32u + bit_index; - -#endif - #define LIGHT_FOREACH_END \ } \ } \ diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_light_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_light_lib.glsl index 094b2db2d0c..6b86273314a 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_light_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_light_lib.glsl @@ -2,50 +2,39 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ -#pragma BLENDER_REQUIRE(draw_math_geom_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_geom_lib.glsl) #pragma BLENDER_REQUIRE(eevee_ltc_lib.glsl) #pragma BLENDER_REQUIRE(eevee_light_iter_lib.glsl) -/* Attenuation cutoff needs to be the same in the shadow loop and the light eval loop. */ -#define LIGHT_ATTENUATION_THRESHOLD 1e-6 - /* ---------------------------------------------------------------------- */ /** \name Light Functions * \{ */ -struct LightVector { - /* World space light vector. From the shading point to the light center. Normalized. */ - vec3 L; - /* Distance from the shading point to the light center. */ - float dist; -}; - -LightVector light_vector_get(LightData light, const bool is_directional, vec3 P) +void light_vector_get(LightData ld, vec3 P, out vec3 L, out float dist) { - LightVector lv; - if (is_directional) { - lv.L = light._back; - lv.dist = 1.0; + /* TODO(fclem): Static branching. */ + if (is_sun_light(ld.type)) { + L = ld._back; + dist = 1.0; } else { - lv.L = light._position - P; - float inv_distance = inversesqrt(length_squared(lv.L)); - lv.L *= inv_distance; - lv.dist = 1.0 / inv_distance; + L = ld._position - P; + dist = inversesqrt(len_squared(L)); + L *= dist; + dist = 1.0 / dist; } - return lv; } /* Light vector to the closest point in the light shape. */ -LightVector light_shape_vector_get(LightData light, const bool is_directional, vec3 P) +void light_shape_vector_get(LightData ld, vec3 P, out vec3 L, out float dist) { - if (!is_directional && is_area_light(light.type)) { - vec3 L = P - light._position; - vec2 closest_point = vec2(dot(light._right, L), dot(light._up, L)); - vec2 max_pos = vec2(light._area_size_x, light._area_size_y); + if (ld.type == LIGHT_RECT || ld.type == LIGHT_ELLIPSE) { + L = P - ld._position; + vec2 closest_point = vec2(dot(ld._right, L), dot(ld._up, L)); + vec2 max_pos = vec2(ld._area_size_x, ld._area_size_y); closest_point /= max_pos; - if (light.type == LIGHT_ELLIPSE) { + if (ld.type == LIGHT_ELLIPSE) { closest_point /= max(1.0, length(closest_point)); } else { @@ -53,28 +42,27 @@ LightVector light_shape_vector_get(LightData light, const bool is_directional, v } closest_point *= max_pos; - vec3 L_prime = light._right * closest_point.x + light._up * closest_point.y; + vec3 L_prime = ld._right * closest_point.x + ld._up * closest_point.y; L = L_prime - L; - float inv_distance = inversesqrt(length_squared(L)); - LightVector lv; - lv.L = L * inv_distance; - lv.dist = 1.0 / inv_distance; - return lv; + dist = inversesqrt(len_squared(L)); + L *= dist; + dist = 1.0 / dist; + } + else { + light_vector_get(ld, P, L, dist); } - /* TODO(@fclem): other light shape? */ - return light_vector_get(light, is_directional, P); } /* Rotate vector to light's local space. Does not translate. */ -vec3 light_world_to_local(LightData light, vec3 L) +vec3 light_world_to_local(LightData ld, vec3 L) { /* Avoid relying on compiler to optimize this. - * vec3 lL = transpose(mat3(light.object_mat)) * L; */ + * vec3 lL = transpose(mat3(ld.object_mat)) * L; */ vec3 lL; - lL.x = dot(light.object_mat[0].xyz, L); - lL.y = dot(light.object_mat[1].xyz, L); - lL.z = dot(light.object_mat[2].xyz, L); + lL.x = dot(ld.object_mat[0].xyz, L); + lL.y = dot(ld.object_mat[1].xyz, L); + lL.z = dot(ld.object_mat[2].xyz, L); return lL; } @@ -89,65 +77,41 @@ vec3 light_local_position_to_world(LightData light, vec3 lP) * http://www.frostbite.com/wp-content/uploads/2014/11/course_notes_moving_frostbite_to_pbr.pdf */ float light_influence_attenuation(float dist, float inv_sqr_influence) { - float factor = square(dist) * inv_sqr_influence; - float fac = saturate(1.0 - square(factor)); - return square(fac); + float factor = sqr(dist) * inv_sqr_influence; + float fac = saturate(1.0 - sqr(factor)); + return sqr(fac); } -float light_spot_attenuation(LightData light, vec3 L) +float light_spot_attenuation(LightData ld, vec3 L) { - vec3 lL = light_world_to_local(light, L); - float ellipse = inversesqrt(1.0 + length_squared(lL.xy * light.spot_size_inv / lL.z)); - float spotmask = smoothstep(0.0, 1.0, ellipse * light._spot_mul + light._spot_bias); - return spotmask * step(0.0, -dot(L, -light._back)); + vec3 lL = light_world_to_local(ld, L); + float ellipse = inversesqrt(1.0 + len_squared(lL.xy * ld.spot_size_inv / lL.z)); + float spotmask = smoothstep(0.0, 1.0, ellipse * ld._spot_mul + ld._spot_bias); + return spotmask; } -float light_attenuation_common(LightData light, const bool is_directional, vec3 L) +float light_attenuation(LightData ld, vec3 L, float dist) { - if (is_directional) { - return 1.0; + float vis = 1.0; + if (ld.type == LIGHT_SPOT) { + vis *= light_spot_attenuation(ld, L); } - if (light.type == LIGHT_SPOT) { - return light_spot_attenuation(light, L); - } - if (is_area_light(light.type)) { - return step(0.0, -dot(L, -light._back)); - } - return 1.0; -} -/** - * Fade light influence when surface is not facing the light. - * L is normalized vector to light shape center. - * Ng is ideally the geometric normal. - */ -float light_attenuation_facing(LightData light, vec3 L, vec3 Ng, bool use_subsurface) -{ - if (use_subsurface) { - return 1.0; + if (ld.type >= LIGHT_SPOT) { + vis *= step(0.0, -dot(L, -ld._back)); } - /* TODO(fclem): Take into consideration the light radius. */ - return float(dot(L, Ng) > 0.0); -} -float light_attenuation_surface( - LightData light, const bool is_directional, vec3 Ng, bool use_subsurface, LightVector lv) -{ - /* TODO(fclem): add cutoff attenuation when back-facing. For now do nothing with Ng. */ - return light_attenuation_common(light, is_directional, lv.L) * - light_attenuation_facing(light, lv.L, Ng, use_subsurface) * - light_influence_attenuation(lv.dist, light.influence_radius_invsqr_surface); -} - -float light_attenuation_volume(LightData light, const bool is_directional, LightVector lv) -{ - return light_attenuation_common(light, is_directional, lv.L) * - light_influence_attenuation(lv.dist, light.influence_radius_invsqr_volume); +#ifdef VOLUME_LIGHTING + vis *= light_influence_attenuation(dist, ld.influence_radius_invsqr_volume); +#else + vis *= light_influence_attenuation(dist, ld.influence_radius_invsqr_surface); +#endif + return vis; } /* Cheaper alternative than evaluating the LTC. * The result needs to be multiplied by BSDF or Phase Function. */ -float light_point_light(LightData light, const bool is_directional, LightVector lv) +float light_point_light(LightData ld, const bool is_directional, vec3 L, float dist) { if (is_directional) { return 1.0; @@ -156,14 +120,14 @@ float light_point_light(LightData light, const bool is_directional, LightVector * http://www.cemyuksel.com/research/pointlightattenuation/pointlightattenuation.pdf * http://www.cemyuksel.com/research/pointlightattenuation/ */ - float d_sqr = square(lv.dist); - float r_sqr = light.radius_squared; + float d_sqr = sqr(dist); + float r_sqr = ld.radius_squared; /* Using reformulation that has better numerical precision. */ - float power = 2.0 / (d_sqr + r_sqr + lv.dist * sqrt(d_sqr + r_sqr)); + float power = 2.0 / (d_sqr + r_sqr + dist * sqrt(d_sqr + r_sqr)); - if (is_area_light(light.type)) { + if (is_area_light(ld.type)) { /* Modulate by light plane orientation / solid angle. */ - power *= saturate(dot(light._back, lv.L)); + power *= saturate(dot(ld._back, L)); } return power; } @@ -177,60 +141,120 @@ float light_sphere_disk_radius(float sphere_radius, float distance_to_sphere) { /* The sine of the half-angle spanned by a sphere light is equal to the tangent of the * half-angle spanned by a disk light with the same radius. */ - return sphere_radius * inversesqrt(1.0 - square(sphere_radius / distance_to_sphere)); + return sphere_radius * inversesqrt(1.0 - sqr(sphere_radius / distance_to_sphere)); } -float light_ltc( - sampler2DArray utility_tx, LightData light, vec3 N, vec3 V, LightVector lv, vec4 ltc_mat) +float light_diffuse(sampler2DArray utility_tx, + const bool is_directional, + LightData ld, + vec3 N, + vec3 V, + vec3 L, + float dist) { - if (light.type == LIGHT_POINT && lv.dist < light._radius) { - /* Inside the sphere light, integrate over the hemisphere. */ - return 1.0; + if (ld.type == LIGHT_POINT) { + if (dist < ld._radius) { + /* Inside, treat as hemispherical light. */ + return 1.0; + } + else { + /* Outside, treat as disk light spanning the same solid angle. */ + /* The result is the same as passing the scaled radius to #ltc_evaluate_disk_simple (see + * #light_ltc), using simplified math here. */ + float r_sq = sqr(ld._radius / dist); + return r_sq * ltc_diffuse_sphere_integral(utility_tx, dot(N, L), r_sq); + } } - - if (light.type == LIGHT_RECT) { + else if (is_directional || ld.type == LIGHT_SPOT) { + float radius = ld._radius / dist; + return ltc_evaluate_disk_simple(utility_tx, radius, dot(N, L)); + } + else if (ld.type == LIGHT_RECT) { vec3 corners[4]; - corners[0] = light._right * light._area_size_x + light._up * -light._area_size_y; - corners[1] = light._right * light._area_size_x + light._up * light._area_size_y; + corners[0] = ld._right * ld._area_size_x + ld._up * -ld._area_size_y; + corners[1] = ld._right * ld._area_size_x + ld._up * ld._area_size_y; corners[2] = -corners[0]; corners[3] = -corners[1]; - vec3 L = lv.L * lv.dist; - corners[0] += L; - corners[1] += L; - corners[2] += L; - corners[3] += L; + corners[0] = normalize(L * dist + corners[0]); + corners[1] = normalize(L * dist + corners[1]); + corners[2] = normalize(L * dist + corners[2]); + corners[3] = normalize(L * dist + corners[3]); + + return ltc_evaluate_quad(utility_tx, corners, N); + } + else /* (ld.type == LIGHT_ELLIPSE) */ { + vec3 points[3]; + points[0] = ld._right * -ld._area_size_x + ld._up * -ld._area_size_y; + points[1] = ld._right * ld._area_size_x + ld._up * -ld._area_size_y; + points[2] = -points[0]; + + points[0] += L * dist; + points[1] += L * dist; + points[2] += L * dist; + + return ltc_evaluate_disk(utility_tx, N, V, mat3(1.0), points); + } +} + +float light_ltc(sampler2DArray utility_tx, + const bool is_directional, + LightData ld, + vec3 N, + vec3 V, + vec3 L, + float dist, + vec4 ltc_mat) +{ + if (ld.type == LIGHT_POINT && dist < ld._radius) { + /* Inside the sphere light, integrate over the hemisphere. */ + return 1.0; + } + else if (is_directional || ld.type != LIGHT_RECT) { + vec3 Px = ld._right; + vec3 Py = ld._up; + + if (is_directional || !is_area_light(ld.type)) { + make_orthonormal_basis(L, Px, Py); + } + + vec3 points[3]; + if (ld.type == LIGHT_POINT) { + /* The sine of the half-angle spanned by a sphere light is equal to the tangent of the + * half-angle spanned by a disk light with the same radius. */ + float radius = light_sphere_disk_radius(ld._radius, dist); + + points[0] = Px * -radius + Py * -radius; + points[1] = Px * radius + Py * -radius; + } + else { + points[0] = Px * -ld._area_size_x + Py * -ld._area_size_y; + points[1] = Px * ld._area_size_x + Py * -ld._area_size_y; + } + points[2] = -points[0]; + + points[0] += L * dist; + points[1] += L * dist; + points[2] += L * dist; + + return ltc_evaluate_disk(utility_tx, N, V, ltc_matrix(ltc_mat), points); + } + else { + vec3 corners[4]; + corners[0] = ld._right * ld._area_size_x + ld._up * -ld._area_size_y; + corners[1] = ld._right * ld._area_size_x + ld._up * ld._area_size_y; + corners[2] = -corners[0]; + corners[3] = -corners[1]; + + corners[0] += L * dist; + corners[1] += L * dist; + corners[2] += L * dist; + corners[3] += L * dist; ltc_transform_quad(N, V, ltc_matrix(ltc_mat), corners); return ltc_evaluate_quad(utility_tx, corners, vec3(0.0, 0.0, 1.0)); } - else { - vec3 Px = light._right; - vec3 Py = light._up; - - if (!is_area_light(light.type)) { - make_orthonormal_basis(lv.L, Px, Py); - } - - vec2 size = vec2(light._area_size_x, light._area_size_y); - if (light.type == LIGHT_POINT) { - /* The sine of the half-angle spanned by a sphere light is equal to the tangent of the - * half-angle spanned by a disk light with the same radius. */ - size = vec2(light_sphere_disk_radius(light._radius, lv.dist)); - } - vec3 points[3]; - points[0] = Px * -size.x + Py * -size.y; - points[1] = Px * size.x + Py * -size.y; - points[2] = -points[0]; - - vec3 L = lv.L * lv.dist; - points[0] += L; - points[1] += L; - points[2] += L; - - return ltc_evaluate_disk(utility_tx, N, V, ltc_matrix(ltc_mat), points); - } } #ifdef SSS_TRANSMITTANCE @@ -240,18 +264,19 @@ float sample_transmittance_profile(float u) } vec3 light_translucent(const bool is_directional, - LightData light, + LightData ld, vec3 N, - LightVector lv, + vec3 L, + float dist, vec3 sss_radius, float delta) { /* TODO(fclem): We should compute the power at the entry point. */ /* NOTE(fclem): we compute the light attenuation using the light vector but the transmittance * using the shadow depth delta. */ - float power = light_point_light(light, is_directional, lv); + float power = light_point_light(ld, is_directional, L, dist); /* Do not add more energy on front faces. Also apply lambertian BSDF. */ - power *= max(0.0, dot(-N, lv.L)) * M_1_PI; + power *= max(0.0, dot(-N, L)) * M_1_PI; sss_radius *= SSS_TRANSMIT_LUT_RADIUS; vec3 channels_co = saturate(delta / sss_radius) * SSS_TRANSMIT_LUT_SCALE + SSS_TRANSMIT_LUT_BIAS; diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_lightprobe_eval_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_lightprobe_eval_lib.glsl index 2b84f468336..8be9bb9786f 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_lightprobe_eval_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_lightprobe_eval_lib.glsl @@ -12,8 +12,6 @@ #pragma BLENDER_REQUIRE(gpu_shader_codegen_lib.glsl) #pragma BLENDER_REQUIRE(eevee_lightprobe_lib.glsl) #pragma BLENDER_REQUIRE(eevee_spherical_harmonics_lib.glsl) -#pragma BLENDER_REQUIRE(eevee_reflection_probe_eval_lib.glsl) -#pragma BLENDER_REQUIRE(gpu_shader_math_base_lib.glsl) /** * Return the brick coordinate inside the grid. @@ -107,21 +105,6 @@ vec3 lightprobe_irradiance_grid_bias_sample_coord(IrradianceGridData grid_data, return 0.5 + cell_start + trilinear_coord; } -SphericalHarmonicL1 lightprobe_irradiance_sample_atlas(sampler3D atlas_tx, vec3 atlas_coord) -{ - vec4 texture_coord = vec4(atlas_coord, float(IRRADIANCE_GRID_BRICK_SIZE)) / - vec3(textureSize(atlas_tx, 0)).xyzz; - SphericalHarmonicL1 sh; - sh.L0.M0 = textureLod(atlas_tx, texture_coord.xyz, 0.0); - texture_coord.z += texture_coord.w; - sh.L1.Mn1 = textureLod(atlas_tx, texture_coord.xyz, 0.0); - texture_coord.z += texture_coord.w; - sh.L1.M0 = textureLod(atlas_tx, texture_coord.xyz, 0.0); - texture_coord.z += texture_coord.w; - sh.L1.Mp1 = textureLod(atlas_tx, texture_coord.xyz, 0.0); - return sh; -} - SphericalHarmonicL1 lightprobe_irradiance_sample( sampler3D atlas_tx, vec3 P, vec3 V, vec3 Ng, const bool do_bias) { @@ -171,144 +154,39 @@ SphericalHarmonicL1 lightprobe_irradiance_sample( vec3 atlas_coord = vec3(vec2(brick.atlas_coord), 0.0) + brick_lP; - return lightprobe_irradiance_sample_atlas(atlas_tx, atlas_coord); -} - -SphericalHarmonicL1 lightprobe_irradiance_world() -{ - return lightprobe_irradiance_sample_atlas(irradiance_atlas_tx, vec3(0.0)); + vec4 texture_coord = vec4(atlas_coord, float(IRRADIANCE_GRID_BRICK_SIZE)) / + vec3(textureSize(atlas_tx, 0)).xyzz; + SphericalHarmonicL1 sh; + sh.L0.M0 = textureLod(atlas_tx, texture_coord.xyz, 0.0); + texture_coord.z += texture_coord.w; + sh.L1.Mn1 = textureLod(atlas_tx, texture_coord.xyz, 0.0); + texture_coord.z += texture_coord.w; + sh.L1.M0 = textureLod(atlas_tx, texture_coord.xyz, 0.0); + texture_coord.z += texture_coord.w; + sh.L1.Mp1 = textureLod(atlas_tx, texture_coord.xyz, 0.0); + return sh; } +/** + * Shorter version without bias. + */ SphericalHarmonicL1 lightprobe_irradiance_sample(vec3 P) { return lightprobe_irradiance_sample(irradiance_atlas_tx, P, vec3(0), vec3(0), false); } -SphericalHarmonicL1 lightprobe_irradiance_sample(vec3 P, vec3 V, vec3 Ng) +void lightprobe_eval(ClosureDiffuse diffuse, + ClosureReflection reflection, + vec3 P, + vec3 Ng, + vec3 V, + inout vec3 out_diffuse, + inout vec3 out_specular) { - return lightprobe_irradiance_sample(irradiance_atlas_tx, P, V, Ng, true); + /* NOTE: Use the diffuse normal for biasing the probe sampling location since it is smoother than + * geometric normal. Could also try to use `interp.N`. */ + SphericalHarmonicL1 irradiance = lightprobe_irradiance_sample( + irradiance_atlas_tx, P, V, diffuse.N, true); + + out_diffuse += spherical_harmonics_evaluate_lambert(diffuse.N, irradiance); } - -#ifdef REFLECTION_PROBE - -struct LightProbeSample { - SphericalHarmonicL1 volume_irradiance; - int spherical_id; -}; - -# if defined(GPU_FRAGMENT_SHADER) -# define UTIL_TEXEL vec2(gl_FragCoord) -# elif defined(GPU_COMPUTE_SHADER) -# define UTIL_TEXEL vec2(gl_GlobalInvocationID) -# else -# define UTIL_TEXEL vec2(gl_VertexID, 0) -# endif - -/** - * Return cached light-probe data at P. - * Ng and V are use for biases. - */ -LightProbeSample lightprobe_load(vec3 P, vec3 Ng, vec3 V) -{ - /* TODO: Dependency hell */ - float noise = 0.0; - - LightProbeSample result; - result.volume_irradiance = lightprobe_irradiance_sample(P, V, Ng); - result.spherical_id = reflection_probes_select(P, noise); - return result; -} - -/* Return the best parallax corrected ray direction from the probe center. */ -vec3 lightprobe_sphere_parallax(ReflectionProbeData probe, vec3 P, vec3 L) -{ - bool is_world = (probe.influence_scale == 0.0); - if (is_world) { - return L; - } - /* Correct reflection ray using parallax volume intersection. */ - vec3 lP = vec4(P, 1.0) * probe.world_to_probe_transposed; - vec3 lL = (mat3x3(probe.world_to_probe_transposed) * L) / probe.parallax_distance; - - float dist = (probe.parallax_shape == SHAPE_ELIPSOID) ? line_unit_sphere_intersect_dist(lP, lL) : - line_unit_box_intersect_dist(lP, lL); - - /* Use distance in world space directly to recover intersection. - * This works because we assume no shear in the probe matrix. */ - vec3 L_new = P + L * dist - probe.location; - - /* TODO(fclem): Roughness adjustment. */ - - return L_new; -} - -/** - * Return spherical sample normalized by irradiance at sample position. - * This avoid most of light leaking and reduce the need for many local probes. - */ -vec3 lightprobe_spherical_sample_normalized_with_parallax( - int probe_index, vec3 P, vec3 L, float lod, SphericalHarmonicL1 P_sh) -{ - ReflectionProbeData probe = reflection_probe_buf[probe_index]; - ReflectionProbeLowFreqLight shading_sh = reflection_probes_extract_low_freq(P_sh); - vec3 normalization_factor = reflection_probes_normalization_eval( - L, shading_sh, probe.low_freq_light); - L = lightprobe_sphere_parallax(probe, P, L); - return normalization_factor * reflection_probes_sample(L, lod, probe.atlas_coord).rgb; -} - -float pdf_to_lod(float pdf) -{ - return 1.0; /* TODO */ -} - -vec3 lightprobe_eval_direction(LightProbeSample samp, vec3 P, vec3 L, float pdf) -{ - vec3 radiance_sh = lightprobe_spherical_sample_normalized_with_parallax( - samp.spherical_id, P, L, pdf_to_lod(pdf), samp.volume_irradiance); - - return radiance_sh; -} - -float lightprobe_roughness_to_cube_sh_mix_fac(float roughness) -{ - /* Temporary. Do something better. */ - return square(saturate(roughness * 4.0 - 2.0)); -} - -float lightprobe_roughness_to_lod(float roughness) -{ - /* Temporary. Do something better. */ - return sqrt(roughness) * 11.0; -} - -vec3 lightprobe_eval(LightProbeSample samp, ClosureDiffuse diffuse, vec3 P, vec3 V, vec2 noise) -{ - vec3 radiance_sh = spherical_harmonics_evaluate_lambert(diffuse.N, samp.volume_irradiance); - return radiance_sh; -} - -vec3 lightprobe_specular_dominant_dir(vec3 N, vec3 V, float roughness) -{ - vec3 R = -reflect(V, N); - float smoothness = 1.0 - roughness; - float fac = smoothness * (sqrt(smoothness) + roughness); - return normalize(mix(N, R, fac)); -} - -vec3 lightprobe_eval( - LightProbeSample samp, ClosureReflection reflection, vec3 P, vec3 V, vec2 noise) -{ - vec3 L = lightprobe_specular_dominant_dir(reflection.N, V, reflection.roughness); - // vec3 L = ray_generate_direction(noise, reflection, V, pdf); - - float lod = lightprobe_roughness_to_lod(reflection.roughness); - vec3 radiance_cube = lightprobe_spherical_sample_normalized_with_parallax( - samp.spherical_id, P, L, lod, samp.volume_irradiance); - - float fac = lightprobe_roughness_to_cube_sh_mix_fac(reflection.roughness); - vec3 radiance_sh = spherical_harmonics_evaluate_lambert(L, samp.volume_irradiance); - return mix(radiance_cube, radiance_sh, fac); -} - -#endif /* REFLECTION_PROBE */ diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_lightprobe_irradiance_bounds_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_lightprobe_irradiance_bounds_comp.glsl index 5af241ea504..8174529f5a3 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_lightprobe_irradiance_bounds_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_lightprobe_irradiance_bounds_comp.glsl @@ -10,7 +10,8 @@ */ #pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl) -#pragma BLENDER_REQUIRE(draw_intersect_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_lib.glsl) +#pragma BLENDER_REQUIRE(common_intersect_lib.glsl) #pragma BLENDER_REQUIRE(eevee_shadow_tilemap_lib.glsl) #pragma BLENDER_REQUIRE(eevee_light_iter_lib.glsl) diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_lightprobe_irradiance_ray_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_lightprobe_irradiance_ray_comp.glsl index 885053fb7b6..254dbd3bfbc 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_lightprobe_irradiance_ray_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_lightprobe_irradiance_ray_comp.glsl @@ -16,6 +16,7 @@ #pragma BLENDER_REQUIRE(eevee_surfel_list_lib.glsl) #pragma BLENDER_REQUIRE(eevee_lightprobe_lib.glsl) #pragma BLENDER_REQUIRE(eevee_reflection_probe_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_lib.glsl) void irradiance_capture(vec3 L, vec3 irradiance, float visibility, inout SphericalHarmonicL1 sh) { @@ -38,7 +39,7 @@ void irradiance_capture_surfel(Surfel surfel, vec3 P, inout SphericalHarmonicL1 irradiance_vis += facing ? surfel.radiance_direct.front : surfel.radiance_direct.back; /* Clamped brightness. */ - float luma = max(1e-8, reduce_max(irradiance_vis.rgb)); + float luma = max(1e-8, max_v3(irradiance_vis.rgb)); irradiance_vis.rgb *= 1.0 - max(0.0, luma - capture_info_buf.clamp_direct) / luma; /* NOTE: The indirect radiance is already normalized and this is wanted, because we are not @@ -52,7 +53,7 @@ void irradiance_capture_surfel(Surfel surfel, vec3 P, inout SphericalHarmonicL1 void validity_capture_surfel(Surfel surfel, vec3 P, inout float validity) { vec3 L = safe_normalize(surfel.position - P); - bool facing = surfel.double_sided || dot(-L, surfel.normal) > 0.0; + bool facing = dot(-L, surfel.normal) > 0.0; validity += float(facing); } @@ -67,13 +68,10 @@ void irradiance_capture_world(vec3 L, inout SphericalHarmonicL1 sh) float visibility = 0.0; if (capture_info_buf.capture_world_direct) { - ReflectionProbeAtlasCoordinate atlas_coord = reinterpret_as_atlas_coord( - capture_info_buf.world_atlas_coord); - - radiance = reflection_probes_sample(L, 0.0, atlas_coord).rgb; + radiance = reflection_probes_world_sample(L, 0.0).rgb; /* Clamped brightness. */ - float luma = max(1e-8, reduce_max(radiance)); + float luma = max(1e-8, max_v3(radiance)); radiance *= 1.0 - max(0.0, luma - capture_info_buf.clamp_direct) / luma; } @@ -116,7 +114,7 @@ void main() surfel_prev = surfel_next; } - vec3 sky_L = drw_world_incident_vector(P); + vec3 sky_L = cameraVec(P); SphericalHarmonicL1 sh; sh.L0.M0 = imageLoad(irradiance_L0_img, grid_coord); diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_lightprobe_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_lightprobe_lib.glsl index 3ae4db73cc7..a898020a8b0 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_lightprobe_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_lightprobe_lib.glsl @@ -2,7 +2,6 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ -#pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl) #pragma BLENDER_REQUIRE(gpu_shader_math_vector_lib.glsl) vec3 lightprobe_irradiance_grid_sample_position(mat4 grid_local_to_world_mat, @@ -45,40 +44,3 @@ ivec3 lightprobe_irradiance_grid_cell_corner(int cell_corner_id) { return (ivec3(cell_corner_id) >> ivec3(0, 1, 2)) & 1; } - -float lightprobe_planar_score(ProbePlanarData planar, vec3 P, vec3 V, vec3 L) -{ - vec3 lP = vec4(P, 1.0) * planar.world_to_object_transposed; - if (any(greaterThan(abs(lP), vec3(1.0)))) { - /* TODO: Transition in Z. Dither? */ - return 0.0; - } - /* Return how much the ray is lined up with the captured ray. */ - vec3 R = -reflect(V, planar.normal); - return saturate(dot(L, R)); -} - -#ifdef PLANAR_PROBES -/** - * Return the best planar probe index for a given light direction vector and position. - */ -int lightprobe_planar_select(vec3 P, vec3 V, vec3 L) -{ - /* Initialize to the score of a camera ray. */ - float best_score = saturate(dot(L, -V)); - int best_index = -1; - - for (int index = 0; index < PLANAR_PROBES_MAX; index++) { - if (probe_planar_buf[index].layer_id == -1) { - /* ProbePlanarData doesn't contain any gap, exit at first item that is invalid. */ - break; - } - float score = lightprobe_planar_score(probe_planar_buf[index], P, V, L); - if (score > best_score) { - best_score = score; - best_index = index; - } - } - return best_index; -} -#endif diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_ltc_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_ltc_lib.glsl index d25de14f27c..7b886ae8805 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_ltc_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_ltc_lib.glsl @@ -10,12 +10,6 @@ * Project page: https://eheitzresearch.wordpress.com/415-2/ */ -#pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl) - -#define LTC_LAMBERT_MAT vec4(1.0, 0.0, 0.0, 1.0) -#define LTC_GGX_MAT(cos_theta, roughness) \ - utility_tx_sample_lut(utility_tx, cos_theta, roughness, UTIL_LTC_MAT_LAYER) - /* Diffuse *clipped* sphere integral. */ float ltc_diffuse_sphere_integral(sampler2DArray utility_tx, float avg_dir_z, float form_factor) { @@ -208,9 +202,9 @@ float ltc_evaluate_disk(sampler2DArray utility_tx, vec3 N, vec3 V, mat3 Minv, ve /* Intermediate step: init ellipse. */ vec3 L_[3]; - L_[0] = R * disk_points[0]; - L_[1] = R * disk_points[1]; - L_[2] = R * disk_points[2]; + L_[0] = mul(R, disk_points[0]); + L_[1] = mul(R, disk_points[1]); + L_[2] = mul(R, disk_points[2]); vec3 C = 0.5 * (L_[0] + L_[2]); vec3 V1 = 0.5 * (L_[1] - L_[2]); diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_motion_blur_dilate_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_motion_blur_dilate_comp.glsl index 78a96308790..285486fe677 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_motion_blur_dilate_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_motion_blur_dilate_comp.glsl @@ -7,7 +7,7 @@ * Outputs the largest intersecting motion vector in the neighborhood. */ -#pragma BLENDER_REQUIRE(draw_math_geom_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_geom_lib.glsl) #pragma BLENDER_REQUIRE(eevee_motion_blur_lib.glsl) #define DEBUG_BYPASS_DILATION 0 diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_motion_blur_flatten_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_motion_blur_flatten_comp.glsl index 46153428cc1..90fc0fd2300 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_motion_blur_flatten_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_motion_blur_flatten_comp.glsl @@ -14,7 +14,7 @@ * Adapted from G3D Innovation Engine implementation. */ -#pragma BLENDER_REQUIRE(draw_math_geom_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_geom_lib.glsl) #pragma BLENDER_REQUIRE(eevee_velocity_lib.glsl) shared uint payload_prev; diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_motion_blur_gather_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_motion_blur_gather_comp.glsl index e3f942c6de8..a1387a89599 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_motion_blur_gather_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_motion_blur_gather_comp.glsl @@ -13,7 +13,8 @@ * by Jorge Jimenez */ -#pragma BLENDER_REQUIRE(draw_view_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_lib.glsl) #pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl) #pragma BLENDER_REQUIRE(eevee_velocity_lib.glsl) #pragma BLENDER_REQUIRE(eevee_motion_blur_lib.glsl) @@ -85,7 +86,7 @@ void gather_sample(vec2 screen_uv, float sample_depth = texture(depth_tx, sample_uv).r; vec4 sample_color = textureLod(in_color_tx, sample_uv, 0.0); - sample_depth = drw_depth_screen_to_view(sample_depth); + sample_depth = get_view_z_from_depth(sample_depth); vec3 weights; weights.xy = sample_weights( @@ -160,7 +161,7 @@ void main() } /* Data of the center pixel of the gather (target). */ - float center_depth = drw_depth_screen_to_view(texelFetch(depth_tx, texel, 0).r); + float center_depth = get_view_z_from_depth(texelFetch(depth_tx, texel, 0).r); vec4 center_motion = motion_blur_sample_velocity(velocity_tx, uv); vec4 center_color = textureLod(in_color_tx, uv, 0.0); diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_nodetree_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_nodetree_lib.glsl index d1e29468f2e..5a9286a0e31 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_nodetree_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_nodetree_lib.glsl @@ -2,11 +2,9 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ -#pragma BLENDER_REQUIRE(draw_view_lib.glsl) -#pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl) -#pragma BLENDER_REQUIRE(gpu_shader_math_base_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_lib.glsl) #pragma BLENDER_REQUIRE(gpu_shader_codegen_lib.glsl) -#pragma BLENDER_REQUIRE(eevee_renderpass_lib.glsl) vec3 g_emission; vec3 g_transmittance; @@ -232,12 +230,12 @@ float ambient_occlusion_eval(vec3 normal, // clang-format off #if defined(GPU_FRAGMENT_SHADER) && defined(MAT_AMBIENT_OCCLUSION) && !defined(MAT_DEPTH) && !defined(MAT_SHADOW) // clang-format on - vec3 vP = drw_point_world_to_view(g_data.P); + vec3 vP = transform_point(ViewMatrix, g_data.P); ivec2 texel = ivec2(gl_FragCoord.xy); OcclusionData data = ambient_occlusion_search( vP, hiz_tx, texel, max_distance, inverted, sample_count); - vec3 V = drw_world_incident_vector(g_data.P); + vec3 V = cameraVec(g_data.P); vec3 N = g_data.N; vec3 Ng = g_data.Ng; @@ -284,7 +282,7 @@ vec3 F_brdf_multi_scatter(vec3 f0, vec3 f90, vec2 lut) vec3 Favg = f0 + (f90 - f0) / 21.0; /* The original paper uses `FssEss * radiance + Fms*Ems * irradiance`, but - * "A Journey Through Implementing Multi-scattering BRDFs and Area Lights" by Steve McAuley + * "A Journey Through Implementing Multiscattering BRDFs and Area Lights" by Steve McAuley * suggests to use `FssEss * radiance + Fms*Ems * radiance` which results in comparable quality. * We handle `radiance` outside of this function, so the result simplifies to: * `FssEss + Fms*Ems = FssEss * (1 + Ems*Favg / (1 - Ems*Favg)) = FssEss / (1 - Ems*Favg)`. @@ -312,7 +310,7 @@ void brdf_f82_tint_lut(vec3 F0, #ifdef EEVEE_UTILITY_TX vec3 split_sum = utility_tx_sample_lut(utility_tx, cos_theta, roughness, UTIL_BSDF_LAYER).rgb; #else - vec3 split_sum = vec3(1.0, 0.0, 0.0); + vec3 split_sum = vec2(1.0, 0.0, 0.0); #endif reflectance = do_multiscatter ? F_brdf_multi_scatter(F0, vec3(1.0), split_sum.xy) : @@ -340,7 +338,7 @@ vec3 lut_coords_bsdf(float cos_theta, float roughness, float ior) float critical_cos = sqrt(1.0 - ior * ior); vec3 coords; - coords.x = square(ior); + coords.x = sqr(ior); coords.y = cos_theta; coords.y -= critical_cos; coords.y /= (coords.y > 0.0) ? (1.0 - critical_cos) : critical_cos; @@ -508,9 +506,9 @@ vec3 coordinate_camera(vec3 P) } else { #ifdef MAT_WORLD - vP = drw_normal_world_to_view(P); + vP = transform_direction(ViewMatrix, P); #else - vP = drw_point_world_to_view(P); + vP = transform_point(ViewMatrix, P); #endif } vP.z = -vP.z; @@ -526,7 +524,7 @@ vec3 coordinate_screen(vec3 P) } else { /* TODO(fclem): Actual camera transform. */ - window.xy = drw_point_world_to_screen(P).xy; + window.xy = project_point(ProjectionMatrix, transform_point(ViewMatrix, P)).xy * 0.5 + 0.5; window.xy = window.xy * uniform_buf.camera.uv_scale + uniform_buf.camera.uv_bias; } return window; @@ -537,7 +535,7 @@ vec3 coordinate_reflect(vec3 P, vec3 N) #ifdef MAT_WORLD return N; #else - return -reflect(drw_world_incident_vector(P), N); + return -reflect(cameraVec(P), N); #endif } @@ -546,7 +544,7 @@ vec3 coordinate_incoming(vec3 P) #ifdef MAT_WORLD return -P; #else - return drw_world_incident_vector(P); + return cameraVec(P); #endif } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_convert_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_convert_frag.glsl deleted file mode 100644 index f900314bc36..00000000000 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_convert_frag.glsl +++ /dev/null @@ -1,94 +0,0 @@ -/* SPDX-FileCopyrightText: 2022-2023 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -/** - * Convert hit list to occupancy bit-field for the material pass. - */ - -#pragma BLENDER_REQUIRE(eevee_occupancy_lib.glsl) - -bool is_front_face_hit(float stored_hit_depth) -{ - return stored_hit_depth < 0.0; -} - -void main() -{ - float hit_depths[VOLUME_HIT_DEPTH_MAX]; - float hit_ordered[VOLUME_HIT_DEPTH_MAX]; - int hit_index[VOLUME_HIT_DEPTH_MAX]; - - ivec2 texel = ivec2(gl_FragCoord.xy); - - int hit_count = int(imageLoad(hit_count_img, texel).x); - hit_count = min(hit_count, VOLUME_HIT_DEPTH_MAX); - - if (hit_count == 0) { - return; - } - - /* Clear the texture for next layer / frame. */ - imageStore(hit_count_img, texel, uvec4(0)); - - for (int i = 0; i < hit_count; i++) { - hit_depths[i] = imageLoad(hit_depth_img, ivec3(texel, i)).r; - } - - for (int i = 0; i < hit_count; i++) { - hit_index[i] = 0; - for (int j = 0; j < hit_count; j++) { - hit_index[i] += int(abs(hit_depths[i]) > abs(hit_depths[j])); - } - } - - for (int i = 0; i < hit_count; i++) { - hit_ordered[hit_index[i]] = hit_depths[i]; - } - -#if 0 /* Debug. Need to adjust the qualifier of the texture adjusted. */ - for (int i = 0; i < hit_count; i++) { - imageStore(hit_depth_img, ivec3(texel, i), vec4(hit_ordered[i])); - } -#endif - - /* Convert to occupancy bits. */ - OccupancyBits occupancy = occupancy_new(); - /* True if last interface was a volume entry. */ - /* Initialized to front facing if first hit is a backface to support camera inside the volume. */ - bool last_frontfacing = !is_front_face_hit(hit_ordered[0]); - /* Bit index of the last interface. */ - int last_bit = 0; - for (int i = 0; i < hit_count; i++) { - bool frontfacing = is_front_face_hit(hit_ordered[i]); - if (last_frontfacing == frontfacing) { - /* Same facing, do not treat as a volume interface. */ - continue; - } - last_frontfacing = frontfacing; - - int occupancy_bit_n = occupancy_bit_index_from_depth(abs(hit_ordered[i]), - uniform_buf.volumes.tex_size.z); - if (last_bit == occupancy_bit_n) { - /* We did not cross a new voxel center. Do nothing. */ - continue; - } - int bit_start = last_bit; - int bit_count = occupancy_bit_n - last_bit; - last_bit = occupancy_bit_n; - - if (last_frontfacing == false) { - /* OccupancyBits is cleared by default. No need to do anything for empty regions. */ - continue; - } - occupancy = occupancy_set_bits_high(occupancy, bit_start, bit_count); - } - - /* Write the occupancy bits */ - for (int i = 0; i < imageSize(occupancy_img).z; i++) { - if (occupancy.bits[i] != 0u) { - /* Note: Doesn't have to be atomic but we need to blend with other method. */ - imageAtomicOr(occupancy_img, ivec3(texel, i), occupancy.bits[i]); - } - } -} diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_lib.glsl deleted file mode 100644 index ee39e138c1f..00000000000 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_lib.glsl +++ /dev/null @@ -1,175 +0,0 @@ -/* SPDX-FileCopyrightText: 2023 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -#pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl) - -struct OccupancyBits { - uint bits[8]; -}; - -int occupancy_bit_index_from_depth(float depth, int bit_count) -{ - /* We want the occupancy at the center of each range a bit covers. - * So we round the depth to the nearest bit. */ - return int(depth * float(bit_count) + 0.5); -} - -/** - * Example with for 16bits per layer and 2 layers. - * 0 Layer0 15 16 Layer1 31 < Bits index from LSB to MSB (left to right) - * |--------------| |--------------| - * 0000000001111111 1111111111111111 < Surf0 - * 0000000000000000 0000001111111111 < Surf1 - * 0000000001111111 1111110000000000 < Result occupancy at each froxel depths - * \a depth in [0..1] range. - * \a bit_count in [1..256] range. - */ -OccupancyBits occupancy_from_depth(float depth, int bit_count) -{ - /* We want the occupancy at the center of each range a bit covers. - * So we round the depth to the nearest bit. */ - int depth_bit_index = occupancy_bit_index_from_depth(depth, bit_count); - - OccupancyBits occupancy; - for (int i = 0; i < 8; i++) { - int shift = clamp(depth_bit_index - i * 32, 0, 32); - /* Cannot bit shift more than 31 positions. */ - occupancy.bits[i] = (shift == 32) ? 0x0u : (~0x0u << uint(shift)); - } - return occupancy; -} - -/** - * Returns an empty structure, cleared to 0. - */ -OccupancyBits occupancy_new() -{ - OccupancyBits occupancy; - for (int i = 0; i < 8; i++) { - occupancy.bits[i] = 0x0u; - } - return occupancy; -} - -/** - * Example with for 16bits per layer and 2 layers. - * 0 Layer0 15 16 Layer1 31 < Bits index from LSB to MSB (left to right) - * |--------------| |--------------| - * 0000000001000010 0001000000000000 < Surf entry points - * 0000000000000000 0100001000000000 < Surf exit points - * 0000000001111111 1001110000000000 < Result occupancy at each froxel depths after resolve - * \a depth in [0..1] range. - * \a bit_count in [1..256] range. - */ -OccupancyBits occupancy_bit_from_depth(float depth, int bit_count) -{ - /* We want the occupancy at the center of each range a bit covers. - * So we round the depth to the nearest bit. */ - int depth_bit_index = occupancy_bit_index_from_depth(depth, bit_count); - - OccupancyBits occupancy; - for (int i = 0; i < 8; i++) { - int shift = depth_bit_index - i * 32; - /* Cannot bit shift more than 31 positions. */ - occupancy.bits[i] = (shift >= 0 && shift < 32) ? (0x1u << uint(shift)) : 0x0u; - } - return occupancy; -} - -/** - * Same as binary OR but for the whole OccupancyBits structure. - */ -OccupancyBits occupancy_or(OccupancyBits a, OccupancyBits b) -{ - OccupancyBits occupancy; - for (int i = 0; i < 8; i++) { - occupancy.bits[i] = a.bits[i] | b.bits[i]; - } - return occupancy; -} - -/** - * Set a series of bits high inside the given OccupancyBits. - */ -OccupancyBits occupancy_set_bits_high(OccupancyBits occupancy, int bit_start, int bit_count) -{ - for (int i = 0; i < bit_count; i++) { - int bit = bit_start + i; - occupancy.bits[bit / 32] |= 1u << uint(bit % 32); - } - return occupancy; -} - -/** - * Same as findLSB but for the whole OccupancyBits structure. - */ -int occupancy_find_lsb(OccupancyBits occupancy) -{ - for (int i = 0; i < 8; i++) { - if (occupancy.bits[i] != 0) { - return findLSB(occupancy.bits[i]) + i * 32; - } - } - return -1; -} - -/** - * Converts the first four occupancy words to a uvec4. - */ -uvec4 occupancy_to_uint4(OccupancyBits occupancy) -{ - return uvec4(occupancy.bits[0], occupancy.bits[1], occupancy.bits[2], occupancy.bits[3]); -} - -/** - * From a entry and exit occupancy tuple, returns if a specific bit is inside the volume. - */ -bool occupancy_bit_resolve(OccupancyBits entry, OccupancyBits exit, int bit_n, int bit_count) -{ - int first_exit = occupancy_find_lsb(exit); - int first_entry = occupancy_find_lsb(entry); - first_exit = (first_exit == -1) ? 99999 : first_exit; - /* Check if the first surface is an exit. If it is, initialize as inside the volume. */ - bool inside_volume = first_exit < first_entry; - for (int j = 0; j <= bit_n / 32; j++) { - uint entry_word = entry.bits[j]; - uint exit_word = exit.bits[j]; - /* TODO(fclem): Could use fewer iteration using findLSB and/or other intrinsics. */ - for (uint i = 0; i < 32; i++) { - if (flag_test(exit_word, 1u << i) && flag_test(entry_word, 1u << i)) { - /* Do nothing. */ - } - else { - if (flag_test(exit_word, 1u << i)) { - inside_volume = false; - } - if (flag_test(entry_word, 1u << i)) { - inside_volume = true; - } - } - if (i + j * 32 == uint(bit_n)) { - return inside_volume; - } - } - } - return inside_volume; -} - -/** - * From a entry and exit occupancy tuple, returns the full occupancy map. - */ -OccupancyBits occupancy_resolve(OccupancyBits entry, OccupancyBits exit, int bit_count) -{ - OccupancyBits occupancy; - for (int j = 0; j < 8; j++) { - for (int i = 0; i < 32; i++) { - bool test = false; - if (i < bit_count - j * 32) { - test = occupancy_bit_resolve(entry, exit, i + j * 32, bit_count); - } - set_flag_from_test(occupancy.bits[j], test, 1u << uint(i)); - } - } - return occupancy; -} diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_test.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_test.glsl deleted file mode 100644 index 724a9b0df43..00000000000 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_test.glsl +++ /dev/null @@ -1,110 +0,0 @@ -/* SPDX-FileCopyrightText: 2023 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -/* Directive for resetting the line numbering so the failing tests lines can be printed. - * This conflict with the shader compiler error logging scheme. - * Comment out for correct compilation error line. */ -// #line 9 - -#pragma BLENDER_REQUIRE(eevee_occupancy_lib.glsl) -#pragma BLENDER_REQUIRE(gpu_shader_test_lib.glsl) - -#define TEST(a, b) if (true) - -void main() -{ - TEST(eevee_occupancy, Occupancy) - { - OccupancyBits occup; - - /* occupancy_from_depth */ - occup = occupancy_from_depth(0.1, 1); - EXPECT_EQ(occupancy_to_uint4(occup), uvec4(0xFFFFFFFFu, ~0u, ~0u, ~0u)); - - occup = occupancy_from_depth(0.6, 1); - EXPECT_EQ(occupancy_to_uint4(occup), uvec4(0xFFFFFFFEu, ~0u, ~0u, ~0u)); - - occup = occupancy_from_depth(0.5, 32); - EXPECT_EQ(occupancy_to_uint4(occup), uvec4(0xFFFF0000u, ~0u, ~0u, ~0u)); - - occup = occupancy_from_depth(0.5, 64); - EXPECT_EQ(occupancy_to_uint4(occup), uvec4(0u, ~0u, ~0u, ~0u)); - - occup = occupancy_from_depth(0.5, 128); - EXPECT_EQ(occupancy_to_uint4(occup), uvec4(0u, 0u, ~0u, ~0u)); - - occup = occupancy_from_depth(33.0 / 64.0, 64); - EXPECT_EQ(occupancy_to_uint4(occup), uvec4(0u, 0xFFFFFFFEu, ~0u, ~0u)); - - /* occupancy_bit_from_depth */ - occup = occupancy_bit_from_depth(0.1, 1); - EXPECT_EQ(occupancy_to_uint4(occup), uvec4(0x00000001u, 0u, 0u, 0u)); - - occup = occupancy_bit_from_depth(0.6, 1); - EXPECT_EQ(occupancy_to_uint4(occup), uvec4(0x00000002u, 0u, 0u, 0u)); - - occup = occupancy_bit_from_depth(0.5, 32); - EXPECT_EQ(occupancy_to_uint4(occup), uvec4(0x00010000u, 0u, 0u, 0u)); - - occup = occupancy_bit_from_depth(0.5, 64); - EXPECT_EQ(occupancy_to_uint4(occup), uvec4(0x00000000u, 0x00000001u, 0u, 0u)); - - occup = occupancy_bit_from_depth(0.5, 128); - EXPECT_EQ(occupancy_to_uint4(occup), uvec4(0x00000000u, 0x00000000u, 0x00000001u, 0u)); - - occup = occupancy_bit_from_depth(33.0 / 64.0, 64); - EXPECT_EQ(occupancy_to_uint4(occup), uvec4(0x00000000u, 0x00000002u, 0u, 0u)); - - /* Test composing occupancy an the expected result. */ - /* Start empty. */ - OccupancyBits entry = occupancy_new(); - OccupancyBits exit = occupancy_new(); - entry = occupancy_or(entry, occupancy_bit_from_depth(1.0 / 32.0, 32)); - /* Second entry at the same depth. Should not change anything. */ - entry = occupancy_or(entry, occupancy_bit_from_depth(1.1 / 32.0, 32)); - /* Exit 2 bits later.*/ - exit = occupancy_or(exit, occupancy_bit_from_depth(3.0 / 32.0, 32)); - /* Second exit. Should not change anything. */ - exit = occupancy_or(exit, occupancy_bit_from_depth(5.0 / 32.0, 32)); - /* Third entry is valid. */ - entry = occupancy_or(entry, occupancy_bit_from_depth(7.0 / 32.0, 32)); - /* Third exit is valid. */ - exit = occupancy_or(exit, occupancy_bit_from_depth(9.0 / 32.0, 32)); - /* Fourth entry is valid. */ - entry = occupancy_or(entry, occupancy_bit_from_depth(11.0 / 32.0, 32)); - /* Fourth exit on the same depth. Cancels the occupancy. */ - exit = occupancy_or(exit, occupancy_bit_from_depth(11.0 / 32.0, 32)); - EXPECT_EQ(entry.bits[0], 2178u); /* 1000 1000 0010 */ - EXPECT_EQ(exit.bits[0], 2600u); /* 1010 0010 1000 */ - - occup = occupancy_resolve(entry, exit, 32); - EXPECT_EQ(occup.bits[0], 390u); /* 0001 1000 0110 */ - - /* Start empty. */ - entry = occupancy_new(); - exit = occupancy_new(); - /* First exit. Anything prior should be considered in volume. */ - exit = occupancy_or(exit, occupancy_bit_from_depth(33.0 / 44.0, 44)); - /* First entry. */ - entry = occupancy_or(entry, occupancy_bit_from_depth(36.0 / 44.0, 44)); - /* Second exit. Should not change anything. */ - exit = occupancy_or(exit, occupancy_bit_from_depth(40.0 / 44.0, 44)); - /* 0000 0001 0000 0000 0000 0000 0000 0000 0000 0000 0000 */ - EXPECT_EQ(occupancy_to_uint4(entry), uvec4(0x00000000u, 0x010u, 0u, 0u)); - /* 0001 0000 0010 0000 0000 0000 0000 0000 0000 0000 0000 */ - EXPECT_EQ(occupancy_to_uint4(exit), uvec4(0x00000000u, 0x102u, 0u, 0u)); - - EXPECT_EQ(occupancy_find_lsb(entry), 36); - EXPECT_EQ(occupancy_find_lsb(exit), 33); - - occup = occupancy_resolve(entry, exit, 44); - /* 0000 1111 0001 1111 1111 1111 1111 1111 1111 1111 1111 */ - EXPECT_EQ(occupancy_to_uint4(occup), uvec4(0xFFFFFFFFu, 0x0F1u, 0u, 0u)); - - occup = occupancy_new(); - occup = occupancy_set_bits_high(occup, 16, 32); - occup = occupancy_set_bits_high(occup, 80, 16); - EXPECT_EQ(occupancy_to_uint4(occup), uvec4(0xFFFF0000u, 0x0000FFFFu, 0xFFFF0000u, 0u)); - } -} diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_octahedron_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_octahedron_lib.glsl index a3cfec16cce..d0d1ea1b9c2 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_octahedron_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_octahedron_lib.glsl @@ -42,19 +42,19 @@ vec3 octahedral_uv_to_direction(vec2 co) * octahedral_uv to a specific probe. */ vec2 octahedral_uv_to_layer_texture_coords(vec2 octahedral_uv, - ReflectionProbeAtlasCoordinate atlas_coord, + ReflectionProbeData probe_data, vec2 texel_size) { /* Fix artifacts near edges. Proved one texel on each side. */ octahedral_uv = octahedral_uv * (1.0 - 2.0 * REFLECTION_PROBE_BORDER_SIZE * texel_size) + REFLECTION_PROBE_BORDER_SIZE * texel_size + 0.5 * texel_size; - int areas_per_dimension = 1 << atlas_coord.layer_subdivision; + int areas_per_dimension = 1 << probe_data.layer_subdivision; vec2 area_scalar = vec2(1.0 / float(areas_per_dimension)); octahedral_uv *= area_scalar; - vec2 area_offset = vec2(atlas_coord.area_index % areas_per_dimension, - atlas_coord.area_index / areas_per_dimension) * + vec2 area_offset = vec2(probe_data.area_index % areas_per_dimension, + probe_data.area_index / areas_per_dimension) * area_scalar; return octahedral_uv + area_offset; } @@ -66,7 +66,9 @@ vec2 octahedral_uv_to_layer_texture_coords(vec2 octahedral_uv, * It also applies wrapping in the additional space near borders. * NOTE: Doesn't apply the translation part of the packing. */ -vec2 octahedral_uv_from_layer_texture_coords(vec2 uv, vec2 texel_size) +vec2 octahedral_uv_from_layer_texture_coords(vec2 uv, + ReflectionProbeData probe_data, + vec2 texel_size) { /* Apply border region. */ @@ -78,14 +80,3 @@ vec2 octahedral_uv_from_layer_texture_coords(vec2 uv, vec2 texel_size) bool is_even = ((checker_pos.x + checker_pos.y) & 1) == 0; return is_even ? fract(shrinked_uv) : vec2(1.0) - fract(shrinked_uv); } - -/* TODO(fclem): Find something better than this. Has no place here but its shared with files that - * needs it. */ -ReflectionProbeAtlasCoordinate reinterpret_as_atlas_coord(ivec4 packed_coord) -{ - ReflectionProbeAtlasCoordinate unpacked; - unpacked.layer = packed_coord.x; - unpacked.layer_subdivision = packed_coord.y; - unpacked.area_index = packed_coord.z; - return unpacked; -} diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_ray_denoise_bilateral_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_ray_denoise_bilateral_comp.glsl index 5561753b298..fbe5b944854 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_ray_denoise_bilateral_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_ray_denoise_bilateral_comp.glsl @@ -15,10 +15,11 @@ * https://www.ea.com/seed/news/seed-dd18-presentation-slides-raytracing */ -#pragma BLENDER_REQUIRE(draw_view_lib.glsl) #pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl) #pragma BLENDER_REQUIRE(gpu_shader_math_vector_lib.glsl) #pragma BLENDER_REQUIRE(gpu_shader_codegen_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_lib.glsl) #pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl) #pragma BLENDER_REQUIRE(eevee_gbuffer_lib.glsl) @@ -29,7 +30,7 @@ float bilateral_depth_weight(vec3 center_N, vec3 center_P, vec3 sample_P) float depth_delta = dot(center_plane_eq, vec4(sample_P, 1.0)); /* TODO(fclem): Scene parameter. This is dependent on scene scale. */ const float scale = 10000.0; - float weight = exp2(-scale * square(depth_delta)); + float weight = exp2(-scale * sqr(depth_delta)); return weight; } @@ -104,7 +105,7 @@ void main() vec2 center_uv = vec2(texel_fullres) * uniform_buf.raytrace.full_resolution_inv; float center_depth = texelFetch(depth_tx, texel_fullres, 0).r; - vec3 center_P = drw_point_screen_to_world(vec3(center_uv, center_depth)); + vec3 center_P = get_world_space_from_depth(center_uv, center_depth); #if defined(RAYTRACE_DIFFUSE) ClosureDiffuse sample_closure, center_closure; @@ -164,7 +165,7 @@ void main() float sample_depth = texelFetch(depth_tx, sample_texel, 0).r; vec2 sample_uv = vec2(sample_texel) * uniform_buf.raytrace.full_resolution_inv; - vec3 sample_P = drw_point_screen_to_world(vec3(sample_uv, sample_depth)); + vec3 sample_P = get_world_space_from_depth(sample_uv, sample_depth); /* Background case. */ if (sample_depth == 0.0) { diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_ray_denoise_spatial_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_ray_denoise_spatial_comp.glsl index 69e76dfe98b..4417ec9a3ca 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_ray_denoise_spatial_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_ray_denoise_spatial_comp.glsl @@ -15,12 +15,30 @@ * https://www.ea.com/seed/news/seed-dd18-presentation-slides-raytracing */ -#pragma BLENDER_REQUIRE(draw_view_lib.glsl) #pragma BLENDER_REQUIRE(gpu_shader_codegen_lib.glsl) #pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl) #pragma BLENDER_REQUIRE(eevee_gbuffer_lib.glsl) #pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl) #pragma BLENDER_REQUIRE(eevee_bxdf_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_lib.glsl) + +void gbuffer_load_closure_data(sampler2DArray gbuf_closure_tx, + ivec2 texel, + out ClosureDiffuse closure) +{ +} + +void gbuffer_load_closure_data(sampler2DArray gbuf_closure_tx, + ivec2 texel, + out ClosureRefraction closure) +{ +} + +void gbuffer_load_closure_data(sampler2DArray gbuf_closure_tx, + ivec2 texel, + out ClosureReflection closure) +{ +} float bxdf_eval(ClosureDiffuse closure, vec3 L, vec3 V) { @@ -37,6 +55,21 @@ float bxdf_eval(ClosureReflection closure, vec3 L, vec3 V) return bsdf_ggx(closure.N, L, V, closure.roughness); } +void neighbor_tile_mask_bit_set(inout uint tile_mask, ivec2 offset) +{ + /* Only valid for a 3x3 neighborhood. */ + offset += 1; + uint shift = offset.x + (offset.y << 2u); + tile_mask |= 1u << shift; +} + +bool neighbor_tile_mask_bit_get(uint tile_mask, ivec2 offset) +{ + offset += 1; + uint shift = offset.x + (offset.y << 2u); + return flag_test(tile_mask, 1u << shift); +} + void main() { const uint tile_size = RAYTRACE_GROUP_SIZE; @@ -50,6 +83,8 @@ void main() return; } + /* Store invalid neighbor tiles to avoid sampling them in the resampling loop. */ + uint invalid_neighbor_tile_mask = 0u; /* Clear neighbor tiles that will not be processed. */ /* TODO(fclem): Optimize this. We don't need to clear the whole ring. */ for (int x = -1; x <= 1; x++) { @@ -60,6 +95,7 @@ void main() ivec2 tile_coord_neighbor = ivec2(tile_coord) + ivec2(x, y); if (!in_image_range(tile_coord_neighbor, tile_mask_img)) { + neighbor_tile_mask_bit_set(invalid_neighbor_tile_mask, ivec2(x, y)); continue; } @@ -70,6 +106,8 @@ void main() imageStore(out_radiance_img, texel_fullres_neighbor, vec4(FLT_11_11_10_MAX, 0.0)); imageStore(out_variance_img, texel_fullres_neighbor, vec4(0.0)); imageStore(out_hit_depth_img, texel_fullres_neighbor, vec4(0.0)); + + neighbor_tile_mask_bit_set(invalid_neighbor_tile_mask, ivec2(x, y)); } } } @@ -84,9 +122,7 @@ void main() } vec2 uv = (vec2(texel_fullres) + 0.5) * uniform_buf.raytrace.full_resolution_inv; - - vec3 P = drw_point_screen_to_world(vec3(uv, 0.5)); - vec3 V = drw_world_incident_vector(P); + vec3 V = transform_direction(ViewMatrixInverse, get_view_vector_from_screen_uv(uv)); GBufferData gbuf = gbuffer_read(gbuf_header_tx, gbuf_closure_tx, gbuf_color_tx, texel_fullres); @@ -114,7 +150,7 @@ void main() sample_count = max(sample_count, 5u); } /* NOTE: Roughness is squared now. */ - closure.roughness = max(1e-3, square(closure.roughness)); + closure.roughness = max(1e-3, sqr(closure.roughness)); #endif vec2 noise = utility_tx_fetch(utility_tx, vec2(texel_fullres), UTIL_BLUE_NOISE_LAYER).ba; @@ -130,12 +166,20 @@ void main() ivec2 offset = ivec2(floor(offset_f + 0.5)); ivec2 sample_texel = texel + offset; + /* Reject samples outside of valid neighbor tiles. */ + ivec2 sample_tile = ivec2(sample_texel * uniform_buf.raytrace.resolution_scale) / + int(tile_size); + ivec2 sample_tile_relative = sample_tile - ivec2(tile_coord); + if (neighbor_tile_mask_bit_get(invalid_neighbor_tile_mask, sample_tile_relative)) { + continue; + } + vec4 ray_data = imageLoad(ray_data_img, sample_texel); float ray_time = imageLoad(ray_time_img, sample_texel).r; vec4 ray_radiance = imageLoad(ray_radiance_img, sample_texel); vec3 ray_direction = ray_data.xyz; - float ray_pdf_inv = abs(ray_data.w); + float ray_pdf_inv = ray_data.w; /* Skip invalid pixels. */ if (ray_pdf_inv == 0.0) { continue; @@ -150,7 +194,7 @@ void main() radiance_accum += ray_radiance.rgb * weight; weight_accum += weight; - rgb_moment += square(ray_radiance.rgb) * weight; + rgb_moment += sqr(ray_radiance.rgb) * weight; } float inv_weight = safe_rcp(weight_accum); @@ -159,11 +203,11 @@ void main() vec3 rgb_mean = radiance_accum; rgb_moment *= inv_weight; - vec3 rgb_variance = abs(rgb_moment - square(rgb_mean)); - float hit_variance = reduce_max(rgb_variance); + vec3 rgb_variance = abs(rgb_moment - sqr(rgb_mean)); + float hit_variance = max_v3(rgb_variance); - float scene_z = drw_depth_screen_to_view(texelFetch(depth_tx, texel_fullres, 0).r); - float hit_depth = drw_depth_view_to_screen(scene_z - closest_hit_time); + float scene_z = get_view_z_from_depth(texelFetch(depth_tx, texel_fullres, 0).r); + float hit_depth = get_depth_from_view_z(scene_z - closest_hit_time); imageStore(out_radiance_img, texel_fullres, vec4(radiance_accum, 0.0)); imageStore(out_variance_img, texel_fullres, vec4(hit_variance)); diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_ray_denoise_temporal_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_ray_denoise_temporal_comp.glsl index 35c86b047ab..553c600c6fc 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_ray_denoise_temporal_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_ray_denoise_temporal_comp.glsl @@ -15,12 +15,13 @@ * https://www.ea.com/seed/news/seed-dd18-presentation-slides-raytracing */ -#pragma BLENDER_REQUIRE(draw_view_lib.glsl) #pragma BLENDER_REQUIRE(gpu_shader_codegen_lib.glsl) #pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl) #pragma BLENDER_REQUIRE(gpu_shader_math_matrix_lib.glsl) #pragma BLENDER_REQUIRE(eevee_gbuffer_lib.glsl) #pragma BLENDER_REQUIRE(eevee_colorspace_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_lib.glsl) struct LocalStatistics { vec3 mean; @@ -56,7 +57,7 @@ LocalStatistics local_statistics_get(ivec2 texel, vec3 center_radiance) } /* Weight corners less to avoid box artifacts. - * Same idea as in "High Quality Temporal Supersampling" by Brian Karis at SIGGRAPH 2014 + * Same idea as in "High Quality Temporal Supersampling" by Brian Karis at Siggraph 2014 * (Slide 32) Simple clamp to min/max of 8 neighbors results in 3x3 box artifacts. */ float weight = (x == y) ? 0.25 : 1.0; /* Use YCoCg for clamping and accumulation to avoid color shift artifacts. */ @@ -182,11 +183,11 @@ void main() /* Surface reprojection. */ /* TODO(fclem): Use per pixel velocity. Is this worth it? */ float scene_depth = texelFetch(depth_tx, texel_fullres, 0).r; - vec3 P = drw_point_screen_to_world(vec3(uv, scene_depth)); + vec3 P = get_world_space_from_depth(uv, scene_depth); vec4 history_radiance = radiance_history_sample(P, local); /* Reflection reprojection. */ float hit_depth = imageLoad(hit_depth_img, texel_fullres).r; - vec3 P_hit = drw_point_screen_to_world(vec3(uv, hit_depth)); + vec3 P_hit = get_world_space_from_depth(uv, hit_depth); history_radiance += radiance_history_sample(P_hit, local); /* Finalize accumulation. */ history_radiance *= safe_rcp(history_radiance.w); @@ -198,8 +199,7 @@ void main() float mix_fac = (history_radiance.w > 1e-3) ? 0.97 : 0.0; /* Reduce blend factor to improve low roughness reflections. Use variance instead for speed. */ mix_fac *= mix(0.75, 1.0, saturate(in_variance * 20.0)); - vec3 out_radiance = mix( - colorspace_safe_color(in_radiance), colorspace_safe_color(history_radiance.rgb), mix_fac); + vec3 out_radiance = mix(safe_color(in_radiance), safe_color(history_radiance.rgb), mix_fac); /* This is feedback next frame as radiance_history_tx. */ imageStore(out_radiance_img, texel_fullres, vec4(out_radiance, 0.0)); diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_ray_generate_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_ray_generate_comp.glsl index 044b000e109..f8152dc6a29 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_ray_generate_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_ray_generate_comp.glsl @@ -38,8 +38,7 @@ void main() } vec2 uv = (vec2(texel_fullres) + 0.5) / vec2(textureSize(gbuf_header_tx, 0).xy); - vec3 P = drw_point_screen_to_world(vec3(uv, 0.5)); - vec3 V = drw_world_incident_vector(P); + vec3 V = transform_direction(ViewMatrixInverse, get_view_vector_from_screen_uv(uv)); vec2 noise = utility_tx_fetch(utility_tx, vec2(texel), UTIL_BLUE_NOISE_LAYER).rg; #if defined(RAYTRACE_DIFFUSE) diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_ray_generate_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_ray_generate_lib.glsl index b61fe9c5849..0255e314a8c 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_ray_generate_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_ray_generate_lib.glsl @@ -8,6 +8,7 @@ #pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl) #pragma BLENDER_REQUIRE(gpu_shader_math_vector_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_lib.glsl) #pragma BLENDER_REQUIRE(eevee_bxdf_sampling_lib.glsl) #pragma BLENDER_REQUIRE(eevee_gbuffer_lib.glsl) #pragma BLENDER_REQUIRE(eevee_ray_types_lib.glsl) @@ -27,7 +28,7 @@ vec3 ray_generate_direction(vec2 noise, ClosureReflection reflection, vec3 V, ou /* Bias the rays so we never get really high energy rays almost parallel to the surface. */ Xi.x = Xi.x * (1.0 - RAY_BIAS_REFLECTION) + RAY_BIAS_REFLECTION; - float roughness_sqr = max(1e-3, square(reflection.roughness)); + float roughness_sqr = max(1e-3, sqr(reflection.roughness)); /* Gives *perfect* reflection for very small roughness. */ if (reflection.roughness < 0.0016) { Xi = vec3(0.0); @@ -46,7 +47,7 @@ vec3 ray_generate_direction(vec2 noise, ClosureRefraction refraction, vec3 V, ou /* Bias the rays so we never get really high energy rays almost parallel to the surface. */ Xi.x = Xi.x * (1.0 - RAY_BIAS_REFRACTION) + RAY_BIAS_REFRACTION; - float roughness_sqr = max(1e-3, square(refraction.roughness)); + float roughness_sqr = max(1e-3, sqr(refraction.roughness)); /* Gives *perfect* refraction for very small roughness. */ if (refraction.roughness < 0.0016) { Xi = vec3(0.0); diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_ray_tile_compact_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_ray_tile_compact_comp.glsl index 973bd08c9ad..01fed6a960f 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_ray_tile_compact_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_ray_tile_compact_comp.glsl @@ -15,43 +15,27 @@ void main() { - int resolution_scale = uniform_buf.raytrace.resolution_scale; ivec2 tile = ivec2(gl_GlobalInvocationID.xy); - /* True if an adjacent tile is tracing and will need this tile data for denoising. */ - bool tile_is_sampled = false; - /* True if this tile is shooting and tracing rays. */ - bool tile_is_tracing = false; - /* Could be optimized if that becomes an issue. */ - for (int x_tile = -1; x_tile <= 1; x_tile++) { - for (int y_tile = -1; y_tile <= 1; y_tile++) { - ivec2 tile_adj = tile + ivec2(x_tile, y_tile); - for (int x = 0; x < resolution_scale; x++) { - for (int y = 0; y < resolution_scale; y++) { - ivec2 full_res_tile = tile_adj * resolution_scale + ivec2(x, y); - if (any(greaterThanEqual(full_res_tile, imageSize(tile_mask_img)))) { - continue; - } - bool denoise_tile_is_used = imageLoad(tile_mask_img, full_res_tile).r != 0u; - if (denoise_tile_is_used) { - if (x_tile == 0 && y_tile == 0) { - /* Dispatch full resolution denoise tile. */ - uint tile_index = atomicAdd(denoise_dispatch_buf.num_groups_x, 1u); - denoise_tiles_buf[tile_index] = packUvec2x16(uvec2(full_res_tile)); - tile_is_tracing = true; - } - else { - /* This denoise tile will sample the target tracing tile. Make sure it is cleared. */ - tile_is_sampled = true; - } - } - } + bool tracing_tile_is_used = false; + for (int x = 0; x < uniform_buf.raytrace.resolution_scale; x++) { + for (int y = 0; y < uniform_buf.raytrace.resolution_scale; y++) { + ivec2 full_res_tile = tile * uniform_buf.raytrace.resolution_scale + ivec2(x, y); + if (any(greaterThanEqual(full_res_tile, imageSize(tile_mask_img)))) { + continue; + } + bool denoise_tile_is_used = imageLoad(tile_mask_img, full_res_tile).r != 0u; + if (denoise_tile_is_used) { + /* Dispatch full resolution denoise tile. */ + uint tile_index = atomicAdd(denoise_dispatch_buf.num_groups_x, 1u); + denoise_tiles_buf[tile_index] = packUvec2x16(uvec2(full_res_tile)); + + tracing_tile_is_used = true; } } } - /* TODO(fclem): we might want to dispatch another type of shader only for clearing. */ - if (tile_is_tracing || tile_is_sampled) { + if (tracing_tile_is_used) { /* Dispatch trace resolution tracing tile. */ uint tile_index = atomicAdd(ray_dispatch_buf.num_groups_x, 1u); ray_tiles_buf[tile_index] = packUvec2x16(uvec2(tile)); diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_ray_trace_fallback_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_ray_trace_fallback_comp.glsl index ddb3d40d765..3c757a652c1 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_ray_trace_fallback_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_ray_trace_fallback_comp.glsl @@ -6,11 +6,13 @@ * Does not use any tracing method. Only rely on local light probes to get the incoming radiance. */ -#pragma BLENDER_REQUIRE(eevee_lightprobe_eval_lib.glsl) +#pragma BLENDER_REQUIRE(eevee_reflection_probe_eval_lib.glsl) #pragma BLENDER_REQUIRE(eevee_bxdf_sampling_lib.glsl) #pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl) #pragma BLENDER_REQUIRE(eevee_ray_types_lib.glsl) #pragma BLENDER_REQUIRE(eevee_ray_trace_screen_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_geom_lib.glsl) void main() { @@ -34,23 +36,19 @@ void main() return; } - vec3 P = drw_point_screen_to_world(vec3(uv, depth)); - vec3 V = drw_world_incident_vector(P); - + vec3 P = get_world_space_from_depth(uv, depth); Ray ray; ray.origin = P; ray.direction = ray_data.xyz; - /* Using ray direction as geometric normal to bias the sampling position. - * This is faster than loading the gbuffer again and averages between reflected and normal - * direction over many rays. */ - vec3 Ng = ray.direction; - LightProbeSample samp = lightprobe_load(P, Ng, V); - vec3 radiance = lightprobe_eval_direction(samp, P, ray.direction, safe_rcp(ray_pdf_inv)); + int closest_probe_id = reflection_probes_find_closest(P); + ReflectionProbeData probe = reflection_probe_buf[closest_probe_id]; + vec3 radiance = reflection_probes_sample(ray.direction, 0.0, probe).rgb; /* Set point really far for correct reprojection of background. */ - float hit_time = 1000.0; + /* TODO(fclem): Could use probe depth / parallax. */ + float hit_time = 10000.0; - float luma = max(1e-8, reduce_max(radiance)); + float luma = max(1e-8, max_v3(radiance)); radiance *= 1.0 - max(0.0, luma - uniform_buf.raytrace.brightness_clamp) / luma; imageStore(ray_time_img, texel, vec4(hit_time)); diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_ray_trace_planar_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_ray_trace_planar_comp.glsl deleted file mode 100644 index 100a0fb8146..00000000000 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_ray_trace_planar_comp.glsl +++ /dev/null @@ -1,101 +0,0 @@ -/* SPDX-FileCopyrightText: 2023 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -/** - * Use screen space tracing against depth buffer of recorded planar capture to find intersection - * with the scene and its radiance. - * This pass runs before the screen trace and evaluates valid rays for planar probes. These rays - * are then tagged to avoid re-evaluation by screen trace. - */ - -#pragma BLENDER_REQUIRE(eevee_lightprobe_eval_lib.glsl) -#pragma BLENDER_REQUIRE(eevee_bxdf_sampling_lib.glsl) -#pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl) -#pragma BLENDER_REQUIRE(eevee_ray_types_lib.glsl) -#pragma BLENDER_REQUIRE(eevee_ray_trace_screen_lib.glsl) - -void main() -{ - const uint tile_size = RAYTRACE_GROUP_SIZE; - uvec2 tile_coord = unpackUvec2x16(tiles_coord_buf[gl_WorkGroupID.x]); - ivec2 texel = ivec2(gl_LocalInvocationID.xy + tile_coord * tile_size); - - vec4 ray_data = imageLoad(ray_data_img, texel); - float ray_pdf_inv = ray_data.w; - - if (ray_pdf_inv == 0.0) { - /* Invalid ray or pixels without ray. Do not trace. */ - imageStore(ray_time_img, texel, vec4(0.0)); - imageStore(ray_radiance_img, texel, vec4(0.0)); - return; - } - - ivec2 texel_fullres = texel * uniform_buf.raytrace.resolution_scale + - uniform_buf.raytrace.resolution_bias; - - float depth = texelFetch(depth_tx, texel_fullres, 0).r; - vec2 uv = (vec2(texel_fullres) + 0.5) * uniform_buf.raytrace.full_resolution_inv; - - vec3 P = drw_point_screen_to_world(vec3(uv, depth)); - vec3 V = drw_world_incident_vector(P); - - int planar_id = lightprobe_planar_select(P, V, ray_data.xyz); - if (planar_id == -1) { - return; - } - - ProbePlanarData planar = probe_planar_buf[planar_id]; - - /* Tag the ray data so that screen trace will not try to evaluate it and override the result. */ - imageStore(ray_data_img, texel, vec4(ray_data.xyz, -ray_data.w)); - - Ray ray; - ray.origin = P; - ray.direction = ray_data.xyz; - - vec3 radiance = vec3(0.0); - float noise_offset = sampling_rng_1D_get(SAMPLING_RAYTRACE_W); - float rand_trace = interlieved_gradient_noise(vec2(texel), 5.0, noise_offset); - - /* TODO(fclem): Take IOR into account in the roughness LOD bias. */ - /* TODO(fclem): pdf to roughness mapping is a crude approximation. Find something better. */ - float roughness = saturate(sample_pdf_uniform_hemisphere() / ray_pdf_inv); - - /* Transform the ray into planar view-space. */ - Ray ray_view; - ray_view.origin = transform_point(planar.viewmat, ray.origin); - ray_view.direction = transform_direction(planar.viewmat, ray.direction); - /* Extend the ray to cover the whole view. */ - ray_view.max_time = 1000.0; - - ScreenTraceHitData hit = raytrace_planar( - uniform_buf.raytrace, planar_depth_tx, planar, rand_trace, ray_view); - - if (hit.valid) { - /* Evaluate radiance at hit-point. */ - radiance = textureLod(planar_radiance_tx, vec3(hit.ss_hit_P.xy, planar_id), 0.0).rgb; - - /* Transmit twice if thickness is set and ray is longer than thickness. */ - // if (thickness > 0.0 && length(ray_data.xyz) > thickness) { - // ray_radiance.rgb *= color; - // } - } - else { - /* Using ray direction as geometric normal to bias the sampling position. - * This is faster than loading the gbuffer again and averages between reflected and normal - * direction over many rays. */ - vec3 Ng = ray.direction; - /* Fallback to nearest light-probe. */ - LightProbeSample samp = lightprobe_load(P, Ng, V); - radiance = lightprobe_eval_direction(samp, P, ray.direction, safe_rcp(ray_pdf_inv)); - /* Set point really far for correct reprojection of background. */ - hit.time = 10000.0; - } - - float luma = max(1e-8, reduce_max(radiance)); - radiance *= 1.0 - max(0.0, luma - uniform_buf.raytrace.brightness_clamp) / luma; - - imageStore(ray_time_img, texel, vec4(hit.time)); - imageStore(ray_radiance_img, texel, vec4(radiance, 0.0)); -} diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_ray_trace_screen_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_ray_trace_screen_comp.glsl index 318b3f95539..21f952f1eab 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_ray_trace_screen_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_ray_trace_screen_comp.glsl @@ -6,11 +6,13 @@ * Use screen space tracing against depth buffer to find intersection with the scene. */ -#pragma BLENDER_REQUIRE(eevee_lightprobe_eval_lib.glsl) +#pragma BLENDER_REQUIRE(eevee_reflection_probe_eval_lib.glsl) #pragma BLENDER_REQUIRE(eevee_bxdf_sampling_lib.glsl) #pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl) #pragma BLENDER_REQUIRE(eevee_ray_types_lib.glsl) #pragma BLENDER_REQUIRE(eevee_ray_trace_screen_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_geom_lib.glsl) void main() { @@ -18,14 +20,15 @@ void main() uvec2 tile_coord = unpackUvec2x16(tiles_coord_buf[gl_WorkGroupID.x]); ivec2 texel = ivec2(gl_LocalInvocationID.xy + tile_coord * tile_size); + ivec2 texel_fullres = texel * uniform_buf.raytrace.resolution_scale + + uniform_buf.raytrace.resolution_bias; + + float depth = texelFetch(depth_tx, texel_fullres, 0).r; + vec2 uv = (vec2(texel_fullres) + 0.5) * uniform_buf.raytrace.full_resolution_inv; + vec4 ray_data = imageLoad(ray_data_img, texel); float ray_pdf_inv = ray_data.w; - if (ray_pdf_inv < 0.0) { - /* Ray destined to planar trace. */ - return; - } - if (ray_pdf_inv == 0.0) { /* Invalid ray or pixels without ray. Do not trace. */ imageStore(ray_time_img, texel, vec4(0.0)); @@ -33,14 +36,7 @@ void main() return; } - ivec2 texel_fullres = texel * uniform_buf.raytrace.resolution_scale + - uniform_buf.raytrace.resolution_bias; - - float depth = texelFetch(depth_tx, texel_fullres, 0).r; - vec2 uv = (vec2(texel_fullres) + 0.5) * uniform_buf.raytrace.full_resolution_inv; - - vec3 P = drw_point_screen_to_world(vec3(uv, depth)); - vec3 V = drw_world_incident_vector(P); + vec3 P = get_world_space_from_depth(uv, depth); Ray ray; ray.origin = P; ray.direction = ray_data.xyz; @@ -91,18 +87,15 @@ void main() // } } else { - /* Using ray direction as geometric normal to bias the sampling position. - * This is faster than loading the gbuffer again and averages between reflected and normal - * direction over many rays. */ - vec3 Ng = ray.direction; /* Fallback to nearest light-probe. */ - LightProbeSample samp = lightprobe_load(P, Ng, V); - radiance = lightprobe_eval_direction(samp, P, ray.direction, safe_rcp(ray_pdf_inv)); + int closest_probe_id = reflection_probes_find_closest(P); + ReflectionProbeData probe = reflection_probe_buf[closest_probe_id]; + radiance = reflection_probes_sample(ray.direction, 0.0, probe).rgb; /* Set point really far for correct reprojection of background. */ hit.time = 10000.0; } - float luma = max(1e-8, reduce_max(radiance)); + float luma = max(1e-8, max_v3(radiance)); radiance *= 1.0 - max(0.0, luma - uniform_buf.raytrace.brightness_clamp) / luma; imageStore(ray_time_img, texel, vec4(hit.time)); diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_ray_trace_screen_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_ray_trace_screen_lib.glsl index 96500e7281a..5b7ce4cf9ba 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_ray_trace_screen_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_ray_trace_screen_lib.glsl @@ -12,15 +12,12 @@ * Many modifications were made for our own usage. */ -#pragma BLENDER_REQUIRE(draw_view_lib.glsl) -#pragma BLENDER_REQUIRE(gpu_shader_math_matrix_lib.glsl) -#pragma BLENDER_REQUIRE(gpu_shader_math_fast_lib.glsl) #pragma BLENDER_REQUIRE(eevee_ray_types_lib.glsl) /* Inputs expected to be in view-space. */ void raytrace_clip_ray_to_near_plane(inout Ray ray) { - float near_dist = drw_view_near(); + float near_dist = get_view_z_from_depth(0.0); if ((ray.origin.z + ray.direction.z * ray.max_time) > near_dist) { ray.max_time = abs((near_dist - ray.origin.z) / ray.direction.z); } @@ -71,7 +68,7 @@ METAL_ATTR ScreenTraceHitData raytrace_screen(RayTraceData rt_data, raytrace_clip_ray_to_near_plane(ray); } - /* NOTE: The 2.0 factor here is because we are applying it in NDC space. */ + /* NOTE: The 2.0 factor here is because we are applying it in. */ ScreenSpaceRay ssray = raytrace_screenspace_ray_create( ray, 2.0 * rt_data.full_resolution_inv, rt_data.thickness); @@ -79,7 +76,7 @@ METAL_ATTR ScreenTraceHitData raytrace_screen(RayTraceData rt_data, if (!allow_self_intersection && ssray.max_time < 1.1) { /* Still output the clipped ray. */ vec3 hit_ssP = ssray.origin.xyz + ssray.direction.xyz * ssray.max_time; - vec3 hit_P = drw_point_screen_to_world(vec3(hit_ssP.xy, saturate(hit_ssP.z))); + vec3 hit_P = get_world_space_from_depth(hit_ssP.xy, saturate(hit_ssP.z)); ray.direction = hit_P - ray.origin; ScreenTraceHitData no_hit; @@ -91,10 +88,10 @@ METAL_ATTR ScreenTraceHitData raytrace_screen(RayTraceData rt_data, ssray.max_time = max(1.1, ssray.max_time); float prev_delta = 0.0, prev_time = 0.0; - float depth_sample = drw_depth_view_to_screen(ray.origin.z); + float depth_sample = get_depth_from_view_z(ray.origin.z); float delta = depth_sample - ssray.origin.z; - float lod_fac = saturate(sqrt_fast(roughness) * 2.0 - 0.4); + float lod_fac = saturate(fast_sqrt(roughness) * 2.0 - 0.4); /* Cross at least one pixel. */ float t = 1.001, time = 1.001; @@ -144,9 +141,9 @@ METAL_ATTR ScreenTraceHitData raytrace_screen(RayTraceData rt_data, ScreenTraceHitData result; result.valid = hit; - result.ss_hit_P = ssray.origin.xyz + ssray.direction.xyz * time; - result.v_hit_P = drw_point_screen_to_view(result.ss_hit_P); /* Convert to world space ray time. */ + result.ss_hit_P = ssray.origin.xyz + ssray.direction.xyz * time; + result.v_hit_P = project_point(drw_view.wininv, result.ss_hit_P * 2.0 - 1.0); result.time = length(result.v_hit_P - ray.origin) / length(ray.direction); #ifdef METAL_AMD_RAYTRACE_WORKAROUND @@ -159,65 +156,3 @@ METAL_ATTR ScreenTraceHitData raytrace_screen(RayTraceData rt_data, } #undef METAL_ATTR - -#ifdef PLANAR_PROBES - -ScreenTraceHitData raytrace_planar(RayTraceData rt_data, - depth2DArray planar_depth_tx, - ProbePlanarData planar, - float stride_rand, - Ray ray) -{ - /* Clip to near plane for perspective view where there is a singularity at the camera origin. */ - if (ProjectionMatrix[3][3] == 0.0) { - raytrace_clip_ray_to_near_plane(ray); - } - - vec2 inv_texture_size = 1.0 / vec2(textureSize(planar_depth_tx, 0).xy); - /* NOTE: The 2.0 factor here is because we are applying it in NDC space. */ - /* TODO(@fclem): This uses the main view's projection matrix, not the planar's one. - * This works fine for reflection, but this prevent the use of any other projection capture. */ - ScreenSpaceRay ssray = raytrace_screenspace_ray_create(ray, 2.0 * inv_texture_size); - - float prev_delta = 0.0, prev_time = 0.0; - float depth_sample = texture(planar_depth_tx, vec3(ssray.origin.xy, planar.layer_id)).r; - float delta = depth_sample - ssray.origin.z; - - float t = 0.0, time = 0.0; - bool hit = false; - const int max_steps = 32; - for (int iter = 1; !hit && (time < ssray.max_time) && (iter < max_steps); iter++) { - float stride = 1.0 + float(iter) * rt_data.quality; - - prev_time = time; - prev_delta = delta; - - time = min(t + stride * stride_rand, ssray.max_time); - t += stride; - - vec4 ss_ray = ssray.origin + ssray.direction * time; - - depth_sample = texture(planar_depth_tx, vec3(ss_ray.xy, planar.layer_id)).r; - - delta = depth_sample - ss_ray.z; - /* Check if the ray is below the surface. */ - hit = (delta < 0.0); - } - /* Reject hit if background. */ - hit = hit && (depth_sample != 1.0); - /* Refine hit using intersection between the sampled height-field and the ray. - * This simplifies nicely to this single line. */ - time = mix(prev_time, time, saturate(prev_delta / (prev_delta - delta))); - - ScreenTraceHitData result; - result.valid = hit; - result.ss_hit_P = ssray.origin.xyz + ssray.direction.xyz * time; - /* TODO(@fclem): This uses the main view's projection matrix, not the planar's one. - * This works fine for reflection, but this prevent the use of any other projection capture. */ - result.v_hit_P = drw_point_screen_to_view(result.ss_hit_P); - /* Convert to world space ray time. */ - result.time = length(result.v_hit_P - ray.origin) / length(ray.direction); - return result; -} - -#endif \ No newline at end of file diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_ray_types_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_ray_types_lib.glsl index fdce5edded8..b8279a12236 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_ray_types_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_ray_types_lib.glsl @@ -2,7 +2,8 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ -#pragma BLENDER_REQUIRE(draw_view_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_lib.glsl) /** * General purpose 3D ray. @@ -36,17 +37,17 @@ void raytrace_screenspace_ray_finalize(inout ScreenSpaceRay ray, vec2 pixel_size ray.direction -= ray.origin; /* If the line is degenerate, make it cover at least one pixel * to not have to handle zero-pixel extent as a special case later */ - if (length_squared(ray.direction.xy) < 0.00001) { + if (len_squared(ray.direction.xy) < 0.00001) { ray.direction.xy = vec2(0.0, 0.00001); } - float ray_len_sqr = length_squared(ray.direction.xyz); + float ray_len_sqr = len_squared(ray.direction.xyz); /* Make ray.direction cover one pixel. */ bool is_more_vertical = abs(ray.direction.x / pixel_size.x) < abs(ray.direction.y / pixel_size.y); ray.direction /= (is_more_vertical) ? abs(ray.direction.y) : abs(ray.direction.x); ray.direction *= (is_more_vertical) ? pixel_size.y : pixel_size.x; /* Clip to segment's end. */ - ray.max_time = sqrt(ray_len_sqr * safe_rcp(length_squared(ray.direction.xyz))); + ray.max_time = sqrt(ray_len_sqr * safe_rcp(len_squared(ray.direction.xyz))); /* Clipping to frustum sides. */ float clip_dist = line_unit_box_intersect_dist_safe(ray.origin.xyz, ray.direction.xyz); ray.max_time = min(ray.max_time, clip_dist); @@ -58,8 +59,8 @@ void raytrace_screenspace_ray_finalize(inout ScreenSpaceRay ray, vec2 pixel_size ScreenSpaceRay raytrace_screenspace_ray_create(Ray ray, vec2 pixel_size) { ScreenSpaceRay ssray; - ssray.origin.xyz = drw_point_view_to_ndc(ray.origin); - ssray.direction.xyz = drw_point_view_to_ndc(ray.origin + ray.direction * ray.max_time); + ssray.origin.xyz = project_point(ProjectionMatrix, ray.origin); + ssray.direction.xyz = project_point(ProjectionMatrix, ray.origin + ray.direction * ray.max_time); raytrace_screenspace_ray_finalize(ssray, pixel_size); return ssray; @@ -68,12 +69,12 @@ ScreenSpaceRay raytrace_screenspace_ray_create(Ray ray, vec2 pixel_size) ScreenSpaceRay raytrace_screenspace_ray_create(Ray ray, vec2 pixel_size, float thickness) { ScreenSpaceRay ssray; - ssray.origin.xyz = drw_point_view_to_ndc(ray.origin); - ssray.direction.xyz = drw_point_view_to_ndc(ray.origin + ray.direction * ray.max_time); + ssray.origin.xyz = project_point(ProjectionMatrix, ray.origin); + ssray.direction.xyz = project_point(ProjectionMatrix, ray.origin + ray.direction * ray.max_time); /* Interpolate thickness in screen space. * Calculate thickness further away to avoid near plane clipping issues. */ - ssray.origin.w = drw_depth_view_to_screen(ray.origin.z - thickness); - ssray.direction.w = drw_depth_view_to_screen(ray.origin.z + ray.direction.z - thickness); + ssray.origin.w = get_depth_from_view_z(ray.origin.z - thickness); + ssray.direction.w = get_depth_from_view_z(ray.origin.z + ray.direction.z - thickness); ssray.origin.w = ssray.origin.w * 2.0 - 1.0; ssray.direction.w = ssray.direction.w * 2.0 - 1.0; diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_eval_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_eval_lib.glsl index 91234a1c22b..e711be6e76b 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_eval_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_eval_lib.glsl @@ -8,27 +8,76 @@ #pragma BLENDER_REQUIRE(eevee_bxdf_sampling_lib.glsl) #pragma BLENDER_REQUIRE(eevee_reflection_probe_lib.glsl) -#ifdef REFLECTION_PROBE -int reflection_probes_select(vec3 P, float random_probe) +int reflection_probes_find_closest(vec3 P) { - for (int index = 0; index < REFLECTION_PROBES_MAX; index++) { - ReflectionProbeData probe_data = reflection_probe_buf[index]; - /* ReflectionProbeData doesn't contain any gap, exit at first item that is invalid. */ - if (probe_data.atlas_coord.layer == -1) { - /* We hit the end of the array. Return last valid index. */ - return index - 1; - } - /* NOTE: The vector-matrix multiplication swapped on purpose to cancel the matrix transpose. */ - vec3 lP = vec4(P, 1.0) * probe_data.world_to_probe_transposed; - float gradient = (probe_data.influence_shape == SHAPE_ELIPSOID) ? - length(lP) : - max(max(abs(lP.x), abs(lP.y)), abs(lP.z)); - float score = saturate(probe_data.influence_bias - gradient * probe_data.influence_scale); - if (score > random_probe) { - return index; + int closest_index = 0; + float closest_distance = FLT_MAX; + + /* ReflectionProbeData doesn't contain any gab, exit at first item that is invalid. */ + for (int index = 1; reflection_probe_buf[index].layer != -1 && index < REFLECTION_PROBES_MAX; + index++) + { + float dist = distance(P, reflection_probe_buf[index].pos.xyz); + if (dist < closest_distance) { + closest_distance = dist; + closest_index = index; } } - /* This should never happen (world probe is always last). */ - return REFLECTION_PROBES_MAX - 1; + return closest_index; } -#endif /* REFLECTION_PROBE */ + +#ifdef EEVEE_UTILITY_TX +vec4 reflection_probe_eval(ClosureReflection reflection, + vec3 P, + vec3 V, + ReflectionProbeData probe_data) +{ + ivec3 texture_size = textureSize(reflectionProbes, 0); + float lod_cube_max = min(log2(float(texture_size.x)) - float(probe_data.layer_subdivision) + 1.0, + float(REFLECTION_PROBE_MIPMAP_LEVELS)); + + /* Pow2f to distributed across lod more evenly */ + float roughness = clamp(pow2f(reflection.roughness), 1e-4f, 0.9999f); + +# if defined(GPU_COMPUTE_SHADER) + vec2 frag_coord = vec2(gl_GlobalInvocationID.xy) + 0.5; +# else + vec2 frag_coord = gl_FragCoord.xy; +# endif + vec2 noise = utility_tx_fetch(utility_tx, frag_coord, UTIL_BLUE_NOISE_LAYER).gb; + vec2 rand = fract(noise + sampling_rng_2D_get(SAMPLING_RAYTRACE_U)); + + vec3 Xi = sample_cylinder(rand); + + /* Microfacet normal */ + vec3 T, B; + make_orthonormal_basis(reflection.N, T, B); + float pdf; + vec3 H = sample_ggx_reflect(Xi, roughness, V, reflection.N, T, B, pdf); + + vec3 L = -reflect(V, H); + float NL = dot(reflection.N, L); + + if (NL > 0.0) { + /* Coarse Approximation of the mapping distortion + * Unit Sphere -> Cube-map Face. */ + const float dist = 4.0 * M_PI / 6.0; + + /* http://http.developer.nvidia.com/GPUGems3/gpugems3_ch20.html : Equation 13 */ + float lod = clamp(probe_data.lod_factor - 0.5 * log2(pdf * dist), 0.0, lod_cube_max); + vec4 l_col = reflection_probes_sample(L, lod, probe_data); + + /* Clamped brightness. */ + /* For artistic freedom this should be read from the scene/reflection probe. + * Note: EEVEE-legacy read the firefly_factor from gi_glossy_clamp. + * Note: Firefly removal should be moved to a different shader and also take SSR into account. + */ + float luma = max(1e-8, max_v3(l_col)); + const float firefly_factor = 1e16; + l_col.rgb *= 1.0 - max(0.0, luma - firefly_factor) / luma; + + return l_col; + } + return vec4(0.0); +} +#endif diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_lib.glsl index 6fc16ed37e0..9044851622c 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_lib.glsl @@ -2,43 +2,20 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ -#pragma BLENDER_REQUIRE(gpu_shader_math_vector_lib.glsl) +#pragma BLENDER_REQUIRE(eevee_cubemap_lib.glsl) #pragma BLENDER_REQUIRE(eevee_octahedron_lib.glsl) -#pragma BLENDER_REQUIRE(eevee_spherical_harmonics_lib.glsl) -#ifdef REFLECTION_PROBE -vec4 reflection_probes_sample(vec3 L, float lod, ReflectionProbeAtlasCoordinate atlas_coord) +vec4 reflection_probes_sample(vec3 L, float lod, ReflectionProbeData probe_data) { vec2 octahedral_uv_packed = octahedral_uv_from_direction(L); - vec2 texel_size = vec2(1.0 / float(1 << (11 - atlas_coord.layer_subdivision))); + vec2 texel_size = vec2(1.0 / float(1 << (11 - probe_data.layer_subdivision))); vec2 octahedral_uv = octahedral_uv_to_layer_texture_coords( - octahedral_uv_packed, atlas_coord, texel_size); - return textureLod(reflection_probes_tx, vec3(octahedral_uv, atlas_coord.layer), lod); + octahedral_uv_packed, probe_data, texel_size); + return textureLod(reflectionProbes, vec3(octahedral_uv, probe_data.layer), lod); } -#endif -ReflectionProbeLowFreqLight reflection_probes_extract_low_freq(SphericalHarmonicL1 sh) +vec3 reflection_probes_world_sample(vec3 L, float lod) { - /* To avoid color shift and negative values, we reduce saturation and directionality. */ - ReflectionProbeLowFreqLight result; - result.ambient = sh.L0.M0.r + sh.L0.M0.g + sh.L0.M0.b; - - mat3x4 L1_per_band; - L1_per_band[0] = sh.L1.Mn1; - L1_per_band[1] = sh.L1.M0; - L1_per_band[2] = sh.L1.Mp1; - - mat4x3 L1_per_comp = transpose(L1_per_band); - result.direction = L1_per_comp[0] + L1_per_comp[1] + L1_per_comp[2]; - - return result; -} - -vec3 reflection_probes_normalization_eval(vec3 L, - ReflectionProbeLowFreqLight numerator, - ReflectionProbeLowFreqLight denominator) -{ - /* TODO(fclem): Adjusting directionality is tricky. - * Needs to be revisited later on. For now only use the ambient term. */ - return vec3(numerator.ambient * safe_rcp(denominator.ambient)); + ReflectionProbeData probe_data = reflection_probe_buf[0]; + return reflection_probes_sample(L, lod, probe_data).rgb; } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_remap_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_remap_comp.glsl index a23bac7bba1..2d608dd93e0 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_remap_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_remap_comp.glsl @@ -6,27 +6,16 @@ #pragma BLENDER_REQUIRE(eevee_octahedron_lib.glsl) -ivec2 probe_area_offset(ReflectionProbeAtlasCoordinate atlas_coord, ivec3 texture_size) -{ - ivec2 octahedral_size = ivec2(texture_size.x >> atlas_coord.layer_subdivision, - texture_size.y >> atlas_coord.layer_subdivision); - int probes_per_dimension = 1 << atlas_coord.layer_subdivision; - ivec2 area_coord = ivec2(atlas_coord.area_index % probes_per_dimension, - atlas_coord.area_index / probes_per_dimension); - ivec2 area_offset = area_coord * octahedral_size; - return area_offset; -} - void main() { - ReflectionProbeAtlasCoordinate probe_coord = reinterpret_as_atlas_coord(probe_coord_packed); - ReflectionProbeAtlasCoordinate world_coord = reinterpret_as_atlas_coord(world_coord_packed); + ReflectionProbeData probe_data = reflection_probe_buf[reflection_probe_index]; ivec3 texture_coord = ivec3(gl_GlobalInvocationID.xyz); ivec3 texture_size = imageSize(octahedral_img); ivec3 octahedral_coord = ivec3(gl_GlobalInvocationID.xyz); - ivec2 octahedral_size = texture_size.xy >> probe_coord.layer_subdivision; + ivec2 octahedral_size = ivec2(texture_size.x >> probe_data.layer_subdivision, + texture_size.y >> probe_data.layer_subdivision); /* Exit when pixel being written doesn't fit in the area reserved for the probe. */ if (any(greaterThanEqual(octahedral_coord.xy, octahedral_size.xy))) { return; @@ -35,26 +24,18 @@ void main() vec2 texel_size = vec2(1.0) / vec2(octahedral_size); vec2 uv = vec2(octahedral_coord.xy) / vec2(octahedral_size.xy); - vec2 octahedral_uv = octahedral_uv_from_layer_texture_coords(uv, texel_size); + vec2 octahedral_uv = octahedral_uv_from_layer_texture_coords(uv, probe_data, texel_size); vec3 R = octahedral_uv_to_direction(octahedral_uv); - vec4 col = textureLod(cubemap_tx, R, float(probe_coord.layer_subdivision)); + vec4 col = textureLod(cubemap_tx, R, float(probe_data.layer_subdivision)); + + int probes_per_dimension = 1 << probe_data.layer_subdivision; + ivec2 area_coord = ivec2(probe_data.area_index % probes_per_dimension, + probe_data.area_index / probes_per_dimension); + ivec2 area_offset = area_coord * octahedral_size; /* Convert transmittance to transparency. */ col.a = 1.0 - col.a; - /* Composite world into reflection probes. */ - bool is_world = all(equal(probe_coord_packed, world_coord_packed)); - if (!is_world && col.a != 1.0) { - vec2 world_octahedral_size = vec2(texture_size.x >> world_coord.layer_subdivision, - texture_size.y >> world_coord.layer_subdivision); - ivec3 world_octahedral_coord = ivec3(ivec2(uv * world_octahedral_size), 0.0); - ivec2 world_area_offset = probe_area_offset(world_coord, texture_size); - vec4 world_col = imageLoad( - octahedral_img, world_octahedral_coord + ivec3(world_area_offset, world_coord.layer)); - col.rgb = mix(world_col.rgb, col.rgb, col.a); - } - - ivec2 area_offset = probe_area_offset(probe_coord, texture_size); - imageStore(octahedral_img, octahedral_coord + ivec3(area_offset, probe_coord.layer), col); + imageStore(octahedral_img, octahedral_coord + ivec3(area_offset, probe_data.layer), col); } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_select_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_select_comp.glsl deleted file mode 100644 index ff9d4081278..00000000000 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_select_comp.glsl +++ /dev/null @@ -1,29 +0,0 @@ -/* SPDX-FileCopyrightText: 2023 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -/** - * Select spherical probes to be considered during render and copy irradiance data from the - * irradiance cache from each spherical probe location except for the world probe. - */ - -#pragma BLENDER_REQUIRE(eevee_lightprobe_eval_lib.glsl) - -void main() -{ - int idx = int(gl_GlobalInvocationID.x); - if (idx >= reflection_probe_count) { - return; - } - - SphericalHarmonicL1 sh; - if (idx == 0) { - sh = lightprobe_irradiance_world(); - } - else { - vec3 probe_center = reflection_probe_buf[idx].location; - sh = lightprobe_irradiance_sample(probe_center); - } - - reflection_probe_buf[idx].low_freq_light = reflection_probes_extract_low_freq(sh); -} diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_update_irradiance_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_update_irradiance_comp.glsl index bd68ce3e248..1ab809498c4 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_update_irradiance_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_reflection_probe_update_irradiance_comp.glsl @@ -6,7 +6,6 @@ #pragma BLENDER_REQUIRE(eevee_reflection_probe_lib.glsl) #pragma BLENDER_REQUIRE(eevee_spherical_harmonics_lib.glsl) -#pragma BLENDER_REQUIRE(eevee_octahedron_lib.glsl) #pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl) void atlas_store(vec4 sh_coefficient, ivec2 atlas_coord, int layer) @@ -33,10 +32,10 @@ void main() cooef.L1.M0 = vec4(0.0); cooef.L1.Mp1 = vec4(0.0); - ReflectionProbeAtlasCoordinate atlas_coord = reinterpret_as_atlas_coord(world_coord_packed); + ReflectionProbeData probe_data = reflection_probe_buf[reflection_probe_index]; const int subdivision_64 = 5; float layer_mipmap = clamp( - subdivision_64 - atlas_coord.layer_subdivision, 0, REFLECTION_PROBE_MIPMAP_LEVELS); + subdivision_64 - probe_data.layer_subdivision, 0, REFLECTION_PROBE_MIPMAP_LEVELS); /* Perform multiple sample. */ uint store_index = gl_LocalInvocationID.x; @@ -47,7 +46,7 @@ void main() { vec2 rand = fract(hammersley_2d(sample_index + sample_offset, total_samples)); vec3 direction = sample_sphere(rand); - vec4 light = reflection_probes_sample(direction, layer_mipmap, atlas_coord); + vec4 light = reflection_probes_sample(direction, layer_mipmap, probe_data); spherical_harmonics_encode_signal_sample(direction, light * sample_weight, cooef); } cooefs[store_index][0] = cooef.L0.M0; diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_sampling_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_sampling_lib.glsl index f6c190d3666..62818385868 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_sampling_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_sampling_lib.glsl @@ -7,7 +7,7 @@ * Also contains some sample mapping functions. */ -#pragma BLENDER_REQUIRE(gpu_shader_math_base_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_lib.glsl) /* -------------------------------------------------------------------- */ /** \name Sampling data. @@ -114,9 +114,9 @@ vec2 hammersley_2d(int i, int sample_count) /* Given 1 random number in [0..1] range, return a random unit circle sample. */ vec2 sample_circle(float rand) { - float phi = (rand - 0.5) * M_TAU; + float phi = (rand - 0.5) * M_2PI; float cos_phi = cos(phi); - float sin_phi = sqrt(1.0 - square(cos_phi)) * sign(phi); + float sin_phi = sqrt(1.0 - sqr(cos_phi)) * sign(phi); return vec2(cos_phi, sin_phi); } @@ -149,7 +149,7 @@ vec3 sample_sphere(vec2 rand) vec3 sample_hemisphere(vec2 rand) { float cos_theta = rand.x; - float sin_theta = safe_sqrt(1.0 - square(cos_theta)); + float sin_theta = safe_sqrt(1.0 - sqr(cos_theta)); return vec3(sin_theta * sample_circle(rand.y), cos_theta); } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_debug_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_debug_frag.glsl index b54a1f750fc..2428999bb7d 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_debug_frag.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_debug_frag.glsl @@ -7,8 +7,8 @@ * See eShadowDebug for more information. */ -#pragma BLENDER_REQUIRE(gpu_shader_debug_gradients_lib.glsl) -#pragma BLENDER_REQUIRE(draw_view_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_lib.glsl) #pragma BLENDER_REQUIRE(eevee_light_iter_lib.glsl) #pragma BLENDER_REQUIRE(eevee_light_lib.glsl) #pragma BLENDER_REQUIRE(eevee_shadow_lib.glsl) @@ -173,7 +173,7 @@ void main() out_color_mul = vec4(1.0); float depth = texelFetch(hiz_tx, ivec2(gl_FragCoord.xy), 0).r; - vec3 P = drw_point_screen_to_world(vec3(uvcoordsvar.xy, depth)); + vec3 P = get_world_space_from_depth(uvcoordsvar.xy, depth); /* Make it pass the depth test. */ gl_FragDepth = depth - 1e-6; diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_lib.glsl index 33db8d15ac8..e1a19312571 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_lib.glsl @@ -2,12 +2,8 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ -#pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl) -#pragma BLENDER_REQUIRE(gpu_shader_math_matrix_lib.glsl) #pragma BLENDER_REQUIRE(eevee_shadow_tilemap_lib.glsl) -#define EEVEE_SHADOW_LIB - float shadow_read_depth_at_tilemap_uv(usampler2DArray atlas_tx, usampler2D tilemaps_tx, int tilemap_index, @@ -34,11 +30,17 @@ float shadow_read_depth_at_tilemap_uv(usampler2DArray atlas_tx, return uintBitsToFloat(texelFetch(atlas_tx, texel, 0).r); } +/* TODO(fclem): Use utildef version. Only here to avoid include order hell with common_math_lib. */ +float shadow_orderedIntBitsToFloat(int int_value) +{ + return intBitsToFloat((int_value < 0) ? (int_value ^ 0x7FFFFFFF) : int_value); +} + struct ShadowEvalResult { - /* Visibility of the light. */ - float light_visibilty; - /* Average occluder distance. In world space linear distance. */ - float occluder_distance; + /* Visibility of the light above the horizon. */ + float surface_light_visibilty; + /* Average occluder distance for rays below the horizon. In world space linear distance. */ + float subsurface_occluder_distance; }; /* ---------------------------------------------------------------------- */ @@ -67,25 +69,6 @@ mat4x4 shadow_projection_perspective( return mat; } -/** - * Convert occluder distance in shadow space to world space distance. - * Assuming the occluder is above the shading point in direction to the shadow projection center. - */ -float shadow_linear_occluder_distance(LightData light, - const bool is_directional, - vec3 lP, - float occluder) -{ - float near = orderedIntBitsToFloat(light.clip_near); - float far = orderedIntBitsToFloat(light.clip_far); - - float occluder_z = (is_directional) ? (occluder * (far - near) + near) : - ((near * far) / (occluder * (near - far) + far)); - float receiver_z = (is_directional) ? -lP.z : max(abs(lP.x), max(abs(lP.y), abs(lP.z))); - - return receiver_z - occluder_z; -} - ShadowEvalResult shadow_punctual_sample_get(usampler2DArray atlas_tx, usampler2D tilemaps_tx, LightData light, @@ -111,8 +94,8 @@ ShadowEvalResult shadow_punctual_sample_get(usampler2DArray atlas_tx, atlas_tx, tilemaps_tx, light.tilemap_index + face_id, uv_P.xy); ShadowEvalResult result; - result.light_visibilty = float(uv_P.z < depth); - result.occluder_distance = shadow_linear_occluder_distance(light, false, lP, depth); + result.surface_light_visibilty = float(uv_P.z < depth); + result.subsurface_occluder_distance = 0.0; /* Not available. */ return result; } @@ -124,8 +107,8 @@ ShadowEvalResult shadow_directional_sample_get(usampler2DArray atlas_tx, vec3 lP = P * mat3(light.object_mat); ShadowCoordinates coord = shadow_directional_coordinates(light, lP); - float clip_near = orderedIntBitsToFloat(light.clip_near); - float clip_far = orderedIntBitsToFloat(light.clip_far); + float clip_near = shadow_orderedIntBitsToFloat(light.clip_near); + float clip_far = shadow_orderedIntBitsToFloat(light.clip_far); /* Assumed to be non-null. */ float z_range = clip_far - clip_near; float dist_to_near_plane = -lP.z - clip_near; @@ -153,8 +136,8 @@ ShadowEvalResult shadow_directional_sample_get(usampler2DArray atlas_tx, atlas_tx, tilemaps_tx, light.tilemap_index + level_relative, tilemap_uv); ShadowEvalResult result; - result.light_visibilty = float(dist_to_near_plane < depth * z_range); - result.occluder_distance = shadow_linear_occluder_distance(light, true, lP, depth); + result.surface_light_visibilty = float(dist_to_near_plane < depth * z_range); + result.subsurface_occluder_distance = 0.0; /* Not available. */ return result; } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_page_clear_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_page_clear_comp.glsl index bcb7a6f586c..ab5e078bb64 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_page_clear_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_page_clear_comp.glsl @@ -8,15 +8,14 @@ * Equivalent to a frame-buffer depth clear but only for pages pushed to the clear_page_buf. */ -#pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_lib.glsl) void main() { - /* We clear the destination pixels directly for the atomicMin technique. */ - uint page_packed = dst_coord_buf[gl_GlobalInvocationID.z]; + uint page_packed = clear_list_buf[gl_GlobalInvocationID.z]; uvec3 page_co = shadow_page_unpack(page_packed); page_co.xy = page_co.xy * SHADOW_PAGE_RES + gl_GlobalInvocationID.xy; /* Clear to FLT_MAX instead of 1 so the far plane doesn't cast shadows onto farther objects. */ - imageStoreFast(shadow_atlas_img, ivec3(page_co), uvec4(floatBitsToUint(FLT_MAX))); + imageStore(shadow_atlas_img, ivec3(page_co), uvec4(floatBitsToUint(FLT_MAX))); } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_page_defrag_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_page_defrag_comp.glsl index 4e8e448e596..8886e067982 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_page_defrag_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_page_defrag_comp.glsl @@ -129,10 +129,4 @@ void main() clear_dispatch_buf.num_groups_x = SHADOW_PAGE_RES / SHADOW_PAGE_CLEAR_GROUP_SIZE; clear_dispatch_buf.num_groups_y = SHADOW_PAGE_RES / SHADOW_PAGE_CLEAR_GROUP_SIZE; clear_dispatch_buf.num_groups_z = 0; - - /* Reset TBDR command indirect buffer. */ - tile_draw_buf.vertex_len = 0u; - tile_draw_buf.instance_len = 1u; - tile_draw_buf.vertex_first = 0u; - tile_draw_buf.base_index = 0u; } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_page_mask_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_page_mask_comp.glsl index ef2cb1b8eb1..6fc09b05b3e 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_page_mask_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_page_mask_comp.glsl @@ -36,21 +36,12 @@ void main() if (thread_active) { int tile_offset = shadow_tile_offset(tile_co, tilemap.tiles_index, lod); tile = shadow_tile_unpack(tiles_buf[tile_offset]); -#ifdef SHADOW_FORCE_LOD0 - if (lod == 0) { - tiles_buf[tile_offset] |= SHADOW_IS_USED; - } - else { - tiles_buf[tile_offset] &= ~SHADOW_IS_USED; - } -#else if (lod > 0 && usage_grid[tile_co.y][tile_co.x] == 4u) { /* Remove the usage flag as this tile is completely covered by higher LOD tiles. */ tiles_buf[tile_offset] &= ~SHADOW_IS_USED; /* Consider this tile occluding lower levels. */ tile.is_used = true; } -#endif /* Reset count for next level. */ usage_grid[tile_co.y / 2][tile_co.x / 2] = 0u; } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_page_tile_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_page_tile_frag.glsl deleted file mode 100644 index f02ce87c4ed..00000000000 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_page_tile_frag.glsl +++ /dev/null @@ -1,71 +0,0 @@ -/* SPDX-FileCopyrightText: 2023 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -/** - * Virtual Shadow map tile shader. - * - * On TBDR, we can use a three-pass method to perform virtual shadow map updates, leveraging - * efficient use of tile-based GPUs. Shadow updates rasterize geometry for each view in much the - * same way as a conventional shadow map render, but for the standard path, there is an additional - * cost of an atomic-min and store to allow for indirection into the atlas. This setup can lead to - * excessive overdraw, rasterization and increased complexity in the material depth fragment - * shader, reducing rendering performance. - * - * On a tile-based GPU, as shadow updates are still relative, we can still leverage on-tile depth - * testing, to avoid atomic-min operations against global memory, and only write out the final - * depth value stored in each tile. Large memory-less render targets are used to create a virtual - * render target, where only the updated regions and layers are processed. - * - * Firstly, invoke an instance of this shader with PASS_CLEAR to clear the depth values to default - * for tiles being updated. The first optimization also enables tiles which are not being updated - * in this pass to be cleared to zero, saving on fragment invocation costs for unused regions of - * the render target. - * This allows us to also skip the compute-based tile clear pass. - * - * Secondly, eevee_surf_shadow_frag is used to generate depth information which is stored - * on tile for the closest fragment. The TBDR path uses a simple variant of this shader - * which just outputs the depth, without any virtual shadow map processing on top. - * - * The third pass then runs, writing out only the highest-level final pixel to memory, - * avoiding the requirement for atomic texture operations. - * - * Output shadow atlas page indirection is calculated in the vertex shader, which generates - * a lattice of quads covering the shadow pages which are to be updated. The quads which - * belong to shadow pages not being updated in this pass are discarded. - **/ - -#pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl) -#pragma BLENDER_REQUIRE(eevee_shadow_tilemap_lib.glsl) - -#if defined(PASS_CLEAR) - -void main() -{ - /* The tile clear pass writes out to tile attachment to ensure raster order groups are satisfied, - * allowing the clear to be guaranteed to happen first, as it is first in submission order. */ - out_tile_depth = FLT_MAX; -} - -#elif defined(PASS_DEPTH_STORE) - -void main() -{ - /* For storing pass, we store the result from depth in tile memory. */ - uint u_depth = floatBitsToUint(in_tile_depth); - /* Quantization bias. Equivalent to `nextafter` in C without all the safety. 1 is not enough. */ - u_depth += 2; - - /* Write result to atlas. */ -# ifdef GPU_METAL - /* NOTE: Use the fastest possible write function without any parameter wrapping or conversion.*/ - shadow_atlas_img.texture->write( - u_depth, ushort2(interp_noperspective.out_texel_xy), interp_flat.out_page_z); -# else - imageStore(shadow_atlas_img, - ivec3(interp_noperspective.out_texel_xy, interp_flat.out_page_z), - uvec4(u_depth)); -# endif -} - -#endif diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_page_tile_vert.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_page_tile_vert.glsl deleted file mode 100644 index 84ef9549592..00000000000 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_page_tile_vert.glsl +++ /dev/null @@ -1,41 +0,0 @@ -/* SPDX-FileCopyrightText: 2023 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -/** - * Virtual Shadow map tile shader. - * - * See fragment shader for more infos. - */ -#pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl) -#pragma BLENDER_REQUIRE(eevee_shadow_tilemap_lib.glsl) - -void main() -{ - int tile_id = gl_VertexID / 6; - int vertex_id = gl_VertexID % 6; - /* Generate Quad with 2 triangle with same winding. - * This way the can be merged on some hardware. */ - int v = (vertex_id > 2) ? (3 - (vertex_id - 3)) : vertex_id; - vec2 tile_corner = vec2(v & 1, v >> 1); - -#ifdef PASS_DEPTH_STORE - /* Load where fragment should write the tile data. */ - uvec3 dst_page_co = shadow_page_unpack(dst_coord_buf[tile_id]); - /* Interpolate output texel */ - interp_noperspective.out_texel_xy = (vec2(dst_page_co.xy) + tile_corner) * vec2(SHADOW_PAGE_RES); - interp_flat.out_page_z = dst_page_co.z; -#endif - - /* Load where the quad should be positioned. */ - uvec3 src_page_co = unpackUvec4x8(src_coord_buf[tile_id]).xyz; - - vec2 uv_pos = (tile_corner + vec2(src_page_co.xy)) / float(SHADOW_TILEMAP_RES); - vec2 ndc_pos = uv_pos * 2.0 - 1.0; - /* We initially clear depth to 1.0 only for update fragments. - * Non-updated tile depth will remain at 0.0 to ensure fragments are discarded. */ - gl_Position = vec4(ndc_pos.x, ndc_pos.y, 1.0, 1.0); - gpu_Layer = int(src_page_co.z); - /* Assumes last viewport will always cover the whole frame-buffer. */ - gpu_ViewportIndex = 15; -} diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tag_update_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tag_update_comp.glsl index 4f73288d052..b4edf058b63 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tag_update_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tag_update_comp.glsl @@ -11,9 +11,8 @@ * tag the appropriate tiles. */ -#pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl) -#pragma BLENDER_REQUIRE(draw_view_lib.glsl) #pragma BLENDER_REQUIRE(common_intersect_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_lib.glsl) #pragma BLENDER_REQUIRE(common_aabb_lib.glsl) #pragma BLENDER_REQUIRE(eevee_shadow_tilemap_lib.glsl) diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tag_usage_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tag_usage_comp.glsl index 29020dc023c..d95445babaa 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tag_usage_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tag_usage_comp.glsl @@ -27,8 +27,8 @@ void main() } vec2 uv = (vec2(texel) + 0.5) / vec2(tex_size); - vec3 vP = drw_point_screen_to_view(vec3(uv, depth)); - vec3 P = drw_point_view_to_world(vP); + vec3 vP = get_view_space_from_depth(uv, depth); + vec3 P = transform_point(ViewMatrixInverse, vP); vec2 pixel = vec2(gl_GlobalInvocationID.xy); shadow_tag_usage(vP, P, pixel); diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tag_usage_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tag_usage_frag.glsl index cfbcca3fb9e..dd9f870e682 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tag_usage_frag.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tag_usage_frag.glsl @@ -10,8 +10,8 @@ * tiles. */ -#pragma BLENDER_REQUIRE(draw_model_lib.glsl) #pragma BLENDER_REQUIRE(eevee_shadow_tag_usage_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_lib.glsl) float ray_aabb(vec3 ray_origin, vec3 ray_direction, vec3 aabb_min, vec3 aabb_max) { @@ -19,8 +19,8 @@ float ray_aabb(vec3 ray_origin, vec3 ray_direction, vec3 aabb_min, vec3 aabb_max vec3 t_mins = (aabb_min - ray_origin) / ray_direction; vec3 t_maxs = (aabb_max - ray_origin) / ray_direction; - float t_min = reduce_max(min(t_mins, t_maxs)); - float t_max = reduce_min(max(t_mins, t_maxs)); + float t_min = max_v3(min(t_mins, t_maxs)); + float t_max = min_v3(max(t_mins, t_maxs)); /* AABB is in the opposite direction. */ if (t_max < 0.0) { @@ -68,14 +68,14 @@ void step_bounding_sphere(vec3 vs_near_plane, for (int x = -1; x <= 1; x += 2) { for (int y = -1; y <= 1; y += 2) { vec3 near_corner = near_center + (near_pixel_size * 0.5 * vec3(x, y, 0)); - sphere_radius = max(sphere_radius, length_squared(near_corner - sphere_center)); + sphere_radius = max(sphere_radius, len_squared(near_corner - sphere_center)); vec3 far_corner = far_center + (far_pixel_size * 0.5 * vec3(x, y, 0)); - sphere_radius = max(sphere_radius, length_squared(far_corner - sphere_center)); + sphere_radius = max(sphere_radius, len_squared(far_corner - sphere_center)); } } - sphere_center = drw_point_view_to_world(sphere_center); + sphere_center = point_view_to_world(sphere_center); sphere_radius = sqrt(sphere_radius); } @@ -84,14 +84,14 @@ void main() vec2 screen_uv = gl_FragCoord.xy / vec2(fb_resolution); float opaque_depth = texelFetch(hiz_tx, ivec2(gl_FragCoord.xy), fb_lod).r; - vec3 ws_opaque = drw_point_screen_to_world(vec3(screen_uv, opaque_depth)); + vec3 ws_opaque = get_world_space_from_depth(screen_uv, opaque_depth); - vec3 ws_near_plane = drw_point_screen_to_world(vec3(screen_uv, 0.0)); + vec3 ws_near_plane = get_world_space_from_depth(screen_uv, 0); vec3 ws_view_direction = normalize(interp.P - ws_near_plane); - vec3 vs_near_plane = drw_point_screen_to_view(vec3(screen_uv, 0.0)); + vec3 vs_near_plane = get_view_space_from_depth(screen_uv, 0); vec3 vs_view_direction = normalize(interp.vP - vs_near_plane); - vec3 ls_near_plane = drw_point_world_to_object(ws_near_plane); - vec3 ls_view_direction = normalize(drw_point_world_to_object(interp.P) - ls_near_plane); + vec3 ls_near_plane = point_world_to_object(ws_near_plane); + vec3 ls_view_direction = normalize(point_world_to_object(interp.P) - ls_near_plane); /* TODO (Miguel Pozo): We could try to ray-cast against the non-inflated bounds first, * and fallback to the inflated ones if there is no hit. @@ -99,7 +99,7 @@ void main() float ls_near_box_t = ray_aabb( ls_near_plane, ls_view_direction, interp_flat.ls_aabb_min, interp_flat.ls_aabb_max); vec3 ls_near_box = ls_near_plane + ls_view_direction * ls_near_box_t; - vec3 ws_near_box = drw_point_object_to_world(ls_near_box); + vec3 ws_near_box = point_object_to_world(ls_near_box); float near_box_t = distance(ws_near_plane, ws_near_box); float far_box_t = distance(ws_near_plane, interp.P); @@ -116,7 +116,7 @@ void main() vec3 P = ws_near_plane + (ws_view_direction * t); float step_radius; step_bounding_sphere(vs_near_plane, vs_view_direction, t, t + step_size, P, step_radius); - vec3 vP = drw_point_world_to_view(P); + vec3 vP = point_world_to_view(P); shadow_tag_usage( vP, P, ws_view_direction, step_radius, t, gl_FragCoord.xy * exp2(float(fb_lod)), 0); diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tag_usage_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tag_usage_lib.glsl index 7be13a72295..39268c335db 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tag_usage_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tag_usage_lib.glsl @@ -9,8 +9,9 @@ * This contains the common logic used for tagging shadows for opaque and transparent receivers. */ -#pragma BLENDER_REQUIRE(draw_view_lib.glsl) -#pragma BLENDER_REQUIRE(draw_intersect_lib.glsl) +#pragma BLENDER_REQUIRE(common_intersect_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_geom_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_lib.glsl) #pragma BLENDER_REQUIRE(eevee_light_iter_lib.glsl) #pragma BLENDER_REQUIRE(eevee_light_lib.glsl) #pragma BLENDER_REQUIRE(eevee_shadow_lib.glsl) @@ -124,8 +125,6 @@ void shadow_tag_usage_tilemap_punctual( } /* Apply resolution ratio. */ footprint_ratio *= tilemap_projection_ratio; - /* Take the frustum padding into account. */ - footprint_ratio *= light.clip_side / orderedIntBitsToFloat(light.clip_near); if (radius == 0) { int face_id = shadow_punctual_face_index_get(lP); diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tag_usage_vert.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tag_usage_vert.glsl index b155091f006..86ced9460ad 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tag_usage_vert.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tag_usage_vert.glsl @@ -9,40 +9,39 @@ * This renders the bounding boxes for transparent objects in order to tag the correct shadows. */ -#pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl) -#pragma BLENDER_REQUIRE(gpu_shader_math_vector_lib.glsl) -#pragma BLENDER_REQUIRE(draw_model_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_lib.glsl) #pragma BLENDER_REQUIRE(common_shape_lib.glsl) /* Inflate bounds by half a pixel as a conservative rasterization alternative, * to ensure the tiles needed by all LOD0 pixels get tagged */ void inflate_bounds(vec3 ls_center, inout vec3 P, inout vec3 lP) { - vec3 vP = drw_point_world_to_view(P); + vec3 vP = point_world_to_view(P); float inflate_scale = pixel_world_radius * exp2(float(fb_lod)); - if (drw_view_is_perspective()) { + bool is_persp = (ProjectionMatrix[3][3] == 0.0); + if (is_persp) { inflate_scale *= -vP.z; } /* Half-pixel. */ inflate_scale *= 0.5; - vec3 vs_inflate_vector = drw_normal_object_to_view(sign(lP - ls_center)); + vec3 vs_inflate_vector = normal_object_to_view(sign(lP - ls_center)); vs_inflate_vector.z = 0; /* Scale the vector so the largest axis length is 1 */ - vs_inflate_vector /= reduce_max(abs(vs_inflate_vector.xy)); + vs_inflate_vector /= max_v2(abs(vs_inflate_vector.xy)); vs_inflate_vector *= inflate_scale; vP += vs_inflate_vector; - P = drw_point_view_to_world(vP); - lP = drw_point_world_to_object(P); + P = point_view_to_world(vP); + lP = point_world_to_object(P); } void main() { - DRW_RESOURCE_ID_VARYING_SET + PASS_RESOURCE_ID - ObjectBounds bounds = bounds_buf[resource_id]; + const ObjectBounds bounds = bounds_buf[resource_id]; Box box = shape_box(bounds.bounding_corners[0].xyz, bounds.bounding_corners[0].xyz + bounds.bounding_corners[1].xyz, @@ -53,14 +52,14 @@ void main() vec3 ws_aabb_max = bounds.bounding_corners[0].xyz + bounds.bounding_corners[1].xyz + bounds.bounding_corners[2].xyz + bounds.bounding_corners[3].xyz; - vec3 ls_center = drw_point_world_to_object(midpoint(ws_aabb_min, ws_aabb_max)); + vec3 ls_center = point_world_to_object((ws_aabb_min + ws_aabb_max) / 2.0); vec3 ls_conservative_min = vec3(FLT_MAX); vec3 ls_conservative_max = vec3(-FLT_MAX); for (int i = 0; i < 8; i++) { vec3 P = box.corners[i]; - vec3 lP = drw_point_world_to_object(P); + vec3 lP = point_world_to_object(P); inflate_bounds(ls_center, P, lP); ls_conservative_min = min(ls_conservative_min, lP); @@ -72,10 +71,10 @@ void main() vec3 lP = mix(ls_conservative_min, ls_conservative_max, max(vec3(0), pos)); - interp.P = drw_point_object_to_world(lP); - interp.vP = drw_point_world_to_view(interp.P); + interp.P = point_object_to_world(lP); + interp.vP = point_world_to_view(interp.P); - gl_Position = drw_point_world_to_homogenous(interp.P); + gl_Position = point_world_to_ndc(interp.P); #if 0 if (gl_VertexID == 0) { @@ -85,7 +84,7 @@ void main() ls_conservative_min + (ls_conservative_max - ls_conservative_min) * vec3(0, 1, 0), ls_conservative_min + (ls_conservative_max - ls_conservative_min) * vec3(0, 0, 1)); for (int i = 0; i < 8; i++) { - debug_box.corners[i] = drw_point_object_to_world(debug_box.corners[i]); + debug_box.corners[i] = point_object_to_world(debug_box.corners[i]); } drw_debug(debug_box); } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tag_usage_volume_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tag_usage_volume_comp.glsl index 505e91b7257..0af1914d771 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tag_usage_volume_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tag_usage_volume_comp.glsl @@ -29,9 +29,9 @@ void main() } vec3 jitter = sampling_rng_3D_get(SAMPLING_VOLUME_U); - vec3 volume_ndc = volume_to_screen((vec3(froxel) + jitter) * uniform_buf.volumes.inv_tex_size); - vec3 vP = drw_point_screen_to_view(vec3(volume_ndc.xy, volume_ndc.z)); - vec3 P = drw_point_view_to_world(vP); + vec3 volume_ndc = volume_to_ndc((vec3(froxel) + jitter) * uniform_buf.volumes.inv_tex_size); + vec3 vP = get_view_space_from_depth(volume_ndc.xy, volume_ndc.z); + vec3 P = point_view_to_world(vP); float depth = texelFetch(hiz_tx, froxel.xy, uniform_buf.volumes.tile_size_lod).r; if (depth < volume_ndc.z) { @@ -42,5 +42,5 @@ void main() uniform_buf.volumes.viewport_size_inv; int bias = uniform_buf.volumes.tile_size_lod; - shadow_tag_usage(vP, P, drw_world_incident_vector(P), 0.01, length(vP), pixel, bias); + shadow_tag_usage(vP, P, cameraVec(P), 0.01, length(vP), pixel, bias); } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tilemap_bounds_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tilemap_bounds_comp.glsl index 0a6237ec778..6a6d9b0621d 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tilemap_bounds_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tilemap_bounds_comp.glsl @@ -11,6 +11,7 @@ */ #pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_lib.glsl) #pragma BLENDER_REQUIRE(common_intersect_lib.glsl) #pragma BLENDER_REQUIRE(eevee_shadow_tilemap_lib.glsl) #pragma BLENDER_REQUIRE(eevee_light_iter_lib.glsl) diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tilemap_finalize_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tilemap_finalize_comp.glsl index 0e0bdd3e867..4a90def49da 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tilemap_finalize_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tilemap_finalize_comp.glsl @@ -12,6 +12,7 @@ #pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl) #pragma BLENDER_REQUIRE(gpu_shader_math_matrix_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_lib.glsl) #pragma BLENDER_REQUIRE(eevee_shadow_tilemap_lib.glsl) shared int rect_min_x; @@ -159,14 +160,9 @@ void main() if (do_page_render) { /* Tag tile as rendered. There is a barrier after the read. So it is safe. */ tiles_buf[tile_index] |= SHADOW_IS_RENDERED; - /* Add page to clear dispatch. */ - uint page_index = atomicAdd(clear_dispatch_buf.num_groups_z, 1u); - /* Add page to tile processing. */ - atomicAdd(tile_draw_buf.vertex_len, 6u); - /* Add page mapping for indexing the page position in atlas and in the frame-buffer. */ - dst_coord_buf[page_index] = page_packed; - src_coord_buf[page_index] = packUvec4x8( - uvec4(relative_tile_co.x, relative_tile_co.y, view_index, 0)); + /* Add page to clear list. */ + uint clear_page_index = atomicAdd(clear_dispatch_buf.num_groups_z, 1u); + clear_list_buf[clear_page_index] = page_packed; /* Statistics. */ atomicAdd(statistics_buf.page_rendered_count, 1); } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tilemap_init_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tilemap_init_comp.glsl index 3ba3b6c2a48..541ce192f5e 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tilemap_init_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tilemap_init_comp.glsl @@ -11,6 +11,7 @@ */ #pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_lib.glsl) #pragma BLENDER_REQUIRE(eevee_shadow_tilemap_lib.glsl) shared int directional_range_changed; diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tilemap_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tilemap_lib.glsl index 0c8febfda06..8dae0552bbd 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tilemap_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tilemap_lib.glsl @@ -2,6 +2,7 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ +#pragma BLENDER_REQUIRE(common_math_lib.glsl) #pragma BLENDER_REQUIRE(common_shape_lib.glsl) /* ---------------------------------------------------------------------- */ @@ -36,7 +37,7 @@ ivec2 shadow_tile_coord_in_atlas(ivec2 tile, int tilemap_index) */ int shadow_tile_offset(ivec2 tile, int tiles_index, int lod) { -#if SHADOW_TILEMAP_LOD > 5 +#if SHADOW_TILEMAP_LOD != 5 # error This needs to be adjusted #endif const int lod0_width = SHADOW_TILEMAP_RES / 1; diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tracing_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tracing_lib.glsl index d71c26d9e30..aaf4c5af78a 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tracing_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tracing_lib.glsl @@ -191,12 +191,8 @@ struct ShadowRayDirectional { LightData light; }; -ShadowRayDirectional shadow_ray_generate_directional(LightData light, - vec2 random_2d, - vec3 lP, - vec3 lNg, - float thickness, - out bool r_is_above_surface) +ShadowRayDirectional shadow_ray_generate_directional( + LightData light, vec2 random_2d, vec3 lP, vec3 lNg, out bool r_is_above_surface) { float clip_near = orderedIntBitsToFloat(light.clip_near); float clip_far = orderedIntBitsToFloat(light.clip_far); @@ -214,10 +210,6 @@ ShadowRayDirectional shadow_ray_generate_directional(LightData light, r_is_above_surface = dot(lNg, direction.xyz) > 0.0; - if (!r_is_above_surface) { - /* Skip the object volume. */ - origin += direction * thickness; - } /* It only make sense to trace where there can be occluder. Clamp by distance to near plane. */ direction *= min(light.shadow_trace_distance, dist_to_near_plane / disk_direction.z); @@ -278,12 +270,8 @@ struct ShadowRayPunctual { }; /* Return ray in UV clip space [0..1]. */ -ShadowRayPunctual shadow_ray_generate_punctual(LightData light, - vec2 random_2d, - vec3 lP, - vec3 lNg, - float thickness, - out bool r_is_above_surface) +ShadowRayPunctual shadow_ray_generate_punctual( + LightData light, vec2 random_2d, float rand, vec3 lP, vec3 lNg, out bool r_is_above_surface) { if (light.type == LIGHT_RECT) { random_2d = random_2d * 2.0 - 1.0; @@ -308,18 +296,6 @@ ShadowRayPunctual shadow_ray_generate_punctual(LightData light, -projection_origin, point_on_light_shape, light.shadow_shape_scale_or_angle); direction = point_on_light_shape - lP; - r_is_above_surface = dot(direction, lNg) > 0.0; - -#ifdef SSS_TRANSMITTANCE - if (!r_is_above_surface) { - float dir_len; - vec3 L = normalize_and_get_length(direction, dir_len); - /* Skip the object volume. Do not push behind the light. */ - float offset_len = saturate(thickness / dir_len); - lP += direction * offset_len; - direction *= 1.0 - offset_len; - } -#endif /* Clip the ray to not cross the near plane. * Scale it so that it encompass the whole cube (with a safety margin). */ @@ -339,24 +315,14 @@ ShadowRayPunctual shadow_ray_generate_punctual(LightData light, vec3 point_on_light_shape = right * random_2d.x + up * random_2d.y; direction = point_on_light_shape - lP; - r_is_above_surface = dot(direction, lNg) > 0.0; - -#ifdef SSS_TRANSMITTANCE - if (!r_is_above_surface) { - float dir_len; - vec3 L = normalize_and_get_length(direction, dir_len); - /* Skip the object volume. Do not push behind the light. */ - float offset_len = saturate(thickness / dir_len); - lP += direction * offset_len; - direction *= 1.0 - offset_len; - } -#endif /* Clip the ray to not cross the light shape. */ float clip_distance = light._radius; direction *= saturate((dist - clip_distance) / dist); } + r_is_above_surface = dot(direction, lNg) > 0.0; + /* Apply shadow origin shift. */ vec3 local_ray_start = lP + projection_origin; vec3 local_ray_end = local_ray_start + direction; @@ -404,16 +370,30 @@ SHADOW_MAP_TRACE_FN(ShadowRayPunctual) /** \name Shadow Evaluation * \{ */ +/** + * Convert occluder distance in shadow space to world space distance. + * Assuming the occluder is above the shading point in direction to the shadow projection center. + */ +float shadow_linear_occluder_distance(LightData light, + const bool is_directional, + vec3 lP, + float occluder) +{ + float near = shadow_orderedIntBitsToFloat(light.clip_near); + float far = shadow_orderedIntBitsToFloat(light.clip_far); + + float occluder_z = (is_directional) ? (occluder * (far - near) + near) : + ((near * far) / (occluder * (near - far) + far)); + float receiver_z = (is_directional) ? -lP.z : max(abs(lP.x), max(abs(lP.y), abs(lP.z))); + + return receiver_z - occluder_z; +} + /** * Evaluate shadowing by casting rays toward the light direction. */ -ShadowEvalResult shadow_eval(LightData light, - const bool is_directional, - vec3 P, - vec3 Ng, - float thickness, - int ray_count, - int ray_step_count) +ShadowEvalResult shadow_eval( + LightData light, const bool is_directional, vec3 P, vec3 Ng, int ray_count, int ray_step_count) { #ifdef EEVEE_SAMPLING_DATA # ifdef GPU_FRAGMENT_SHADER @@ -443,7 +423,7 @@ ShadowEvalResult shadow_eval(LightData light, float surface_hit = 0.0; float surface_ray_count = 0.0; - float subsurface_hit = 0.0; + float subsurface_occluder_depth = 0.0; float subsurface_ray_count = 0.0; for (int ray_index = 0; ray_index < ray_count && ray_index < SHADOW_MAX_RAY; ray_index++) { vec2 random_ray_2d = fract(hammersley_2d(ray_index, ray_count) + random_shadow_3d.xy); @@ -455,12 +435,12 @@ ShadowEvalResult shadow_eval(LightData light, ShadowMapTraceResult trace; if (is_directional) { ShadowRayDirectional clip_ray = shadow_ray_generate_directional( - light, random_ray_2d, lP, lNg, thickness, is_above_surface); + light, random_ray_2d, lP, lNg, is_above_surface); trace = shadow_map_trace(clip_ray, ray_step_count, random_shadow_3d.z); } else { ShadowRayPunctual clip_ray = shadow_ray_generate_punctual( - light, random_ray_2d, lP, lNg, thickness, is_above_surface); + light, random_ray_2d, random_shadow_3d.z, lP, lNg, is_above_surface); trace = shadow_map_trace(clip_ray, ray_step_count, random_shadow_3d.z); } @@ -469,16 +449,18 @@ ShadowEvalResult shadow_eval(LightData light, surface_ray_count += 1.0; } else { - subsurface_hit += float(trace.has_hit); + subsurface_occluder_depth += trace.occluder_depth; subsurface_ray_count += 1.0; } } /* Average samples. */ ShadowEvalResult result; - result.light_visibilty = saturate(1.0 - surface_hit * safe_rcp(surface_ray_count)); - result.light_visibilty = min(result.light_visibilty, - saturate(1.0 - subsurface_hit * safe_rcp(surface_ray_count))); - result.occluder_distance = 0.0; /* Unused. Could reintroduced if needed. */ + result.surface_light_visibilty = (surface_ray_count == 0.0) ? + 1.0 : + saturate(1.0 - (surface_hit * safe_rcp(surface_ray_count))); + subsurface_occluder_depth *= safe_rcp(subsurface_ray_count); + result.subsurface_occluder_distance = shadow_linear_occluder_distance( + light, is_directional, lP, subsurface_occluder_depth); return result; } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_spherical_harmonics_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_spherical_harmonics_lib.glsl index 682c84c91e6..3022d49d6d2 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_spherical_harmonics_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_spherical_harmonics_lib.glsl @@ -191,27 +191,6 @@ vec4 spherical_harmonics_L2_evaluate(vec3 direction, SphericalHarmonicBandL2 L2) spherical_harmonics_L2_Mp2(direction) * L2.Mp2; } -vec3 spherical_harmonics_evaluate(vec3 direction, SphericalHarmonicL0 sh) -{ - vec3 radiance = spherical_harmonics_L0_evaluate(direction, sh.L0).rgb; - return max(vec3(0.0), radiance); -} - -vec3 spherical_harmonics_evaluate(vec3 direction, SphericalHarmonicL1 sh) -{ - vec3 radiance = spherical_harmonics_L0_evaluate(direction, sh.L0).rgb + - spherical_harmonics_L1_evaluate(direction, sh.L1).rgb; - return max(vec3(0.0), radiance); -} - -vec3 spherical_harmonics_evaluate(vec3 direction, SphericalHarmonicL2 sh) -{ - vec3 radiance = spherical_harmonics_L0_evaluate(direction, sh.L0).rgb + - spherical_harmonics_L1_evaluate(direction, sh.L1).rgb + - spherical_harmonics_L2_evaluate(direction, sh.L2).rgb; - return max(vec3(0.0), radiance); -} - /** \} */ /* -------------------------------------------------------------------- */ diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_subsurface_convolve_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_subsurface_convolve_comp.glsl index cd092344720..bd6ecf08e7e 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_subsurface_convolve_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_subsurface_convolve_comp.glsl @@ -5,19 +5,20 @@ /** * Process in screen space the diffuse radiance input to mimic subsurface transmission. * - * This implementation follows the technique described in the SIGGRAPH presentation: - * "Efficient screen space subsurface scattering SIGGRAPH 2018" + * This implementation follows the technique described in the siggraph presentation: + * "Efficient screen space subsurface scattering Siggraph 2018" * by Evgenii Golubev * * But, instead of having all the precomputed weights for all three color primaries, * we precompute a weight profile texture to be able to support per pixel AND per channel radius. */ -#pragma BLENDER_REQUIRE(draw_view_lib.glsl) #pragma BLENDER_REQUIRE(gpu_shader_math_rotation_lib.glsl) #pragma BLENDER_REQUIRE(gpu_shader_math_matrix_lib.glsl) #pragma BLENDER_REQUIRE(gpu_shader_codegen_lib.glsl) #pragma BLENDER_REQUIRE(eevee_gbuffer_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_geom_lib.glsl) #pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl) shared vec3 cached_radiance[SUBSURFACE_GROUP_SIZE][SUBSURFACE_GROUP_SIZE]; @@ -76,7 +77,7 @@ void main(void) cache_populate(center_uv); float depth = texelFetch(depth_tx, texel, 0).r; - vec3 vP = drw_point_screen_to_view(vec3(center_uv, depth)); + vec3 vP = get_view_space_from_depth(center_uv, depth); GBufferData gbuf = gbuffer_read(gbuf_header_tx, gbuf_closure_tx, gbuf_color_tx, texel); @@ -84,7 +85,7 @@ void main(void) return; } - float max_radius = reduce_max(gbuf.diffuse.sss_radius); + float max_radius = max_v3(gbuf.diffuse.sss_radius); float homcoord = ProjectionMatrix[2][3] * vP.z + ProjectionMatrix[3][3]; vec2 sample_scale = vec2(ProjectionMatrix[0][0], ProjectionMatrix[1][1]) * @@ -93,7 +94,7 @@ void main(void) /* Avoid too small radii that have float imprecision. */ vec3 clamped_sss_radius = max(vec3(1e-4), gbuf.diffuse.sss_radius / max_radius) * max_radius; /* Scale albedo because we can have HDR value caused by BSDF sampling. */ - vec3 albedo = gbuf.diffuse.color / max(1e-6, reduce_max(gbuf.diffuse.color)); + vec3 albedo = gbuf.diffuse.color / max(1e-6, max_v3(gbuf.diffuse.color)); vec3 d = burley_setup(clamped_sss_radius, albedo); /* Do not rotate too much to avoid too much cache misses. */ @@ -115,7 +116,7 @@ void main(void) continue; } /* Slide 34. */ - vec3 sample_vP = drw_point_screen_to_view(vec3(sample_uv, samp.depth)); + vec3 sample_vP = get_view_space_from_depth(sample_uv, samp.depth); float r = distance(sample_vP, vP); vec3 weight = burley_eval(d, r) * pdf_inv; @@ -127,6 +128,5 @@ void main(void) /* Put result in direct diffuse. */ imageStore(out_direct_light_img, texel, vec4(accum_radiance, 0.0)); - /* Clear the indirect pass since its content has been merged and convolved with direct light. */ imageStore(out_indirect_light_img, texel, vec4(0.0, 0.0, 0.0, 0.0)); } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_subsurface_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_subsurface_lib.glsl deleted file mode 100644 index 36d75b4e721..00000000000 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_subsurface_lib.glsl +++ /dev/null @@ -1,32 +0,0 @@ -/* SPDX-FileCopyrightText: 2023 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -/** - * Various utilities related to object subsurface light transport. - * - * Required resources: - * - utility_tx - */ - -#pragma BLENDER_REQUIRE(eevee_shadow_lib.glsl) - -float subsurface_transmittance_profile(float u) -{ - return utility_tx_sample(utility_tx, vec2(u, 0.0), UTIL_SSS_TRANSMITTANCE_PROFILE_LAYER).r; -} - -/** - * Returns the amount of light that can travels through a uniform medium and exit at the backface. - */ -vec3 subsurface_transmission(vec3 sss_radii, float thickness) -{ - sss_radii *= SSS_TRANSMIT_LUT_RADIUS; - vec3 channels_co = saturate(thickness / sss_radii) * SSS_TRANSMIT_LUT_SCALE + - SSS_TRANSMIT_LUT_BIAS; - vec3 translucency; - translucency.x = (sss_radii.x > 0.0) ? subsurface_transmittance_profile(channels_co.x) : 0.0; - translucency.y = (sss_radii.y > 0.0) ? subsurface_transmittance_profile(channels_co.y) : 0.0; - translucency.z = (sss_radii.z > 0.0) ? subsurface_transmittance_profile(channels_co.z) : 0.0; - return translucency; -} diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_subsurface_setup_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_subsurface_setup_comp.glsl index 5aeb200b859..c9cbd014f82 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_subsurface_setup_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_subsurface_setup_comp.glsl @@ -7,9 +7,9 @@ * processing. */ -#pragma BLENDER_REQUIRE(draw_view_lib.glsl) #pragma BLENDER_REQUIRE(eevee_gbuffer_lib.glsl) -#pragma BLENDER_REQUIRE(gpu_shader_math_vector_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_lib.glsl) shared uint has_visible_sss; @@ -29,7 +29,7 @@ void main(void) vec3 radiance = imageLoad(direct_light_img, texel).rgb + imageLoad(indirect_light_img, texel).rgb; - float max_radius = reduce_max(gbuf.diffuse.sss_radius); + float max_radius = max_v3(gbuf.diffuse.sss_radius); imageStore(radiance_img, texel, vec4(radiance, 0.0)); imageStore(object_id_img, texel, uvec4(gbuf.diffuse.sss_id)); @@ -37,7 +37,7 @@ void main(void) vec2 center_uv = (vec2(texel) + 0.5) / vec2(textureSize(gbuf_header_tx, 0)); float depth = texelFetch(depth_tx, texel, 0).r; /* TODO(fclem): Check if this simplifies. */ - float vPz = drw_depth_screen_to_view(depth); + float vPz = get_view_z_from_depth(depth); float homcoord = ProjectionMatrix[2][3] * vPz + ProjectionMatrix[3][3]; float sample_scale = ProjectionMatrix[0][0] * (0.5 * max_radius / homcoord); float pixel_footprint = sample_scale * float(textureSize(gbuf_header_tx, 0).x); diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_capture_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_capture_frag.glsl index dc5713bf578..7bd3dfa4f67 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_capture_frag.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_capture_frag.glsl @@ -9,9 +9,10 @@ * into other surface shaders. */ -#pragma BLENDER_REQUIRE(draw_view_lib.glsl) #pragma BLENDER_REQUIRE(gpu_shader_math_vector_lib.glsl) #pragma BLENDER_REQUIRE(eevee_gbuffer_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_lib.glsl) #pragma BLENDER_REQUIRE(common_hair_lib.glsl) #pragma BLENDER_REQUIRE(eevee_surf_lib.glsl) #pragma BLENDER_REQUIRE(eevee_nodetree_lib.glsl) @@ -41,7 +42,7 @@ void main() if (capture_info_buf.do_surfel_count) { /* Generate a surfel only once. This check allow cases where no axis is dominant. */ - vec3 vNg = drw_normal_world_to_view(g_data.Ng); + vec3 vNg = normal_world_to_view(g_data.Ng); bool is_surface_view_aligned = dominant_axis(vNg) == 2; if (is_surface_view_aligned) { uint surfel_id = atomicAdd(capture_info_buf.surfel_len, 1u); @@ -52,10 +53,9 @@ void main() surfel_buf[surfel_id].radiance_direct.front.rgb = g_emission; surfel_buf[surfel_id].radiance_direct.front.a = 0.0; /* TODO(fclem): 2nd surface evaluation. */ - surfel_buf[surfel_id].albedo_back = is_double_sided ? albedo : vec3(0); - surfel_buf[surfel_id].radiance_direct.back.rgb = is_double_sided ? g_emission : vec3(0); + surfel_buf[surfel_id].albedo_back = albedo; + surfel_buf[surfel_id].radiance_direct.back.rgb = g_emission; surfel_buf[surfel_id].radiance_direct.back.a = 0.0; - surfel_buf[surfel_id].double_sided = is_double_sided; if (!capture_info_buf.capture_emission) { surfel_buf[surfel_id].radiance_direct.front.rgb = vec3(0.0); diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_deferred_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_deferred_frag.glsl index 39beb6f2cfe..15c9b413523 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_deferred_frag.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_deferred_frag.glsl @@ -9,19 +9,21 @@ * Some render-pass are written during this pass. */ -#pragma BLENDER_REQUIRE(draw_view_lib.glsl) #pragma BLENDER_REQUIRE(eevee_gbuffer_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_lib.glsl) #pragma BLENDER_REQUIRE(common_hair_lib.glsl) #pragma BLENDER_REQUIRE(eevee_ambient_occlusion_lib.glsl) #pragma BLENDER_REQUIRE(eevee_surf_lib.glsl) #pragma BLENDER_REQUIRE(eevee_nodetree_lib.glsl) +#pragma BLENDER_REQUIRE(eevee_renderpass_lib.glsl) #pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl) vec4 closure_to_rgba(Closure cl) { vec4 out_color; out_color.rgb = g_emission; - out_color.a = saturate(1.0 - average(g_transmittance)); + out_color.a = saturate(1.0 - avg(g_transmittance)); /* Reset for the next closure tree. */ closure_weights_reset(); @@ -45,6 +47,9 @@ void main() g_holdout = saturate(g_holdout); + out_transmittance = vec4(1.0 - g_holdout); + float transmittance_mono = saturate(avg(g_transmittance)); + float thickness = nodetree_thickness(); g_diffuse_data.color *= g_diffuse_data.weight; @@ -146,5 +151,5 @@ void main() out_radiance.rgb *= 1.0 - g_holdout; out_transmittance.rgb = g_transmittance; - out_transmittance.a = saturate(average(g_transmittance)); + out_transmittance.a = saturate(avg(g_transmittance)); } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_depth_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_depth_frag.glsl index 9fac616d64d..f262eb72ba4 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_depth_frag.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_depth_frag.glsl @@ -6,7 +6,8 @@ * Depth shader that can stochastically discard transparent pixel. */ -#pragma BLENDER_REQUIRE(draw_view_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_lib.glsl) #pragma BLENDER_REQUIRE(common_hair_lib.glsl) #pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl) #pragma BLENDER_REQUIRE(eevee_nodetree_lib.glsl) @@ -18,7 +19,7 @@ vec4 closure_to_rgba(Closure cl) { vec4 out_color; out_color.rgb = g_emission; - out_color.a = saturate(1.0 - average(g_transmittance)); + out_color.a = saturate(1.0 - avg(g_transmittance)); /* Reset for the next closure tree. */ closure_weights_reset(); @@ -33,26 +34,11 @@ void main() nodetree_surface(); -# ifdef MAT_FORWARD - /* Pre-pass only allows fully opaque areas to cut through all transparent layers. */ - float threshold = 0.0; -# else float noise_offset = sampling_rng_1D_get(SAMPLING_TRANSPARENCY); - float threshold = transparency_hashed_alpha_threshold(1.0, noise_offset, g_data.P); -# endif + float random_threshold = transparency_hashed_alpha_threshold(1.0, noise_offset, g_data.P); - float transparency = average(g_transmittance); - if (transparency > threshold) { - discard; - return; - } -#endif - -#ifdef MAT_CLIP_PLANE - /* Do not use hardware clip planes as they modify the rasterization (some GPUs add vertices). - * This would in turn create a discrepancy between the pre-pass depth and the G-buffer depth - * which exhibits missing pixels data. */ - if (clip_interp.clip_distance > 0.0) { + float transparency = avg(g_transmittance); + if (transparency > random_threshold) { discard; return; } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_forward_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_forward_frag.glsl index 0fdd48b7f71..92ed4ec6bf9 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_forward_frag.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_forward_frag.glsl @@ -8,16 +8,16 @@ * This is used by alpha blended materials and materials using Shader to RGB nodes. */ -#pragma BLENDER_REQUIRE(draw_view_lib.glsl) #pragma BLENDER_REQUIRE(eevee_light_eval_lib.glsl) -#pragma BLENDER_REQUIRE(eevee_lightprobe_eval_lib.glsl) #pragma BLENDER_REQUIRE(eevee_ambient_occlusion_lib.glsl) #pragma BLENDER_REQUIRE(eevee_nodetree_lib.glsl) #pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl) #pragma BLENDER_REQUIRE(eevee_surf_lib.glsl) -#pragma BLENDER_REQUIRE(eevee_subsurface_lib.glsl) +#pragma BLENDER_REQUIRE(eevee_renderpass_lib.glsl) #pragma BLENDER_REQUIRE(eevee_volume_lib.glsl) #pragma BLENDER_REQUIRE(common_hair_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_lib.glsl) vec4 closure_to_rgba(Closure cl) { @@ -26,38 +26,26 @@ vec4 closure_to_rgba(Closure cl) vec3 refraction_light = vec3(0.0); float shadow = 1.0; - float vPz = dot(drw_view_forward(), g_data.P) - dot(drw_view_forward(), drw_view_position()); - vec3 V = drw_world_incident_vector(g_data.P); + float vP_z = dot(cameraForward, g_data.P) - dot(cameraForward, cameraPos); - ClosureLightStack stack; - - ClosureLight cl_diff; - cl_diff.N = g_diffuse_data.N; - cl_diff.ltc_mat = LTC_LAMBERT_MAT; - cl_diff.type = LIGHT_DIFFUSE; - stack.cl[0] = cl_diff; - - ClosureLight cl_refl; - cl_refl.N = g_reflection_data.N; - cl_refl.ltc_mat = LTC_GGX_MAT(dot(g_reflection_data.N, V), g_reflection_data.roughness); - cl_refl.type = LIGHT_SPECULAR; - stack.cl[1] = cl_refl; - - float thickness = 0.01; /* TODO(fclem) thickness. */ - light_eval(stack, g_data.P, g_data.Ng, V, vPz, thickness); - - vec2 noise_probe = interlieved_gradient_noise(gl_FragCoord.xy, vec2(0, 1), vec2(0.0)); - LightProbeSample samp = lightprobe_load(g_data.P, g_data.Ng, V); - - diffuse_light += lightprobe_eval(samp, g_diffuse_data, g_data.P, V, noise_probe); - reflection_light += lightprobe_eval(samp, g_reflection_data, g_data.P, V, noise_probe); + light_eval(g_diffuse_data, + g_reflection_data, + g_data.P, + g_data.Ng, + cameraVec(g_data.P), + vP_z, + 0.01 /* TODO(fclem) thickness. */, + diffuse_light, + reflection_light, + shadow); vec4 out_color; out_color.rgb = g_emission; - out_color.rgb += g_diffuse_data.color * g_diffuse_data.weight * stack.cl[0].light_shadowed; - out_color.rgb += g_reflection_data.color * g_reflection_data.weight * stack.cl[1].light_shadowed; + out_color.rgb += g_diffuse_data.color * g_diffuse_data.weight * diffuse_light; + out_color.rgb += g_reflection_data.color * g_reflection_data.weight * reflection_light; + out_color.rgb += g_refraction_data.color * g_refraction_data.weight * refraction_light; - out_color.a = saturate(1.0 - average(g_transmittance)); + out_color.a = saturate(1.0 - avg(g_transmittance)); /* Reset for the next closure tree. */ closure_weights_reset(); @@ -83,45 +71,23 @@ void main() float thickness = nodetree_thickness(); - float vPz = dot(drw_view_forward(), g_data.P) - dot(drw_view_forward(), drw_view_position()); - vec3 V = drw_world_incident_vector(g_data.P); - - ClosureLightStack stack; - - ClosureLight cl_diff; - cl_diff.N = g_diffuse_data.N; - cl_diff.ltc_mat = LTC_LAMBERT_MAT; - cl_diff.type = LIGHT_DIFFUSE; - stack.cl[0] = cl_diff; - - ClosureLight cl_refl; - cl_refl.N = g_reflection_data.N; - cl_refl.ltc_mat = LTC_GGX_MAT(dot(g_reflection_data.N, V), g_reflection_data.roughness); - cl_refl.type = LIGHT_SPECULAR; - stack.cl[1] = cl_refl; - -#ifdef SSS_TRANSMITTANCE - ClosureLight cl_sss; - cl_sss.N = -g_diffuse_data.N; - cl_sss.ltc_mat = LTC_LAMBERT_MAT; - cl_sss.type = LIGHT_DIFFUSE; - stack.cl[2] = cl_sss; -#endif - - light_eval(stack, g_data.P, g_data.Ng, V, vPz, thickness); - - vec3 diffuse_light = stack.cl[0].light_shadowed; - vec3 reflection_light = stack.cl[1].light_shadowed; + vec3 diffuse_light = vec3(0.0); + vec3 reflection_light = vec3(0.0); vec3 refraction_light = vec3(0.0); -#ifdef SSS_TRANSMITTANCE - diffuse_light += stack.cl[2].light_shadowed; -#endif + float shadow = 1.0; - vec2 noise_probe = interlieved_gradient_noise(gl_FragCoord.xy, vec2(0, 1), vec2(0.0)); - LightProbeSample samp = lightprobe_load(g_data.P, g_data.Ng, V); + float vP_z = dot(cameraForward, g_data.P) - dot(cameraForward, cameraPos); - diffuse_light += lightprobe_eval(samp, g_diffuse_data, g_data.P, V, noise_probe); - reflection_light += lightprobe_eval(samp, g_reflection_data, g_data.P, V, noise_probe); + light_eval(g_diffuse_data, + g_reflection_data, + g_data.P, + g_data.Ng, + cameraVec(g_data.P), + vP_z, + thickness, + diffuse_light, + reflection_light, + shadow); g_diffuse_data.color *= g_diffuse_data.weight; g_reflection_data.color *= g_reflection_data.weight; @@ -155,18 +121,6 @@ void main() cryptomatte_object_buf[resource_id], node_tree.crypto_hash, 0.0); imageStore(rp_cryptomatte_img, out_texel, cryptomatte_output); } - - vec3 radiance_shadowed = stack.cl[0].light_shadowed; - vec3 radiance_unshadowed = stack.cl[0].light_unshadowed; - radiance_shadowed += stack.cl[1].light_shadowed; - radiance_unshadowed += stack.cl[1].light_unshadowed; -# ifdef SSS_TRANSMITTANCE - radiance_shadowed += stack.cl[2].light_shadowed; - radiance_unshadowed += stack.cl[2].light_unshadowed; -# endif - - vec3 shadows = radiance_shadowed / safe_rcp(radiance_unshadowed); - output_renderpass_color(uniform_buf.render_pass.normal_id, vec4(out_normal, 1.0)); output_renderpass_color(uniform_buf.render_pass.position_id, vec4(g_data.P, 1.0)); output_renderpass_color(uniform_buf.render_pass.diffuse_color_id, @@ -175,7 +129,7 @@ void main() output_renderpass_color(uniform_buf.render_pass.specular_color_id, vec4(specular_color, 1.0)); output_renderpass_color(uniform_buf.render_pass.specular_light_id, vec4(specular_light, 1.0)); output_renderpass_color(uniform_buf.render_pass.emission_id, vec4(g_emission, 1.0)); - output_renderpass_value(uniform_buf.render_pass.shadow_id, average(shadows)); + output_renderpass_value(uniform_buf.render_pass.shadow_id, shadow); /** NOTE: AO is done on its own pass. */ #endif @@ -196,5 +150,5 @@ void main() out_radiance.rgb *= 1.0 - g_holdout; out_transmittance.rgb = g_transmittance; - out_transmittance.a = saturate(average(g_transmittance)); + out_transmittance.a = saturate(avg(g_transmittance)); } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_lib.glsl index 30871cd7d05..e31b23aeafa 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_lib.glsl @@ -2,9 +2,7 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ -#pragma BLENDER_REQUIRE(draw_model_lib.glsl) -#pragma BLENDER_REQUIRE(gpu_shader_math_base_lib.glsl) -#pragma BLENDER_REQUIRE(gpu_shader_math_vector_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_lib.glsl) #pragma BLENDER_REQUIRE(gpu_shader_codegen_lib.glsl) #pragma BLENDER_REQUIRE(eevee_nodetree_lib.glsl) #pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl) @@ -12,16 +10,6 @@ #if defined(USE_BARYCENTRICS) && defined(GPU_FRAGMENT_SHADER) && defined(MAT_GEOM_MESH) vec3 barycentric_distances_get() { -# if defined(GPU_METAL) - /* Calculate Barycentric distances from available parameters in Metal. */ - float wp_delta = length(dfdx(interp.P)) + length(dfdy(interp.P)); - float bc_delta = length(dfdx(gpu_BaryCoord)) + length(dfdy(gpu_BaryCoord)); - float rate_of_change = wp_delta / bc_delta; - vec3 dists; - dists.x = rate_of_change * (1.0 - gpu_BaryCoord.x); - dists.y = rate_of_change * (1.0 - gpu_BaryCoord.y); - dists.z = rate_of_change * (1.0 - gpu_BaryCoord.z); -# else /* NOTE: No need to undo perspective divide since it has not been applied. */ vec3 pos0 = (ProjectionMatrixInverse * gpu_position_at_vertex(0)).xyz; vec3 pos1 = (ProjectionMatrixInverse * gpu_position_at_vertex(1)).xyz; @@ -39,8 +27,7 @@ vec3 barycentric_distances_get() dists.y = sqrt(dot(edge10, edge10) - d * d); d = dot(d10, edge21); dists.z = sqrt(dot(edge21, edge21) - d * d); -# endif - return dists; + return dists.xyz; } #endif @@ -54,39 +41,35 @@ void init_globals_mesh() void init_globals_curves() { -#if defined(MAT_GEOM_CURVES) /* Shade as a cylinder. */ - float cos_theta = curve_interp.time_width / curve_interp.thickness; -# if defined(GPU_FRAGMENT_SHADER) + float cos_theta = interp.curves_time_width / interp.curves_thickness; +#if defined(GPU_FRAGMENT_SHADER) && defined(MAT_GEOM_CURVES) if (hairThicknessRes == 1) { -# ifdef EEVEE_UTILITY_TX /* Random cosine normal distribution on the hair surface. */ float noise = utility_tx_fetch(utility_tx, gl_FragCoord.xy, UTIL_BLUE_NOISE_LAYER).x; -# ifdef EEVEE_SAMPLING_DATA +# ifdef EEVEE_SAMPLING_DATA /* Needs to check for SAMPLING_DATA, * otherwise Surfel and World (?!?!) shader validation fails. */ noise = fract(noise + sampling_rng_1D_get(SAMPLING_CURVES_U)); -# endif - cos_theta = noise * 2.0 - 1.0; -# endif - } # endif + cos_theta = noise * 2.0 - 1.0; + } +#endif float sin_theta = sqrt(max(0.0, 1.0 - cos_theta * cos_theta)); - g_data.N = g_data.Ni = normalize(interp.N * sin_theta + curve_interp.binormal * cos_theta); + g_data.N = g_data.Ni = normalize(interp.N * sin_theta + interp.curves_binormal * cos_theta); /* Costly, but follows cycles per pixel tangent space (not following curve shape). */ - vec3 V = drw_world_incident_vector(g_data.P); - g_data.curve_T = -curve_interp.tangent; + vec3 V = cameraVec(g_data.P); + g_data.curve_T = -interp.curves_tangent; g_data.curve_B = cross(V, g_data.curve_T); g_data.curve_N = safe_normalize(cross(g_data.curve_T, g_data.curve_B)); g_data.is_strand = true; - g_data.hair_time = curve_interp.time; - g_data.hair_thickness = curve_interp.thickness; - g_data.hair_strand_id = curve_interp_flat.strand_id; -# if defined(USE_BARYCENTRICS) && defined(GPU_FRAGMENT_SHADER) - g_data.barycentric_coords = hair_resolve_barycentric(curve_interp.barycentric_coords); -# endif + g_data.hair_time = interp.curves_time; + g_data.hair_thickness = interp.curves_thickness; + g_data.hair_strand_id = interp_flat.curves_strand_id; +#if defined(USE_BARYCENTRICS) && defined(GPU_FRAGMENT_SHADER) && defined(MAT_GEOM_CURVES) + g_data.barycentric_coords = hair_resolve_barycentric(interp.barycentric_coords); #endif } @@ -109,7 +92,7 @@ void init_globals() g_data.hair_strand_id = 0; g_data.ray_type = RAY_TYPE_CAMERA; /* TODO */ g_data.ray_depth = 0.0; - g_data.ray_length = distance(g_data.P, drw_view_position()); + g_data.ray_length = distance(g_data.P, cameraPos); g_data.barycentric_coords = vec2(0.0); g_data.barycentric_dists = vec3(0.0); @@ -134,6 +117,13 @@ void init_interface() #ifdef GPU_VERTEX_SHADER interp.P = vec3(0.0); interp.N = vec3(0.0); + interp.barycentric_coords = vec2(0.0); + interp.curves_tangent = vec3(0.0); + interp.curves_binormal = vec3(0.0); + interp.curves_time = 0.0; + interp.curves_time_width = 0.0; + interp.curves_thickness = 0.0; + interp_flat.curves_strand_id = 0; drw_ResourceID_iface.resource_index = resource_id; #endif } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_occupancy_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_occupancy_frag.glsl deleted file mode 100644 index 18e7dac896d..00000000000 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_occupancy_frag.glsl +++ /dev/null @@ -1,75 +0,0 @@ -/* SPDX-FileCopyrightText: 2017-2023 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -/** - * Pre-pass that voxelizes an object on frustum aligned voxels. - * - * There is two method available: - * - * - Fast method: For each fragment we compute the amount of - * froxels center in-front of it. We then convert that - * into occupancy bitmask that we apply to the occupancy - * texture using imageAtomicXor. This flips the bit for each - * surfaces encountered along the camera ray. - * This is straight forward and works well for any manifold - * geometry. - * - * - Accurate method: - * For each fragment we write the fragment depth - * in a list (contained in one array texture). This list - * is then processed by a full-screen pass (see - * `eevee_occupancy_convert_frag.glsl`) that sorts and - * converts all the hits to the occupancy bits. This - * emulate Cycles behavior by considering only back-face - * hits as exit events and front-face hits as entry events. - * The result stores it to the occupancy texture using - * bit-wise OR operation to compose it with other non-hit - * list objects. This also decouple the hit-list evaluation - * complexity from the material evaluation shader. - * - */ - -#pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl) -#pragma BLENDER_REQUIRE(eevee_nodetree_lib.glsl) -#pragma BLENDER_REQUIRE(eevee_surf_lib.glsl) -#pragma BLENDER_REQUIRE(eevee_attributes_lib.glsl) -#pragma BLENDER_REQUIRE(eevee_velocity_lib.glsl) -#pragma BLENDER_REQUIRE(eevee_volume_lib.glsl) -#pragma BLENDER_REQUIRE(eevee_occupancy_lib.glsl) - -vec4 closure_to_rgba(Closure cl) -{ - return vec4(0.0); -} - -void main() -{ - ivec2 texel = ivec2(gl_FragCoord.xy); - float vPz = dot(drw_view_forward(), interp.P) - dot(drw_view_forward(), drw_view_position()); - /* Apply jitter here instead of modifying the projection matrix. - * This is because the depth range and mapping function changes. */ - /* TODO(fclem): Jitter the camera for the other 2 dimension. */ - float jitter = sampling_rng_1D_get(SAMPLING_VOLUME_W) * uniform_buf.volumes.inv_tex_size.z; - float volume_z = view_z_to_volume_z(vPz) - jitter; - - if (use_fast_method) { - OccupancyBits occupancy_bits = occupancy_from_depth(volume_z, uniform_buf.volumes.tex_size.z); - for (int i = 0; i < imageSize(occupancy_img).z; i++) { - /* Negate occupancy bits before XORing so that meshes clipped by the near plane fill the - * space between the inner part of the mesh and the near plane. - * It doesn't change anything for closed meshes. */ - occupancy_bits.bits[i] = ~occupancy_bits.bits[i]; - if (occupancy_bits.bits[i] != 0u) { - imageAtomicXor(occupancy_img, ivec3(texel, i), occupancy_bits.bits[i]); - } - } - } - else { - uint hit_id = imageAtomicAdd(hit_count_img, texel, 1u); - if (hit_id < VOLUME_HIT_DEPTH_MAX) { - float value = gl_FrontFacing ? volume_z : -volume_z; - imageStore(hit_depth_img, ivec3(texel, hit_id), vec4(value)); - } - } -} diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_shadow_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_shadow_frag.glsl index 61429f6da53..a53117501c4 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_shadow_frag.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_shadow_frag.glsl @@ -11,7 +11,8 @@ * the destination texel. */ -#pragma BLENDER_REQUIRE(draw_view_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_lib.glsl) #pragma BLENDER_REQUIRE(eevee_attributes_lib.glsl) #pragma BLENDER_REQUIRE(eevee_surf_lib.glsl) #pragma BLENDER_REQUIRE(eevee_nodetree_lib.glsl) @@ -19,11 +20,6 @@ #pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl) #pragma BLENDER_REQUIRE(eevee_shadow_tilemap_lib.glsl) -vec4 closure_to_rgba(Closure cl) -{ - return vec4(0.0); -} - void main() { #ifdef MAT_TRANSPARENT @@ -34,16 +30,14 @@ void main() float noise_offset = sampling_rng_1D_get(SAMPLING_TRANSPARENCY); float random_threshold = transparency_hashed_alpha_threshold(1.0, noise_offset, g_data.P); - float transparency = average(g_transmittance); + float transparency = avg(g_transmittance); if (transparency > random_threshold) { discard; return; } #endif - float f_depth = gl_FragCoord.z + fwidth(gl_FragCoord.z); - -#ifdef SHADOW_UPDATE_ATOMIC_RASTER +#ifdef USE_ATOMIC ivec2 texel_co = ivec2(gl_FragCoord.xy); /* Using bitwise ops is way faster than integer ops. */ @@ -60,16 +54,9 @@ void main() ivec3 page = ivec3(shadow_page_unpack(page_packed)); ivec3 out_texel = ivec3((page.xy << page_shift) | texel_page, page.z); - - uint u_depth = floatBitsToUint(f_depth); + uint u_depth = floatBitsToUint(gl_FragCoord.z + fwidth(gl_FragCoord.z)); /* Quantization bias. Equivalent to `nextafter()` in C without all the safety. */ u_depth += 2; imageAtomicMin(shadow_atlas_img, out_texel, u_depth); #endif - -#ifdef SHADOW_UPDATE_TBDR - /* Store output depth in tile memory using F32 attachment. NOTE: As depth testing is enabled, - * only the closest fragment will store the result. */ - out_depth = f_depth; -#endif } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_world_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_world_frag.glsl index f8b5e7cd9bf..2a889aef48b 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_world_frag.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_world_frag.glsl @@ -8,16 +8,12 @@ * Outputs shading parameter per pixel using a set of randomized BSDFs. */ -#pragma BLENDER_REQUIRE(draw_view_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_lib.glsl) #pragma BLENDER_REQUIRE(eevee_attributes_lib.glsl) #pragma BLENDER_REQUIRE(eevee_surf_lib.glsl) #pragma BLENDER_REQUIRE(eevee_nodetree_lib.glsl) -#pragma BLENDER_REQUIRE(eevee_colorspace_lib.glsl) - -vec4 closure_to_rgba(Closure cl) -{ - return vec4(0.0); -} +#pragma BLENDER_REQUIRE(eevee_renderpass_lib.glsl) void main() { @@ -26,7 +22,7 @@ void main() init_globals(); /* View position is passed to keep accuracy. */ - g_data.N = drw_normal_view_to_world(drw_view_incident_vector(interp.P)); + g_data.N = normal_view_to_world(viewCameraVec(interp.P)); g_data.Ng = g_data.N; g_data.P = -g_data.N; attrib_load(); @@ -35,13 +31,12 @@ void main() g_holdout = saturate(g_holdout); - out_background.rgb = colorspace_safe_color(g_emission) * (1.0 - g_holdout); - out_background.a = saturate(average(g_transmittance)) * g_holdout; + out_background.rgb = safe_color(g_emission) * (1.0 - g_holdout); + out_background.a = saturate(avg(g_transmittance)) * g_holdout; /* World opacity. */ out_background = mix(vec4(0.0, 0.0, 0.0, 1.0), out_background, world_opacity_fade); -#ifdef MAT_RENDER_PASS_SUPPORT /* Clear Render Buffers. */ ivec2 texel = ivec2(gl_FragCoord.xy); @@ -61,5 +56,4 @@ void main() output_renderpass_value(uniform_buf.render_pass.shadow_id, 1.0); /** NOTE: AO is done on its own pass. */ imageStore(rp_cryptomatte_img, texel, vec4(0.0)); -#endif } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_surfel_light_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_surfel_light_comp.glsl index 1febad8adb5..31c5210d08a 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_surfel_light_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_surfel_light_comp.glsl @@ -8,35 +8,89 @@ #pragma BLENDER_REQUIRE(eevee_light_eval_lib.glsl) +void light_eval_surfel( + ClosureDiffuse diffuse, vec3 P, vec3 Ng, float thickness, inout vec3 out_diffuse) +{ + /* Dummy closure. Not used. */ + ClosureReflection reflection; + reflection.N = vec3(1.0, 0.0, 0.0); + reflection.roughness = 0.0; + vec3 out_specular = vec3(0.0); + /* Dummy ltc mat parameters. Not used since we have no reflections. */ + vec4 ltc_mat_dummy = utility_tx_sample(utility_tx, vec2(0.0), UTIL_LTC_MAT_LAYER); + + vec3 V = Ng; + float vP_z = 0.0; + float out_shadow_unused; + + LIGHT_FOREACH_BEGIN_DIRECTIONAL (light_cull_buf, l_idx) { + light_eval_ex(diffuse, + reflection, + true, + P, + Ng, + V, + vP_z, + thickness, + ltc_mat_dummy, + l_idx, + out_diffuse, + out_specular, + out_shadow_unused); + } + LIGHT_FOREACH_END + + LIGHT_FOREACH_BEGIN_LOCAL_NO_CULL(light_cull_buf, l_idx) + { + light_eval_ex(diffuse, + reflection, + false, + P, + Ng, + V, + vP_z, + thickness, + ltc_mat_dummy, + l_idx, + out_diffuse, + out_specular, + out_shadow_unused); + } + LIGHT_FOREACH_END +} + void main() { int index = int(gl_GlobalInvocationID.x); - if (index >= int(capture_info_buf.surfel_len)) { + if (index >= capture_info_buf.surfel_len) { return; } Surfel surfel = surfel_buf[index]; - ClosureLightStack stack; - stack.cl[0].N = surfel.normal; - stack.cl[0].ltc_mat = LTC_LAMBERT_MAT; - stack.cl[0].type = LIGHT_DIFFUSE; + ClosureDiffuse diffuse_data; + diffuse_data.N = surfel.normal; + /* TODO: These could be saved inside the surfel. */ + diffuse_data.sss_radius = vec3(0.0); + diffuse_data.sss_id = 0u; + float thickness = 0.0; - /* There is no view dependent effect as we evaluate everything using diffuse. */ - vec3 V = surfel.normal; - vec3 Ng = surfel.normal; - vec3 P = surfel.position; - light_eval(stack, P, Ng, V); + vec3 diffuse_light = vec3(0.0); + vec3 reflection_light = vec3(0.0); + + light_eval_surfel(diffuse_data, surfel.position, surfel.normal, thickness, diffuse_light); if (capture_info_buf.capture_indirect) { - surfel_buf[index].radiance_direct.front.rgb += stack.cl[0].light_shadowed * - surfel.albedo_front; + surfel_buf[index].radiance_direct.front.rgb += diffuse_light * surfel.albedo_front; } - stack.cl[0].N = -surfel.normal; - light_eval(stack, P, Ng, V); + diffuse_data.N = -surfel.normal; + diffuse_light = vec3(0.0); + reflection_light = vec3(0.0); + + light_eval_surfel(diffuse_data, surfel.position, -surfel.normal, thickness, diffuse_light); if (capture_info_buf.capture_indirect) { - surfel_buf[index].radiance_direct.back.rgb += stack.cl[0].light_shadowed * surfel.albedo_back; + surfel_buf[index].radiance_direct.back.rgb += diffuse_light * surfel.albedo_back; } } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_surfel_list_build_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_surfel_list_build_comp.glsl index 3b611709b11..e43b79ef03c 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_surfel_list_build_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_surfel_list_build_comp.glsl @@ -32,5 +32,5 @@ void main() surfel_buf[surfel_index].ray_distance = ray_distance; /* NOTE: We only need to init the `list_start_buf` to -1 for the whole list to be valid since * every surfel will load its `next` value from the list head. */ - surfel_buf[surfel_index].next = atomicExchange(list_start_buf[list_index], surfel_index); + // surfel_buf[surfel_index].next = atomicExchange(list_start_buf[list_index], surfel_index); } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_surfel_list_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_surfel_list_lib.glsl index 79eb2318853..f9ee28d5093 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_surfel_list_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_surfel_list_lib.glsl @@ -2,7 +2,7 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ -#pragma BLENDER_REQUIRE(draw_view_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_lib.glsl) #pragma BLENDER_REQUIRE(gpu_shader_math_matrix_lib.glsl) /** @@ -12,9 +12,10 @@ */ int surfel_list_index_get(ivec2 ray_grid_size, vec3 P, out float r_ray_distance) { - vec3 ssP = drw_point_world_to_screen(P); - r_ray_distance = -ssP.z; - ivec2 ray_coord_on_grid = ivec2(ssP.xy * vec2(ray_grid_size)); + vec4 hP = point_world_to_ndc(P); + r_ray_distance = -hP.z; + vec2 ssP = hP.xy * 0.5 + 0.5; + ivec2 ray_coord_on_grid = ivec2(ssP * vec2(ray_grid_size)); ray_coord_on_grid = clamp(ray_coord_on_grid, ivec2(0), ray_grid_size - 1); int list_index = ray_coord_on_grid.y * ray_grid_size.x + ray_coord_on_grid.x; diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_surfel_ray_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_surfel_ray_comp.glsl index 03526c81417..2105f3b075b 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_surfel_ray_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_surfel_ray_comp.glsl @@ -11,11 +11,11 @@ * Dispatched as 1 thread per surfel. */ -#pragma BLENDER_REQUIRE(draw_view_lib.glsl) #pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl) #pragma BLENDER_REQUIRE(gpu_shader_math_base_lib.glsl) -#pragma BLENDER_REQUIRE(gpu_shader_math_vector_lib.glsl) #pragma BLENDER_REQUIRE(eevee_reflection_probe_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_lib.glsl) float avg_albedo(vec3 albedo) { @@ -25,7 +25,7 @@ float avg_albedo(vec3 albedo) void radiance_transfer(inout Surfel surfel, vec3 in_radiance, float in_visibility, vec3 L) { /* Clamped brightness. */ - float luma = max(1e-8, reduce_max(in_radiance)); + float luma = max(1e-8, max_v3(in_radiance)); in_radiance *= 1.0 - max(0.0, luma - capture_info_buf.clamp_indirect) / luma; float NL = dot(surfel.normal, L); @@ -88,10 +88,7 @@ void radiance_transfer_world(inout Surfel receiver, vec3 L) float visibility = 0.0; if (capture_info_buf.capture_world_indirect) { - ReflectionProbeAtlasCoordinate atlas_coord = reinterpret_as_atlas_coord( - capture_info_buf.world_atlas_coord); - - radiance = reflection_probes_sample(L, 0.0, atlas_coord).rgb; + radiance = reflection_probes_world_sample(L, 0.0).rgb; } if (capture_info_buf.capture_visibility_indirect) { @@ -110,7 +107,7 @@ void main() Surfel surfel = surfel_buf[surfel_index]; - vec3 sky_L = drw_world_incident_vector(surfel.position); + vec3 sky_L = cameraVec(surfel.position); if (surfel.next > -1) { Surfel surfel_next = surfel_buf[surfel.next]; diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_thickness_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_thickness_lib.glsl deleted file mode 100644 index d5cf1f818b7..00000000000 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_thickness_lib.glsl +++ /dev/null @@ -1,62 +0,0 @@ -/* SPDX-FileCopyrightText: 2023 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -/** - * Various utilities related to object subsurface thickness approximation. - * - * Required resources: - * - atlas_tx - * - tilemaps_tx - */ - -#pragma BLENDER_REQUIRE(eevee_shadow_lib.glsl) - -void thickness_from_shadow_single( - uint l_idx, const bool is_directional, vec3 P, vec3 Ng, inout vec2 thickness) -{ - LightData light = light_buf[l_idx]; - - if (light.tilemap_index == LIGHT_NO_SHADOW) { - return; - } - - LightVector lv = light_vector_get(light, is_directional, P); - /* Note that we reverse the surface normal to reject surfaces facing the light. */ - float attenuation = light_attenuation_surface(light, is_directional, Ng, true, lv); - if ((attenuation < LIGHT_ATTENUATION_THRESHOLD)) { - return; - } - /* Weight result by facing ratio to avoid harsh transitions. */ - float weight = saturate(dot(lv.L, -Ng)); - ShadowEvalResult result = shadow_sample( - is_directional, shadow_atlas_tx, shadow_tilemaps_tx, light, P); - /* Flatten the accumulation to avoid weighting the outliers too much. */ - thickness += vec2(safe_sqrt(result.occluder_distance), 1.0) * weight; -} - -#define THICKNESS_NO_VALUE 1.0e6 -/** - * Return the apparent thickness of an object behind surface considering all shadow maps - * available. If no shadow-map has a record of the other side of the surface, this function - * returns THICKNESS_NO_VALUE. - */ -float thickness_from_shadow(vec3 P, vec3 Ng, float vPz) -{ - /* Bias surface inward to avoid shadow map aliasing. */ - P += -Ng * uniform_buf.shadow.normal_bias; - - vec2 thickness = vec2(0.0); - - LIGHT_FOREACH_BEGIN_DIRECTIONAL (light_cull_buf, l_idx) { - thickness_from_shadow_single(l_idx, true, P, Ng, thickness); - } - LIGHT_FOREACH_END - - LIGHT_FOREACH_BEGIN_LOCAL (light_cull_buf, light_zbin_buf, light_tile_buf, PIXEL, vPz, l_idx) { - thickness_from_shadow_single(l_idx, false, P, Ng, thickness); - } - LIGHT_FOREACH_END - - return (thickness.y > 0.0) ? square(thickness.x / thickness.y) : THICKNESS_NO_VALUE; -} diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_velocity_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_velocity_lib.glsl index 07f5cf7381b..cce6b35a08c 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_velocity_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_velocity_lib.glsl @@ -2,8 +2,7 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ -#pragma BLENDER_REQUIRE(gpu_shader_math_matrix_lib.glsl) -#pragma BLENDER_REQUIRE(draw_view_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_lib.glsl) #pragma BLENDER_REQUIRE(eevee_camera_lib.glsl) vec4 velocity_pack(vec4 data) @@ -72,12 +71,12 @@ vec4 velocity_resolve(vec4 vector, vec2 uv, float depth) bool is_background = (depth == 1.0); if (is_background) { /* NOTE: Use viewCameraVec to avoid imprecision if camera is far from origin. */ - vec3 vV = drw_view_incident_vector(drw_point_screen_to_view(vec3(uv, 1.0))); + vec3 vV = viewCameraVec(get_view_space_from_depth(uv, 1.0)); return velocity_background(vV); } else { /* Static geometry. No translation in world space. */ - vec3 P = drw_point_screen_to_world(vec3(uv, depth)); + vec3 P = get_world_space_from_depth(uv, depth); return velocity_surface(P, P, P); } } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_volume_integration_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_volume_integration_comp.glsl index 2ef8c94e1da..9ab19fdce28 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_volume_integration_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_volume_integration_comp.glsl @@ -8,7 +8,6 @@ /* Step 3 : Integrate for each froxel the final amount of light * scattered back to the viewer and the amount of transmittance. */ -#pragma BLENDER_REQUIRE(draw_view_lib.glsl) #pragma BLENDER_REQUIRE(eevee_volume_lib.glsl) void main() @@ -26,8 +25,8 @@ void main() /* Compute view ray. */ vec2 uvs = (vec2(texel) + vec2(0.5)) / vec2(tex_size.xy); - vec3 ss_cell = volume_to_screen(vec3(uvs, 1e-5)); - vec3 view_cell = drw_point_screen_to_view(ss_cell); + vec3 ndc_cell = volume_to_ndc(vec3(uvs, 1e-5)); + vec3 view_cell = get_view_space_from_depth(ndc_cell.xy, ndc_cell.z); float prev_ray_len; float orig_ray_len; diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_volume_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_volume_lib.glsl index 04731eaac80..3349d84af51 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_volume_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_volume_lib.glsl @@ -7,7 +7,7 @@ * - uniform_buf.volumes */ -#pragma BLENDER_REQUIRE(draw_view_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_lib.glsl) #pragma BLENDER_REQUIRE(eevee_light_lib.glsl) #pragma BLENDER_REQUIRE(eevee_shadow_lib.glsl) @@ -35,23 +35,23 @@ float view_z_to_volume_z(float depth) return view_z_to_volume_z(near, far, distribution, is_persp, depth); } -/* Volume texture normalized coordinates to screen UVs (special range [0, 1]). */ -vec3 volume_to_screen(vec3 coord) +/* Volume texture normalized coordinates to NDC (special range [0, 1]). */ +vec3 volume_to_ndc(vec3 coord) { coord.z = volume_z_to_view_z(coord.z); - coord.z = drw_depth_view_to_screen(coord.z); + coord.z = get_depth_from_view_z(coord.z); coord.xy /= uniform_buf.volumes.coord_scale; return coord; } -vec3 screen_to_volume(vec3 coord) +vec3 ndc_to_volume(vec3 coord) { float near = uniform_buf.volumes.depth_near; float far = uniform_buf.volumes.depth_far; float distribution = uniform_buf.volumes.depth_distribution; vec2 coord_scale = uniform_buf.volumes.coord_scale; /* Implemented in eevee_shader_shared.cc */ - return screen_to_volume(ProjectionMatrix, near, far, distribution, coord_scale, coord); + return ndc_to_volume(ProjectionMatrix, near, far, distribution, coord_scale, coord); } float volume_phase_function_isotropic() @@ -59,29 +59,30 @@ float volume_phase_function_isotropic() return 1.0 / (4.0 * M_PI); } -float volume_phase_function(vec3 V, vec3 L, float g) +float volume_phase_function(vec3 v, vec3 l, float g) { /* Henyey-Greenstein. */ - float cos_theta = dot(V, L); + float cos_theta = dot(v, l); g = clamp(g, -1.0 + 1e-3, 1.0 - 1e-3); float sqr_g = g * g; return (1 - sqr_g) / max(1e-8, 4.0 * M_PI * pow(1 + sqr_g - 2 * g * cos_theta, 3.0 / 2.0)); } -vec3 volume_light(LightData light, const bool is_directional, LightVector lv) +vec3 volume_light(LightData ld, vec3 L, float l_dist) { float power = 1.0; - if (!is_directional) { - float volume_radius_squared = light.radius_squared; + if (ld.type != LIGHT_SUN) { + + float volume_radius_squared = ld.radius_squared; float light_clamp = uniform_buf.volumes.light_clamp; if (light_clamp != 0.0) { /* 0.0 light clamp means it's disabled. */ - float max_power = reduce_max(light.color) * light.power[LIGHT_VOLUME]; + float max_power = max_v3(ld.color) * ld.volume_power; if (max_power > 0.0) { /* The limit of the power attenuation function when the distance to the light goes to 0 is * `2 / r^2` where r is the light radius. We need to find the right radius that emits at * most the volume light upper bound. Inverting the function we get: */ - float min_radius_squared = 1.0 / (0.5 * light_clamp / max_power); + float min_radius_squared = 1.0f / (0.5f * light_clamp / max_power); /* Square it here to avoid a multiplication inside the shader. */ volume_radius_squared = max(volume_radius_squared, min_radius_squared); } @@ -92,56 +93,60 @@ vec3 volume_light(LightData light, const bool is_directional, LightVector lv) * http://www.cemyuksel.com/research/pointlightattenuation/pointlightattenuation.pdf * http://www.cemyuksel.com/research/pointlightattenuation/ */ - float d = lv.dist; - float d_sqr = square(d); + float d = l_dist; + float d_sqr = sqr(d); float r_sqr = volume_radius_squared; /* Using reformulation that has better numerical precision. */ power = 2.0 / (d_sqr + r_sqr + d * sqrt(d_sqr + r_sqr)); - if (light.type == LIGHT_RECT || light.type == LIGHT_ELLIPSE) { + if (ld.type == LIGHT_RECT || ld.type == LIGHT_ELLIPSE) { /* Modulate by light plane orientation / solid angle. */ - power *= saturate(dot(light._back, lv.L)); + power *= saturate(dot(ld._back, L)); } } - return light.color * light.power[LIGHT_VOLUME] * power; + return ld.color * ld.volume_power * power; } #define VOLUMETRIC_SHADOW_MAX_STEP 128.0 -vec3 volume_shadow( - LightData ld, const bool is_directional, vec3 P, LightVector lv, sampler3D extinction_tx) +vec3 volume_shadow(LightData ld, vec3 ray_wpos, vec3 L, float l_dist, sampler3D extinction_tx) { #if defined(VOLUME_SHADOW) if (uniform_buf.volumes.shadow_steps == 0) { return vec3(1.0); } + vec4 l_vector = vec4(L * l_dist, l_dist); + /* Heterogeneous volume shadows. */ - float dd = lv.dist / uniform_buf.volumes.shadow_steps; - vec3 L = lv.L * lv.dist / uniform_buf.volumes.shadow_steps; + float dd = l_vector.w / uniform_buf.volumes.shadow_steps; + L = l_vector.xyz / uniform_buf.volumes.shadow_steps; - if (is_directional) { + if (is_sun_light(ld.type)) { /* For sun light we scan the whole frustum. So we need to get the correct endpoints. */ - vec3 ndcP = drw_point_world_to_ndc(P); - vec3 ndcL = drw_point_world_to_ndc(P + lv.L * lv.dist) - ndcP; + vec3 ndcP = project_point(ProjectionMatrix, transform_point(ViewMatrix, ray_wpos)); + vec3 ndcL = project_point(ProjectionMatrix, + transform_point(ViewMatrix, ray_wpos + l_vector.xyz)) - + ndcP; - vec3 ndc_frustum_isect = ndcP + ndcL * line_unit_box_intersect_dist_safe(ndcP, ndcL); + vec3 frustum_isect = ndcP + ndcL * line_unit_box_intersect_dist_safe(ndcP, ndcL); - L = drw_point_ndc_to_world(ndc_frustum_isect) - P; + vec4 L_hom = ViewMatrixInverse * (ProjectionMatrixInverse * vec4(frustum_isect, 1.0)); + L = (L_hom.xyz / L_hom.w) - ray_wpos; L /= uniform_buf.volumes.shadow_steps; dd = length(L); } /* TODO use shadow maps instead. */ vec3 shadow = vec3(1.0); - for (float t = 1.0; t < VOLUMETRIC_SHADOW_MAX_STEP && t <= uniform_buf.volumes.shadow_steps; - t += 1.0) + for (float s = 1.0; s < VOLUMETRIC_SHADOW_MAX_STEP && s <= uniform_buf.volumes.shadow_steps; + s += 1.0) { - vec3 pos = P + L * t; + vec3 pos = ray_wpos + L * s; - vec3 ndc = drw_point_world_to_ndc(pos); - vec3 volume_co = screen_to_volume(drw_ndc_to_screen(ndc)); + vec3 ndc = project_point(ProjectionMatrix, transform_point(ViewMatrix, pos)); + vec3 volume_co = ndc_to_volume(ndc * 0.5 + 0.5); /* Let the texture be clamped to edge. This reduce visual glitches. */ vec3 s_extinction = texture(extinction_tx, volume_co).rgb; @@ -170,7 +175,7 @@ struct VolumeResolveSample { VolumeResolveSample volume_resolve(vec3 ndc_P, sampler3D transmittance_tx, sampler3D scattering_tx) { - vec3 coord = screen_to_volume(ndc_P); + vec3 coord = ndc_to_volume(ndc_P); VolumeResolveSample volume; volume.scattering = texture(scattering_tx, coord).rgb; diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_volume_material_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_volume_material_comp.glsl index 7906fe384cc..2fd291026ef 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_volume_material_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_volume_material_comp.glsl @@ -5,11 +5,12 @@ #pragma BLENDER_REQUIRE(eevee_volume_lib.glsl) /* Needed includes for shader nodes. */ +#pragma BLENDER_REQUIRE(common_math_lib.glsl) +#pragma BLENDER_REQUIRE(common_view_lib.glsl) #pragma BLENDER_REQUIRE(common_attribute_lib.glsl) #pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl) #pragma BLENDER_REQUIRE(eevee_attributes_lib.glsl) #pragma BLENDER_REQUIRE(eevee_nodetree_lib.glsl) -#pragma BLENDER_REQUIRE(eevee_occupancy_lib.glsl) /* Based on Frosbite Unified Volumetric. * https://www.ea.com/frostbite/news/physically-based-unified-volumetric-rendering-in-frostbite */ @@ -30,7 +31,7 @@ GlobalData init_globals(vec3 wP) surf.barycentric_dists = vec3(0.0); surf.ray_type = RAY_TYPE_CAMERA; surf.ray_depth = 0.0; - surf.ray_length = distance(surf.P, drw_view_position()); + surf.ray_length = distance(surf.P, cameraPos); return surf; } @@ -50,23 +51,8 @@ void main() return; } -#ifdef MAT_GEOM_VOLUME_OBJECT - /** Check occupancy map. Discard thread if froxel is empty. */ - /* Shift for 32bits per layer. Avoid integer modulo and division. */ - const int shift = 5; - const int mask = int(~(0xFFFFFFFFu << 5u)); - /* Divide by 32. */ - int occupancy_layer = froxel.z >> shift; - /* Modulo 32. */ - uint occupancy_shift = froxel.z & mask; - uint occupancy_bits = imageLoad(occupancy_img, ivec3(froxel.xy, occupancy_layer)).r; - if (((occupancy_bits >> occupancy_shift) & 1u) == 0u) { - return; - } -#endif - vec3 jitter = sampling_rng_3D_get(SAMPLING_VOLUME_U); - vec3 ndc_cell = volume_to_screen((vec3(froxel) + jitter) * uniform_buf.volumes.inv_tex_size); + vec3 ndc_cell = volume_to_ndc((vec3(froxel) + jitter) * uniform_buf.volumes.inv_tex_size); vec3 vP = get_view_space_from_depth(ndc_cell.xy, ndc_cell.z); vec3 wP = point_view_to_world(vP); diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_volume_resolve_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_volume_resolve_frag.glsl index 16e588c6f8c..a127bc2bd94 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_volume_resolve_frag.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_volume_resolve_frag.glsl @@ -18,7 +18,7 @@ void main() vec3(uvs, scene_depth), volume_transmittance_tx, volume_scattering_tx); out_radiance = vec4(vol.scattering, 0.0); - out_transmittance = vec4(vol.transmittance, saturate(average(vol.transmittance))); + out_transmittance = vec4(vol.transmittance, saturate(avg(vol.transmittance))); if (uniform_buf.render_pass.volume_light_id >= 0) { imageStore(rp_color_img, diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_volume_scatter_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_volume_scatter_comp.glsl index 9d5e0a7538b..e75dff15897 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_volume_scatter_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_volume_scatter_comp.glsl @@ -9,6 +9,7 @@ * Also do the temporal reprojection to fight aliasing artifacts. */ #pragma BLENDER_REQUIRE(gpu_shader_math_vector_lib.glsl) +#pragma BLENDER_REQUIRE(common_math_lib.glsl) /* Included here to avoid requiring lightprobe resources for all volume lib users. */ #pragma BLENDER_REQUIRE(eevee_lightprobe_eval_lib.glsl) @@ -25,30 +26,27 @@ vec3 volume_scatter_light_eval( { LightData light = light_buf[l_idx]; - if (light.power[LIGHT_VOLUME] == 0.0) { + if (light.volume_power == 0.0) { return vec3(0); } - LightVector lv = light_shape_vector_get(light, is_directional, P); + vec3 L; + float l_dist; + light_shape_vector_get(light, P, L, l_dist); - float attenuation = light_attenuation_volume(light, is_directional, lv); - if (attenuation < LIGHT_ATTENUATION_THRESHOLD) { + float visibility = light_attenuation(light, L, l_dist); + if (light.tilemap_index != LIGHT_NO_SHADOW && (visibility > 0.0)) { + ShadowEvalResult samp = shadow_sample( + is_directional, shadow_atlas_tx, shadow_tilemaps_tx, light, P); + visibility *= samp.surface_light_visibilty; + } + + if (visibility < 1e-4) { return vec3(0); } - float visibility = attenuation; - if (light.tilemap_index != LIGHT_NO_SHADOW) { - visibility *= shadow_sample(is_directional, shadow_atlas_tx, shadow_tilemaps_tx, light, P) - .light_visibilty; - } - - if (visibility < LIGHT_ATTENUATION_THRESHOLD) { - return vec3(0); - } - - vec3 Li = volume_light(light, is_directional, lv) * - volume_shadow(light, is_directional, P, lv, extinction_tx); - return Li * visibility * volume_phase_function(-V, lv.L, s_anisotropy); + vec3 Li = volume_light(light, L, l_dist) * volume_shadow(light, P, L, l_dist, extinction_tx); + return Li * visibility * volume_phase_function(-V, L, s_anisotropy); } #endif @@ -67,11 +65,10 @@ void main() vec3 s_scattering = imageLoad(in_scattering_img, froxel).rgb; vec3 jitter = sampling_rng_3D_get(SAMPLING_VOLUME_U); - vec3 volume_screen = volume_to_screen((vec3(froxel) + jitter) * - uniform_buf.volumes.inv_tex_size); - vec3 vP = drw_point_screen_to_view(volume_screen); - vec3 P = drw_point_view_to_world(vP); - vec3 V = drw_world_incident_vector(P); + vec3 volume_ndc = volume_to_ndc((vec3(froxel) + jitter) * uniform_buf.volumes.inv_tex_size); + vec3 vP = get_view_space_from_depth(volume_ndc.xy, volume_ndc.z); + vec3 P = point_view_to_world(vP); + vec3 V = cameraVec(P); vec2 phase = imageLoad(in_phase_img, froxel).rg; /* Divide by phase total weight, to compute the mean anisotropy. */ diff --git a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_deferred_info.hh b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_deferred_info.hh index 41906b819d9..0056a7515ff 100644 --- a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_deferred_info.hh +++ b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_deferred_info.hh @@ -9,13 +9,11 @@ image(slot, format, Qualifier::WRITE, ImageType::FLOAT_2D, name, Frequency::PASS) #define image_in(slot, format, name) \ image(slot, format, Qualifier::READ, ImageType::FLOAT_2D, name, Frequency::PASS) -#define image_array_out(slot, qualifier, format, name) \ - image(slot, format, qualifier, ImageType::FLOAT_2D_ARRAY, name, Frequency::PASS) GPU_SHADER_CREATE_INFO(eevee_gbuffer_data) - .sampler(8, ImageType::UINT_2D, "gbuf_header_tx") - .sampler(9, ImageType::FLOAT_2D_ARRAY, "gbuf_closure_tx") - .sampler(10, ImageType::FLOAT_2D_ARRAY, "gbuf_color_tx"); + .sampler(7, ImageType::UINT_2D, "gbuf_header_tx") + .sampler(8, ImageType::FLOAT_2D_ARRAY, "gbuf_closure_tx") + .sampler(9, ImageType::FLOAT_2D_ARRAY, "gbuf_color_tx"); GPU_SHADER_CREATE_INFO(eevee_deferred_light) .fragment_source("eevee_deferred_light_frag.glsl") @@ -27,7 +25,6 @@ GPU_SHADER_CREATE_INFO(eevee_deferred_light) .image_out(3, GPU_RGBA16F, "direct_reflect_img") .image_out(4, GPU_RGBA16F, "direct_refract_img") .define("SSS_TRANSMITTANCE") - .define("LIGHT_CLOSURE_EVAL_COUNT", "3") .additional_info("eevee_shared", "eevee_gbuffer_data", "eevee_utility_texture", @@ -64,27 +61,6 @@ GPU_SHADER_CREATE_INFO(eevee_deferred_capture_eval) /* Inputs. */ .fragment_out(0, Type::VEC4, "out_radiance") .define("SSS_TRANSMITTANCE") - .additional_info("eevee_shared", - "eevee_gbuffer_data", - "eevee_utility_texture", - "eevee_sampling_data", - "eevee_light_data", - "eevee_shadow_data", - "eevee_hiz_data", - "eevee_volume_probe_data", - "draw_view", - "draw_fullscreen") - .fragment_source("eevee_deferred_capture_frag.glsl") - .do_static_compilation(true); - -GPU_SHADER_CREATE_INFO(eevee_deferred_planar_eval) - /* Early fragment test is needed to avoid processing fragments without correct GBuffer data. */ - .early_fragment_test(true) - /* Inputs. */ - .fragment_out(0, Type::VEC4, "out_radiance") - .define("REFLECTION_PROBE") - .define("SSS_TRANSMITTANCE") - .define("LIGHT_CLOSURE_EVAL_COUNT", "2") .additional_info("eevee_shared", "eevee_gbuffer_data", "eevee_utility_texture", @@ -95,7 +71,7 @@ GPU_SHADER_CREATE_INFO(eevee_deferred_planar_eval) "eevee_hiz_data", "draw_view", "draw_fullscreen") - .fragment_source("eevee_deferred_planar_frag.glsl") + .fragment_source("eevee_deferred_capture_frag.glsl") .do_static_compilation(true); #undef image_out diff --git a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_hiz_info.hh b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_hiz_info.hh index ffe0aa3d715..3ea9474777e 100644 --- a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_hiz_info.hh +++ b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_hiz_info.hh @@ -21,16 +21,10 @@ GPU_SHADER_CREATE_INFO(eevee_hiz_update) .image(4, GPU_R32F, Qualifier::WRITE, ImageType::FLOAT_2D, "out_mip_4") .image(5, GPU_R32F, Qualifier::READ_WRITE, ImageType::FLOAT_2D, "out_mip_5") .image(6, GPU_R32F, Qualifier::WRITE, ImageType::FLOAT_2D, "out_mip_6") + .image(7, GPU_R32F, Qualifier::WRITE, ImageType::FLOAT_2D, "out_mip_7") .push_constant(Type::BOOL, "update_mip_0") .compute_source("eevee_hiz_update_comp.glsl"); -GPU_SHADER_CREATE_INFO(eevee_hiz_update_layer) - .do_static_compilation(true) - .define("HIZ_LAYER") - .sampler(1, ImageType::DEPTH_2D_ARRAY, "depth_layered_tx") - .push_constant(Type::INT, "layer_id") - .additional_info("eevee_hiz_update"); - GPU_SHADER_CREATE_INFO(eevee_hiz_debug) .do_static_compilation(true) .fragment_out(0, Type::VEC4, "out_debug_color_add", DualBlend::SRC_0) diff --git a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_irradiance_cache_info.hh b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_irradiance_cache_info.hh index c7581ec56d9..660665291cb 100644 --- a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_irradiance_cache_info.hh +++ b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_irradiance_cache_info.hh @@ -73,11 +73,9 @@ GPU_SHADER_CREATE_INFO(eevee_surfel_common) GPU_SHADER_CREATE_INFO(eevee_surfel_light) .define("SURFEL_LIGHT") - .define("LIGHT_ITER_FORCE_NO_CULLING") .local_group_size(SURFEL_GROUP_SIZE) .additional_info("eevee_shared", "draw_view", - "eevee_global_ubo", "eevee_utility_texture", "eevee_surfel_common", "eevee_light_data", @@ -197,7 +195,7 @@ GPU_SHADER_CREATE_INFO(eevee_lightprobe_irradiance_load) .compute_source("eevee_lightprobe_irradiance_load_comp.glsl") .do_static_compilation(true); -GPU_SHADER_CREATE_INFO(eevee_volume_probe_data) +GPU_SHADER_CREATE_INFO(eevee_lightprobe_data) .uniform_buf(IRRADIANCE_GRID_BUF_SLOT, "IrradianceGridData", "grids_infos_buf[IRRADIANCE_GRID_MAX]") @@ -206,13 +204,4 @@ GPU_SHADER_CREATE_INFO(eevee_volume_probe_data) .storage_buf(IRRADIANCE_BRICK_BUF_SLOT, Qualifier::READ, "uint", "bricks_infos_buf[]") .sampler(IRRADIANCE_ATLAS_TEX_SLOT, ImageType::FLOAT_3D, "irradiance_atlas_tx"); -GPU_SHADER_CREATE_INFO(eevee_lightprobe_data) - .additional_info("eevee_reflection_probe_data", "eevee_volume_probe_data"); - -GPU_SHADER_CREATE_INFO(eevee_lightprobe_planar_data) - .define("REFLECTION_PROBE") - .uniform_buf(PLANAR_PROBE_BUF_SLOT, "ProbePlanarData", "probe_planar_buf[PLANAR_PROBES_MAX]") - .sampler(PLANAR_PROBE_RADIANCE_TEX_SLOT, ImageType::FLOAT_2D_ARRAY, "planar_radiance_tx") - .sampler(PLANAR_PROBE_DEPTH_TEX_SLOT, ImageType::DEPTH_2D_ARRAY, "planar_depth_tx"); - /** \} */ diff --git a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh index 5742e70bbde..439b554b78a 100644 --- a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh +++ b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh @@ -26,34 +26,18 @@ GPU_SHADER_CREATE_INFO(eevee_utility_texture) .define("EEVEE_UTILITY_TX") .sampler(RBUFS_UTILITY_TEX_SLOT, ImageType::FLOAT_2D_ARRAY, "utility_tx"); -GPU_SHADER_INTERFACE_INFO(eevee_clip_plane_iface, "clip_interp") - .smooth(Type::FLOAT, "clip_distance"); - -GPU_SHADER_CREATE_INFO(eevee_clip_plane) - .vertex_out(eevee_clip_plane_iface) - .uniform_buf(CLIP_PLANE_BUF, "ClipPlaneData", "clip_plane") - .define("MAT_CLIP_PLANE"); - /** \} */ /* -------------------------------------------------------------------- */ /** \name Surface Mesh Type * \{ */ -/* Common interface */ -GPU_SHADER_INTERFACE_INFO(eevee_surf_iface, "interp") - /* World Position. */ - .smooth(Type::VEC3, "P") - /* World Normal. */ - .smooth(Type::VEC3, "N"); - GPU_SHADER_CREATE_INFO(eevee_geom_mesh) .additional_info("eevee_shared") .define("MAT_GEOM_MESH") .vertex_in(0, Type::VEC3, "pos") .vertex_in(1, Type::VEC3, "nor") .vertex_source("eevee_geom_mesh_vert.glsl") - .vertex_out(eevee_surf_iface) .additional_info("draw_modelmat_new", "draw_resource_id_varying", "draw_view"); GPU_SHADER_INTERFACE_INFO(eevee_surf_point_cloud_iface, "point_cloud_interp") @@ -66,7 +50,6 @@ GPU_SHADER_CREATE_INFO(eevee_geom_point_cloud) .additional_info("eevee_shared") .define("MAT_GEOM_POINT_CLOUD") .vertex_source("eevee_geom_point_cloud_vert.glsl") - .vertex_out(eevee_surf_iface) .vertex_out(eevee_surf_point_cloud_iface) .vertex_out(eevee_surf_point_cloud_flat_iface) /* TODO(Miguel Pozo): Remove once we get rid of old EEVEE. */ @@ -78,41 +61,16 @@ GPU_SHADER_CREATE_INFO(eevee_geom_point_cloud) "draw_resource_id_varying", "draw_view"); -GPU_SHADER_CREATE_INFO(eevee_geom_volume) - .additional_info("eevee_shared") - .define("MAT_GEOM_VOLUME") - .vertex_in(0, Type::VEC3, "pos") - .vertex_out(eevee_surf_iface) - .vertex_source("eevee_geom_volume_vert.glsl") - .additional_info("draw_modelmat_new", - "draw_object_infos_new", - "draw_resource_id_varying", - "draw_view"); - GPU_SHADER_CREATE_INFO(eevee_geom_gpencil) .additional_info("eevee_shared") .define("MAT_GEOM_GPENCIL") .vertex_source("eevee_geom_gpencil_vert.glsl") - .vertex_out(eevee_surf_iface) .additional_info("draw_gpencil_new", "draw_resource_id_varying", "draw_resource_id_new"); -GPU_SHADER_INTERFACE_INFO(eevee_surf_curve_iface, "curve_interp") - .smooth(Type::VEC2, "barycentric_coords") - .smooth(Type::VEC3, "tangent") - .smooth(Type::VEC3, "binormal") - .smooth(Type::FLOAT, "time") - .smooth(Type::FLOAT, "time_width") - .smooth(Type::FLOAT, "thickness"); -GPU_SHADER_INTERFACE_INFO(eevee_surf_curve_flat_iface, "curve_interp_flat") - .flat(Type::INT, "strand_id"); - GPU_SHADER_CREATE_INFO(eevee_geom_curves) .additional_info("eevee_shared") .define("MAT_GEOM_CURVES") .vertex_source("eevee_geom_curves_vert.glsl") - .vertex_out(eevee_surf_iface) - .vertex_out(eevee_surf_curve_iface) - .vertex_out(eevee_surf_curve_flat_iface) .additional_info("draw_modelmat_new", "draw_resource_id_varying", "draw_view", @@ -124,7 +82,6 @@ GPU_SHADER_CREATE_INFO(eevee_geom_world) .define("MAT_GEOM_WORLD") .builtins(BuiltinBits::VERTEX_ID) .vertex_source("eevee_geom_world_vert.glsl") - .vertex_out(eevee_surf_iface) .additional_info("draw_modelmat_new", "draw_resource_id_varying", "draw_view"); /** \} */ @@ -133,6 +90,18 @@ GPU_SHADER_CREATE_INFO(eevee_geom_world) /** \name Surface * \{ */ +GPU_SHADER_INTERFACE_INFO(eevee_surf_iface, "interp") + .smooth(Type::VEC3, "P") + .smooth(Type::VEC3, "N") + .smooth(Type::VEC2, "barycentric_coords") + .smooth(Type::VEC3, "curves_tangent") + .smooth(Type::VEC3, "curves_binormal") + .smooth(Type::FLOAT, "curves_time") + .smooth(Type::FLOAT, "curves_time_width") + .smooth(Type::FLOAT, "curves_thickness"); +GPU_SHADER_INTERFACE_INFO(eevee_surf_flat_iface, "interp_flat") + .flat(Type::INT, "curves_strand_id"); + #define image_out(slot, qualifier, format, name) \ image(slot, format, qualifier, ImageType::FLOAT_2D, name, Frequency::PASS) #define image_array_out(slot, qualifier, format, name) \ @@ -149,6 +118,8 @@ GPU_SHADER_CREATE_INFO(eevee_cryptomatte_out) .image_out(RBUFS_CRYPTOMATTE_SLOT, Qualifier::WRITE, GPU_RGBA32F, "rp_cryptomatte_img"); GPU_SHADER_CREATE_INFO(eevee_surf_deferred) + .vertex_out(eevee_surf_iface) + .vertex_out(eevee_surf_flat_iface) /* NOTE: This removes the possibility of using gl_FragDepth. */ .early_fragment_test(true) /* Direct output. (Emissive, Holdout) */ @@ -162,23 +133,23 @@ GPU_SHADER_CREATE_INFO(eevee_surf_deferred) .fragment_source("eevee_surf_deferred_frag.glsl") .additional_info("eevee_global_ubo", "eevee_utility_texture", - /* Added at runtime because of test shaders not having `node_tree`. */ - // "eevee_render_pass_out", - // "eevee_cryptomatte_out", "eevee_sampling_data", - "eevee_hiz_data"); + "eevee_hiz_data", + /* Added at runtime because of test shaders not having `node_tree`. */ + // "eevee_render_pass_out", + "eevee_cryptomatte_out"); GPU_SHADER_CREATE_INFO(eevee_surf_forward) + .vertex_out(eevee_surf_iface) + .vertex_out(eevee_surf_flat_iface) /* Early fragment test is needed for render passes support for forward surfaces. */ /* NOTE: This removes the possibility of using gl_FragDepth. */ .early_fragment_test(true) .fragment_out(0, Type::VEC4, "out_radiance", DualBlend::SRC_0) .fragment_out(0, Type::VEC4, "out_transmittance", DualBlend::SRC_1) .fragment_source("eevee_surf_forward_frag.glsl") - .define("LIGHT_CLOSURE_EVAL_COUNT", "3") .additional_info("eevee_global_ubo", "eevee_light_data", - "eevee_lightprobe_data", "eevee_utility_texture", "eevee_sampling_data", "eevee_shadow_data", @@ -190,61 +161,57 @@ GPU_SHADER_CREATE_INFO(eevee_surf_forward) ); GPU_SHADER_CREATE_INFO(eevee_surf_capture) + .vertex_out(eevee_surf_iface) + .vertex_out(eevee_surf_flat_iface) .define("MAT_CAPTURE") .storage_buf(SURFEL_BUF_SLOT, Qualifier::WRITE, "Surfel", "surfel_buf[]") .storage_buf(CAPTURE_BUF_SLOT, Qualifier::READ_WRITE, "CaptureInfoData", "capture_info_buf") - .push_constant(Type::BOOL, "is_double_sided") .fragment_source("eevee_surf_capture_frag.glsl") .additional_info("eevee_global_ubo", "eevee_utility_texture"); GPU_SHADER_CREATE_INFO(eevee_surf_depth) .define("MAT_DEPTH") + .vertex_out(eevee_surf_iface) + .vertex_out(eevee_surf_flat_iface) .fragment_source("eevee_surf_depth_frag.glsl") .additional_info("eevee_global_ubo", "eevee_sampling_data", "eevee_utility_texture"); GPU_SHADER_CREATE_INFO(eevee_surf_world) + .vertex_out(eevee_surf_iface) + .vertex_out(eevee_surf_flat_iface) .push_constant(Type::FLOAT, "world_opacity_fade") .fragment_out(0, Type::VEC4, "out_background") .fragment_source("eevee_surf_world_frag.glsl") .additional_info("eevee_global_ubo", - /* Optionally added depending on the material. */ - // "eevee_render_pass_out", - // "eevee_cryptomatte_out", + "eevee_render_pass_out", + "eevee_cryptomatte_out", "eevee_utility_texture"); GPU_SHADER_CREATE_INFO(eevee_surf_shadow) - .define("DRW_VIEW_LEN", STRINGIFY(SHADOW_VIEW_MAX)) + .define("DRW_VIEW_LEN", "64") .define("MAT_SHADOW") - .builtins(BuiltinBits::VIEWPORT_INDEX | BuiltinBits::LAYER) - .storage_buf(SHADOW_VIEWPORT_INDEX_BUF_SLOT, - Qualifier::READ, - "uint", - "viewport_index_buf[SHADOW_VIEW_MAX]") - .fragment_source("eevee_surf_shadow_frag.glsl") - .additional_info("eevee_global_ubo", "eevee_utility_texture", "eevee_sampling_data"); - -GPU_SHADER_CREATE_INFO(eevee_surf_shadow_atomic) - .additional_info("eevee_surf_shadow") - .define("SHADOW_UPDATE_ATOMIC_RASTER") + .define("USE_ATOMIC") + .builtins(BuiltinBits::VIEWPORT_INDEX) + .builtins(BuiltinBits::LAYER) .builtins(BuiltinBits::TEXTURE_ATOMIC) - /* Early fragment test for speeding up platforms that requires a depth buffer. */ - /* NOTE: This removes the possibility of using gl_FragDepth. */ - .early_fragment_test(true) + .vertex_out(eevee_surf_iface) + .vertex_out(eevee_surf_flat_iface) .storage_buf(SHADOW_RENDER_MAP_BUF_SLOT, Qualifier::READ, "uint", "render_map_buf[SHADOW_RENDER_MAP_SIZE]") + .storage_buf(SHADOW_VIEWPORT_INDEX_BUF_SLOT, + Qualifier::READ, + "uint", + "viewport_index_buf[SHADOW_VIEW_MAX]") + .storage_buf(SHADOW_PAGE_INFO_SLOT, Qualifier::READ, "ShadowPagesInfoData", "pages_infos_buf") .image(SHADOW_ATLAS_IMG_SLOT, GPU_R32UI, Qualifier::READ_WRITE, ImageType::UINT_2D_ARRAY, - "shadow_atlas_img"); - -GPU_SHADER_CREATE_INFO(eevee_surf_shadow_tbdr) - .additional_info("eevee_surf_shadow") - .define("SHADOW_UPDATE_TBDR") - /* F32 color attachment for on-tile depth accumulation without atomics. */ - .fragment_out(0, Type::FLOAT, "out_depth", DualBlend::NONE, SHADOW_ROG_ID); + "shadow_atlas_img") + .fragment_source("eevee_surf_shadow_frag.glsl") + .additional_info("eevee_global_ubo", "eevee_utility_texture", "eevee_sampling_data"); #undef image_out #undef image_array_out @@ -260,7 +227,6 @@ GPU_SHADER_CREATE_INFO(eevee_volume_material_common) .local_group_size(VOLUME_GROUP_SIZE, VOLUME_GROUP_SIZE, VOLUME_GROUP_SIZE) .define("VOLUMETRICS") .additional_info("draw_modelmat_new_common", - /* TODO(fclem): Legacy API. To remove. */ "draw_resource_id_uniform", "draw_view", "eevee_shared", @@ -291,7 +257,6 @@ GPU_SHADER_CREATE_INFO(eevee_volume_object) Qualifier::READ_WRITE, ImageType::FLOAT_3D, "out_phase_img") - .image(VOLUME_OCCUPANCY_SLOT, GPU_R32UI, Qualifier::READ, ImageType::UINT_3D, "occupancy_img") .additional_info("eevee_volume_material_common", "draw_object_infos_new", "draw_volume_infos"); GPU_SHADER_CREATE_INFO(eevee_volume_world) @@ -318,23 +283,22 @@ GPU_SHADER_CREATE_INFO(eevee_volume_world) .define("MAT_GEOM_VOLUME_WORLD") .additional_info("eevee_volume_material_common"); -GPU_SHADER_CREATE_INFO(eevee_surf_occupancy) - .define("MAT_OCCUPANCY") - .builtins(BuiltinBits::TEXTURE_ATOMIC) - .push_constant(Type::BOOL, "use_fast_method") - .image(VOLUME_HIT_DEPTH_SLOT, GPU_R32F, Qualifier::WRITE, ImageType::FLOAT_3D, "hit_depth_img") - .image(VOLUME_HIT_COUNT_SLOT, - GPU_R32UI, - Qualifier::READ_WRITE, - ImageType::UINT_2D, - "hit_count_img") - .image(VOLUME_OCCUPANCY_SLOT, - GPU_R32UI, - Qualifier::READ_WRITE, - ImageType::UINT_3D, - "occupancy_img") - .fragment_source("eevee_surf_occupancy_frag.glsl") - .additional_info("eevee_global_ubo", "eevee_sampling_data"); +#if 0 /* TODO */ +GPU_SHADER_INTERFACE_INFO(eevee_volume_iface, "interp") + .smooth(Type::VEC3, "P_start") + .smooth(Type::VEC3, "P_end"); + +GPU_SHADER_CREATE_INFO(eevee_volume_deferred) + .sampler(0, ImageType::DEPTH_2D, "depth_max_tx") + .vertex_in(0, Type::VEC3, "pos") + .vertex_out(eevee_volume_iface) + .fragment_out(0, Type::UVEC4, "out_volume_data") + .fragment_out(1, Type::VEC4, "out_transparency_data") + .additional_info("eevee_shared") + .vertex_source("eevee_volume_vert.glsl") + .fragment_source("eevee_volume_deferred_frag.glsl") + .additional_info("draw_fullscreen"); +#endif /** \} */ @@ -357,12 +321,10 @@ GPU_SHADER_CREATE_INFO(eevee_material_stub) # define EEVEE_MAT_GEOM_VARIATIONS(prefix, ...) \ EEVEE_MAT_FINAL_VARIATION(prefix##_world, "eevee_geom_world", __VA_ARGS__) \ - /* Turned off until dependency on common_view/math_lib are sorted out. */ \ - /* EEVEE_MAT_FINAL_VARIATION(prefix##_gpencil, "eevee_geom_gpencil", __VA_ARGS__) */ \ + EEVEE_MAT_FINAL_VARIATION(prefix##_gpencil, "eevee_geom_gpencil", __VA_ARGS__) \ EEVEE_MAT_FINAL_VARIATION(prefix##_curves, "eevee_geom_curves", __VA_ARGS__) \ EEVEE_MAT_FINAL_VARIATION(prefix##_mesh, "eevee_geom_mesh", __VA_ARGS__) \ - EEVEE_MAT_FINAL_VARIATION(prefix##_point_cloud, "eevee_geom_point_cloud", __VA_ARGS__) \ - EEVEE_MAT_FINAL_VARIATION(prefix##_volume, "eevee_geom_volume", __VA_ARGS__) + EEVEE_MAT_FINAL_VARIATION(prefix##_point_cloud, "eevee_geom_point_cloud", __VA_ARGS__) # define EEVEE_MAT_PIPE_VARIATIONS(name, ...) \ EEVEE_MAT_GEOM_VARIATIONS(name##_world, "eevee_surf_world", __VA_ARGS__) \ @@ -370,9 +332,7 @@ GPU_SHADER_CREATE_INFO(eevee_material_stub) EEVEE_MAT_GEOM_VARIATIONS(name##_deferred, "eevee_surf_deferred", __VA_ARGS__) \ EEVEE_MAT_GEOM_VARIATIONS(name##_forward, "eevee_surf_forward", __VA_ARGS__) \ EEVEE_MAT_GEOM_VARIATIONS(name##_capture, "eevee_surf_capture", __VA_ARGS__) \ - EEVEE_MAT_GEOM_VARIATIONS(name##_occupancy, "eevee_surf_occupancy", __VA_ARGS__) \ - EEVEE_MAT_GEOM_VARIATIONS(name##_shadow_atomic, "eevee_surf_shadow_atomic", __VA_ARGS__) \ - EEVEE_MAT_GEOM_VARIATIONS(name##_shadow_tbdr, "eevee_surf_shadow_tbdr", __VA_ARGS__) + EEVEE_MAT_GEOM_VARIATIONS(name##_shadow, "eevee_surf_shadow", __VA_ARGS__) EEVEE_MAT_PIPE_VARIATIONS(eevee_surface, "eevee_material_stub") diff --git a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_reflection_probe_info.hh b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_reflection_probe_info.hh index e0719bccfc4..1eec1064fdc 100644 --- a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_reflection_probe_info.hh +++ b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_reflection_probe_info.hh @@ -10,19 +10,20 @@ * \{ */ GPU_SHADER_CREATE_INFO(eevee_reflection_probe_data) - .define("REFLECTION_PROBE") .uniform_buf(REFLECTION_PROBE_BUF_SLOT, "ReflectionProbeData", "reflection_probe_buf[REFLECTION_PROBES_MAX]") - .sampler(REFLECTION_PROBE_TEX_SLOT, ImageType::FLOAT_2D_ARRAY, "reflection_probes_tx"); + .sampler(REFLECTION_PROBE_TEX_SLOT, ImageType::FLOAT_2D_ARRAY, "reflectionProbes"); /* Sample cubemap and remap into an octahedral texture. */ GPU_SHADER_CREATE_INFO(eevee_reflection_probe_remap) .local_group_size(REFLECTION_PROBE_GROUP_SIZE, REFLECTION_PROBE_GROUP_SIZE) - .push_constant(Type::IVEC4, "probe_coord_packed") - .push_constant(Type::IVEC4, "world_coord_packed") + .push_constant(Type::INT, "reflection_probe_index") + .uniform_buf(REFLECTION_PROBE_BUF_SLOT, + "ReflectionProbeData", + "reflection_probe_buf[REFLECTION_PROBES_MAX]") .sampler(0, ImageType::FLOAT_CUBE, "cubemap_tx") - .image(0, GPU_RGBA16F, Qualifier::READ_WRITE, ImageType::FLOAT_2D_ARRAY, "octahedral_img") + .image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D_ARRAY, "octahedral_img") .compute_source("eevee_reflection_probe_remap_comp.glsl") .additional_info("eevee_shared") .do_static_compilation(true); @@ -31,23 +32,10 @@ GPU_SHADER_CREATE_INFO(eevee_reflection_probe_remap) * world brick of the irradiance cache. */ GPU_SHADER_CREATE_INFO(eevee_reflection_probe_update_irradiance) .local_group_size(REFLECTION_PROBE_SH_GROUP_SIZE, 1) - .define("REFLECTION_PROBE") - .push_constant(Type::IVEC4, "world_coord_packed") - .sampler(0, ImageType::FLOAT_2D_ARRAY, "reflection_probes_tx") + .push_constant(Type::INT, "reflection_probe_index") .image(0, GPU_RGBA16F, Qualifier::READ_WRITE, ImageType::FLOAT_3D, "irradiance_atlas_img") - .additional_info("eevee_shared") + .additional_info("eevee_shared", "eevee_reflection_probe_data") .compute_source("eevee_reflection_probe_update_irradiance_comp.glsl") .do_static_compilation(true); -GPU_SHADER_CREATE_INFO(eevee_reflection_probe_select) - .local_group_size(REFLECTION_PROBE_SELECT_GROUP_SIZE) - .storage_buf(0, - Qualifier::READ_WRITE, - "ReflectionProbeData", - "reflection_probe_buf[REFLECTION_PROBES_MAX]") - .push_constant(Type::INT, "reflection_probe_count") - .additional_info("eevee_shared", "eevee_volume_probe_data") - .compute_source("eevee_reflection_probe_select_comp.glsl") - .do_static_compilation(true); - /** \} */ diff --git a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_shadow_info.hh b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_shadow_info.hh index 23d3bd2aa67..285f6bfdea5 100644 --- a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_shadow_info.hh +++ b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_shadow_info.hh @@ -155,8 +155,7 @@ GPU_SHADER_CREATE_INFO(eevee_shadow_page_defrag) .storage_buf(3, Qualifier::READ_WRITE, "uint", "pages_free_buf[]") .storage_buf(4, Qualifier::READ_WRITE, "uvec2", "pages_cached_buf[]") .storage_buf(5, Qualifier::WRITE, "DispatchCommand", "clear_dispatch_buf") - .storage_buf(6, Qualifier::WRITE, "DrawCommand", "tile_draw_buf") - .storage_buf(7, Qualifier::READ_WRITE, "ShadowStatistics", "statistics_buf") + .storage_buf(6, Qualifier::READ_WRITE, "ShadowStatistics", "statistics_buf") .additional_info("eevee_shared") .compute_source("eevee_shadow_page_defrag_comp.glsl"); @@ -183,75 +182,26 @@ GPU_SHADER_CREATE_INFO(eevee_shadow_tilemap_finalize) .storage_buf(3, Qualifier::WRITE, "ViewMatrices", "view_infos_buf[SHADOW_VIEW_MAX]") .storage_buf(4, Qualifier::READ_WRITE, "ShadowStatistics", "statistics_buf") .storage_buf(5, Qualifier::READ_WRITE, "DispatchCommand", "clear_dispatch_buf") - .storage_buf(6, Qualifier::READ_WRITE, "DrawCommand", "tile_draw_buf") - .storage_buf(7, Qualifier::WRITE, SHADOW_PAGE_PACKED, "dst_coord_buf[SHADOW_RENDER_MAP_SIZE]") - .storage_buf(8, Qualifier::WRITE, SHADOW_PAGE_PACKED, "src_coord_buf[SHADOW_RENDER_MAP_SIZE]") - .storage_buf(9, Qualifier::WRITE, SHADOW_PAGE_PACKED, "render_map_buf[SHADOW_RENDER_MAP_SIZE]") - .storage_buf(10, Qualifier::WRITE, "uint", "viewport_index_buf[SHADOW_VIEW_MAX]") - .storage_buf(11, Qualifier::READ, "ShadowTileMapClip", "tilemaps_clip_buf[]") - /* 12 is the minimum number of storage buf we require. Do not go above this limit. */ + .storage_buf(6, Qualifier::WRITE, SHADOW_PAGE_PACKED, "clear_list_buf[SHADOW_RENDER_MAP_SIZE]") + .storage_buf(7, Qualifier::WRITE, SHADOW_PAGE_PACKED, "render_map_buf[SHADOW_RENDER_MAP_SIZE]") + .storage_buf(8, Qualifier::WRITE, "uint", "viewport_index_buf[SHADOW_VIEW_MAX]") + .storage_buf(9, Qualifier::READ, "ShadowTileMapClip", "tilemaps_clip_buf[]") .image(0, GPU_R32UI, Qualifier::WRITE, ImageType::UINT_2D, "tilemaps_img") .additional_info("eevee_shared") .compute_source("eevee_shadow_tilemap_finalize_comp.glsl"); -/* AtomicMin clear implementation. */ GPU_SHADER_CREATE_INFO(eevee_shadow_page_clear) .do_static_compilation(true) .local_group_size(SHADOW_PAGE_CLEAR_GROUP_SIZE, SHADOW_PAGE_CLEAR_GROUP_SIZE) .storage_buf(2, Qualifier::READ, "ShadowPagesInfoData", "pages_infos_buf") - .storage_buf(6, Qualifier::READ, SHADOW_PAGE_PACKED, "dst_coord_buf[SHADOW_RENDER_MAP_SIZE]") - .additional_info("eevee_shared") - .compute_source("eevee_shadow_page_clear_comp.glsl") - .image(SHADOW_ATLAS_IMG_SLOT, - GPU_R32UI, - Qualifier::READ_WRITE, - ImageType::UINT_2D_ARRAY, - "shadow_atlas_img"); - -/* TBDR clear implementation. */ -GPU_SHADER_CREATE_INFO(eevee_shadow_page_tile_clear) - .do_static_compilation(true) - .define("PASS_CLEAR") - .additional_info("eevee_shared") - .builtins(BuiltinBits::VIEWPORT_INDEX | BuiltinBits::LAYER) - .storage_buf(8, Qualifier::READ, SHADOW_PAGE_PACKED, "src_coord_buf[SHADOW_RENDER_MAP_SIZE]") - .vertex_source("eevee_shadow_page_tile_vert.glsl") - .fragment_source("eevee_shadow_page_tile_frag.glsl") - .fragment_out(0, Type::FLOAT, "out_tile_depth", DualBlend::NONE, SHADOW_ROG_ID); - -#ifdef APPLE -/* Metal supports USHORT which saves a bit of performance here. */ -# define PAGE_Z_TYPE Type::USHORT -#else -# define PAGE_Z_TYPE Type::UINT -#endif - -/* Interface for passing precalculated values in accumulation vertex to frag. */ -GPU_SHADER_INTERFACE_INFO(eevee_shadow_page_tile_store_noperspective_iface, "interp_noperspective") - .no_perspective(Type::VEC2, "out_texel_xy"); -GPU_SHADER_INTERFACE_INFO(eevee_shadow_page_tile_store_flat_iface, "interp_flat") - .flat(PAGE_Z_TYPE, "out_page_z"); - -#undef PAGE_Z_TYPE - -/* 2nd tile pass to store shadow depths in atlas. */ -GPU_SHADER_CREATE_INFO(eevee_shadow_page_tile_store) - .do_static_compilation(true) - .define("PASS_DEPTH_STORE") - .additional_info("eevee_shared") - .builtins(BuiltinBits::VIEWPORT_INDEX | BuiltinBits::LAYER) - .storage_buf(7, Qualifier::READ, SHADOW_PAGE_PACKED, "dst_coord_buf[SHADOW_RENDER_MAP_SIZE]") - .storage_buf(8, Qualifier::READ, SHADOW_PAGE_PACKED, "src_coord_buf[SHADOW_RENDER_MAP_SIZE]") - .subpass_in(0, Type::FLOAT, "in_tile_depth", SHADOW_ROG_ID) + .storage_buf(6, Qualifier::READ, SHADOW_PAGE_PACKED, "clear_list_buf[SHADOW_RENDER_MAP_SIZE]") .image(SHADOW_ATLAS_IMG_SLOT, GPU_R32UI, Qualifier::READ_WRITE, ImageType::UINT_2D_ARRAY, "shadow_atlas_img") - .vertex_out(eevee_shadow_page_tile_store_noperspective_iface) - .vertex_out(eevee_shadow_page_tile_store_flat_iface) - .vertex_source("eevee_shadow_page_tile_vert.glsl") - .fragment_source("eevee_shadow_page_tile_frag.glsl"); + .additional_info("eevee_shared") + .compute_source("eevee_shadow_page_clear_comp.glsl"); /** \} */ diff --git a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_subsurface_info.hh b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_subsurface_info.hh index 79b4f515eea..2a44dd72b66 100644 --- a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_subsurface_info.hh +++ b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_subsurface_info.hh @@ -12,7 +12,7 @@ GPU_SHADER_CREATE_INFO(eevee_subsurface_setup) .additional_info("draw_view", "eevee_shared", "eevee_gbuffer_data") .sampler(2, ImageType::DEPTH_2D, "depth_tx") .image(0, GPU_RGBA16F, Qualifier::READ, ImageType::FLOAT_2D, "direct_light_img") - .image(1, RAYTRACE_RADIANCE_FORMAT, Qualifier::READ, ImageType::FLOAT_2D, "indirect_light_img") + .image(1, GPU_RGBA16F, Qualifier::READ, ImageType::FLOAT_2D, "indirect_light_img") .image(2, SUBSURFACE_OBJECT_ID_FORMAT, Qualifier::WRITE, ImageType::UINT_2D, "object_id_img") .image(3, SUBSURFACE_RADIANCE_FORMAT, Qualifier::WRITE, ImageType::FLOAT_2D, "radiance_img") .storage_buf(0, Qualifier::WRITE, "uint", "convolve_tile_buf[]") @@ -28,9 +28,5 @@ GPU_SHADER_CREATE_INFO(eevee_subsurface_convolve) .sampler(4, ImageType::UINT_2D, "object_id_tx") .storage_buf(0, Qualifier::READ, "uint", "tiles_coord_buf[]") .image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "out_direct_light_img") - .image(1, - RAYTRACE_RADIANCE_FORMAT, - Qualifier::WRITE, - ImageType::FLOAT_2D, - "out_indirect_light_img") + .image(1, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "out_indirect_light_img") .compute_source("eevee_subsurface_convolve_comp.glsl"); diff --git a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_tracing_info.hh b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_tracing_info.hh index 652735e2218..7371ec5919c 100644 --- a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_tracing_info.hh +++ b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_tracing_info.hh @@ -65,31 +65,17 @@ EEVEE_RAYTRACE_CLOSURE_VARIATION(eevee_ray_generate) GPU_SHADER_CREATE_INFO(eevee_ray_trace_fallback) .do_static_compilation(true) .local_group_size(RAYTRACE_GROUP_SIZE, RAYTRACE_GROUP_SIZE) - .additional_info("eevee_shared", "eevee_global_ubo", "draw_view", "eevee_lightprobe_data") + .additional_info("eevee_shared", + "eevee_global_ubo", + "draw_view", + "eevee_reflection_probe_data") .image(0, GPU_RGBA16F, Qualifier::READ, ImageType::FLOAT_2D, "ray_data_img") .image(1, RAYTRACE_RAYTIME_FORMAT, Qualifier::WRITE, ImageType::FLOAT_2D, "ray_time_img") .image(2, RAYTRACE_RADIANCE_FORMAT, Qualifier::WRITE, ImageType::FLOAT_2D, "ray_radiance_img") .sampler(1, ImageType::DEPTH_2D, "depth_tx") - .storage_buf(5, Qualifier::READ, "uint", "tiles_coord_buf[]") + .storage_buf(4, Qualifier::READ, "uint", "tiles_coord_buf[]") .compute_source("eevee_ray_trace_fallback_comp.glsl"); -GPU_SHADER_CREATE_INFO(eevee_ray_trace_planar) - .do_static_compilation(true) - .local_group_size(RAYTRACE_GROUP_SIZE, RAYTRACE_GROUP_SIZE) - .define("PLANAR_PROBES") - .additional_info("eevee_shared", - "eevee_global_ubo", - "eevee_sampling_data", - "draw_view", - "eevee_lightprobe_data", - "eevee_lightprobe_planar_data") - .image(0, GPU_RGBA16F, Qualifier::READ_WRITE, ImageType::FLOAT_2D, "ray_data_img") - .image(1, RAYTRACE_RAYTIME_FORMAT, Qualifier::WRITE, ImageType::FLOAT_2D, "ray_time_img") - .image(2, RAYTRACE_RADIANCE_FORMAT, Qualifier::WRITE, ImageType::FLOAT_2D, "ray_radiance_img") - .sampler(2, ImageType::DEPTH_2D, "depth_tx") - .storage_buf(5, Qualifier::READ, "uint", "tiles_coord_buf[]") - .compute_source("eevee_ray_trace_planar_comp.glsl"); - GPU_SHADER_CREATE_INFO(eevee_ray_trace_screen) .local_group_size(RAYTRACE_GROUP_SIZE, RAYTRACE_GROUP_SIZE) .additional_info("eevee_shared", @@ -97,13 +83,13 @@ GPU_SHADER_CREATE_INFO(eevee_ray_trace_screen) "eevee_sampling_data", "draw_view", "eevee_hiz_data", - "eevee_lightprobe_data") + "eevee_reflection_probe_data") .image(0, GPU_RGBA16F, Qualifier::READ, ImageType::FLOAT_2D, "ray_data_img") .image(1, RAYTRACE_RAYTIME_FORMAT, Qualifier::WRITE, ImageType::FLOAT_2D, "ray_time_img") .image(2, RAYTRACE_RADIANCE_FORMAT, Qualifier::WRITE, ImageType::FLOAT_2D, "ray_radiance_img") .sampler(0, ImageType::FLOAT_2D, "screen_radiance_tx") .sampler(1, ImageType::DEPTH_2D, "depth_tx") - .storage_buf(5, Qualifier::READ, "uint", "tiles_coord_buf[]") + .storage_buf(4, Qualifier::READ, "uint", "tiles_coord_buf[]") .compute_source("eevee_ray_trace_screen_comp.glsl"); EEVEE_RAYTRACE_CLOSURE_VARIATION(eevee_ray_trace_screen) diff --git a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_volume_info.hh b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_volume_info.hh index 13a79bc211f..d2e44d5f352 100644 --- a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_volume_info.hh +++ b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_volume_info.hh @@ -60,23 +60,6 @@ GPU_SHADER_CREATE_INFO(eevee_volume_scatter_with_lights) .sampler(0, ImageType::FLOAT_3D, "extinction_tx") .do_static_compilation(true); -GPU_SHADER_CREATE_INFO(eevee_volume_occupancy_convert) - .additional_info("eevee_shared", "eevee_global_ubo", "draw_fullscreen") - .builtins(BuiltinBits::TEXTURE_ATOMIC) - .image(VOLUME_HIT_DEPTH_SLOT, GPU_R32F, Qualifier::READ, ImageType::FLOAT_3D, "hit_depth_img") - .image(VOLUME_HIT_COUNT_SLOT, - GPU_R32UI, - Qualifier::READ_WRITE, - ImageType::UINT_2D, - "hit_count_img") - .image(VOLUME_OCCUPANCY_SLOT, - GPU_R32UI, - Qualifier::READ_WRITE, - ImageType::UINT_3D, - "occupancy_img") - .fragment_source("eevee_occupancy_convert_frag.glsl") - .do_static_compilation(true); - GPU_SHADER_CREATE_INFO(eevee_volume_integration) .additional_info("eevee_shared", "eevee_global_ubo", "draw_view") .compute_source("eevee_volume_integration_comp.glsl") diff --git a/source/blender/draw/engines/gpencil/gpencil_cache_utils.cc b/source/blender/draw/engines/gpencil/gpencil_cache_utils.cc index e6f07630a5f..59eca62b573 100644 --- a/source/blender/draw/engines/gpencil/gpencil_cache_utils.cc +++ b/source/blender/draw/engines/gpencil/gpencil_cache_utils.cc @@ -63,11 +63,11 @@ GPENCIL_tObject *gpencil_object_cache_add(GPENCIL_PrivateData *pd, Object *ob) * strokes not aligned with the object axes. Maybe we could try to * compute the minimum axis of all strokes. But this would be more * computationally heavy and should go into the GPData evaluation. */ - const std::optional bbox = BKE_object_boundbox_get(ob); + const BoundBox *bbox = BKE_object_boundbox_get(ob); /* Convert bbox to matrix */ float mat[4][4], size[3], center[3]; - BKE_boundbox_calc_size_aabb(&bbox.value(), size); - BKE_boundbox_calc_center_aabb(&bbox.value(), center); + BKE_boundbox_calc_size_aabb(bbox, size); + BKE_boundbox_calc_center_aabb(bbox, center); unit_m4(mat); copy_v3_v3(mat[3], center); /* Avoid division by 0.0 later. */ diff --git a/source/blender/draw/engines/gpencil/gpencil_engine.cc b/source/blender/draw/engines/gpencil/gpencil_engine.cc index ca94a9d93db..344c4b8281a 100644 --- a/source/blender/draw/engines/gpencil/gpencil_engine.cc +++ b/source/blender/draw/engines/gpencil/gpencil_engine.cc @@ -140,7 +140,7 @@ class Instance { { switch (object_ref.object->type) { case OB_GREASE_PENCIL: - objects.sync_grease_pencil(manager, object_ref, main_fb_, depth_tx_, main_ps_); + objects.sync_grease_pencil(manager, object_ref, main_fb_, main_ps_); break; case OB_LAMP: lights.sync(object_ref); diff --git a/source/blender/draw/engines/gpencil/gpencil_layer.hh b/source/blender/draw/engines/gpencil/gpencil_layer.hh index cd085774300..4af2e191549 100644 --- a/source/blender/draw/engines/gpencil/gpencil_layer.hh +++ b/source/blender/draw/engines/gpencil/gpencil_layer.hh @@ -36,7 +36,7 @@ class LayerModule { { /* TODO(fclem): All of this is placeholder. */ gpLayer gp_layer; - // gp_layer.vertex_color_opacity = 0.0f; unused + gp_layer.vertex_color_opacity = 0.0f; gp_layer.thickness_offset = 0.0f; gp_layer.tint = float4(1.0f, 1.0f, 1.0f, 0.0f); gp_layer.stroke_index_offset = 0.0f; diff --git a/source/blender/draw/engines/gpencil/gpencil_object.hh b/source/blender/draw/engines/gpencil/gpencil_object.hh index 328d77df395..b11beadc4d2 100644 --- a/source/blender/draw/engines/gpencil/gpencil_object.hh +++ b/source/blender/draw/engines/gpencil/gpencil_object.hh @@ -8,8 +8,6 @@ #pragma once -#include "BLI_math_quaternion_types.hh" - #include "BKE_grease_pencil.hh" #include "BKE_image.h" #include "DRW_gpu_wrapper.hh" @@ -54,11 +52,8 @@ class ObjectModule { bool use_stroke_fill_ = true; bool use_vfx_ = true; bool is_render_ = true; - bool is_persp_ = true; - /** Forward vector used to sort gpencil objects. */ float3 camera_forward_; - float3 camera_pos_; /** Scene current frame. */ float current_frame_ = 0; @@ -95,14 +90,12 @@ class ObjectModule { void begin_sync(Depsgraph *depsgraph, const View &main_view) { - camera_forward_ = main_view.forward(); - camera_pos_ = main_view.location(); + camera_forward_ = float3(main_view.viewinv()[2]); current_frame_ = DEG_get_ctime(depsgraph); is_object_fb_needed_ = false; is_layer_fb_needed_ = false; - is_persp_ = main_view.is_persp(); /* TODO(fclem): Shrink buffer. */ // objects_buf_.shrink(); } @@ -110,13 +103,12 @@ class ObjectModule { void sync_grease_pencil(Manager &manager, ObjectRef &object_ref, Framebuffer &main_fb, - TextureFromPool &depth_tx, PassSortable &main_ps) { using namespace blender::bke::greasepencil; Object *object = object_ref.object; - const GreasePencil &grease_pencil = *static_cast(object->data); + GreasePencil &grease_pencil = *static_cast(object->data); if (grease_pencil.drawings().is_empty()) { return; @@ -196,14 +188,6 @@ class ObjectModule { object_subpass.draw(geom, handle); } - float4x4 plane_mat = get_object_plane_mat(*object); - ResourceHandle handle_plane_mat = manager.resource_handle(plane_mat); - object_subpass.framebuffer_set(&DRW_viewport_framebuffer_list_get()->depth_only_fb); - object_subpass.state_set(DRW_STATE_DEPTH_LESS | DRW_STATE_WRITE_DEPTH); - object_subpass.shader_set(shaders_.static_shader_get(DEPTH_MERGE)); - object_subpass.bind_texture("depthBuf", (object_has_vfx) ? nullptr : &depth_tx); - object_subpass.draw(DRW_cache_quad_get(), handle_plane_mat); - /* TODO: Do object VFX. */ #if 0 if (object_has_vfx) { @@ -265,56 +249,6 @@ class ObjectModule { { return objects_buf_.size() > 0; } - - float4x4 get_object_plane_mat(const Object &object) - { - using namespace math; - /* Find the normal most likely to represent the gpObject. */ - /* TODO: This does not work quite well if you use - * strokes not aligned with the object axes. Maybe we could try to - * compute the minimum axis of all strokes. But this would be more - * computationally heavy and should go into the GPData evaluation. */ - BLI_assert(object.type == OB_GREASE_PENCIL); - const GreasePencil &grease_pencil = *static_cast(object.data); - const std::optional> bounds = grease_pencil.bounds_min_max(); - if (!bounds) { - return float4x4::identity(); - } - - /* Convert bbox to matrix */ - const float3 size = float3(bounds->max - bounds->min) + 1e-8f; - const float3 center = midpoint(bounds->min, bounds->max); - - /* BBox space to World. */ - const float4x4 object_to_world = float4x4(object.object_to_world); - float4x4 bbox_mat = object_to_world * - from_loc_rot_scale(center, Quaternion::identity(), size); - float3 plane_normal; - if (is_persp_) { - /* BBox center to camera vector. */ - plane_normal = camera_pos_ - bbox_mat.location(); - } - else { - plane_normal = camera_forward_; - } - /* World to BBox space. */ - float4x4 bbox_mat_inv = invert(bbox_mat); - /* mat_inv_t is a "normal" matrix which will transform - * BBox normal space to world space. */ - float4x4 bbox_mat_inv_t = transpose(bbox_mat_inv); - - /* Normalize the vector in BBox space. */ - plane_normal = normalize(transform_direction(bbox_mat_inv, plane_normal)); - plane_normal = normalize(transform_direction(bbox_mat_inv_t, plane_normal)); - - /* Define a matrix that will be used to render a triangle to merge the depth of the rendered - * gpencil object with the rest of the scene. */ - float4x4 plane_mat = from_up_axis(plane_normal); - float radius = length(transform_direction(object_to_world, size)); - plane_mat = scale(plane_mat, float3(radius)); - plane_mat.location() = transform_point(object_to_world, center); - return plane_mat; - } }; } // namespace blender::draw::greasepencil diff --git a/source/blender/draw/engines/gpencil/gpencil_shader.cc b/source/blender/draw/engines/gpencil/gpencil_shader.cc index 31263797099..c600d9e3c87 100644 --- a/source/blender/draw/engines/gpencil/gpencil_shader.cc +++ b/source/blender/draw/engines/gpencil/gpencil_shader.cc @@ -74,7 +74,7 @@ const char *ShaderModule::static_shader_create_info_name_get(eShaderType shader_ case LAYER_BLEND: return "gpencil_layer_blend"; case DEPTH_MERGE: - return "grease_pencil_depth_merge"; + return "gpencil_depth_merge"; case MASK_INVERT: return "gpencil_mask_invert"; case FX_COMPOSITE: diff --git a/source/blender/draw/engines/gpencil/shaders/grease_pencil_depth_merge_vert.glsl b/source/blender/draw/engines/gpencil/shaders/grease_pencil_depth_merge_vert.glsl deleted file mode 100644 index 64529a62424..00000000000 --- a/source/blender/draw/engines/gpencil/shaders/grease_pencil_depth_merge_vert.glsl +++ /dev/null @@ -1,9 +0,0 @@ -/* SPDX-FileCopyrightText: 2020-2022 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ -#pragma BLENDER_REQUIRE(draw_model_lib.glsl) - -void main() -{ - gl_Position = drw_point_object_to_homogenous(pos); -} diff --git a/source/blender/draw/engines/gpencil/shaders/grease_pencil_frag.glsl b/source/blender/draw/engines/gpencil/shaders/grease_pencil_frag.glsl deleted file mode 100644 index 79050586088..00000000000 --- a/source/blender/draw/engines/gpencil/shaders/grease_pencil_frag.glsl +++ /dev/null @@ -1,142 +0,0 @@ -/* SPDX-FileCopyrightText: 2020-2023 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -#pragma BLENDER_REQUIRE(common_grease_pencil_lib.glsl) -#pragma BLENDER_REQUIRE(common_colormanagement_lib.glsl) - -float length_squared(vec2 v) -{ - return dot(v, v); -} -float length_squared(vec3 v) -{ - return dot(v, v); -} - -vec3 gpencil_lighting(void) -{ - vec3 light_accum = vec3(0.0); - for (int i = 0; i < GPENCIL_LIGHT_BUFFER_LEN; i++) { - if (gp_lights[i]._color.x == -1.0) { - break; - } - vec3 L = gp_lights[i]._position - gp_interp.pos; - float vis = 1.0; - gpLightType type = floatBitsToUint(gp_lights[i]._type); - /* Spot Attenuation. */ - if (type == GP_LIGHT_TYPE_SPOT) { - mat3 rot_scale = mat3(gp_lights[i]._right, gp_lights[i]._up, gp_lights[i]._forward); - vec3 local_L = rot_scale * L; - local_L /= abs(local_L.z); - float ellipse = inversesqrt(length_squared(local_L)); - vis *= smoothstep(0.0, 1.0, (ellipse - gp_lights[i]._spot_size) / gp_lights[i]._spot_blend); - /* Also mask +Z cone. */ - vis *= step(0.0, local_L.z); - } - /* Inverse square decay. Skip for suns. */ - float L_len_sqr = length_squared(L); - if (type < GP_LIGHT_TYPE_SUN) { - vis /= L_len_sqr; - } - else { - L = gp_lights[i]._forward; - L_len_sqr = 1.0; - } - /* Lambertian falloff */ - if (type != GP_LIGHT_TYPE_AMBIENT) { - L /= sqrt(L_len_sqr); - vis *= clamp(dot(gpNormal, L), 0.0, 1.0); - } - light_accum += vis * gp_lights[i]._color; - } - /* Clamp to avoid NaNs. */ - return clamp(light_accum, 0.0, 1e10); -} - -void main() -{ - vec4 col; - if (flag_test(gp_interp_flat.mat_flag, GP_STROKE_TEXTURE_USE)) { - bool premul = flag_test(gp_interp_flat.mat_flag, GP_STROKE_TEXTURE_PREMUL); - col = texture_read_as_linearrgb(gpStrokeTexture, premul, gp_interp.uv); - } - else if (flag_test(gp_interp_flat.mat_flag, GP_FILL_TEXTURE_USE)) { - bool use_clip = flag_test(gp_interp_flat.mat_flag, GP_FILL_TEXTURE_CLIP); - vec2 uvs = (use_clip) ? clamp(gp_interp.uv, 0.0, 1.0) : gp_interp.uv; - bool premul = flag_test(gp_interp_flat.mat_flag, GP_FILL_TEXTURE_PREMUL); - col = texture_read_as_linearrgb(gpFillTexture, premul, uvs); - } - else if (flag_test(gp_interp_flat.mat_flag, GP_FILL_GRADIENT_USE)) { - bool radial = flag_test(gp_interp_flat.mat_flag, GP_FILL_GRADIENT_RADIAL); - float fac = clamp(radial ? length(gp_interp.uv * 2.0 - 1.0) : gp_interp.uv.x, 0.0, 1.0); - uint matid = gp_interp_flat.mat_flag >> GPENCIl_MATID_SHIFT; - col = mix(gp_materials[matid].fill_color, gp_materials[matid].fill_mix_color, fac); - } - else /* SOLID */ { - col = vec4(1.0); - } - col.rgb *= col.a; - - /* Composite all other colors on top of texture color. - * Everything is pre-multiply by `col.a` to have the stencil effect. */ - fragColor = col * gp_interp.color_mul + col.a * gp_interp.color_add; - - fragColor.rgb *= gpencil_lighting(); - - fragColor *= gpencil_stroke_round_cap_mask(gp_interp_flat.sspos.xy, - gp_interp_flat.sspos.zw, - gp_interp_flat.aspect, - gp_interp_noperspective.thickness.x, - gp_interp_noperspective.hardness); - - /* To avoid aliasing artifacts, we reduce the opacity of small strokes. */ - fragColor *= smoothstep(0.0, 1.0, gp_interp_noperspective.thickness.y); - - /* Holdout materials. */ - if (flag_test(gp_interp_flat.mat_flag, GP_STROKE_HOLDOUT | GP_FILL_HOLDOUT)) { - revealColor = fragColor.aaaa; - } - else { - /* NOT holdout materials. - * For compatibility with colored alpha buffer. - * Note that we are limited to mono-chromatic alpha blending here - * because of the blend equation and the limit of 1 color target - * when using custom color blending. */ - revealColor = vec4(0.0, 0.0, 0.0, fragColor.a); - - if (fragColor.a < 0.001) { - discard; - return; - } - } - - vec2 fb_size = max(vec2(textureSize(gpSceneDepthTexture, 0).xy), - vec2(textureSize(gpMaskTexture, 0).xy)); - vec2 uvs = gl_FragCoord.xy / fb_size; - /* Manual depth test */ - float scene_depth = texture(gpSceneDepthTexture, uvs).r; - if (gl_FragCoord.z > scene_depth) { - discard; - return; - } - - /* FIXME(@fclem): Alas. This is bad for performance but it's the easiest way to not get - * depth written where the mask obliterate the layer. */ - float mask = texture(gpMaskTexture, uvs).r; - if (mask < 0.001) { - discard; - return; - } - - /* We override the fragment depth using the fragment shader to ensure a constant value. - * This has a cost as the depth test cannot happen early. - * We could do this in the vertex shader but then perspective interpolation of uvs and - * fragment clipping gets really complicated. */ - if (gp_interp_flat.depth >= 0.0) { - gl_FragDepth = gp_interp_flat.depth; - } - else { - gl_FragDepth = gl_FragCoord.z; - } -} diff --git a/source/blender/draw/engines/gpencil/shaders/grease_pencil_vert.glsl b/source/blender/draw/engines/gpencil/shaders/grease_pencil_vert.glsl index f931c15092b..b123931642c 100644 --- a/source/blender/draw/engines/gpencil/shaders/grease_pencil_vert.glsl +++ b/source/blender/draw/engines/gpencil/shaders/grease_pencil_vert.glsl @@ -8,7 +8,7 @@ void gpencil_color_output(vec4 stroke_col, vec4 vert_col, float vert_strength, f { /* Mix stroke with other colors. */ vec4 mixed_col = stroke_col; - mixed_col.rgb = mix(mixed_col.rgb, vert_col.rgb, vert_col.a); + mixed_col.rgb = mix(mixed_col.rgb, vert_col.rgb, vert_col.a * gpVertexColorOpacity); mixed_col.rgb = mix(mixed_col.rgb, gpLayerTint.rgb, gpLayerTint.a); mixed_col.a *= vert_strength * gpLayerOpacity; /** diff --git a/source/blender/draw/engines/gpencil/shaders/infos/gpencil_info.hh b/source/blender/draw/engines/gpencil/shaders/infos/gpencil_info.hh index af893352351..1cd9bf4ae8e 100644 --- a/source/blender/draw/engines/gpencil/shaders/infos/gpencil_info.hh +++ b/source/blender/draw/engines/gpencil/shaders/infos/gpencil_info.hh @@ -85,7 +85,7 @@ GPU_SHADER_CREATE_INFO(gpencil_geometry_next) .vertex_out(gpencil_geometry_flat_iface) .vertex_out(gpencil_geometry_noperspective_iface) .vertex_source("grease_pencil_vert.glsl") - .fragment_source("grease_pencil_frag.glsl") + .fragment_source("gpencil_frag.glsl") .additional_info("draw_gpencil_new") .depth_write(DepthWrite::ANY); @@ -126,16 +126,6 @@ GPU_SHADER_CREATE_INFO(gpencil_depth_merge) .depth_write(DepthWrite::ANY) .additional_info("draw_view"); -GPU_SHADER_CREATE_INFO(grease_pencil_depth_merge) - .do_static_compilation(true) - .define("strokeOrder3d", "false") - .sampler(0, ImageType::DEPTH_2D, "depthBuf") - .vertex_in(0, Type::VEC3, "pos") - .vertex_source("grease_pencil_depth_merge_vert.glsl") - .fragment_source("gpencil_depth_merge_frag.glsl") - .depth_write(DepthWrite::ANY) - .additional_info("draw_modelmat_new", "draw_view"); - /** \} */ /* -------------------------------------------------------------------- */ diff --git a/source/blender/draw/engines/image/shaders/image_engine_color_vert.glsl b/source/blender/draw/engines/image/shaders/image_engine_color_vert.glsl index 5155286ff12..336d0ceb6d1 100644 --- a/source/blender/draw/engines/image/shaders/image_engine_color_vert.glsl +++ b/source/blender/draw/engines/image/shaders/image_engine_color_vert.glsl @@ -10,6 +10,5 @@ void main() uv_screen = image_pos.xy; vec4 position = point_world_to_ndc(image_pos); - position.z = 0.0; gl_Position = position; } diff --git a/source/blender/draw/engines/image/shaders/image_engine_depth_vert.glsl b/source/blender/draw/engines/image/shaders/image_engine_depth_vert.glsl index a59032f4bcc..1992214536e 100644 --- a/source/blender/draw/engines/image/shaders/image_engine_depth_vert.glsl +++ b/source/blender/draw/engines/image/shaders/image_engine_depth_vert.glsl @@ -10,6 +10,5 @@ void main() uv_image = uv; vec4 position = point_world_to_ndc(image_pos); - position.z = 0.0; gl_Position = position; } diff --git a/source/blender/draw/engines/overlay/overlay_extra.cc b/source/blender/draw/engines/overlay/overlay_extra.cc index e7b0d2bf870..fae1211f975 100644 --- a/source/blender/draw/engines/overlay/overlay_extra.cc +++ b/source/blender/draw/engines/overlay/overlay_extra.cc @@ -360,21 +360,21 @@ static void OVERLAY_bounds(OVERLAY_ExtraCallBuffers *cb, return; } - std::optional bb = BKE_object_boundbox_get(ob); + const BoundBox *bb = BKE_object_boundbox_get(ob); BoundBox bb_local; - if (!bb) { + if (bb == nullptr) { const float min[3] = {-1.0f, -1.0f, -1.0f}, max[3] = {1.0f, 1.0f, 1.0f}; BKE_boundbox_init_from_minmax(&bb_local, min, max); - bb.emplace(bb_local); + bb = &bb_local; } - BKE_boundbox_calc_size_aabb(&*bb, size); + BKE_boundbox_calc_size_aabb(bb, size); if (around_origin) { zero_v3(center); } else { - BKE_boundbox_calc_center_aabb(&*bb, center); + BKE_boundbox_calc_center_aabb(bb, center); } switch (boundtype) { @@ -737,7 +737,7 @@ void OVERLAY_lightprobe_cache_populate(OVERLAY_Data *vedata, Object *ob) copy_m4_m4(instdata.mat, ob->object_to_world); switch (prb->type) { - case LIGHTPROBE_TYPE_SPHERE: + case LIGHTPROBE_TYPE_CUBE: instdata.clip_sta = show_clipping ? prb->clipsta : -1.0; instdata.clip_end = show_clipping ? prb->clipend : -1.0; DRW_buffer_add_entry(cb->probe_cube, color_p, &instdata); @@ -757,7 +757,7 @@ void OVERLAY_lightprobe_cache_populate(OVERLAY_Data *vedata, Object *ob) OVERLAY_empty_shape(cb, ob->object_to_world, dist, shape, color_p); } break; - case LIGHTPROBE_TYPE_VOLUME: + case LIGHTPROBE_TYPE_GRID: instdata.clip_sta = show_clipping ? prb->clipsta : -1.0; instdata.clip_end = show_clipping ? prb->clipend : -1.0; DRW_buffer_add_entry(cb->probe_grid, color_p, &instdata); @@ -787,7 +787,7 @@ void OVERLAY_lightprobe_cache_populate(OVERLAY_Data *vedata, Object *ob) DRW_shgroup_call_procedural_points(grp, nullptr, cell_count); } break; - case LIGHTPROBE_TYPE_PLANE: + case LIGHTPROBE_TYPE_PLANAR: DRW_buffer_add_entry(cb->probe_planar, color_p, &instdata); if (DRW_state_is_select() && (prb->flag & LIGHTPROBE_FLAG_SHOW_DATA)) { diff --git a/source/blender/draw/engines/overlay/overlay_grease_pencil.cc b/source/blender/draw/engines/overlay/overlay_grease_pencil.cc index 958104f814f..d7cb92098f2 100644 --- a/source/blender/draw/engines/overlay/overlay_grease_pencil.cc +++ b/source/blender/draw/engines/overlay/overlay_grease_pencil.cc @@ -8,8 +8,6 @@ #include "DRW_render.h" -#include "ED_grease_pencil.hh" - #include "BKE_grease_pencil.hh" #include "overlay_private.hh" @@ -18,9 +16,6 @@ void OVERLAY_edit_grease_pencil_cache_init(OVERLAY_Data *vedata) { OVERLAY_PassList *psl = vedata->psl; OVERLAY_PrivateData *pd = vedata->stl->pd; - const DRWContextState *draw_ctx = DRW_context_state_get(); - const eAttrDomain selection_domain = ED_grease_pencil_selection_domain_get( - draw_ctx->scene->toolsettings); GPUShader *sh; DRWShadingGroup *grp; @@ -29,32 +24,19 @@ void OVERLAY_edit_grease_pencil_cache_init(OVERLAY_Data *vedata) DRW_STATE_BLEND_ALPHA; DRW_PASS_CREATE(psl->edit_grease_pencil_ps, (state | pd->clipping_state)); - sh = OVERLAY_shader_edit_particle_strand(); - grp = pd->edit_grease_pencil_wires_grp = DRW_shgroup_create(sh, psl->edit_grease_pencil_ps); + sh = OVERLAY_shader_edit_particle_point(); + grp = pd->edit_grease_pencil_points_grp = DRW_shgroup_create(sh, psl->edit_grease_pencil_ps); DRW_shgroup_uniform_block(grp, "globalsBlock", G_draw.block_ubo); - - if (selection_domain == ATTR_DOMAIN_POINT) { - sh = OVERLAY_shader_edit_particle_point(); - grp = pd->edit_grease_pencil_points_grp = DRW_shgroup_create(sh, psl->edit_grease_pencil_ps); - DRW_shgroup_uniform_block(grp, "globalsBlock", G_draw.block_ubo); - } } void OVERLAY_edit_grease_pencil_cache_populate(OVERLAY_Data *vedata, Object *ob) { OVERLAY_PrivateData *pd = vedata->stl->pd; - DRWShadingGroup *lines_grp = pd->edit_grease_pencil_wires_grp; - if (lines_grp) { - GPUBatch *geom_lines = DRW_cache_grease_pencil_edit_lines_get(ob, pd->cfra); - - DRW_shgroup_call_no_cull(lines_grp, geom_lines, ob); - } - DRWShadingGroup *points_grp = pd->edit_grease_pencil_points_grp; if (points_grp) { - GPUBatch *geom_points = DRW_cache_grease_pencil_edit_points_get(ob, pd->cfra); - DRW_shgroup_call_no_cull(points_grp, geom_points, ob); + GPUBatch *geom = DRW_cache_grease_pencil_edit_points_get(ob, pd->cfra); + DRW_shgroup_call_no_cull(points_grp, geom, ob); } } diff --git a/source/blender/draw/engines/overlay/overlay_outline.cc b/source/blender/draw/engines/overlay/overlay_outline.cc index c7337edc01c..b215dff64f7 100644 --- a/source/blender/draw/engines/overlay/overlay_outline.cc +++ b/source/blender/draw/engines/overlay/overlay_outline.cc @@ -33,11 +33,11 @@ static void gpencil_depth_plane(Object *ob, float r_plane[4]) * strokes not aligned with the object axes. Maybe we could try to * compute the minimum axis of all strokes. But this would be more * computationally heavy and should go into the GPData evaluation. */ - const BoundBox bbox = *BKE_object_boundbox_get(ob); + const BoundBox *bbox = BKE_object_boundbox_get(ob); /* Convert bbox to matrix */ float mat[4][4], size[3], center[3]; - BKE_boundbox_calc_size_aabb(&bbox, size); - BKE_boundbox_calc_center_aabb(&bbox, center); + BKE_boundbox_calc_size_aabb(bbox, size); + BKE_boundbox_calc_center_aabb(bbox, center); unit_m4(mat); copy_v3_v3(mat[3], center); /* Avoid division by 0.0 later. */ diff --git a/source/blender/draw/engines/overlay/overlay_private.hh b/source/blender/draw/engines/overlay/overlay_private.hh index 390c1addc7c..7cf71331dda 100644 --- a/source/blender/draw/engines/overlay/overlay_private.hh +++ b/source/blender/draw/engines/overlay/overlay_private.hh @@ -251,7 +251,6 @@ struct OVERLAY_PrivateData { DRWShadingGroup *edit_lattice_wires_grp; DRWShadingGroup *edit_gpencil_points_grp; DRWShadingGroup *edit_gpencil_wires_grp; - DRWShadingGroup *edit_grease_pencil_wires_grp; DRWShadingGroup *edit_grease_pencil_points_grp; DRWShadingGroup *edit_gpencil_curve_handle_grp; DRWShadingGroup *edit_gpencil_curve_points_grp; diff --git a/source/blender/draw/engines/select/select_debug_engine.cc b/source/blender/draw/engines/select/select_debug_engine.cc index cb438ac34f0..bb01aec3a2b 100644 --- a/source/blender/draw/engines/select/select_debug_engine.cc +++ b/source/blender/draw/engines/select/select_debug_engine.cc @@ -12,12 +12,12 @@ #include "DNA_vec_types.h" #include "DRW_engine.h" -#include "DRW_select_buffer.hh" +#include "DRW_select_buffer.h" #include "draw_cache.h" #include "draw_manager.h" -#include "select_engine.hh" +#include "select_engine.h" #define SELECT_DEBUG_ENGINE "SELECT_DEBUG_ENGINE" diff --git a/source/blender/draw/engines/select/select_defines.hh b/source/blender/draw/engines/select/select_defines.h similarity index 100% rename from source/blender/draw/engines/select/select_defines.hh rename to source/blender/draw/engines/select/select_defines.h diff --git a/source/blender/draw/engines/select/select_draw_utils.cc b/source/blender/draw/engines/select/select_draw_utils.cc index bc3590bc306..81f1e3f28c6 100644 --- a/source/blender/draw/engines/select/select_draw_utils.cc +++ b/source/blender/draw/engines/select/select_draw_utils.cc @@ -20,16 +20,30 @@ #include "DEG_depsgraph.hh" #include "DEG_depsgraph_query.hh" -#include "DRW_select_buffer.hh" +#include "DRW_select_buffer.h" #include "draw_cache_impl.hh" -#include "select_private.hh" +#include "select_private.h" /* -------------------------------------------------------------------- */ /** \name Draw Utilities * \{ */ +void select_id_object_min_max(Object *obj, float r_min[3], float r_max[3]) +{ + const BoundBox *bb; + BMEditMesh *em = BKE_editmesh_from_object(obj); + if (em) { + bb = BKE_editmesh_cage_boundbox_get(obj, em); + } + else { + bb = BKE_object_boundbox_get(obj); + } + copy_v3_v3(r_min, bb->vec[0]); + copy_v3_v3(r_max, bb->vec[6]); +} + short select_id_get_object_select_mode(Scene *scene, Object *ob) { short r_select_mode = 0; diff --git a/source/blender/draw/engines/select/select_engine.cc b/source/blender/draw/engines/select/select_engine.cc index 95d40a75e6b..19d8165e007 100644 --- a/source/blender/draw/engines/select/select_engine.cc +++ b/source/blender/draw/engines/select/select_engine.cc @@ -15,13 +15,13 @@ #include "UI_resources.hh" #include "DRW_engine.h" -#include "DRW_select_buffer.hh" +#include "DRW_select_buffer.h" #include "draw_cache_impl.hh" #include "draw_manager.h" -#include "select_engine.hh" -#include "select_private.hh" +#include "select_engine.h" +#include "select_private.h" #define SELECT_ENGINE "SELECT_ENGINE" @@ -33,6 +33,7 @@ static struct { SELECTID_Shaders sh_data[GPU_SHADER_CFG_LEN]; SELECTID_Context context; + uint runtime_new_objects; } e_data = {nullptr}; /* Engine data */ /* -------------------------------------------------------------------- */ @@ -101,8 +102,23 @@ static void select_engine_init(void *vedata) } { - /* Create view with depth offset */ + /* Create view from a subregion */ const DRWView *view_default = DRW_view_default_get(); + float viewmat[4][4], winmat[4][4], winmat_subregion[4][4]; + DRW_view_viewmat_get(view_default, viewmat, false); + DRW_view_winmat_get(view_default, winmat, false); + projmat_from_subregion(winmat, + blender::int2{draw_ctx->region->winx, draw_ctx->region->winy}, + e_data.context.last_rect.xmin, + e_data.context.last_rect.xmax, + e_data.context.last_rect.ymin, + e_data.context.last_rect.ymax, + winmat_subregion); + + stl->g_data->view_subregion = DRW_view_create( + viewmat, winmat_subregion, nullptr, nullptr, nullptr); + + /* Create view with depth offset */ stl->g_data->view_faces = (DRWView *)view_default; stl->g_data->view_edges = DRW_view_create_with_zoffset(view_default, draw_ctx->rv3d, 1.0f); stl->g_data->view_verts = DRW_view_create_with_zoffset(view_default, draw_ctx->rv3d, 1.1f); @@ -125,9 +141,8 @@ static void select_cache_init(void *vedata) } DRWState state = DRW_STATE_DEFAULT; - if (RV3D_CLIPPING_ENABLED(draw_ctx->v3d, draw_ctx->rv3d)) { - state |= DRW_STATE_CLIP_PLANES; - } + state |= RV3D_CLIPPING_ENABLED(draw_ctx->v3d, draw_ctx->rv3d) ? DRW_STATE_CLIP_PLANES : + DRWState(0); bool retopology_occlusion = RETOPOLOGY_ENABLED(draw_ctx->v3d) && !XRAY_ENABLED(draw_ctx->v3d); float retopology_offset = RETOPOLOGY_OFFSET(draw_ctx->v3d); @@ -170,48 +185,95 @@ static void select_cache_init(void *vedata) } } - /* Create selection data. */ - for (uint sel_id : e_data.context.objects.index_range()) { - Object *obj_eval = e_data.context.objects[sel_id]; - DrawData *data = DRW_drawdata_ensure( - &obj_eval->id, &draw_engine_select_type, sizeof(SELECTID_ObjectData), nullptr, nullptr); - SELECTID_ObjectData *sel_data = reinterpret_cast(data); + /* Check if the viewport has changed. */ + float(*persmat)[4] = draw_ctx->rv3d->persmat; + e_data.context.is_dirty = !compare_m4m4(e_data.context.persmat, persmat, FLT_EPSILON); - data->recalc = 0; - sel_data->drawn_index = sel_id; - sel_data->in_pass = false; - sel_data->is_drawn = false; + if (!e_data.context.is_dirty) { + /* Check if any of the drawn objects have been transformed. */ + Object **ob = &e_data.context.objects_drawn[0]; + for (uint i = e_data.context.objects_drawn_len; i--; ob++) { + DrawData *data = DRW_drawdata_get(&(*ob)->id, &draw_engine_select_type); + if (data && (data->recalc & ID_RECALC_TRANSFORM) != 0) { + data->recalc &= ~ID_RECALC_TRANSFORM; + e_data.context.is_dirty = true; + } + } } - copy_m4_m4(e_data.context.persmat, draw_ctx->rv3d->persmat); - e_data.context.index_drawn_len = 1; - select_engine_framebuffer_setup(); - GPU_framebuffer_bind(e_data.framebuffer_select_id); - GPU_framebuffer_clear_color_depth(e_data.framebuffer_select_id, blender::float4{0.0f}, 1.0f); + if (e_data.context.is_dirty) { + /* Remove all tags from drawn or culled objects. */ + copy_m4_m4(e_data.context.persmat, persmat); + e_data.context.objects_drawn_len = 0; + e_data.context.index_drawn_len = 1; + select_engine_framebuffer_setup(); + GPU_framebuffer_bind(e_data.framebuffer_select_id); + GPU_framebuffer_clear_color_depth(e_data.framebuffer_select_id, blender::float4{0.0f}, 1.0f); + } + e_data.runtime_new_objects = 0; } static void select_cache_populate(void *vedata, Object *ob) { SELECTID_StorageList *stl = ((SELECTID_Data *)vedata)->stl; - SELECTID_ObjectData *sel_data = (SELECTID_ObjectData *)DRW_drawdata_get( - &ob->id, &draw_engine_select_type); + const DRWContextState *draw_ctx = DRW_context_state_get(); - if (!sel_data || sel_data->is_drawn) { - if (sel_data) { - /* Remove data, object is not in array. */ - DrawDataList *drawdata = DRW_drawdatalist_from_id(&ob->id); - BLI_freelinkN((ListBase *)drawdata, sel_data); - } - - /* This object is not in the array. It is here to participate in the depth buffer. */ + const bool retopology_occlusion = RETOPOLOGY_ENABLED(draw_ctx->v3d) && + !XRAY_ENABLED(draw_ctx->v3d); + if (retopology_occlusion && !DRW_object_is_in_edit_mode(ob)) { if (ob->dt >= OB_SOLID) { GPUBatch *geom_faces = DRW_mesh_batch_cache_get_surface(static_cast(ob->data)); DRW_shgroup_call_obmat(stl->g_data->shgrp_occlude, geom_faces, ob->object_to_world); } + return; } - else if (!sel_data->in_pass) { - const DRWContextState *draw_ctx = DRW_context_state_get(); - ObjectOffsets *ob_offsets = &e_data.context.index_offsets[sel_data->drawn_index]; + + SELECTID_ObjectData *sel_data = (SELECTID_ObjectData *)DRW_drawdata_get( + &ob->id, &draw_engine_select_type); + + if (!e_data.context.is_dirty && sel_data && sel_data->is_drawn) { + /* The object indices have already been drawn. Fill depth pass. + * Optimization: Most of the time this depth pass is not used. */ + Mesh *me = static_cast(ob->data); + if (e_data.context.select_mode & SCE_SELECT_FACE) { + GPUBatch *geom_faces = DRW_mesh_batch_cache_get_triangles_with_select_id(me); + DRW_shgroup_call_obmat(stl->g_data->shgrp_depth_only, geom_faces, ob->object_to_world); + } + else if (ob->dt >= OB_SOLID) { +#ifdef USE_CAGE_OCCLUSION + GPUBatch *geom_faces = DRW_mesh_batch_cache_get_triangles_with_select_id(me); +#else + struct GPUBatch *geom_faces = DRW_mesh_batch_cache_get_surface(me); +#endif + DRW_shgroup_call_obmat(stl->g_data->shgrp_depth_only, geom_faces, ob->object_to_world); + } + + if (e_data.context.select_mode & SCE_SELECT_EDGE) { + GPUBatch *geom_edges = DRW_mesh_batch_cache_get_edges_with_select_id(me); + DRW_shgroup_call_obmat(stl->g_data->shgrp_depth_only, geom_edges, ob->object_to_world); + } + + if (e_data.context.select_mode & SCE_SELECT_VERTEX) { + GPUBatch *geom_verts = DRW_mesh_batch_cache_get_verts_with_select_id(me); + DRW_shgroup_call_obmat(stl->g_data->shgrp_depth_only, geom_verts, ob->object_to_world); + } + return; + } + + float min[3], max[3]; + select_id_object_min_max(ob, min, max); + + if (DRW_culling_min_max_test(stl->g_data->view_subregion, ob->object_to_world, min, max)) { + if (sel_data == nullptr) { + sel_data = (SELECTID_ObjectData *)DRW_drawdata_ensure( + &ob->id, &draw_engine_select_type, sizeof(SELECTID_ObjectData), nullptr, nullptr); + } + sel_data->dd.recalc = 0; + sel_data->drawn_index = e_data.context.objects_drawn_len; + sel_data->is_drawn = true; + + ObjectOffsets *ob_offsets = &e_data.context.index_offsets[e_data.context.objects_drawn_len]; + uint offset = e_data.context.index_drawn_len; select_id_draw_object(vedata, draw_ctx->v3d, @@ -223,8 +285,13 @@ static void select_cache_populate(void *vedata, Object *ob) &ob_offsets->face); ob_offsets->offset = offset; - sel_data->in_pass = true; e_data.context.index_drawn_len = ob_offsets->vert; + e_data.context.objects_drawn[e_data.context.objects_drawn_len] = ob; + e_data.context.objects_drawn_len++; + e_data.runtime_new_objects++; + } + else if (sel_data) { + sel_data->is_drawn = false; } } @@ -233,6 +300,11 @@ static void select_draw_scene(void *vedata) SELECTID_StorageList *stl = ((SELECTID_Data *)vedata)->stl; SELECTID_PassList *psl = ((SELECTID_Data *)vedata)->psl; + if (!e_data.runtime_new_objects) { + /* Nothing new needs to be drawn. */ + return; + } + DRW_view_set_active(stl->g_data->view_faces); if (!DRW_pass_is_empty(psl->depth_only_pass)) { @@ -256,14 +328,6 @@ static void select_draw_scene(void *vedata) DRW_view_set_active(stl->g_data->view_verts); DRW_draw_pass(psl->select_id_vert_pass); } - - /* Mark objects from the array to later identify which ones are not in the array. */ - for (Object *obj_eval : e_data.context.objects) { - DrawData *data = DRW_drawdata_ensure( - &obj_eval->id, &draw_engine_select_type, sizeof(SELECTID_ObjectData), nullptr, nullptr); - SELECTID_ObjectData *sel_data = reinterpret_cast(data); - sel_data->is_drawn = true; - } } static void select_engine_free() @@ -276,8 +340,9 @@ static void select_engine_free() DRW_TEXTURE_FREE_SAFE(e_data.texture_u32); GPU_FRAMEBUFFER_FREE_SAFE(e_data.framebuffer_select_id); - e_data.context.objects.reinitialize(0); - e_data.context.index_offsets.reinitialize(0); + MEM_SAFE_FREE(e_data.context.objects); + MEM_SAFE_FREE(e_data.context.index_offsets); + MEM_SAFE_FREE(e_data.context.objects_drawn); } /** \} */ diff --git a/source/blender/draw/engines/select/select_engine.hh b/source/blender/draw/engines/select/select_engine.h similarity index 90% rename from source/blender/draw/engines/select/select_engine.hh rename to source/blender/draw/engines/select/select_engine.h index c2a80c25bae..881d2e8cf0b 100644 --- a/source/blender/draw/engines/select/select_engine.hh +++ b/source/blender/draw/engines/select/select_engine.h @@ -8,6 +8,10 @@ #pragma once +#ifdef __cplusplus +extern "C" { +#endif + /* `select_engine.cc` */ extern DrawEngineType draw_engine_select_type; @@ -20,9 +24,14 @@ extern DrawEngineType draw_engine_debug_select_type; #endif struct SELECTID_Context *DRW_select_engine_context_get(void); + struct GPUFrameBuffer *DRW_engine_select_framebuffer_get(void); struct GPUTexture *DRW_engine_select_texture_get(void); /* select_instance.cc */ extern DrawEngineType draw_engine_select_next_type; + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/draw/engines/select/select_instance.cc b/source/blender/draw/engines/select/select_instance.cc index 793378fdde4..9df4c7594f8 100644 --- a/source/blender/draw/engines/select/select_instance.cc +++ b/source/blender/draw/engines/select/select_instance.cc @@ -10,7 +10,7 @@ #include "GPU_capabilities.h" -#include "select_engine.hh" +#include "select_engine.h" #include "../overlay/overlay_next_instance.hh" #include "select_instance.hh" diff --git a/source/blender/draw/engines/select/select_instance.hh b/source/blender/draw/engines/select/select_instance.hh index 0f7765063e6..f612f480000 100644 --- a/source/blender/draw/engines/select/select_instance.hh +++ b/source/blender/draw/engines/select/select_instance.hh @@ -19,7 +19,7 @@ #include "gpu_shader_create_info.hh" -#include "select_defines.hh" +#include "select_defines.h" #include "select_shader_shared.hh" namespace blender::draw::select { diff --git a/source/blender/draw/engines/select/select_private.hh b/source/blender/draw/engines/select/select_private.h similarity index 63% rename from source/blender/draw/engines/select/select_private.hh rename to source/blender/draw/engines/select/select_private.h index ab350e76ec0..7e505edd8f3 100644 --- a/source/blender/draw/engines/select/select_private.hh +++ b/source/blender/draw/engines/select/select_private.h @@ -12,34 +12,38 @@ #include "DRW_render.h" +#ifdef __cplusplus +extern "C" { +#endif + /* GPUViewport.storage * Is freed every time the viewport engine changes. */ -struct SELECTID_StorageList { +typedef struct SELECTID_StorageList { struct SELECTID_PrivateData *g_data; -}; +} SELECTID_StorageList; -struct SELECTID_PassList { - DRWPass *depth_only_pass; - DRWPass *select_id_face_pass; - DRWPass *select_id_edge_pass; - DRWPass *select_id_vert_pass; -}; +typedef struct SELECTID_PassList { + struct DRWPass *depth_only_pass; + struct DRWPass *select_id_face_pass; + struct DRWPass *select_id_edge_pass; + struct DRWPass *select_id_vert_pass; +} SELECTID_PassList; -struct SELECTID_Data { +typedef struct SELECTID_Data { void *engine_type; DRWViewportEmptyList *fbl; DRWViewportEmptyList *txl; SELECTID_PassList *psl; SELECTID_StorageList *stl; -}; +} SELECTID_Data; -struct SELECTID_Shaders { +typedef struct SELECTID_Shaders { /* Depth Pre Pass */ - GPUShader *select_id_flat; - GPUShader *select_id_uniform; -}; + struct GPUShader *select_id_flat; + struct GPUShader *select_id_uniform; +} SELECTID_Shaders; -struct SELECTID_PrivateData { +typedef struct SELECTID_PrivateData { DRWShadingGroup *shgrp_depth_only; DRWShadingGroup *shgrp_occlude; DRWShadingGroup *shgrp_face_unif; @@ -47,13 +51,15 @@ struct SELECTID_PrivateData { DRWShadingGroup *shgrp_edge; DRWShadingGroup *shgrp_vert; + DRWView *view_subregion; DRWView *view_faces; DRWView *view_edges; DRWView *view_verts; -}; /* Transient data */ +} SELECTID_PrivateData; /* Transient data */ /* `select_draw_utils.cc` */ +void select_id_object_min_max(struct Object *obj, float r_min[3], float r_max[3]); short select_id_get_object_select_mode(Scene *scene, Object *ob); void select_id_draw_object(void *vedata, View3D *v3d, @@ -63,3 +69,7 @@ void select_id_draw_object(void *vedata, uint *r_vert_offset, uint *r_edge_offset, uint *r_face_offset); + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/draw/engines/select/shaders/infos/select_id_info.hh b/source/blender/draw/engines/select/shaders/infos/select_id_info.hh index 345ebeaa9b6..80a61c18069 100644 --- a/source/blender/draw/engines/select/shaders/infos/select_id_info.hh +++ b/source/blender/draw/engines/select/shaders/infos/select_id_info.hh @@ -3,7 +3,7 @@ * SPDX-License-Identifier: GPL-2.0-or-later */ #include "gpu_shader_create_info.hh" -#include "select_defines.hh" +#include "select_defines.h" /* -------------------------------------------------------------------- */ /** \name Select ID for Edit Mesh Selection diff --git a/source/blender/draw/engines/workbench/workbench_shadow.cc b/source/blender/draw/engines/workbench/workbench_shadow.cc index 2ceb036bcc8..06f1bbdff20 100644 --- a/source/blender/draw/engines/workbench/workbench_shadow.cc +++ b/source/blender/draw/engines/workbench/workbench_shadow.cc @@ -183,11 +183,11 @@ void ShadowPass::ShadowView::setup(View &view, float3 light_direction, bool forc bool ShadowPass::ShadowView::debug_object_culling(Object *ob) { printf("Test %s\n", ob->id.name); - const BoundBox _bbox = *BKE_object_boundbox_get(ob); + const BoundBox *_bbox = BKE_object_boundbox_get(ob); for (int p : IndexRange(extruded_frustum_.planes_count)) { float4 plane = extruded_frustum_.planes[p]; bool separating_axis = true; - for (float3 corner : _bbox.vec) { + for (float3 corner : _bbox->vec) { corner = math::transform_point(float4x4(ob->object_to_world), corner); float signed_distance = math::dot(corner, float3(plane)) - plane.w; if (signed_distance <= 0) { diff --git a/source/blender/draw/intern/DRW_gpu_wrapper.hh b/source/blender/draw/intern/DRW_gpu_wrapper.hh index 3bdbc6ae6bd..f2b71a21bba 100644 --- a/source/blender/draw/intern/DRW_gpu_wrapper.hh +++ b/source/blender/draw/intern/DRW_gpu_wrapper.hh @@ -64,8 +64,6 @@ #include "draw_manager.h" #include "draw_texture_pool.h" -#include "BKE_global.h" - #include "BLI_math_vector_types.hh" #include "BLI_span.hh" #include "BLI_utildefines.h" @@ -248,11 +246,6 @@ class StorageCommon : public DataBuffer, NonMovable, NonCopyable GPU_storagebuf_clear_to_zero(ssbo_); } - void async_flush_to_host() - { - GPU_storagebuf_sync_to_host(ssbo_); - } - void read() { GPU_storagebuf_read(ssbo_, this->data_); @@ -569,7 +562,6 @@ class Texture : NonCopyable { tx_ = create(UNPACK3(extent), mip_len, format, usage, data, false, false); } - Texture(Texture &&other) = default; ~Texture() { free(); @@ -865,25 +857,6 @@ class Texture : NonCopyable { GPU_texture_clear(tx_, GPU_DATA_INT, &values[0]); } - /** - * Clear the texture to NaN for floats, or a to debug value for integers. - * (For debugging uninitialized data issues) - */ - void debug_clear() - { - if (GPU_texture_has_float_format(this->tx_) || GPU_texture_has_normalized_format(this->tx_)) { - this->clear(float4(NAN_FLT)); - } - else if (GPU_texture_has_integer_format(this->tx_)) { - if (GPU_texture_has_signed_format(this->tx_)) { - this->clear(int4(0xF0F0F0F0)); - } - else { - this->clear(uint4(0xF0F0F0F0)); - } - } - } - /** * Returns a buffer containing the texture data for the specified miplvl. * The memory block needs to be manually freed by MEM_freeN(). @@ -949,9 +922,6 @@ class Texture : NonCopyable { } if (tx_ == nullptr) { tx_ = create(w, h, d, mip_len, format, usage, data, layered, cubemap); - if (data == nullptr && (G.debug & G_DEBUG_GPU)) { - debug_clear(); - } return true; } return false; @@ -1010,10 +980,6 @@ class TextureFromPool : public Texture, NonMovable { this->tx_ = DRW_texture_pool_texture_acquire( DST.vmempool->texture_pool, UNPACK2(extent), format, usage); - - if (G.debug & G_DEBUG_GPU) { - debug_clear(); - } } void release() diff --git a/source/blender/draw/intern/draw_cache.h b/source/blender/draw/intern/draw_cache.h index d6df94513d6..821f8d12951 100644 --- a/source/blender/draw/intern/draw_cache.h +++ b/source/blender/draw/intern/draw_cache.h @@ -281,7 +281,6 @@ void DRW_cache_gpencil_sbuffer_clear(struct Object *ob); struct GPUBatch *DRW_cache_grease_pencil_get(struct Object *ob, int cfra); struct GPUBatch *DRW_cache_grease_pencil_edit_points_get(struct Object *ob, int cfra); -struct GPUBatch *DRW_cache_grease_pencil_edit_lines_get(struct Object *ob, int cfra); struct GPUVertBuf *DRW_cache_grease_pencil_position_buffer_get(struct Object *ob, int cfra); struct GPUVertBuf *DRW_cache_grease_pencil_color_buffer_get(struct Object *ob, int cfra); diff --git a/source/blender/draw/intern/draw_cache_extract_mesh_render_data.cc b/source/blender/draw/intern/draw_cache_extract_mesh_render_data.cc index c3b5ee31834..e5594396362 100644 --- a/source/blender/draw/intern/draw_cache_extract_mesh_render_data.cc +++ b/source/blender/draw/intern/draw_cache_extract_mesh_render_data.cc @@ -11,12 +11,9 @@ #include "MEM_guardedalloc.h" #include "BLI_array.hh" -#include "BLI_array_utils.hh" #include "BLI_enumerable_thread_specific.hh" -#include "BLI_index_mask.hh" #include "BLI_math_matrix.h" #include "BLI_task.hh" -#include "BLI_virtual_array.hh" #include "BKE_attribute.hh" #include "BKE_editmesh.h" @@ -355,70 +352,39 @@ void mesh_render_data_update_looptris(MeshRenderData &mr, } } -static bool bm_edge_is_sharp(const BMEdge *const &edge) -{ - return !BM_elem_flag_test(edge, BM_ELEM_SMOOTH); -} - -static bool bm_face_is_sharp(const BMFace *const &face) -{ - return !BM_elem_flag_test(face, BM_ELEM_SMOOTH); -} - -/** - * Returns whether loop normals are required because of mixed sharp and smooth flags. - * Similar to #Mesh::normals_domain(). - */ -static bool bm_loop_normals_required(BMesh *bm) -{ - using namespace blender; - using namespace blender::bke; - if (bm->totface == 0) { - return false; - } - - if (CustomData_has_layer(&bm->ldata, CD_CUSTOMLOOPNORMAL)) { - return true; - } - - BM_mesh_elem_table_ensure(bm, BM_FACE); - const VArray sharp_faces = VArray::ForDerivedSpan( - Span(bm->ftable, bm->totface)); - const array_utils::BooleanMix face_mix = array_utils::booleans_mix_calc(sharp_faces); - if (face_mix == array_utils::BooleanMix::AllTrue) { - return false; - } - - BM_mesh_elem_table_ensure(bm, BM_EDGE); - const VArray sharp_edges = VArray::ForDerivedSpan( - Span(bm->etable, bm->totedge)); - const array_utils::BooleanMix edge_mix = array_utils::booleans_mix_calc(sharp_edges); - if (edge_mix == array_utils::BooleanMix::AllTrue) { - return false; - } - - if (edge_mix == array_utils::BooleanMix::AllFalse && - face_mix == array_utils::BooleanMix::AllFalse) { - return false; - } - - return true; -} - void mesh_render_data_update_normals(MeshRenderData &mr, const eMRDataType data_flag) { + Mesh *me = mr.me; + const bool is_auto_smooth = (me->flag & ME_AUTOSMOOTH) != 0; + const float split_angle = is_auto_smooth ? me->smoothresh : float(M_PI); + if (mr.extract_type != MR_EXTRACT_BMESH) { /* Mesh */ mr.vert_normals = mr.me->vert_normals(); if (data_flag & (MR_DATA_POLY_NOR | MR_DATA_LOOP_NOR | MR_DATA_TAN_LOOP_NOR)) { mr.face_normals = mr.me->face_normals(); } - if (((data_flag & MR_DATA_LOOP_NOR) && ELEM(mr.me->normals_domain(), - blender::bke::MeshNormalDomain::Corner, - blender::bke::MeshNormalDomain::Face)) || - (data_flag & MR_DATA_TAN_LOOP_NOR)) - { - mr.loop_normals = mr.me->corner_normals(); + if (((data_flag & MR_DATA_LOOP_NOR) && is_auto_smooth) || (data_flag & MR_DATA_TAN_LOOP_NOR)) { + mr.loop_normals.reinitialize(mr.corner_verts.size()); + const blender::short2 *clnors = static_cast( + CustomData_get_layer(&mr.me->loop_data, CD_CUSTOMLOOPNORMAL)); + const bool *sharp_edges = static_cast( + CustomData_get_layer_named(&mr.me->edge_data, CD_PROP_BOOL, "sharp_edge")); + blender::bke::mesh::normals_calc_loop(mr.vert_positions, + mr.edges, + mr.faces, + mr.corner_verts, + mr.corner_edges, + mr.me->corner_to_face_map(), + mr.vert_normals, + mr.face_normals, + sharp_edges, + mr.sharp_faces, + clnors, + is_auto_smooth, + split_angle, + nullptr, + mr.loop_normals); } } else { @@ -426,9 +392,7 @@ void mesh_render_data_update_normals(MeshRenderData &mr, const eMRDataType data_ if (data_flag & MR_DATA_POLY_NOR) { /* Use #BMFace.no instead. */ } - if (((data_flag & MR_DATA_LOOP_NOR) && bm_loop_normals_required(mr.bm)) || - (data_flag & MR_DATA_TAN_LOOP_NOR)) - { + if (((data_flag & MR_DATA_LOOP_NOR) && is_auto_smooth) || (data_flag & MR_DATA_TAN_LOOP_NOR)) { const float(*vert_coords)[3] = nullptr; const float(*vert_normals)[3] = nullptr; @@ -440,19 +404,19 @@ void mesh_render_data_update_normals(MeshRenderData &mr, const eMRDataType data_ face_normals = reinterpret_cast(mr.bm_face_normals.data()); } - mr.bm_loop_normals.reinitialize(mr.loop_len); + mr.loop_normals.reinitialize(mr.loop_len); const int clnors_offset = CustomData_get_offset(&mr.bm->ldata, CD_CUSTOMLOOPNORMAL); BM_loops_calc_normal_vcos(mr.bm, vert_coords, vert_normals, face_normals, - true, - reinterpret_cast(mr.bm_loop_normals.data()), + is_auto_smooth, + split_angle, + reinterpret_cast(mr.loop_normals.data()), nullptr, nullptr, clnors_offset, false); - mr.loop_normals = mr.bm_loop_normals; } } } diff --git a/source/blender/draw/intern/draw_cache_impl_grease_pencil.cc b/source/blender/draw/intern/draw_cache_impl_grease_pencil.cc index 05a99f6f3f0..4f7ffd2701c 100644 --- a/source/blender/draw/intern/draw_cache_impl_grease_pencil.cc +++ b/source/blender/draw/intern/draw_cache_impl_grease_pencil.cc @@ -37,14 +37,11 @@ struct GreasePencilBatchCache { /** Batches */ GPUBatch *geom_batch; GPUBatch *edit_points; - GPUBatch *edit_lines; /* Crazy-space point positions for original points. */ GPUVertBuf *edit_points_pos; /* Selection of original points. */ GPUVertBuf *edit_points_selection; - /* Indices for lines segments. */ - GPUIndexBuf *edit_line_indices; /** Cache is dirty. */ bool is_dirty; @@ -142,10 +139,8 @@ static void grease_pencil_batch_cache_clear(GreasePencil &grease_pencil) GPU_INDEXBUF_DISCARD_SAFE(cache->ibo); GPU_BATCH_DISCARD_SAFE(cache->edit_points); - GPU_BATCH_DISCARD_SAFE(cache->edit_lines); GPU_VERTBUF_DISCARD_SAFE(cache->edit_points_pos); GPU_VERTBUF_DISCARD_SAFE(cache->edit_points_selection); - GPU_INDEXBUF_DISCARD_SAFE(cache->edit_line_indices); cache->is_dirty = true; } @@ -192,66 +187,7 @@ BLI_INLINE int32_t pack_rotation_aspect_hardness(float rot, float asp, float har return packed; } -static void grease_pencil_edit_lines_batch_ensure( - const Span drawings, - GreasePencilBatchCache *cache) -{ - using namespace blender::bke::greasepencil; - int total_line_ids_num = 0; - - for (const Drawing *drawing : drawings) { - const bke::CurvesGeometry &curves = drawing->strokes(); - /* Add one id for the restart after every curve. */ - total_line_ids_num += curves.curves_num(); - /* Add one id for every non-cyclic segment. */ - total_line_ids_num += curves.points_num(); - /* Add one id for the last segment of every cyclic curve. */ - total_line_ids_num += array_utils::count_booleans(curves.cyclic()); - } - - GPUIndexBufBuilder elb; - GPU_indexbuf_init_ex(&elb, - GPU_PRIM_LINE_STRIP, - total_line_ids_num, - GPU_vertbuf_get_vertex_len(cache->edit_points_pos)); - - /* Fill buffers with data. */ - int drawing_start_offset = 0; - for (const int drawing_i : drawings.index_range()) { - const Drawing &drawing = *drawings[drawing_i]; - const bke::CurvesGeometry &curves = drawing.strokes(); - const OffsetIndices points_by_curve = curves.points_by_curve(); - const VArray cyclic = curves.cyclic(); - - threading::parallel_for(curves.curves_range(), 512, [&](IndexRange range) { - for (const int curve_i : range) { - const IndexRange points = points_by_curve[curve_i]; - const bool is_cyclic = cyclic[curve_i]; - - for (const int point : points) { - GPU_indexbuf_add_generic_vert(&elb, point + drawing_start_offset); - } - - if (is_cyclic) { - GPU_indexbuf_add_generic_vert(&elb, points.first() + drawing_start_offset); - } - - GPU_indexbuf_add_primitive_restart(&elb); - } - }); - drawing_start_offset += curves.points_num(); - } - - cache->edit_line_indices = GPU_indexbuf_build(&elb); - - cache->edit_lines = GPU_batch_create( - GPU_PRIM_LINE_STRIP, cache->edit_points_pos, cache->edit_line_indices); - GPU_batch_vertbuf_add(cache->edit_lines, cache->edit_points_selection, false); -} - -static void grease_pencil_geom_batch_ensure(const GreasePencil &grease_pencil, - int cfra, - const bool lines) +static void grease_pencil_geom_batch_ensure(GreasePencil &grease_pencil, int cfra) { using namespace blender::bke::greasepencil; BLI_assert(grease_pencil.runtime != nullptr); @@ -267,9 +203,9 @@ static void grease_pencil_geom_batch_ensure(const GreasePencil &grease_pencil, BLI_assert(cache->geom_batch == nullptr); /* Get the visible drawings. */ - Vector drawings; + Vector drawings; grease_pencil.foreach_visible_drawing( - cfra, [&](const int /*layer_index*/, const Drawing &drawing) { drawings.append(&drawing); }); + cfra, [&](int /*drawing_index*/, Drawing &drawing) { drawings.append(&drawing); }); /* First, count how many vertices and triangles are needed for the whole object. Also record the * offsets into the curves for the vertices and triangles. */ @@ -320,6 +256,13 @@ static void grease_pencil_geom_batch_ensure(const GreasePencil &grease_pencil, tris_start_offsets_per_visible_drawing.append(std::move(tris_start_offsets)); } + if (grease_pencil.runtime->has_stroke_buffer()) { + const int num_buffer_points = grease_pencil.runtime->stroke_buffer().size(); + total_verts_num += 1 + num_buffer_points + 1; + total_triangles_num += num_buffer_points * 2; + /* TODO: triangles for stroke buffer. */ + } + static GPUVertFormat format_edit_points_pos = {0}; if (format_edit_points_pos.attr_len == 0) { GPU_vertformat_attr_add(&format_edit_points_pos, "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT); @@ -373,8 +316,6 @@ static void grease_pencil_geom_batch_ensure(const GreasePencil &grease_pencil, const VArray cyclic = curves.cyclic(); const VArray radii = drawing.radii(); const VArray opacities = drawing.opacities(); - const VArray vertex_colors = *attributes.lookup_or_default( - "vertex_color", ATTR_DOMAIN_POINT, ColorGeometry4f(0.0f, 0.0f, 0.0f, 0.0f)); /* Assumes that if the ".selection" attribute does not exist, all points are selected. */ const VArray selection_float = *attributes.lookup_or_default( ".selection", ATTR_DOMAIN_POINT, true); @@ -417,8 +358,9 @@ static void grease_pencil_geom_batch_ensure(const GreasePencil &grease_pencil, /* TODO: Populate fill UVs. */ s_vert.uv_fill[0] = s_vert.uv_fill[1] = 0; - copy_v4_v4(c_vert.vcol, vertex_colors[point_i]); - copy_v4_v4(c_vert.fcol, vertex_colors[point_i]); + /* TODO: Populate vertex color and fill color. */ + copy_v4_v4(c_vert.vcol, float4(0.0f, 0.0f, 0.0f, 0.0f)); + copy_v4_v4(c_vert.fcol, float4(0.0f, 0.0f, 0.0f, 0.0f)); c_vert.fcol[3] = (int(c_vert.fcol[3] * 10000.0f) * 10.0f) + 1.0f; int v_mat = (verts_range[idx] << GP_VERTEX_ID_SHIFT) | GP_IS_STROKE_VERTEX_BIT; @@ -482,8 +424,49 @@ static void grease_pencil_geom_batch_ensure(const GreasePencil &grease_pencil, }); } - if (lines) { - grease_pencil_edit_lines_batch_ensure(drawings, cache); + if (grease_pencil.runtime->has_stroke_buffer()) { + Span points = grease_pencil.runtime->stroke_buffer(); + const int verts_start_offset = v_offset; + const int num_verts = 1 + points.size() + 1; + IndexRange verts_range = IndexRange(verts_start_offset, num_verts); + MutableSpan verts_slice = verts.slice(verts_range); + MutableSpan cols_slice = cols.slice(verts_range); + const int material_nr = grease_pencil.runtime->stroke_cache.mat; + + verts_slice.first().mat = -1; + for (const int i : IndexRange(points.size())) { + const int idx = i + 1; + GreasePencilStrokeVert &s_vert = verts_slice[idx]; + GreasePencilColorVert &c_vert = cols_slice[idx]; + const bke::greasepencil::StrokePoint &point = points[i]; + + copy_v3_v3(s_vert.pos, point.position); + s_vert.radius = point.radius; + s_vert.opacity = point.opacity; + s_vert.point_id = verts_range[idx]; + s_vert.stroke_id = verts_range.first(); + s_vert.mat = material_nr; + + /* TODO */ + s_vert.packed_asp_hard_rot = pack_rotation_aspect_hardness(0.0f, 1.0f, 1.0f); + /* TODO */ + s_vert.u_stroke = 0; + /* TODO */ + s_vert.uv_fill[0] = s_vert.uv_fill[1] = 0; + + /* TODO */ + copy_v4_v4(c_vert.vcol, float4(0.0f, 0.0f, 0.0f, 0.0f)); + copy_v4_v4(c_vert.fcol, float4(0.0f, 0.0f, 0.0f, 0.0f)); + + /* TODO */ + c_vert.fcol[3] = (int(c_vert.fcol[3] * 10000.0f) * 10.0f) + 1.0f; + + int v_mat = (verts_range[idx] << GP_VERTEX_ID_SHIFT) | GP_IS_STROKE_VERTEX_BIT; + GPU_indexbuf_add_tri_verts(&ibo, v_mat + 0, v_mat + 1, v_mat + 2); + GPU_indexbuf_add_tri_verts(&ibo, v_mat + 2, v_mat + 1, v_mat + 3); + } + + verts_slice.last().mat = -1; } /* Mark last 2 verts as invalid. */ @@ -552,7 +535,7 @@ GPUBatch *DRW_cache_grease_pencil_get(Object *ob, int cfra) using namespace blender::draw; GreasePencil &grease_pencil = *static_cast(ob->data); GreasePencilBatchCache *cache = grease_pencil_batch_cache_get(grease_pencil); - grease_pencil_geom_batch_ensure(grease_pencil, cfra, false); + grease_pencil_geom_batch_ensure(grease_pencil, cfra); return cache->geom_batch; } @@ -562,27 +545,17 @@ GPUBatch *DRW_cache_grease_pencil_edit_points_get(Object *ob, int cfra) using namespace blender::draw; GreasePencil &grease_pencil = *static_cast(ob->data); GreasePencilBatchCache *cache = grease_pencil_batch_cache_get(grease_pencil); - grease_pencil_geom_batch_ensure(grease_pencil, cfra, false); + grease_pencil_geom_batch_ensure(grease_pencil, cfra); return cache->edit_points; } -GPUBatch *DRW_cache_grease_pencil_edit_lines_get(Object *ob, int cfra) -{ - using namespace blender::draw; - GreasePencil &grease_pencil = *static_cast(ob->data); - GreasePencilBatchCache *cache = grease_pencil_batch_cache_get(grease_pencil); - grease_pencil_geom_batch_ensure(grease_pencil, cfra, true); - - return cache->edit_lines; -} - GPUVertBuf *DRW_cache_grease_pencil_position_buffer_get(Object *ob, int cfra) { using namespace blender::draw; GreasePencil &grease_pencil = *static_cast(ob->data); GreasePencilBatchCache *cache = grease_pencil_batch_cache_get(grease_pencil); - grease_pencil_geom_batch_ensure(grease_pencil, cfra, true); + grease_pencil_geom_batch_ensure(grease_pencil, cfra); return cache->vbo; } @@ -592,7 +565,7 @@ GPUVertBuf *DRW_cache_grease_pencil_color_buffer_get(Object *ob, int cfra) using namespace blender::draw; GreasePencil &grease_pencil = *static_cast(ob->data); GreasePencilBatchCache *cache = grease_pencil_batch_cache_get(grease_pencil); - grease_pencil_geom_batch_ensure(grease_pencil, cfra, false); + grease_pencil_geom_batch_ensure(grease_pencil, cfra); return cache->vbo_col; } diff --git a/source/blender/draw/intern/draw_cache_impl_subdivision.cc b/source/blender/draw/intern/draw_cache_impl_subdivision.cc index 5aacb8aa5a6..ab855adb901 100644 --- a/source/blender/draw/intern/draw_cache_impl_subdivision.cc +++ b/source/blender/draw/intern/draw_cache_impl_subdivision.cc @@ -23,7 +23,7 @@ #include "BLI_linklist.h" #include "BLI_string.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_virtual_array.hh" #include "PIL_time.h" @@ -2152,8 +2152,9 @@ static bool draw_subdiv_create_requested_buffers(Object *ob, runtime_data->stats_totloop = draw_cache.num_subdiv_loops; draw_cache.use_custom_loop_normals = (runtime_data->use_loop_normals) && - mesh_eval->normals_domain() == - blender::bke::MeshNormalDomain::Corner; + (mesh_eval->flag & ME_AUTOSMOOTH) && + CustomData_has_layer(&mesh_eval->loop_data, + CD_CUSTOMLOOPNORMAL); if (DRW_ibo_requested(mbc.buff.ibo.tris)) { draw_subdiv_cache_ensure_mat_offsets(draw_cache, mesh_eval, batch_cache.mat_len); diff --git a/source/blender/draw/intern/draw_command.cc b/source/blender/draw/intern/draw_command.cc index 6aa50f1d94d..45c334330b7 100644 --- a/source/blender/draw/intern/draw_command.cc +++ b/source/blender/draw/intern/draw_command.cc @@ -37,26 +37,6 @@ void FramebufferBind::execute() const GPU_framebuffer_bind(*framebuffer); } -void SubPassTransition::execute() const -{ - /* TODO(fclem): Require framebuffer bind to always be part of the pass so that we can track it - * inside RecordingState. */ - GPUFrameBuffer *framebuffer = GPU_framebuffer_active_get(); - /* Unpack to the real enum type. */ - const GPUAttachmentState states[9] = { - GPUAttachmentState(depth_state), - GPUAttachmentState(color_states[0]), - GPUAttachmentState(color_states[1]), - GPUAttachmentState(color_states[2]), - GPUAttachmentState(color_states[3]), - GPUAttachmentState(color_states[4]), - GPUAttachmentState(color_states[5]), - GPUAttachmentState(color_states[6]), - GPUAttachmentState(color_states[7]), - }; - GPU_framebuffer_subpass_transition_array(framebuffer, states, ARRAY_SIZE(states)); -} - void ResourceBind::execute() const { if (slot == -1) { @@ -278,26 +258,6 @@ std::string FramebufferBind::serialize() const (*framebuffer == nullptr ? "nullptr" : GPU_framebuffer_get_name(*framebuffer)) + ")"; } -std::string SubPassTransition::serialize() const -{ - auto to_str = [](GPUAttachmentState state) { - return (state != GPU_ATTACHEMENT_IGNORE) ? - ((state == GPU_ATTACHEMENT_WRITE) ? "write" : "read") : - "ignore"; - }; - - return std::string(".subpass_transition(\n") + - "depth=" + to_str(GPUAttachmentState(depth_state)) + ",\n" + - "color0=" + to_str(GPUAttachmentState(color_states[0])) + ",\n" + - "color1=" + to_str(GPUAttachmentState(color_states[1])) + ",\n" + - "color2=" + to_str(GPUAttachmentState(color_states[2])) + ",\n" + - "color3=" + to_str(GPUAttachmentState(color_states[3])) + ",\n" + - "color4=" + to_str(GPUAttachmentState(color_states[4])) + ",\n" + - "color5=" + to_str(GPUAttachmentState(color_states[5])) + ",\n" + - "color6=" + to_str(GPUAttachmentState(color_states[6])) + ",\n" + - "color7=" + to_str(GPUAttachmentState(color_states[7])) + "\n)"; -} - std::string ResourceBind::serialize() const { switch (type) { @@ -682,7 +642,7 @@ void DrawMultiBuf::bind(RecordingState &state, group.vertex_len = num_input_primitives * GPU_shader_get_ssbo_vertex_fetch_num_verts_per_prim(group.gpu_shader); /* Override base index to -1, as all SSBO calls are submitted as non-indexed, with the - * index buffer indirection handled within the implementation. This is to ensure + * index buffer indirection handled within the implemnetation. This is to ensure * command generation can correctly assigns baseInstance in the non-indexed formatting. */ group.base_index = -1; } diff --git a/source/blender/draw/intern/draw_command.hh b/source/blender/draw/intern/draw_command.hh index 6f31e80584f..00c0b7293d5 100644 --- a/source/blender/draw/intern/draw_command.hh +++ b/source/blender/draw/intern/draw_command.hh @@ -100,7 +100,6 @@ enum class Type : uint8_t { PushConstant, ResourceBind, ShaderBind, - SubPassTransition, StateSet, StencilSet, @@ -135,16 +134,6 @@ struct FramebufferBind { std::string serialize() const; }; -struct SubPassTransition { - /** \note uint8_t storing `GPUAttachmentState` for compactness. */ - uint8_t depth_state; - /** \note 8 is GPU_FB_MAX_COLOR_ATTACHMENT. */ - uint8_t color_states[8]; - - void execute() const; - std::string serialize() const; -}; - struct ResourceBind { GPUSamplerState sampler; int slot; @@ -401,7 +390,6 @@ union Undetermined { ShaderBind shader_bind; ResourceBind resource_bind; FramebufferBind framebuffer_bind; - SubPassTransition subpass_transition; PushConstant push_constant; Draw draw; DrawMulti draw_multi; diff --git a/source/blender/draw/intern/draw_curves.cc b/source/blender/draw/intern/draw_curves.cc index 507254a0624..042979ad511 100644 --- a/source/blender/draw/intern/draw_curves.cc +++ b/source/blender/draw/intern/draw_curves.cc @@ -8,7 +8,7 @@ * \brief Contains procedural GPU hair drawing methods. */ -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_utildefines.h" #include "DNA_curves_types.h" diff --git a/source/blender/draw/intern/draw_defines.h b/source/blender/draw/intern/draw_defines.h index 6b2248d4a7d..7b1936347cf 100644 --- a/source/blender/draw/intern/draw_defines.h +++ b/source/blender/draw/intern/draw_defines.h @@ -37,10 +37,3 @@ #define DRW_FINALIZE_GROUP_SIZE 64 /* Must be multiple of 32. Set to 32 for shader simplicity. */ #define DRW_VISIBILITY_GROUP_SIZE 32 - -/** - * The maximum of indexable views is dictated by: - * - The UBO limit (16KiB) of the ViewMatrices container. - * - The maximum resource index supported for shaders using multi-view (see DRW_VIEW_SHIFT). - */ -#define DRW_VIEW_MAX 64 diff --git a/source/blender/draw/intern/draw_hair.cc b/source/blender/draw/intern/draw_hair.cc index 729dfb97931..06bda743ab9 100644 --- a/source/blender/draw/intern/draw_hair.cc +++ b/source/blender/draw/intern/draw_hair.cc @@ -10,7 +10,7 @@ #include "DRW_render.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_utildefines.h" #include "DNA_collection_types.h" diff --git a/source/blender/draw/intern/draw_manager_c.cc b/source/blender/draw/intern/draw_manager_c.cc index 9bc6108c80f..014eeb6a0ba 100644 --- a/source/blender/draw/intern/draw_manager_c.cc +++ b/source/blender/draw/intern/draw_manager_c.cc @@ -95,7 +95,7 @@ #include "engines/gpencil/gpencil_engine.h" #include "engines/image/image_engine.h" #include "engines/overlay/overlay_engine.h" -#include "engines/select/select_engine.hh" +#include "engines/select/select_engine.h" #include "engines/workbench/workbench_engine.h" #include "GPU_context.h" @@ -103,7 +103,7 @@ #include "DEG_depsgraph.hh" #include "DEG_depsgraph_query.hh" -#include "DRW_select_buffer.hh" +#include "DRW_select_buffer.h" /** Render State: No persistent data between draw calls. */ DRWManager DST = {nullptr}; @@ -839,7 +839,6 @@ static bool id_type_can_have_drawdata(const short id_type) case ID_SCE: case ID_TE: case ID_MSK: - case ID_MC: return true; /* no DrawData */ @@ -2752,20 +2751,21 @@ void DRW_draw_depth_loop(Depsgraph *depsgraph, drw_manager_exit(&DST); } -void DRW_draw_select_id(Depsgraph *depsgraph, ARegion *region, View3D *v3d) +void DRW_draw_select_id(Depsgraph *depsgraph, ARegion *region, View3D *v3d, const rcti *rect) { SELECTID_Context *sel_ctx = DRW_select_engine_context_get(); GPUViewport *viewport = WM_draw_region_get_viewport(region); if (!viewport) { /* Selection engine requires a viewport. * TODO(@germano): This should be done internally in the engine. */ + sel_ctx->is_dirty = true; + sel_ctx->objects_drawn_len = 0; sel_ctx->index_drawn_len = 1; return; } Scene *scene = DEG_get_evaluated_scene(depsgraph); ViewLayer *view_layer = DEG_get_evaluated_view_layer(depsgraph); - RegionView3D *rv3d = static_cast(region->regiondata); /* Reset before using it. */ drw_state_prepare_clean_for_draw(&DST); @@ -2774,7 +2774,7 @@ void DRW_draw_select_id(Depsgraph *depsgraph, ARegion *region, View3D *v3d) BKE_view_layer_synced_ensure(scene, view_layer); DST.draw_ctx = {}; DST.draw_ctx.region = region; - DST.draw_ctx.rv3d = rv3d; + DST.draw_ctx.rv3d = static_cast(region->regiondata); DST.draw_ctx.v3d = v3d; DST.draw_ctx.scene = scene; DST.draw_ctx.view_layer = view_layer; @@ -2790,13 +2790,17 @@ void DRW_draw_select_id(Depsgraph *depsgraph, ARegion *region, View3D *v3d) UI_SetTheme(SPACE_VIEW3D, RGN_TYPE_WINDOW); DRW_globals_update(); - /* Select Engine */ + /* Init Select Engine */ + sel_ctx->last_rect = *rect; + use_drw_engine(&draw_engine_select_type); drw_engines_init(); { drw_engines_cache_init(); - for (Object *obj_eval : sel_ctx->objects) { + Object **obj = &sel_ctx->objects[0]; + for (uint remaining = sel_ctx->objects_len; remaining--; obj++) { + Object *obj_eval = DEG_get_evaluated_object(depsgraph, *obj); drw_engines_cache_populate(obj_eval); } @@ -3036,13 +3040,16 @@ void DRW_engine_register(DrawEngineType *draw_engine_type) g_registered_engines.len = BLI_listbase_count(&g_registered_engines.engines); } +void DRW_engines_register_experimental() +{ + if (U.experimental.enable_eevee_next) { + RE_engines_register(&DRW_engine_viewport_eevee_next_type); + } +} + void DRW_engines_register() { RE_engines_register(&DRW_engine_viewport_eevee_type); - /* Always register EEVEE Next so it can be used in background mode with `--factory-startup`. - * (Needed for tests). */ - RE_engines_register(&DRW_engine_viewport_eevee_next_type); - RE_engines_register(&DRW_engine_viewport_workbench_type); DRW_engine_register(&draw_engine_gpencil_type); diff --git a/source/blender/draw/intern/draw_manager_data.cc b/source/blender/draw/intern/draw_manager_data.cc index 2e0fef44f6f..63ef95a3add 100644 --- a/source/blender/draw/intern/draw_manager_data.cc +++ b/source/blender/draw/intern/draw_manager_data.cc @@ -730,7 +730,7 @@ static void drw_call_obinfos_init(DRWObjectInfos *ob_infos, Object *ob) static void drw_call_culling_init(DRWCullingState *cull, Object *ob) { - std::optional bbox; + const BoundBox *bbox; if (ob != nullptr && (bbox = BKE_object_boundbox_get(ob))) { float corner[3]; /* Get BoundSphere center and radius from the BoundBox. */ @@ -1361,8 +1361,7 @@ static void drw_sculpt_generate_calls(DRWSculptCallbackData *scd) Mesh *mesh = static_cast(scd->ob->data); BKE_pbvh_update_normals(pbvh, mesh->runtime->subdiv_ccg); - BKE_pbvh_draw_cb(*mesh, - pbvh, + BKE_pbvh_draw_cb(pbvh, update_only_visible, &update_frustum, &draw_frustum, @@ -1403,15 +1402,15 @@ void DRW_shgroup_call_sculpt(DRWShadingGroup *shgroup, int attrs_num = 0; /* NOTE: these are NOT #eCustomDataType, they are extended values, ASAN may warn about this. */ - attrs[attrs_num++] = PBVHAttrReq(ATTR_DOMAIN_POINT, eCustomDataType(CD_PBVH_CO_TYPE)); - attrs[attrs_num++] = PBVHAttrReq(ATTR_DOMAIN_POINT, eCustomDataType(CD_PBVH_NO_TYPE)); + attrs[attrs_num++].type = (eCustomDataType)CD_PBVH_CO_TYPE; + attrs[attrs_num++].type = (eCustomDataType)CD_PBVH_NO_TYPE; if (use_mask) { - attrs[attrs_num++] = PBVHAttrReq(ATTR_DOMAIN_POINT, eCustomDataType(CD_PBVH_MASK_TYPE)); + attrs[attrs_num++].type = (eCustomDataType)CD_PBVH_MASK_TYPE; } if (use_fset) { - attrs[attrs_num++] = PBVHAttrReq(ATTR_DOMAIN_FACE, eCustomDataType(CD_PBVH_FSET_TYPE)); + attrs[attrs_num++].type = (eCustomDataType)CD_PBVH_FSET_TYPE; } Mesh *me = BKE_object_get_original_mesh(ob); diff --git a/source/blender/draw/intern/draw_manager_shader.cc b/source/blender/draw/intern/draw_manager_shader.cc index 383402ae126..10033e1ac83 100644 --- a/source/blender/draw/intern/draw_manager_shader.cc +++ b/source/blender/draw/intern/draw_manager_shader.cc @@ -14,7 +14,7 @@ #include "BLI_dynstr.h" #include "BLI_listbase.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_threads.h" #include "BKE_context.h" @@ -64,8 +64,13 @@ struct DRWShaderCompiler { bool own_context; }; -static void drw_deferred_shader_compilation_exec(void *custom_data, - wmJobWorkerStatus *worker_status) +static void drw_deferred_shader_compilation_exec( + void *custom_data, + /* Cannot be const, this function implements wm_jobs_start_callback. + * NOLINTNEXTLINE: readability-non-const-parameter. */ + bool *stop, + bool * /*do_update*/, + float * /*progress*/) { GPU_render_begin(); DRWShaderCompiler *comp = (DRWShaderCompiler *)custom_data; @@ -85,7 +90,7 @@ static void drw_deferred_shader_compilation_exec(void *custom_data, GPU_context_active_set(blender_gpu_context); while (true) { - if (worker_status->stop != 0) { + if (*stop != 0) { /* We don't want user to be able to cancel the compilation * but wm can kill the task if we are closing blender. */ break; diff --git a/source/blender/draw/intern/draw_pass.hh b/source/blender/draw/intern/draw_pass.hh index 9a71da49ef5..040512db87a 100644 --- a/source/blender/draw/intern/draw_pass.hh +++ b/source/blender/draw/intern/draw_pass.hh @@ -214,14 +214,6 @@ class PassBase { */ void framebuffer_set(GPUFrameBuffer **framebuffer); - /** - * Start a new sub-pass and change framebuffer attachments status. - * \note Affect the currently bound framebuffer at the time of submission and execution. - * \note States are copied and stored in the command. - */ - void subpass_transition(GPUAttachmentState depth_attachment, - Span color_attachments); - /** * Bind a material shader along with its associated resources. Any following bind() or * push_constant() call will use its interface. @@ -558,9 +550,6 @@ template void PassBase::submit(command::RecordingState &state) const case command::Type::FramebufferBind: commands_[header.index].framebuffer_bind.execute(); break; - case command::Type::SubPassTransition: - commands_[header.index].subpass_transition.execute(); - break; case command::Type::ShaderBind: commands_[header.index].shader_bind.execute(state); break; @@ -622,9 +611,6 @@ template std::string PassBase::serialize(std::string line_prefix) co case Type::FramebufferBind: ss << line_prefix << commands_[header.index].framebuffer_bind.serialize() << std::endl; break; - case Type::SubPassTransition: - ss << line_prefix << commands_[header.index].subpass_transition.serialize() << std::endl; - break; case Type::ShaderBind: ss << line_prefix << commands_[header.index].shader_bind.serialize() << std::endl; break; @@ -855,25 +841,6 @@ template inline void PassBase::framebuffer_set(GPUFrameBuffer **fram create_command(Type::FramebufferBind).framebuffer_bind = {framebuffer}; } -template -inline void PassBase::subpass_transition(GPUAttachmentState depth_attachment, - Span color_attachments) -{ - uint8_t color_states[8] = {GPU_ATTACHEMENT_IGNORE}; - for (auto i : color_attachments.index_range()) { - color_states[i] = uint8_t(color_attachments[i]); - } - create_command(Type::SubPassTransition).subpass_transition = {uint8_t(depth_attachment), - {color_states[0], - color_states[1], - color_states[2], - color_states[3], - color_states[4], - color_states[5], - color_states[6], - color_states[7]}}; -} - template inline void PassBase::material_set(Manager &manager, GPUMaterial *material) { GPUPass *gpupass = GPU_material_get_pass(material); diff --git a/source/blender/draw/intern/draw_pbvh.cc b/source/blender/draw/intern/draw_pbvh.cc index 043dcae839b..6223c4f7abc 100644 --- a/source/blender/draw/intern/draw_pbvh.cc +++ b/source/blender/draw/intern/draw_pbvh.cc @@ -198,59 +198,6 @@ void extract_data_corner_faces(const PBVH_GPU_Args &args, } } -template const T &bmesh_cd_vert_get(const BMVert &vert, const int offset) -{ - return *static_cast(POINTER_OFFSET(vert.head.data, offset)); -} - -template const T &bmesh_cd_loop_get(const BMLoop &loop, const int offset) -{ - return *static_cast(POINTER_OFFSET(loop.head.data, offset)); -} - -template const T &bmesh_cd_face_get(const BMFace &face, const int offset) -{ - return *static_cast(POINTER_OFFSET(face.head.data, offset)); -} - -template -void extract_data_vert_bmesh(const PBVH_GPU_Args &args, const int cd_offset, GPUVertBuf &vbo) -{ - VBOT *data = static_cast(GPU_vertbuf_get_data(&vbo)); - - for (const BMFace *f : *args.bm_faces) { - if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { - continue; - } - const BMLoop *l = f->l_first; - *data = convert_value(bmesh_cd_vert_get(*l->prev->v, cd_offset)); - data++; - *data = convert_value(bmesh_cd_vert_get(*l->v, cd_offset)); - data++; - *data = convert_value(bmesh_cd_vert_get(*l->next->v, cd_offset)); - data++; - } -} - -template -void extract_data_corner_bmesh(const PBVH_GPU_Args &args, const int cd_offset, GPUVertBuf &vbo) -{ - VBOT *data = static_cast(GPU_vertbuf_get_data(&vbo)); - - for (const BMFace *f : *args.bm_faces) { - if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { - continue; - } - const BMLoop *l = f->l_first; - *data = convert_value(bmesh_cd_loop_get(*l->prev, cd_offset)); - data++; - *data = convert_value(bmesh_cd_loop_get(*l, cd_offset)); - data++; - *data = convert_value(bmesh_cd_loop_get(*l->next, cd_offset)); - data++; - } -} - struct PBVHBatch { Vector vbos; GPUBatch *tris = nullptr, *lines = nullptr; @@ -351,11 +298,12 @@ struct PBVHBatches { break; } case PBVH_BMESH: { - for (const BMFace *f : *args.bm_faces) { + GSET_FOREACH_BEGIN (BMFace *, f, args.bm_faces) { if (!BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { count++; } } + GSET_FOREACH_END(); } } @@ -479,7 +427,8 @@ struct PBVHBatches { face_no = convert_value(args.face_normals[face_i]); last_face = face_i; } - std::fill_n(data, 3, face_no); + + *data = *(data + 1) = *(data + 2) = face_no; data += 3; } else { @@ -576,8 +525,6 @@ struct PBVHBatches { break; case CD_PBVH_FSET_TYPE: { - BLI_assert(vbo.domain == ATTR_DOMAIN_FACE); - const int *face_sets = args.face_sets; if (!face_sets) { @@ -705,14 +652,11 @@ struct PBVHBatches { extract_data_vert_faces(args, {mask, args.me->totvert}, vert_buf); } else { - MutableSpan(static_cast(GPU_vertbuf_get_data(vbo.vert_buf)), totvert) - .fill(0.0f); + MutableSpan(static_cast(GPU_vertbuf_get_data(vbo.vert_buf)), totvert).fill(0); } break; } case CD_PBVH_FSET_TYPE: { - BLI_assert(vbo.domain == ATTR_DOMAIN_FACE); - const int *face_sets = static_cast( CustomData_get_layer_named(args.face_data, CD_PROP_INT32, ".sculpt_face_set")); uchar4 *data = static_cast(GPU_vertbuf_get_data(vbo.vert_buf)); @@ -738,7 +682,7 @@ struct PBVHBatches { fset_color[0] = fset_color[1] = fset_color[2] = UCHAR_MAX; } } - std::fill_n(data, 3, fset_color); + *data = *(data + 1) = *(data + 2) = fset_color; data += 3; } } @@ -803,6 +747,20 @@ struct PBVHBatches { void fill_vbo_bmesh(PBVHVbo &vbo, const PBVH_GPU_Args &args) { + auto foreach_bmesh = [&](FunctionRef callback) { + GSET_FOREACH_BEGIN (BMFace *, f, args.bm_faces) { + if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { + continue; + } + + BMLoop *l = f->l_first; + callback(l->prev); + callback(l); + callback(l->next); + } + GSET_FOREACH_END(); + }; + faces_count = tris_count = count_faces(args); int existing_num = GPU_vertbuf_get_vertex_len(vbo.vert_buf); @@ -826,121 +784,53 @@ struct PBVHBatches { } #endif - const eCustomDataType type = eCustomDataType(vbo.type); - if (vbo.domain == ATTR_DOMAIN_POINT) { - const int cd_offset = CustomData_get_offset_named(&args.bm->vdata, type, vbo.name.c_str()); - switch (type) { - case CD_PROP_COLOR: - extract_data_vert_bmesh(args, cd_offset, *vbo.vert_buf); - return; - case CD_PROP_BYTE_COLOR: - extract_data_vert_bmesh(args, cd_offset, *vbo.vert_buf); - return; - case CD_PROP_FLOAT2: - extract_data_vert_bmesh(args, cd_offset, *vbo.vert_buf); - return; - default: - break; - } - } - else if (vbo.domain == ATTR_DOMAIN_CORNER) { - const int cd_offset = CustomData_get_offset_named(&args.bm->ldata, type, vbo.name.c_str()); - switch (type) { - case CD_PROP_COLOR: - extract_data_corner_bmesh(args, cd_offset, *vbo.vert_buf); - return; - case CD_PROP_BYTE_COLOR: - extract_data_corner_bmesh(args, cd_offset, *vbo.vert_buf); - return; - case CD_PROP_FLOAT2: - extract_data_corner_bmesh(args, cd_offset, *vbo.vert_buf); - return; - default: - break; - } - } - switch (vbo.type) { - case CD_PBVH_CO_TYPE: { - float3 *data = static_cast(GPU_vertbuf_get_data(vbo.vert_buf)); - for (const BMFace *f : *args.bm_faces) { - if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { - continue; - } - const BMLoop *l = f->l_first; - *data = l->prev->v->co; - data++; - *data = l->v->co; - data++; - *data = l->next->v->co; - data++; - } + case CD_PROP_COLOR: + case CD_PROP_BYTE_COLOR: { + ushort4 white = {USHRT_MAX, USHRT_MAX, USHRT_MAX, USHRT_MAX}; + + foreach_bmesh([&](BMLoop * /*l*/) { + *static_cast(GPU_vertbuf_raw_step(&access)) = white; + }); break; } - case CD_PBVH_NO_TYPE: { - short4 *data = static_cast(GPU_vertbuf_get_data(vbo.vert_buf)); - for (const BMFace *f : *args.bm_faces) { - if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { - continue; - } - if (BM_elem_flag_test(f, BM_ELEM_SMOOTH)) { - const BMLoop *l = f->l_first; - *data = convert_value(l->prev->v->no); - data++; - *data = convert_value(l->v->no); - data++; - *data = convert_value(l->next->v->no); - data++; - } - else { - std::fill_n(data, 3, convert_value(f->no)); - data += 3; - } - } + case CD_PBVH_CO_TYPE: + foreach_bmesh( + [&](BMLoop *l) { *static_cast(GPU_vertbuf_raw_step(&access)) = l->v->co; }); break; - } + + case CD_PBVH_NO_TYPE: + foreach_bmesh([&](BMLoop *l) { + short no[3]; + bool smooth = BM_elem_flag_test(l->f, BM_ELEM_SMOOTH); + + normal_float_to_short_v3(no, smooth ? l->v->no : l->f->no); + *static_cast(GPU_vertbuf_raw_step(&access)) = no; + }); + break; + case CD_PBVH_MASK_TYPE: { - if (args.cd_mask_layer != -1) { - extract_data_vert_bmesh(args, args.cd_mask_layer, *vbo.vert_buf); + int cd_mask = args.cd_mask_layer; + + if (cd_mask == -1) { + foreach_bmesh( + [&](BMLoop * /*l*/) { *static_cast(GPU_vertbuf_raw_step(&access)) = 0; }); } else { - MutableSpan(static_cast(GPU_vertbuf_get_data(vbo.vert_buf)), - GPU_vertbuf_get_vertex_len(vbo.vert_buf)) - .fill(0.0f); + foreach_bmesh([&](BMLoop *l) { + float mask = BM_ELEM_CD_GET_FLOAT(l->v, cd_mask); + + *static_cast(GPU_vertbuf_raw_step(&access)) = mask; + }); } break; } case CD_PBVH_FSET_TYPE: { - BLI_assert(vbo.domain == ATTR_DOMAIN_FACE); + uchar3 white(UCHAR_MAX, UCHAR_MAX, UCHAR_MAX); - const int cd_offset = CustomData_get_offset_named( - &args.bm->pdata, CD_PROP_INT32, ".sculpt_face_set"); - - uchar4 *data = static_cast(GPU_vertbuf_get_data(vbo.vert_buf)); - if (cd_offset != -1) { - for (const BMFace *f : *args.bm_faces) { - if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { - continue; - } - - const int fset = bmesh_cd_face_get(*f, cd_offset); - - uchar4 fset_color; - if (fset != args.face_sets_color_default) { - BKE_paint_face_set_overlay_color_get(fset, args.face_sets_color_seed, fset_color); - } - else { - /* Skip for the default color face set to render it white. */ - fset_color[0] = fset_color[1] = fset_color[2] = UCHAR_MAX; - } - std::fill_n(data, 3, fset_color); - data += 3; - } - } - else { - MutableSpan(data, GPU_vertbuf_get_vertex_len(vbo.vert_buf)).fill(uchar4(255)); - } - break; + foreach_bmesh([&](BMLoop * /*l*/) { + *static_cast(GPU_vertbuf_raw_step(&access)) = white; + }); } } } @@ -1154,7 +1044,7 @@ struct PBVHBatches { int v_index = 0; lines_count = 0; - for (const BMFace *f : *args.bm_faces) { + GSET_FOREACH_BEGIN (BMFace *, f, args.bm_faces) { if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { continue; } @@ -1166,6 +1056,7 @@ struct PBVHBatches { lines_count += 3; v_index += 3; } + GSET_FOREACH_END(); lines_index = GPU_indexbuf_build(&elb_lines); } diff --git a/source/blender/draw/intern/draw_pointcloud.cc b/source/blender/draw/intern/draw_pointcloud.cc index 0f792c5c1ab..89d361c1a27 100644 --- a/source/blender/draw/intern/draw_pointcloud.cc +++ b/source/blender/draw/intern/draw_pointcloud.cc @@ -6,7 +6,7 @@ * \ingroup draw */ -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_utildefines.h" #include "DNA_pointcloud_types.h" diff --git a/source/blender/draw/intern/draw_resource.hh b/source/blender/draw/intern/draw_resource.hh index b8c5471d92c..8b8b647e049 100644 --- a/source/blender/draw/intern/draw_resource.hh +++ b/source/blender/draw/intern/draw_resource.hh @@ -161,8 +161,8 @@ inline void ObjectBounds::sync() inline void ObjectBounds::sync(Object &ob) { - const std::optional bbox = BKE_object_boundbox_get(&ob); - if (!bbox) { + const BoundBox *bbox = BKE_object_boundbox_get(&ob); + if (bbox == nullptr) { bounding_sphere.w = -1.0f; /* Disable test. */ return; } diff --git a/source/blender/draw/intern/draw_sculpt.cc b/source/blender/draw/intern/draw_sculpt.cc index 18d9be27397..fa02ff7d1d4 100644 --- a/source/blender/draw/intern/draw_sculpt.cc +++ b/source/blender/draw/intern/draw_sculpt.cc @@ -139,8 +139,7 @@ static Vector sculpt_batches_get_ex( data.attrs = attrs; data.attrs_len = attrs_len; - BKE_pbvh_draw_cb(*mesh, - pbvh, + BKE_pbvh_draw_cb(pbvh, update_only_visible, &update_frustum, &draw_frustum, diff --git a/source/blender/draw/intern/draw_select_buffer.cc b/source/blender/draw/intern/draw_select_buffer.cc index 518313fc470..15e0782ddaa 100644 --- a/source/blender/draw/intern/draw_select_buffer.cc +++ b/source/blender/draw/intern/draw_select_buffer.cc @@ -23,31 +23,11 @@ #include "DEG_depsgraph_query.hh" #include "DRW_engine.h" -#include "DRW_select_buffer.hh" +#include "DRW_select_buffer.h" #include "draw_manager.h" -#include "../engines/select/select_engine.hh" - -bool SELECTID_Context::is_dirty(RegionView3D *rv3d) -{ - /* Check if the viewport has changed. */ - float(*persmat)[4] = rv3d->persmat; - bool is_dirty = !compare_m4m4(this->persmat, persmat, FLT_EPSILON); - - if (!is_dirty) { - /* Check if any of the drawn objects have been transformed. */ - for (Object *obj_eval : this->objects) { - DrawData *data = DRW_drawdata_get(&obj_eval->id, &draw_engine_select_type); - if (!data || (data->recalc & ID_RECALC_TRANSFORM)) { - is_dirty = true; - break; - } - } - } - - return is_dirty; -} +#include "../engines/select/select_engine.h" /* -------------------------------------------------------------------- */ /** \name Buffer of select ID's @@ -71,14 +51,10 @@ uint *DRW_select_buffer_read( rcti rect_clamp = *rect; if (BLI_rcti_isect(&r, &rect_clamp, &rect_clamp)) { SELECTID_Context *select_ctx = DRW_select_engine_context_get(); - RegionView3D *rv3d = static_cast(region->regiondata); DRW_gpu_context_enable(); - - if (select_ctx->is_dirty(rv3d)) { - /* Update drawing. */ - DRW_draw_select_id(depsgraph, region, v3d); - } + /* Update the drawing. */ + DRW_draw_select_id(depsgraph, region, v3d, rect); if (select_ctx->index_drawn_len > 1) { BLI_assert(region->winx == GPU_texture_width(DRW_engine_select_texture_get()) && @@ -101,7 +77,7 @@ uint *DRW_select_buffer_read( r_buf); if (!BLI_rcti_compare(rect, &rect_clamp)) { - /* The rect has been clamped so we need to realign the buffer and fill in the blanks */ + /* The rect has been clamped so you need to realign the buffer and fill in the blanks */ GPU_select_buffer_stride_realign(rect, &rect_clamp, r_buf); } } @@ -399,7 +375,7 @@ bool DRW_select_buffer_elem_get(const uint sel_id, uint elem_id = 0; uint base_index = 0; - for (; base_index < select_ctx->objects.size(); base_index++) { + for (; base_index < select_ctx->objects_drawn_len; base_index++) { ObjectOffsets *base_ofs = &select_ctx->index_offsets[base_index]; if (base_ofs->face > sel_id) { @@ -419,14 +395,15 @@ bool DRW_select_buffer_elem_get(const uint sel_id, } } - if (base_index == select_ctx->objects.size()) { + if (base_index == select_ctx->objects_drawn_len) { return false; } *r_elem = elem_id; if (r_base_index) { - *r_base_index = base_index; + Object *obj_orig = DEG_get_original_object(select_ctx->objects_drawn[base_index]); + *r_base_index = obj_orig->runtime.select_id; } if (r_elem_type) { @@ -447,7 +424,7 @@ uint DRW_select_buffer_context_offset_for_object_elem(Depsgraph *depsgraph, SELECTID_ObjectData *sel_data = (SELECTID_ObjectData *)DRW_drawdata_get( &ob_eval->id, &draw_engine_select_type); - if (!sel_data) { + if (!sel_data || !sel_data->is_drawn) { return 0; } @@ -472,21 +449,28 @@ uint DRW_select_buffer_context_offset_for_object_elem(Depsgraph *depsgraph, /** \name Context * \{ */ -void DRW_select_buffer_context_create(Depsgraph *depsgraph, - Base **bases, - const uint bases_len, - short select_mode) +void DRW_select_buffer_context_create(Base **bases, const uint bases_len, short select_mode) { SELECTID_Context *select_ctx = DRW_select_engine_context_get(); - select_ctx->objects.reinitialize(bases_len); - select_ctx->index_offsets.reinitialize(bases_len); + select_ctx->objects = static_cast( + MEM_reallocN(select_ctx->objects, sizeof(*select_ctx->objects) * bases_len)); + + select_ctx->index_offsets = static_cast( + MEM_reallocN(select_ctx->index_offsets, sizeof(*select_ctx->index_offsets) * bases_len)); + + select_ctx->objects_drawn = static_cast( + MEM_reallocN(select_ctx->objects_drawn, sizeof(*select_ctx->objects_drawn) * bases_len)); for (uint base_index = 0; base_index < bases_len; base_index++) { Object *obj = bases[base_index]->object; - select_ctx->objects[base_index] = DEG_get_evaluated_object(depsgraph, obj); + select_ctx->objects[base_index] = obj; + + /* Weak but necessary for `DRW_select_buffer_elem_get`. */ + obj->runtime.select_id = base_index; } + select_ctx->objects_len = bases_len; select_ctx->select_mode = select_mode; memset(select_ctx->persmat, 0, sizeof(select_ctx->persmat)); } diff --git a/source/blender/draw/intern/draw_shader.cc b/source/blender/draw/intern/draw_shader.cc index 603be456ded..687be379810 100644 --- a/source/blender/draw/intern/draw_shader.cc +++ b/source/blender/draw/intern/draw_shader.cc @@ -9,7 +9,7 @@ #include "DRW_render.h" #include "BLI_dynstr.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "GPU_batch.h" #include "GPU_index_buffer.h" diff --git a/source/blender/draw/intern/draw_shader_shared.h b/source/blender/draw/intern/draw_shader_shared.h index a4ff34091fc..7fc3fc8fb20 100644 --- a/source/blender/draw/intern/draw_shader_shared.h +++ b/source/blender/draw/intern/draw_shader_shared.h @@ -66,6 +66,13 @@ extern "C" { /** \name Views * \{ */ +/** + * The maximum of indexable views is dictated by: + * - The UBO limit (16KiB) of the ViewMatrices container. + * - The maximum resource index supported for shaders using multi-view (see DRW_VIEW_SHIFT). + */ +#define DRW_VIEW_MAX 64 + #ifndef DRW_VIEW_LEN /* Single-view case (default). */ # define drw_view_id 0 diff --git a/source/blender/draw/intern/draw_view.cc b/source/blender/draw/intern/draw_view.cc index 1ade1063f5d..ad8bdeefbf6 100644 --- a/source/blender/draw/intern/draw_view.cc +++ b/source/blender/draw/intern/draw_view.cc @@ -84,10 +84,6 @@ void View::frustum_boundbox_calc(int view_id) for (float4 &corner : corners) { mul_m4_v3(data_[view_id].viewinv.ptr(), corner); corner.w = 1.0; - /* Special case for planar reflection. */ - if (is_inverted_) { - corner.z = -corner.z; - } } } @@ -105,11 +101,6 @@ void View::frustum_culling_planes_calc(int view_id) /* Normalize. */ for (float4 &plane : culling_[view_id].frustum_planes.planes) { plane.w /= normalize_v3(plane); - - /* Special case for planar reflection. */ - if (is_inverted_) { - plane.z = -plane.z; - } } } diff --git a/source/blender/draw/intern/draw_view.hh b/source/blender/draw/intern/draw_view.hh index 093ede09e18..a7f4def4092 100644 --- a/source/blender/draw/intern/draw_view.hh +++ b/source/blender/draw/intern/draw_view.hh @@ -120,18 +120,6 @@ class View { return -(data_[view_id].winmat[3][2] + 1.0f) / data_[view_id].winmat[2][2]; } - const float3 &location(int view_id = 0) const - { - BLI_assert(view_id < view_len_); - return data_[view_id].viewinv.location(); - } - - const float3 &forward(int view_id = 0) const - { - BLI_assert(view_id < view_len_); - return data_[view_id].viewinv.z_axis(); - } - const float4x4 &viewmat(int view_id = 0) const { BLI_assert(view_id < view_len_); diff --git a/source/blender/draw/intern/draw_view_c.cc b/source/blender/draw/intern/draw_view_c.cc index 05862ccf64f..25502549be1 100644 --- a/source/blender/draw/intern/draw_view_c.cc +++ b/source/blender/draw/intern/draw_view_c.cc @@ -97,53 +97,51 @@ void DRW_draw_cursor() ARegion *region = draw_ctx->region; Scene *scene = draw_ctx->scene; ViewLayer *view_layer = draw_ctx->view_layer; - if (!is_cursor_visible(draw_ctx, scene, view_layer)) { - return; - } GPU_color_mask(true, true, true, true); GPU_depth_mask(false); GPU_depth_test(GPU_DEPTH_NONE); - /* Get cursor data into quaternion form */ - const View3DCursor *cursor = &scene->cursor; + if (is_cursor_visible(draw_ctx, scene, view_layer)) { + int co[2]; - int co[2]; - if (ED_view3d_project_int_global( - region, cursor->location, co, V3D_PROJ_TEST_NOP | V3D_PROJ_TEST_CLIP_NEAR) != - V3D_PROJ_RET_OK) - { - return; - } + /* Get cursor data into quaternion form */ + const View3DCursor *cursor = &scene->cursor; - RegionView3D *rv3d = static_cast(region->regiondata); + if (ED_view3d_project_int_global( + region, cursor->location, co, V3D_PROJ_TEST_NOP | V3D_PROJ_TEST_CLIP_NEAR) == + V3D_PROJ_RET_OK) + { + RegionView3D *rv3d = static_cast(region->regiondata); - float cursor_quat[4]; - BKE_scene_cursor_rot_to_quat(cursor, cursor_quat); + float cursor_quat[4]; + BKE_scene_cursor_rot_to_quat(cursor, cursor_quat); - /* Draw nice Anti Aliased cursor. */ - GPU_line_width(1.0f); - GPU_blend(GPU_BLEND_ALPHA); - GPU_line_smooth(true); + /* Draw nice Anti Aliased cursor. */ + GPU_line_width(1.0f); + GPU_blend(GPU_BLEND_ALPHA); + GPU_line_smooth(true); - float eps = 1e-5f; - rv3d->viewquat[0] = -rv3d->viewquat[0]; - bool is_aligned = compare_v4v4(cursor_quat, rv3d->viewquat, eps); - if (is_aligned == false) { - float tquat[4]; - rotation_between_quats_to_quat(tquat, rv3d->viewquat, cursor_quat); - is_aligned = tquat[0] - eps < -1.0f; - } - rv3d->viewquat[0] = -rv3d->viewquat[0]; + float eps = 1e-5f; + rv3d->viewquat[0] = -rv3d->viewquat[0]; + bool is_aligned = compare_v4v4(cursor_quat, rv3d->viewquat, eps); + if (is_aligned == false) { + float tquat[4]; + rotation_between_quats_to_quat(tquat, rv3d->viewquat, cursor_quat); + is_aligned = tquat[0] - eps < -1.0f; + } + rv3d->viewquat[0] = -rv3d->viewquat[0]; - /* Draw lines */ - if (is_aligned == false) { - uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); - immUniformThemeColor3(TH_VIEW_OVERLAY); - immBegin(GPU_PRIM_LINES, 12); + /* Draw lines */ + if (is_aligned == false) { + uint pos = GPU_vertformat_attr_add( + immVertexFormat(), "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT); + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); + immUniformThemeColor3(TH_VIEW_OVERLAY); + immBegin(GPU_PRIM_LINES, 12); - const float scale = ED_view3d_pixel_size_no_ui_scale(rv3d, cursor->location) * U.widget_unit; + const float scale = ED_view3d_pixel_size_no_ui_scale(rv3d, cursor->location) * + U.widget_unit; #define CURSOR_VERT(axis_vec, axis, fac) \ immVertex3f(pos, \ @@ -158,38 +156,40 @@ void DRW_draw_cursor() } \ ((void)0) - for (int axis = 0; axis < 3; axis++) { - float axis_vec[3] = {0}; - axis_vec[axis] = scale; - mul_qt_v3(cursor_quat, axis_vec); - CURSOR_EDGE(axis_vec, axis, +); - CURSOR_EDGE(axis_vec, axis, -); - } + for (int axis = 0; axis < 3; axis++) { + float axis_vec[3] = {0}; + axis_vec[axis] = scale; + mul_qt_v3(cursor_quat, axis_vec); + CURSOR_EDGE(axis_vec, axis, +); + CURSOR_EDGE(axis_vec, axis, -); + } #undef CURSOR_VERT #undef CURSOR_EDGE - immEnd(); - immUnbindProgram(); + immEnd(); + immUnbindProgram(); + } + + float original_proj[4][4]; + GPU_matrix_projection_get(original_proj); + GPU_matrix_push(); + ED_region_pixelspace(region); + GPU_matrix_translate_2f(co[0] + 0.5f, co[1] + 0.5f); + GPU_matrix_scale_2f(U.widget_unit, U.widget_unit); + + GPUBatch *cursor_batch = DRW_cache_cursor_get(is_aligned); + GPUShader *shader = GPU_shader_get_builtin_shader(GPU_SHADER_3D_FLAT_COLOR); + GPU_batch_set_shader(cursor_batch, shader); + + GPU_batch_draw(cursor_batch); + + GPU_blend(GPU_BLEND_NONE); + GPU_line_smooth(false); + GPU_matrix_pop(); + GPU_matrix_projection_set(original_proj); + } } - - float original_proj[4][4]; - GPU_matrix_projection_get(original_proj); - GPU_matrix_push(); - ED_region_pixelspace(region); - GPU_matrix_translate_2f(co[0] + 0.5f, co[1] + 0.5f); - GPU_matrix_scale_2f(U.widget_unit, U.widget_unit); - - GPUBatch *cursor_batch = DRW_cache_cursor_get(is_aligned); - GPUShader *shader = GPU_shader_get_builtin_shader(GPU_SHADER_3D_FLAT_COLOR); - GPU_batch_set_shader(cursor_batch, shader); - - GPU_batch_draw(cursor_batch); - - GPU_blend(GPU_BLEND_NONE); - GPU_line_smooth(false); - GPU_matrix_pop(); - GPU_matrix_projection_set(original_proj); } /* -------------------------------------------------------------------- */ diff --git a/source/blender/draw/intern/mesh_extractors/extract_mesh.hh b/source/blender/draw/intern/mesh_extractors/extract_mesh.hh index 4aebdde050f..92adf795b86 100644 --- a/source/blender/draw/intern/mesh_extractors/extract_mesh.hh +++ b/source/blender/draw/intern/mesh_extractors/extract_mesh.hh @@ -67,7 +67,6 @@ struct MeshRenderData { blender::Span bm_vert_normals; blender::Span bm_face_normals; blender::Span bm_face_centers; - blender::Array bm_loop_normals; const int *v_origindex, *e_origindex, *p_origindex; int edge_crease_ofs; @@ -92,7 +91,6 @@ struct MeshRenderData { const int *material_indices; blender::Span vert_normals; blender::Span face_normals; - blender::Span loop_normals; const bool *hide_vert; const bool *hide_edge; const bool *hide_poly; @@ -100,6 +98,7 @@ struct MeshRenderData { const bool *select_edge; const bool *select_poly; const bool *sharp_faces; + blender::Array loop_normals; blender::Span loose_verts; blender::Span loose_edges; diff --git a/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_pos_nor.cc b/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_pos_nor.cc index 5185a0a8a16..831688e8d23 100644 --- a/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_pos_nor.cc +++ b/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_pos_nor.cc @@ -258,16 +258,18 @@ static void extract_pos_nor_init_subdiv(const DRWSubdivCache &subdiv_cache, draw_subdiv_extract_pos_nor(subdiv_cache, flags_buffer, vbo, orco_vbo); if (subdiv_cache.use_custom_loop_normals) { - const Mesh *coarse_mesh = subdiv_cache.mesh; - const Span corner_normals = coarse_mesh->corner_normals(); + Mesh *coarse_mesh = subdiv_cache.mesh; + const float(*loop_normals)[3] = static_cast( + CustomData_get_layer(&coarse_mesh->loop_data, CD_NORMAL)); + BLI_assert(loop_normals != nullptr); GPUVertBuf *src_custom_normals = GPU_vertbuf_calloc(); GPU_vertbuf_init_with_format(src_custom_normals, get_custom_normals_format()); GPU_vertbuf_data_alloc(src_custom_normals, coarse_mesh->totloop); memcpy(GPU_vertbuf_get_data(src_custom_normals), - corner_normals.data(), - corner_normals.size_in_bytes()); + loop_normals, + sizeof(float[3]) * coarse_mesh->totloop); GPUVertBuf *dst_custom_normals = GPU_vertbuf_calloc(); GPU_vertbuf_init_build_on_device( diff --git a/source/blender/draw/intern/shaders/common_grease_pencil_lib.glsl b/source/blender/draw/intern/shaders/common_grease_pencil_lib.glsl index 937cbb706c1..ab7cf94bffa 100644 --- a/source/blender/draw/intern/shaders/common_grease_pencil_lib.glsl +++ b/source/blender/draw/intern/shaders/common_grease_pencil_lib.glsl @@ -66,8 +66,18 @@ vec2 gpencil_project_to_screenspace(vec4 v, vec4 viewport_size) float gpencil_stroke_thickness_modulate(float thickness, vec4 ndc_pos, vec4 viewport_size) { - /* World space point size. */ - thickness *= gpThicknessScale * ProjectionMatrix[1][1] * viewport_size.y; + /* Modify stroke thickness by object and layer factors. */ + thickness = max(1.0, thickness * gpThicknessScale + gpThicknessOffset); + + if (gpThicknessIsScreenSpace) { + /* Multiply offset by view Z so that offset is constant in screen-space. + * (e.i: does not change with the distance to camera) */ + thickness *= ndc_pos.w; + } + else { + /* World space point size. */ + thickness *= gpThicknessWorldScale * ProjectionMatrix[1][1] * viewport_size.y; + } return thickness; } @@ -75,7 +85,7 @@ float gpencil_clamp_small_stroke_thickness(float thickness, vec4 ndc_pos) { /* To avoid aliasing artifacts, we clamp the line thickness and * reduce its opacity in the fragment shader. */ - float min_thickness = ndc_pos.w * 0.00065; + float min_thickness = ndc_pos.w * 1.3; thickness = max(min_thickness, thickness); return thickness; diff --git a/source/blender/draw/intern/shaders/common_intersect_lib.glsl b/source/blender/draw/intern/shaders/common_intersect_lib.glsl index 99c61aee838..435f34063fb 100644 --- a/source/blender/draw/intern/shaders/common_intersect_lib.glsl +++ b/source/blender/draw/intern/shaders/common_intersect_lib.glsl @@ -129,7 +129,7 @@ IsectFrustum isect_data_setup(Frustum shape) /** \name View Intersection functions. * \{ */ -#ifdef DRW_VIEW_CULLING_INFO +#ifdef COMMON_VIEW_LIB_GLSL bool intersect_view(Pyramid pyramid) { diff --git a/source/blender/draw/intern/shaders/common_math_lib.glsl b/source/blender/draw/intern/shaders/common_math_lib.glsl index 8e7c415533a..7802865a4ab 100644 --- a/source/blender/draw/intern/shaders/common_math_lib.glsl +++ b/source/blender/draw/intern/shaders/common_math_lib.glsl @@ -114,8 +114,6 @@ float avg(vec4 v) { return dot(vec4(1.0 / 4.0), v); } /* WORKAROUND: To be removed once we port all code to use gpu_shader_math_base_lib.glsl. */ #ifndef GPU_SHADER_MATH_BASE_LIB_GLSL float safe_rcp(float a) { return (a != 0.0) ? (1.0 / a) : 0.0; } -float safe_sqrt(float a) { return sqrt(max(a, 0.0)); } -float safe_acos(float a) { return acos(clamp(a, -1.0, 1.0)); } #endif #ifndef GPU_SHADER_MATH_VECTOR_LIB_GLSL vec2 safe_rcp(vec2 a) { return select(vec2(0.0), (1.0 / a), notEqual(a, vec2(0.0))); } @@ -123,6 +121,10 @@ vec3 safe_rcp(vec3 a) { return select(vec3(0.0), (1.0 / a), notEqual(a, vec3(0.0 vec4 safe_rcp(vec4 a) { return select(vec4(0.0), (1.0 / a), notEqual(a, vec4(0.0))); } #endif +float safe_sqrt(float a) { return sqrt(max(a, 0.0)); } + +float safe_acos(float a) { return acos(clamp(a, -1.0, 1.0)); } + float sqr(float a) { return a * a; } vec2 sqr(vec2 a) { return a * a; } vec3 sqr(vec3 a) { return a * a; } @@ -144,10 +146,10 @@ bool flag_test(int flag, int val) { return (flag & val) != 0; } void set_flag_from_test(inout uint value, bool test, uint flag) { if (test) { value |= flag; } else { value &= ~flag; } } void set_flag_from_test(inout int value, bool test, int flag) { if (test) { value |= flag; } else { value &= ~flag; } } +#endif #define weighted_sum(val0, val1, val2, val3, weights) ((val0 * weights[0] + val1 * weights[1] + val2 * weights[2] + val3 * weights[3]) * safe_rcp(sum(weights))) #define weighted_sum_array(val, weights) ((val[0] * weights[0] + val[1] * weights[1] + val[2] * weights[2] + val[3] * weights[3]) * safe_rcp(sum(weights))) -#endif /* clang-format on */ diff --git a/source/blender/draw/intern/shaders/common_shape_lib.glsl b/source/blender/draw/intern/shaders/common_shape_lib.glsl index a5219748eda..b0b65a07920 100644 --- a/source/blender/draw/intern/shaders/common_shape_lib.glsl +++ b/source/blender/draw/intern/shaders/common_shape_lib.glsl @@ -8,6 +8,8 @@ * allow for more than one constructor per type. */ +#pragma BLENDER_REQUIRE(common_math_geom_lib.glsl) + /* ---------------------------------------------------------------------- */ /** \name Circle * \{ */ diff --git a/source/blender/draw/intern/shaders/draw_intersect_lib.glsl b/source/blender/draw/intern/shaders/draw_intersect_lib.glsl deleted file mode 100644 index 9aaf6e104e6..00000000000 --- a/source/blender/draw/intern/shaders/draw_intersect_lib.glsl +++ /dev/null @@ -1,520 +0,0 @@ -/* SPDX-FileCopyrightText: 2022-2023 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -/** - * Intersection library used for culling. - * Results are meant to be conservative. - */ - -#pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl) -#pragma BLENDER_REQUIRE(draw_math_geom_lib.glsl) -#pragma BLENDER_REQUIRE(common_shape_lib.glsl) - -/* ---------------------------------------------------------------------- */ -/** \name Plane extraction functions. - * \{ */ - -/** \a v1 and \a v2 are vectors on the plane. \a p is a point on the plane. */ -vec4 isect_plane_setup(vec3 p, vec3 v1, vec3 v2) -{ - vec3 normal_to_plane = normalize(cross(v1, v2)); - return vec4(normal_to_plane, -dot(normal_to_plane, p)); -} - -struct IsectPyramid { - vec3 corners[5]; - vec4 planes[5]; -}; - -IsectPyramid isect_data_setup(Pyramid shape) -{ - vec3 A1 = shape.corners[1] - shape.corners[0]; - vec3 A2 = shape.corners[2] - shape.corners[0]; - vec3 A3 = shape.corners[3] - shape.corners[0]; - vec3 A4 = shape.corners[4] - shape.corners[0]; - vec3 S4 = shape.corners[4] - shape.corners[1]; - vec3 S2 = shape.corners[2] - shape.corners[1]; - - IsectPyramid data; - data.planes[0] = isect_plane_setup(shape.corners[0], A2, A1); - data.planes[1] = isect_plane_setup(shape.corners[0], A3, A2); - data.planes[2] = isect_plane_setup(shape.corners[0], A4, A3); - data.planes[3] = isect_plane_setup(shape.corners[0], A1, A4); - data.planes[4] = isect_plane_setup(shape.corners[1], S2, S4); - for (int i = 0; i < 5; i++) { - data.corners[i] = shape.corners[i]; - } - return data; -} - -struct IsectBox { - vec3 corners[8]; - vec4 planes[6]; -}; - -IsectBox isect_data_setup(Box shape) -{ - vec3 A1 = shape.corners[1] - shape.corners[0]; - vec3 A3 = shape.corners[3] - shape.corners[0]; - vec3 A4 = shape.corners[4] - shape.corners[0]; - - IsectBox data; - data.planes[0] = isect_plane_setup(shape.corners[0], A3, A1); - data.planes[1] = isect_plane_setup(shape.corners[0], A4, A3); - data.planes[2] = isect_plane_setup(shape.corners[0], A1, A4); - /* Assumes that the box is actually a box! */ - data.planes[3] = vec4(-data.planes[0].xyz, -dot(-data.planes[0].xyz, shape.corners[6])); - data.planes[4] = vec4(-data.planes[1].xyz, -dot(-data.planes[1].xyz, shape.corners[6])); - data.planes[5] = vec4(-data.planes[2].xyz, -dot(-data.planes[2].xyz, shape.corners[6])); - for (int i = 0; i < 8; i++) { - data.corners[i] = shape.corners[i]; - } - return data; -} - -/* Construct box from 1 corner point + 3 side vectors. */ -IsectBox isect_data_setup(vec3 origin, vec3 side_x, vec3 side_y, vec3 side_z) -{ - IsectBox data; - data.corners[0] = origin; - data.corners[1] = origin + side_x; - data.corners[2] = origin + side_y + side_x; - data.corners[3] = origin + side_y; - data.corners[4] = data.corners[0] + side_z; - data.corners[5] = data.corners[1] + side_z; - data.corners[6] = data.corners[2] + side_z; - data.corners[7] = data.corners[3] + side_z; - - data.planes[0] = isect_plane_setup(data.corners[0], side_y, side_z); - data.planes[1] = isect_plane_setup(data.corners[0], side_x, side_y); - data.planes[2] = isect_plane_setup(data.corners[0], side_z, side_x); - /* Assumes that the box is actually a box! */ - data.planes[3] = vec4(-data.planes[0].xyz, -dot(-data.planes[0].xyz, data.corners[6])); - data.planes[4] = vec4(-data.planes[1].xyz, -dot(-data.planes[1].xyz, data.corners[6])); - data.planes[5] = vec4(-data.planes[2].xyz, -dot(-data.planes[2].xyz, data.corners[6])); - - return data; -} - -struct IsectFrustum { - vec3 corners[8]; - vec4 planes[6]; -}; - -IsectFrustum isect_data_setup(Frustum shape) -{ - vec3 A1 = shape.corners[1] - shape.corners[0]; - vec3 A3 = shape.corners[3] - shape.corners[0]; - vec3 A4 = shape.corners[4] - shape.corners[0]; - vec3 B5 = shape.corners[5] - shape.corners[6]; - vec3 B7 = shape.corners[7] - shape.corners[6]; - vec3 B2 = shape.corners[2] - shape.corners[6]; - - IsectFrustum data; - data.planes[0] = isect_plane_setup(shape.corners[0], A3, A1); - data.planes[1] = isect_plane_setup(shape.corners[0], A4, A3); - data.planes[2] = isect_plane_setup(shape.corners[0], A1, A4); - data.planes[3] = isect_plane_setup(shape.corners[6], B7, B5); - data.planes[4] = isect_plane_setup(shape.corners[6], B5, B2); - data.planes[5] = isect_plane_setup(shape.corners[6], B2, B7); - for (int i = 0; i < 8; i++) { - data.corners[i] = shape.corners[i]; - } - return data; -} - -/** \} */ - -/* ---------------------------------------------------------------------- */ -/** \name View Intersection functions. - * \{ */ - -#ifdef DRW_VIEW_CULLING_INFO - -bool intersect_view(Pyramid pyramid) -{ - bool intersects = true; - - /* Do Pyramid vertices vs Frustum planes. */ - for (int p = 0; p < 6; ++p) { - bool is_any_vertex_on_positive_side = false; - for (int v = 0; v < 5; ++v) { - float test = dot(drw_view_culling.frustum_planes.planes[p], vec4(pyramid.corners[v], 1.0)); - if (test > 0.0) { - is_any_vertex_on_positive_side = true; - break; - } - } - bool all_vertex_on_negative_side = !is_any_vertex_on_positive_side; - if (all_vertex_on_negative_side) { - intersects = false; - break; - } - } - - if (!intersects) { - return intersects; - } - - /* Now do Frustum vertices vs Pyramid planes. */ - IsectPyramid i_pyramid = isect_data_setup(pyramid); - for (int p = 0; p < 5; ++p) { - bool is_any_vertex_on_positive_side = false; - for (int v = 0; v < 8; ++v) { - float test = dot(i_pyramid.planes[p], - vec4(drw_view_culling.frustum_corners.corners[v].xyz, 1.0)); - if (test > 0.0) { - is_any_vertex_on_positive_side = true; - break; - } - } - bool all_vertex_on_negative_side = !is_any_vertex_on_positive_side; - if (all_vertex_on_negative_side) { - intersects = false; - break; - } - } - return intersects; -} - -bool intersect_view(Box box) -{ - bool intersects = true; - - /* Do Box vertices vs Frustum planes. */ - for (int p = 0; p < 6; ++p) { - bool is_any_vertex_on_positive_side = false; - for (int v = 0; v < 8; ++v) { - float test = dot(drw_view_culling.frustum_planes.planes[p], vec4(box.corners[v], 1.0)); - if (test > 0.0) { - is_any_vertex_on_positive_side = true; - break; - } - } - bool all_vertex_on_negative_side = !is_any_vertex_on_positive_side; - if (all_vertex_on_negative_side) { - intersects = false; - break; - } - } - - if (!intersects) { - return intersects; - } - - /* Now do Frustum vertices vs Box planes. */ - IsectBox i_box = isect_data_setup(box); - for (int p = 0; p < 6; ++p) { - bool is_any_vertex_on_positive_side = false; - for (int v = 0; v < 8; ++v) { - float test = dot(i_box.planes[p], - vec4(drw_view_culling.frustum_corners.corners[v].xyz, 1.0)); - if (test > 0.0) { - is_any_vertex_on_positive_side = true; - break; - } - } - bool all_vertex_on_negative_side = !is_any_vertex_on_positive_side; - if (all_vertex_on_negative_side) { - intersects = false; - break; - } - } - - return intersects; -} - -bool intersect_view(IsectBox i_box) -{ - bool intersects = true; - - /* Do Box vertices vs Frustum planes. */ - for (int p = 0; p < 6; ++p) { - bool is_any_vertex_on_positive_side = false; - for (int v = 0; v < 8; ++v) { - float test = dot(drw_view_culling.frustum_planes.planes[p], vec4(i_box.corners[v], 1.0)); - if (test > 0.0) { - is_any_vertex_on_positive_side = true; - break; - } - } - bool all_vertex_on_negative_side = !is_any_vertex_on_positive_side; - if (all_vertex_on_negative_side) { - intersects = false; - break; - } - } - - if (!intersects) { - return intersects; - } - - for (int p = 0; p < 6; ++p) { - bool is_any_vertex_on_positive_side = false; - for (int v = 0; v < 8; ++v) { - float test = dot(i_box.planes[p], - vec4(drw_view_culling.frustum_corners.corners[v].xyz, 1.0)); - if (test > 0.0) { - is_any_vertex_on_positive_side = true; - break; - } - } - bool all_vertex_on_negative_side = !is_any_vertex_on_positive_side; - if (all_vertex_on_negative_side) { - intersects = false; - break; - } - } - - return intersects; -} - -bool intersect_view(Sphere sphere) -{ - bool intersects = true; - - for (int p = 0; p < 6 && intersects; ++p) { - float dist_to_plane = dot(drw_view_culling.frustum_planes.planes[p], vec4(sphere.center, 1.0)); - if (dist_to_plane < -sphere.radius) { - intersects = false; - } - } - /* TODO reject false positive. */ - return intersects; -} - -#endif - -/** \} */ - -/* ---------------------------------------------------------------------- */ -/** \name Shape vs. Shape Intersection functions. - * \{ */ - -bool intersect(IsectPyramid i_pyramid, Box box) -{ - bool intersects = true; - - /* Do Box vertices vs Pyramid planes. */ - for (int p = 0; p < 5; ++p) { - bool is_any_vertex_on_positive_side = false; - for (int v = 0; v < 8; ++v) { - float test = dot(i_pyramid.planes[p], vec4(box.corners[v], 1.0)); - if (test > 0.0) { - is_any_vertex_on_positive_side = true; - break; - } - } - bool all_vertex_on_negative_side = !is_any_vertex_on_positive_side; - if (all_vertex_on_negative_side) { - intersects = false; - break; - } - } - - if (!intersects) { - return intersects; - } - - /* Now do Pyramid vertices vs Box planes. */ - IsectBox i_box = isect_data_setup(box); - for (int p = 0; p < 6; ++p) { - bool is_any_vertex_on_positive_side = false; - for (int v = 0; v < 5; ++v) { - float test = dot(i_box.planes[p], vec4(i_pyramid.corners[v], 1.0)); - if (test > 0.0) { - is_any_vertex_on_positive_side = true; - break; - } - } - bool all_vertex_on_negative_side = !is_any_vertex_on_positive_side; - if (all_vertex_on_negative_side) { - intersects = false; - break; - } - } - return intersects; -} - -bool intersect(IsectPyramid i_pyramid, IsectBox i_box) -{ - bool intersects = true; - - /* Do Box vertices vs Pyramid planes. */ - for (int p = 0; p < 5; ++p) { - bool is_any_vertex_on_positive_side = false; - for (int v = 0; v < 8; ++v) { - float test = dot(i_pyramid.planes[p], vec4(i_box.corners[v], 1.0)); - if (test > 0.0) { - is_any_vertex_on_positive_side = true; - break; - } - } - bool all_vertex_on_negative_side = !is_any_vertex_on_positive_side; - if (all_vertex_on_negative_side) { - intersects = false; - break; - } - } - - if (!intersects) { - return intersects; - } - - /* Now do Pyramid vertices vs Box planes. */ - for (int p = 0; p < 6; ++p) { - bool is_any_vertex_on_positive_side = false; - for (int v = 0; v < 5; ++v) { - float test = dot(i_box.planes[p], vec4(i_pyramid.corners[v], 1.0)); - if (test > 0.0) { - is_any_vertex_on_positive_side = true; - break; - } - } - bool all_vertex_on_negative_side = !is_any_vertex_on_positive_side; - if (all_vertex_on_negative_side) { - intersects = false; - break; - } - } - return intersects; -} - -bool intersect(IsectFrustum i_frustum, Pyramid pyramid) -{ - bool intersects = true; - - /* Do Pyramid vertices vs Frustum planes. */ - for (int p = 0; p < 6; ++p) { - bool is_any_vertex_on_positive_side = false; - for (int v = 0; v < 5; ++v) { - float test = dot(i_frustum.planes[p], vec4(pyramid.corners[v], 1.0)); - if (test > 0.0) { - is_any_vertex_on_positive_side = true; - break; - } - } - bool all_vertex_on_negative_side = !is_any_vertex_on_positive_side; - if (all_vertex_on_negative_side) { - intersects = false; - break; - } - } - - if (!intersects) { - return intersects; - } - - /* Now do Frustum vertices vs Pyramid planes. */ - IsectPyramid i_pyramid = isect_data_setup(pyramid); - for (int p = 0; p < 5; ++p) { - bool is_any_vertex_on_positive_side = false; - for (int v = 0; v < 8; ++v) { - float test = dot(i_pyramid.planes[p], vec4(i_frustum.corners[v].xyz, 1.0)); - if (test > 0.0) { - is_any_vertex_on_positive_side = true; - break; - } - } - bool all_vertex_on_negative_side = !is_any_vertex_on_positive_side; - if (all_vertex_on_negative_side) { - intersects = false; - break; - } - } - return intersects; -} - -bool intersect(IsectFrustum i_frustum, Box box) -{ - bool intersects = true; - - /* Do Box vertices vs Frustum planes. */ - for (int p = 0; p < 6; ++p) { - bool is_any_vertex_on_positive_side = false; - for (int v = 0; v < 8; ++v) { - float test = dot(i_frustum.planes[p], vec4(box.corners[v], 1.0)); - if (test > 0.0) { - is_any_vertex_on_positive_side = true; - break; - } - } - bool all_vertex_on_negative_side = !is_any_vertex_on_positive_side; - if (all_vertex_on_negative_side) { - intersects = false; - break; - } - } - - if (!intersects) { - return intersects; - } - - /* Now do Frustum vertices vs Box planes. */ - IsectBox i_box = isect_data_setup(box); - for (int p = 0; p < 6; ++p) { - bool is_any_vertex_on_positive_side = false; - for (int v = 0; v < 8; ++v) { - float test = dot(i_box.planes[p], vec4(i_frustum.corners[v].xyz, 1.0)); - if (test > 0.0) { - is_any_vertex_on_positive_side = true; - break; - } - } - bool all_vertex_on_negative_side = !is_any_vertex_on_positive_side; - if (all_vertex_on_negative_side) { - intersects = false; - break; - } - } - - return intersects; -} - -bool intersect(IsectFrustum i_frustum, Sphere sphere) -{ - bool intersects = true; - for (int p = 0; p < 6; ++p) { - float dist_to_plane = dot(i_frustum.planes[p], vec4(sphere.center, 1.0)); - if (dist_to_plane < -sphere.radius) { - intersects = false; - break; - } - } - return intersects; -} - -bool intersect(Cone cone, Sphere sphere) -{ - /** - * Following "Improve Tile-based Light Culling with Spherical-sliced Cone" - * by Eric Zhang - * https://lxjk.github.io/2018/03/25/Improve-Tile-based-Light-Culling-with-Spherical-sliced-Cone.html - */ - float sphere_distance = length(sphere.center); - float sphere_distance_rcp = safe_rcp(sphere_distance); - float sphere_sin = saturate(sphere.radius * sphere_distance_rcp); - float sphere_cos = sqrt(1.0 - sphere_sin * sphere_sin); - float cone_aperture_sin = sqrt(1.0 - cone.angle_cos * cone.angle_cos); - - float cone_sphere_center_cos = dot(sphere.center * sphere_distance_rcp, cone.direction); - /* cos(A+B) = cos(A) * cos(B) - sin(A) * sin(B). */ - float cone_sphere_angle_sum_cos = (sphere.radius > sphere_distance) ? - -1.0 : - (cone.angle_cos * sphere_cos - - cone_aperture_sin * sphere_sin); - /* Comparing cosines instead of angles since we are interested - * only in the monotonic region [0 .. M_PI / 2]. This saves costly `acos()` calls. */ - bool intersects = (cone_sphere_center_cos >= cone_sphere_angle_sum_cos); - - return intersects; -} - -bool intersect(Circle circle_a, Circle circle_b) -{ - return distance_squared(circle_a.center, circle_b.center) < - square(circle_a.radius + circle_b.radius); -} - -/** \} */ diff --git a/source/blender/draw/intern/shaders/draw_math_geom_lib.glsl b/source/blender/draw/intern/shaders/draw_math_geom_lib.glsl deleted file mode 100644 index 5feeeab6090..00000000000 --- a/source/blender/draw/intern/shaders/draw_math_geom_lib.glsl +++ /dev/null @@ -1,208 +0,0 @@ -/* SPDX-FileCopyrightText: 2020-2022 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -/** - * Version of common_math_geom_lib.glsl that doesn't rely on common_math_lib. - * This should ultimately be rewritten inside a gpu_shader lib with higher quality standard. - */ - -#pragma BLENDER_REQUIRE(gpu_shader_math_vector_lib.glsl) - -/* ---------------------------------------------------------------------- */ -/** \name Math intersection & projection functions. - * \{ */ - -vec4 plane_from_quad(vec3 v0, vec3 v1, vec3 v2, vec3 v3) -{ - vec3 nor = normalize(cross(v2 - v1, v0 - v1) + cross(v0 - v3, v2 - v3)); - return vec4(nor, -dot(nor, v2)); -} - -vec4 plane_from_tri(vec3 v0, vec3 v1, vec3 v2) -{ - vec3 nor = normalize(cross(v2 - v1, v0 - v1)); - return vec4(nor, -dot(nor, v2)); -} - -float point_plane_projection_dist(vec3 line_origin, vec3 plane_origin, vec3 plane_normal) -{ - return dot(plane_normal, plane_origin - line_origin); -} - -float point_line_projection_dist(vec2 point, vec2 line_origin, vec2 line_normal) -{ - return dot(line_normal, line_origin - point); -} - -float line_plane_intersect_dist(vec3 line_origin, - vec3 line_direction, - vec3 plane_origin, - vec3 plane_normal) -{ - return dot(plane_normal, plane_origin - line_origin) / dot(plane_normal, line_direction); -} - -float line_plane_intersect_dist(vec3 line_origin, vec3 line_direction, vec4 plane) -{ - vec3 plane_co = plane.xyz * (-plane.w / length_squared(plane.xyz)); - vec3 h = line_origin - plane_co; - return -dot(plane.xyz, h) / dot(plane.xyz, line_direction); -} - -vec3 line_plane_intersect(vec3 line_origin, - vec3 line_direction, - vec3 plane_origin, - vec3 plane_normal) -{ - float dist = line_plane_intersect_dist(line_origin, line_direction, plane_origin, plane_normal); - return line_origin + line_direction * dist; -} - -vec3 line_plane_intersect(vec3 line_origin, vec3 line_direction, vec4 plane) -{ - float dist = line_plane_intersect_dist(line_origin, line_direction, plane); - return line_origin + line_direction * dist; -} - -float line_aligned_plane_intersect_dist(vec3 line_origin, vec3 line_direction, vec3 plane_origin) -{ - /* aligned plane normal */ - vec3 L = plane_origin - line_origin; - float disk_dist = length(L); - vec3 plane_normal = -normalize(L); - return -disk_dist / dot(plane_normal, line_direction); -} - -vec3 line_aligned_plane_intersect(vec3 line_origin, vec3 line_direction, vec3 plane_origin) -{ - float dist = line_aligned_plane_intersect_dist(line_origin, line_direction, plane_origin); - if (dist < 0) { - /* if intersection is behind we fake the intersection to be - * really far and (hopefully) not inside the radius of interest */ - dist = 1e16; - } - return line_origin + line_direction * dist; -} - -/** - * Returns intersection distance between the unit sphere and the line - * with the assumption that \a line_origin is contained in the unit sphere. - * It will always returns the farthest intersection. - */ -float line_unit_sphere_intersect_dist(vec3 line_origin, vec3 line_direction) -{ - float a = dot(line_direction, line_direction); - float b = dot(line_direction, line_origin); - float c = dot(line_origin, line_origin) - 1; - - float dist = 1e15; - float determinant = b * b - a * c; - if (determinant >= 0) { - dist = (sqrt(determinant) - b) / a; - } - - return dist; -} - -/** - * Returns minimum intersection distance between the unit box and the line - * with the assumption that \a line_origin is contained in the unit box. - * In other words, it will always returns the farthest intersection. - */ -float line_unit_box_intersect_dist(vec3 line_origin, vec3 line_direction) -{ - /* https://seblagarde.wordpress.com/2012/09/29/image-based-lighting-approaches-and-parallax-corrected-cubemap/ - */ - vec3 first_plane = (vec3(1.0) - line_origin) / line_direction; - vec3 second_plane = (vec3(-1.0) - line_origin) / line_direction; - vec3 farthest_plane = max(first_plane, second_plane); - return reduce_min(farthest_plane); -} - -float line_unit_box_intersect_dist_safe(vec3 line_origin, vec3 line_direction) -{ - vec3 safe_line_direction = max(vec3(1e-8), abs(line_direction)) * - select(vec3(1.0), -vec3(1.0), lessThan(line_direction, vec3(0.0))); - return line_unit_box_intersect_dist(line_origin, safe_line_direction); -} - -/** - * Same as line_unit_box_intersect_dist but for 2D case. - */ -float line_unit_square_intersect_dist(vec2 line_origin, vec2 line_direction) -{ - vec2 first_plane = (vec2(1.0) - line_origin) / line_direction; - vec2 second_plane = (vec2(-1.0) - line_origin) / line_direction; - vec2 farthest_plane = max(first_plane, second_plane); - return reduce_min(farthest_plane); -} - -float line_unit_square_intersect_dist_safe(vec2 line_origin, vec2 line_direction) -{ - vec2 safe_line_direction = max(vec2(1e-8), abs(line_direction)) * - select(vec2(1.0), -vec2(1.0), lessThan(line_direction, vec2(0.0))); - return line_unit_square_intersect_dist(line_origin, safe_line_direction); -} - -/** - * Returns clipping distance (intersection with the nearest plane) with the given axis-aligned - * bound box along \a line_direction. - * Safe even if \a line_direction is degenerate. - * It assumes that an intersection exists (i.e: that \a line_direction points towards the AABB). - */ -float line_aabb_clipping_dist(vec3 line_origin, vec3 line_direction, vec3 aabb_min, vec3 aabb_max) -{ - vec3 safe_dir = select(line_direction, vec3(1e-5), lessThan(abs(line_direction), vec3(1e-5))); - vec3 dir_inv = 1.0 / safe_dir; - - vec3 first_plane = (aabb_min - line_origin) * dir_inv; - vec3 second_plane = (aabb_max - line_origin) * dir_inv; - vec3 nearest_plane = min(first_plane, second_plane); - return reduce_max(nearest_plane); -} - -/** \} */ - -/* ---------------------------------------------------------------------- */ -/** \name Other useful functions. - * \{ */ - -void make_orthonormal_basis(vec3 N, out vec3 T, out vec3 B) -{ - vec3 up_vector = abs(N.z) < 0.99999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0); - T = normalize(cross(up_vector, N)); - B = cross(N, T); -} - -/* ---- Encode / Decode Normal buffer data ---- */ -/* From http://aras-p.info/texts/CompactNormalStorage.html - * Using Method #4: Sphere-map Transform */ -vec2 normal_encode(vec3 n, vec3 view) -{ - float p = sqrt(n.z * 8.0 + 8.0); - return n.xy / p + 0.5; -} - -vec3 normal_decode(vec2 enc, vec3 view) -{ - vec2 fenc = enc * 4.0 - 2.0; - float f = dot(fenc, fenc); - float g = sqrt(1.0 - f / 4.0); - vec3 n; - n.xy = fenc * g; - n.z = 1 - f / 2; - return n; -} - -vec3 tangent_to_world(vec3 vector, vec3 N, vec3 T, vec3 B) -{ - return T * vector.x + B * vector.y + N * vector.z; -} - -vec3 world_to_tangent(vec3 vector, vec3 N, vec3 T, vec3 B) -{ - return vec3(dot(T, vector), dot(B, vector), dot(N, vector)); -} - -/** \} */ diff --git a/source/blender/draw/intern/shaders/draw_model_lib.glsl b/source/blender/draw/intern/shaders/draw_model_lib.glsl deleted file mode 100644 index 5718686aa68..00000000000 --- a/source/blender/draw/intern/shaders/draw_model_lib.glsl +++ /dev/null @@ -1,120 +0,0 @@ -/* SPDX-FileCopyrightText: 2018-2023 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -#pragma BLENDER_REQUIRE(draw_view_lib.glsl) - -#ifndef DRAW_MODELMAT_CREATE_INFO -# error Missing draw_modelmat additional create info on shader create info -#endif - -#if defined(UNIFORM_RESOURCE_ID) -/* TODO(fclem): Legacy API. To remove. */ -# define resource_id drw_ResourceID -# define DRW_RESOURCE_ID_VARYING_SET - -#elif defined(GPU_VERTEX_SHADER) -# if defined(UNIFORM_RESOURCE_ID_NEW) -# define resource_id (drw_ResourceID >> DRW_VIEW_SHIFT) -# else -# define resource_id gpu_InstanceIndex -# endif -# define DRW_RESOURCE_ID_VARYING_SET drw_ResourceID_iface.resource_index = resource_id; - -#elif defined(GPU_GEOMETRY_SHADER) -# define resource_id drw_ResourceID_iface_in[0].resource_index - -#elif defined(GPU_FRAGMENT_SHADER) -# define resource_id drw_ResourceID_iface.resource_index -#endif - -mat4x4 drw_modelmat() -{ - return drw_matrix_buf[resource_id].model; -} -mat4x4 drw_modelinv() -{ - return drw_matrix_buf[resource_id].model_inverse; -} - -/** - * Usually Normal matrix is `transpose(inverse(ViewMatrix * ModelMatrix))`. - * - * But since it is slow to multiply matrices we decompose it. Decomposing - * inversion and transposition both invert the product order leaving us with - * the same original order: - * transpose(ViewMatrixInverse) * transpose(ModelMatrixInverse) - * - * Knowing that the view matrix is orthogonal, the transpose is also the inverse. - * NOTE: This is only valid because we are only using the mat3 of the ViewMatrixInverse. - * ViewMatrix * transpose(ModelMatrixInverse) - */ -mat3x3 drw_normat() -{ - return transpose(mat3x3(drw_modelinv())); -} -mat3x3 drw_norinv() -{ - return transpose(mat3x3(drw_modelmat())); -} - -/* -------------------------------------------------------------------- */ -/** \name Transform Normal - * - * Space conversion helpers for normal vectors. - * \{ */ - -vec3 drw_normal_object_to_world(vec3 lN) -{ - return (drw_normat() * lN); -} -vec3 drw_normal_world_to_object(vec3 N) -{ - return (drw_norinv() * N); -} - -vec3 drw_normal_object_to_view(vec3 lN) -{ - return (mat3x3(drw_view.viewmat) * (drw_normat() * lN)); -} -vec3 drw_normal_view_to_object(vec3 vN) -{ - return (drw_norinv() * (mat3x3(drw_view.viewinv) * vN)); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Transform Normal - * - * Space conversion helpers for points (coordinates). - * \{ */ - -vec3 drw_point_object_to_world(vec3 lP) -{ - return (drw_modelmat() * vec4(lP, 1.0)).xyz; -} -vec3 drw_point_world_to_object(vec3 P) -{ - return (drw_modelinv() * vec4(P, 1.0)).xyz; -} - -vec3 drw_point_object_to_view(vec3 lP) -{ - return (drw_view.viewmat * (drw_modelmat() * vec4(lP, 1.0))).xyz; -} -vec3 drw_point_view_to_object(vec3 vP) -{ - return (drw_modelinv() * (drw_view.viewinv * vec4(vP, 1.0))).xyz; -} - -vec4 drw_point_object_to_homogenous(vec3 lP) -{ - return (drw_view.winmat * (drw_view.viewmat * (drw_modelmat() * vec4(lP, 1.0)))); -} -vec3 drw_point_object_to_ndc(vec3 lP) -{ - return drw_perspective_divide(drw_point_object_to_homogenous(lP)); -} - -/** \} */ diff --git a/source/blender/draw/intern/shaders/draw_view_info.hh b/source/blender/draw/intern/shaders/draw_view_info.hh index f7e0cbf8ce6..b6e3e715676 100644 --- a/source/blender/draw/intern/shaders/draw_view_info.hh +++ b/source/blender/draw/intern/shaders/draw_view_info.hh @@ -48,13 +48,11 @@ GPU_SHADER_CREATE_INFO(draw_resource_handle) GPU_SHADER_CREATE_INFO(draw_view) .uniform_buf(DRW_VIEW_UBO_SLOT, "ViewMatrices", "drw_view_[DRW_VIEW_LEN]", Frequency::PASS) - .define("DRAW_VIEW_CREATE_INFO") .define("drw_view", "drw_view_[drw_view_id]") .typedef_source("draw_shader_shared.h"); GPU_SHADER_CREATE_INFO(draw_view_culling) .uniform_buf(DRW_VIEW_CULLING_UBO_SLOT, "ViewCullingData", "drw_view_culling_[DRW_VIEW_LEN]") - .define("DRW_VIEW_CULLING_INFO") .define("drw_view_culling", "drw_view_culling_[drw_view_id]") .typedef_source("draw_shader_shared.h"); @@ -171,8 +169,11 @@ GPU_SHADER_CREATE_INFO(draw_gpencil_new) .sampler(0, ImageType::FLOAT_BUFFER, "gp_pos_tx") .sampler(1, ImageType::FLOAT_BUFFER, "gp_col_tx") /* Per Object */ - .define("gpThicknessScale", "1.0") /* TODO(fclem): Replace with object info. */ + .define("gpThicknessScale", "1.0") /* TODO(fclem): Replace with object info. */ + .define("gpThicknessWorldScale", "1.0 / 2000.0") /* TODO(fclem): Same as above. */ + .define("gpThicknessIsScreenSpace", "(gpThicknessWorldScale < 0.0)") /* Per Layer */ + .define("gpThicknessOffset", "0.0") /* TODO(fclem): Remove. */ .additional_info("draw_modelmat_new", "draw_resource_id_varying", "draw_view", @@ -197,8 +198,8 @@ GPU_SHADER_CREATE_INFO(draw_resource_finalize) GPU_SHADER_CREATE_INFO(draw_view_finalize) .do_static_compilation(true) - .local_group_size(DRW_VIEW_MAX) - .define("DRW_VIEW_LEN", STRINGIFY(DRW_VIEW_MAX)) + .local_group_size(64) /* DRW_VIEW_MAX */ + .define("DRW_VIEW_LEN", "64") .storage_buf(0, Qualifier::READ_WRITE, "ViewCullingData", "view_culling_buf[DRW_VIEW_LEN]") .compute_source("draw_view_finalize_comp.glsl") .additional_info("draw_view"); @@ -206,7 +207,7 @@ GPU_SHADER_CREATE_INFO(draw_view_finalize) GPU_SHADER_CREATE_INFO(draw_visibility_compute) .do_static_compilation(true) .local_group_size(DRW_VISIBILITY_GROUP_SIZE) - .define("DRW_VIEW_LEN", STRINGIFY(DRW_VIEW_MAX)) + .define("DRW_VIEW_LEN", "64") .storage_buf(0, Qualifier::READ, "ObjectBounds", "bounds_buf[]") .storage_buf(1, Qualifier::READ_WRITE, "uint", "visibility_buf[]") .push_constant(Type::INT, "resource_len") @@ -278,7 +279,6 @@ GPU_SHADER_CREATE_INFO(draw_resource_handle_new).define("resource_handle", "drw_ GPU_SHADER_CREATE_INFO(draw_modelmat_new_common) .typedef_source("draw_shader_shared.h") .storage_buf(DRW_OBJ_MAT_SLOT, Qualifier::READ, "ObjectMatrices", "drw_matrix_buf[]") - .define("DRAW_MODELMAT_CREATE_INFO") .define("drw_ModelMatrixInverse", "drw_matrix_buf[resource_id].model_inverse") .define("drw_ModelMatrix", "drw_matrix_buf[resource_id].model") /* TODO For compatibility with old shaders. To be removed. */ diff --git a/source/blender/draw/intern/shaders/draw_view_lib.glsl b/source/blender/draw/intern/shaders/draw_view_lib.glsl deleted file mode 100644 index 83e719414bb..00000000000 --- a/source/blender/draw/intern/shaders/draw_view_lib.glsl +++ /dev/null @@ -1,188 +0,0 @@ -/* SPDX-FileCopyrightText: 2018-2023 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -#ifndef DRAW_VIEW_CREATE_INFO -# error Missing draw_view additional create info on shader create info -#endif - -/* Returns true if the current view has a perspective projection matrix. */ -bool drw_view_is_perspective() -{ - return drw_view.winmat[3][3] == 0.0; -} - -/* Returns the view forward vector, going towards the viewer. */ -vec3 drw_view_forward() -{ - return drw_view.viewinv[2].xyz; -} - -/* Returns the view origin. */ -vec3 drw_view_position() -{ - return drw_view.viewinv[3].xyz; -} - -/* Returns the projection matrix far clip distance. */ -float drw_view_far() -{ - if (drw_view_is_perspective()) { - return -drw_view.winmat[3][2] / (drw_view.winmat[2][2] + 1.0); - } - return -(drw_view.winmat[3][2] - 1.0) / drw_view.winmat[2][2]; -} - -/* Returns the projection matrix near clip distance. */ -float drw_view_near() -{ - if (drw_view_is_perspective()) { - return -drw_view.winmat[3][2] / (drw_view.winmat[2][2] - 1.0); - } - return -(drw_view.winmat[3][2] + 1.0) / drw_view.winmat[2][2]; -} - -/** - * Returns the world incident vector `V` (going towards the viewer) - * from the world position `P` and the current view. - */ -vec3 drw_world_incident_vector(vec3 P) -{ - return drw_view_is_perspective() ? normalize(drw_view_position() - P) : drw_view_forward(); -} - -/** - * Returns the view incident vector `vV` (going towards the viewer) - * from the view position `vP` and the current view. - */ -vec3 drw_view_incident_vector(vec3 vP) -{ - return drw_view_is_perspective() ? normalize(-vP) : vec3(0.0, 0.0, 1.0); -} - -/** - * Transform position on screen UV space [0..1] to Normalized Device Coordinate space [-1..1]. - */ -vec3 drw_screen_to_ndc(vec3 ss_P) -{ - return ss_P * 2.0 - 1.0; -} -vec2 drw_screen_to_ndc(vec2 ss_P) -{ - return ss_P * 2.0 - 1.0; -} -float drw_screen_to_ndc(float ss_P) -{ - return ss_P * 2.0 - 1.0; -} - -/** - * Transform position in Normalized Device Coordinate [-1..1] to screen UV space [0..1]. - */ -vec3 drw_ndc_to_screen(vec3 ndc_P) -{ - return ndc_P * 0.5 + 0.5; -} -vec2 drw_ndc_to_screen(vec2 ndc_P) -{ - return ndc_P * 0.5 + 0.5; -} -float drw_ndc_to_screen(float ndc_P) -{ - return ndc_P * 0.5 + 0.5; -} - -/* -------------------------------------------------------------------- */ -/** \name Transform Normal - * \{ */ - -vec3 drw_normal_view_to_world(vec3 vN) -{ - return (mat3x3(drw_view.viewinv) * vN); -} - -vec3 drw_normal_world_to_view(vec3 N) -{ - return (mat3x3(drw_view.viewmat) * N); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Transform Position - * \{ */ - -vec3 drw_perspective_divide(vec4 hs_P) -{ - return hs_P.xyz / hs_P.w; -} - -vec3 drw_point_view_to_world(vec3 vP) -{ - return (drw_view.viewinv * vec4(vP, 1.0)).xyz; -} -vec4 drw_point_view_to_homogenous(vec3 vP) -{ - return (drw_view.winmat * vec4(vP, 1.0)); -} -vec3 drw_point_view_to_ndc(vec3 vP) -{ - return drw_perspective_divide(drw_point_view_to_homogenous(vP)); -} - -vec3 drw_point_world_to_view(vec3 P) -{ - return (drw_view.viewmat * vec4(P, 1.0)).xyz; -} -vec4 drw_point_world_to_homogenous(vec3 P) -{ - return (drw_view.winmat * (drw_view.viewmat * vec4(P, 1.0))); -} -vec3 drw_point_world_to_ndc(vec3 P) -{ - return drw_perspective_divide(drw_point_world_to_homogenous(P)); -} - -vec3 drw_point_ndc_to_view(vec3 ssP) -{ - return drw_perspective_divide(drw_view.wininv * vec4(ssP, 1.0)); -} -vec3 drw_point_ndc_to_world(vec3 ssP) -{ - return drw_point_view_to_world(drw_point_ndc_to_view(ssP)); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Transform Screen Positions - * \{ */ - -vec3 drw_point_view_to_screen(vec3 vP) -{ - return drw_ndc_to_screen(drw_point_view_to_ndc(vP)); -} -vec3 drw_point_world_to_screen(vec3 vP) -{ - return drw_ndc_to_screen(drw_point_world_to_ndc(vP)); -} - -vec3 drw_point_screen_to_view(vec3 ssP) -{ - return drw_point_ndc_to_view(drw_screen_to_ndc(ssP)); -} -vec3 drw_point_screen_to_world(vec3 ssP) -{ - return drw_point_view_to_world(drw_point_screen_to_view(ssP)); -} - -float drw_depth_view_to_screen(float v_depth) -{ - return drw_point_view_to_screen(vec3(0.0, 0.0, v_depth)).z; -} -float drw_depth_screen_to_view(float ss_depth) -{ - return drw_point_screen_to_view(vec3(0.0, 0.0, ss_depth)).z; -} - -/** \} */ diff --git a/source/blender/draw/intern/shaders/draw_view_reconstruction_lib.glsl b/source/blender/draw/intern/shaders/draw_view_reconstruction_lib.glsl deleted file mode 100644 index 16fd838228c..00000000000 --- a/source/blender/draw/intern/shaders/draw_view_reconstruction_lib.glsl +++ /dev/null @@ -1,82 +0,0 @@ -/* SPDX-FileCopyrightText: 2018-2023 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -#pragma BLENDER_REQUIRE(draw_view_lib.glsl) - -/* Similar to https://atyuwen.github.io/posts/normal-reconstruction/. - * This samples the depth buffer 4 time for each direction to get the most correct - * implicit normal reconstruction out of the depth buffer. */ -vec3 view_position_derivative_from_depth(sampler2D scene_depth_tx, - ivec2 extent, - vec2 uv, - ivec2 texel, - ivec2 offset, - vec3 vP, - float depth_center) -{ - vec4 H; - H.x = texelFetch(scene_depth_tx, texel - offset * 2, 0).r; - H.y = texelFetch(scene_depth_tx, texel - offset, 0).r; - H.z = texelFetch(scene_depth_tx, texel + offset, 0).r; - H.w = texelFetch(scene_depth_tx, texel + offset * 2, 0).r; - - vec2 uv_offset = vec2(offset) / vec2(extent); - vec2 uv1 = uv - uv_offset * 2.0; - vec2 uv2 = uv - uv_offset; - vec2 uv3 = uv + uv_offset; - vec2 uv4 = uv + uv_offset * 2.0; - - /* Fix issue with depth precision. Take even larger diff. */ - vec4 diff = abs(vec4(depth_center, H.yzw) - H.x); - if (reduce_max(diff) < 2.4e-7 && all(lessThan(diff.xyz, diff.www))) { - vec3 P1 = drw_point_screen_to_view(vec3(uv1, H.x)); - vec3 P3 = drw_point_screen_to_view(vec3(uv3, H.w)); - return 0.25 * (P3 - P1); - } - /* Simplified (H.xw + 2.0 * (H.yz - H.xw)) - depth_center */ - vec2 deltas = abs((2.0 * H.yz - H.xw) - depth_center); - if (deltas.x < deltas.y) { - return vP - drw_point_screen_to_view(vec3(uv2, H.y)); - } - return drw_point_screen_to_view(vec3(uv3, H.z)) - vP; -} - -struct SurfaceReconstructResult { - /* View position. */ - vec3 vP; - /* View geometric normal. */ - vec3 vNg; - /* Screen depth [0..1]. Corresponding to the depth buffer value. */ - float depth; - /* True if the pixel has background depth. */ - bool is_background; -}; - -/** - * Reconstruct surface information from the depth buffer. - * Use adjacent pixel info to reconstruct normals. - - * \a extent is the valid region of depth_tx. - * \a texel is the pixel coordinate [0..extent-1] to reconstruct. - */ -SurfaceReconstructResult view_reconstruct_from_depth(sampler2D scene_depth_tx, - ivec2 extent, - ivec2 texel) -{ - SurfaceReconstructResult result; - result.depth = texelFetch(scene_depth_tx, texel, 0).r; - result.is_background = (result.depth == 1.0); - vec2 uv = (vec2(texel) + vec2(0.5)) / vec2(extent); - result.vP = drw_point_screen_to_view(vec3(uv, result.depth)); - if (result.is_background) { - result.vNg = drw_view_incident_vector(result.vP); - return result; - } - vec3 dPdx = view_position_derivative_from_depth( - scene_depth_tx, extent, uv, texel, ivec2(1, 0), result.vP, result.depth); - vec3 dPdy = view_position_derivative_from_depth( - scene_depth_tx, extent, uv, texel, ivec2(0, 1), result.vP, result.depth); - result.vNg = safe_normalize(cross(dPdx, dPdy)); - return result; -} diff --git a/source/blender/draw/tests/draw_pass_test.cc b/source/blender/draw/tests/draw_pass_test.cc index 2f22d222384..451a81b9029 100644 --- a/source/blender/draw/tests/draw_pass_test.cc +++ b/source/blender/draw/tests/draw_pass_test.cc @@ -29,7 +29,6 @@ static void test_draw_pass_all_commands() /* Won't be dereferenced. */ GPUVertBuf *vbo = (GPUVertBuf *)1; GPUIndexBuf *ibo = (GPUIndexBuf *)1; - GPUFrameBuffer *fb = nullptr; float4 color(1.0f, 1.0f, 1.0f, 0.0f); int3 dispatch_size(1); @@ -43,8 +42,6 @@ static void test_draw_pass_all_commands() const int color_location = GPU_shader_get_uniform(sh, "color"); const int mvp_location = GPU_shader_get_uniform(sh, "ModelViewProjectionMatrix"); pass.shader_set(sh); - pass.framebuffer_set(&fb); - pass.subpass_transition(GPU_ATTACHEMENT_IGNORE, {GPU_ATTACHEMENT_WRITE, GPU_ATTACHEMENT_READ}); pass.bind_texture("image", tex); pass.bind_texture("image", &tex); pass.bind_image("missing_image", tex); /* Should not crash. */ @@ -83,18 +80,6 @@ static void test_draw_pass_all_commands() << " .stencil_set(write_mask=0b10000000, reference=0b00001111, compare_mask=0b10001111)" << std::endl; expected << " .shader_bind(gpu_shader_3D_image_color)" << std::endl; - expected << " .framebuffer_bind(nullptr)" << std::endl; - expected << " .subpass_transition(" << std::endl; - expected << "depth=ignore," << std::endl; - expected << "color0=write," << std::endl; - expected << "color1=read," << std::endl; - expected << "color2=ignore," << std::endl; - expected << "color3=ignore," << std::endl; - expected << "color4=ignore," << std::endl; - expected << "color5=ignore," << std::endl; - expected << "color6=ignore," << std::endl; - expected << "color7=ignore" << std::endl; - expected << ")" << std::endl; expected << " .bind_texture(0, sampler=internal)" << std::endl; expected << " .bind_texture_ref(0, sampler=internal)" << std::endl; expected << " .bind_image(-1)" << std::endl; diff --git a/source/blender/draw/tests/eevee_test.cc b/source/blender/draw/tests/eevee_test.cc index ecfb94740c1..c61b7af4232 100644 --- a/source/blender/draw/tests/eevee_test.cc +++ b/source/blender/draw/tests/eevee_test.cc @@ -502,8 +502,7 @@ class TestDefrag { ShadowPageHeapBuf pages_free_data = {"PagesFreeBuf"}; ShadowPageCacheBuf pages_cached_data = {"PagesCachedBuf"}; ShadowPagesInfoDataBuf pages_infos_data = {"PagesInfosBuf"}; - StorageBuffer clear_dispatch_buf; - StorageBuffer tile_draw_buf; + StorageBuffer clear_draw_buf; ShadowStatisticsBuf statistics_buf = {"statistics_buf"}; public: @@ -573,9 +572,8 @@ class TestDefrag { pass.bind_ssbo("pages_infos_buf", pages_infos_data); pass.bind_ssbo("pages_free_buf", pages_free_data); pass.bind_ssbo("pages_cached_buf", pages_cached_data); - pass.bind_ssbo("clear_dispatch_buf", clear_dispatch_buf); - pass.bind_ssbo("tile_draw_buf", tile_draw_buf); pass.bind_ssbo("statistics_buf", statistics_buf); + pass.bind_ssbo("clear_draw_buf", clear_draw_buf); pass.dispatch(int3(1, 1, 1)); pass.barrier(GPU_BARRIER_BUFFER_UPDATE); @@ -809,9 +807,6 @@ static void test_eevee_shadow_finalize() tilemap.viewmat = float4x4::identity(); tilemap.tiles_index = 0; tilemap.clip_data_index = 0; - tilemap.clip_far = 10.0f; - tilemap.clip_near = 1.0f; - tilemap.half_size = 1.0f; tilemap.projection_type = SHADOW_PROJECTION_CUBEFACE; tilemaps_data.append(tilemap); @@ -846,9 +841,7 @@ static void test_eevee_shadow_finalize() StorageArrayBuffer shadow_multi_view_buf = {"ShadowMultiView"}; StorageBuffer clear_dispatch_buf; - StorageBuffer tile_draw_buf; - StorageArrayBuffer dst_coord_buf = {"dst_coord_buf"}; - StorageArrayBuffer src_coord_buf = {"src_coord_buf"}; + StorageArrayBuffer clear_list_buf = {"clear_list_buf"}; StorageArrayBuffer render_map_buf = {"render_map_buf"}; StorageArrayBuffer viewport_index_buf = {"viewport_index_buf"}; @@ -864,9 +857,7 @@ static void test_eevee_shadow_finalize() pass.bind_ssbo("view_infos_buf", shadow_multi_view_buf); pass.bind_ssbo("statistics_buf", statistics_buf); pass.bind_ssbo("clear_dispatch_buf", clear_dispatch_buf); - pass.bind_ssbo("tile_draw_buf", tile_draw_buf); - pass.bind_ssbo("dst_coord_buf", dst_coord_buf); - pass.bind_ssbo("src_coord_buf", src_coord_buf); + pass.bind_ssbo("clear_list_buf", clear_list_buf); pass.bind_ssbo("render_map_buf", render_map_buf); pass.bind_ssbo("viewport_index_buf", viewport_index_buf); pass.bind_ssbo("pages_infos_buf", pages_infos_data); diff --git a/source/blender/editors/animation/anim_channels_defines.cc b/source/blender/editors/animation/anim_channels_defines.cc index 5f99fad06e4..1de1537ec6d 100644 --- a/source/blender/editors/animation/anim_channels_defines.cc +++ b/source/blender/editors/animation/anim_channels_defines.cc @@ -8,8 +8,6 @@ #include -#include "ANIM_keyframing.hh" - #include "MEM_guardedalloc.h" #include "BLI_blenlib.h" @@ -5034,14 +5032,14 @@ static void achannel_setting_slider_cb(bContext *C, void *id_poin, void *fcu_poi } /* insert a keyframe for this F-Curve */ - done = blender::animrig::insert_keyframe_direct(reports, - ptr, - prop, - fcu, - &anim_eval_context, - eBezTriple_KeyframeType(ts->keyframe_type), - nla_context, - flag); + done = insert_keyframe_direct(reports, + ptr, + prop, + fcu, + &anim_eval_context, + eBezTriple_KeyframeType(ts->keyframe_type), + nla_context, + flag); if (done) { if (adt->action != nullptr) { @@ -5104,14 +5102,14 @@ static void achannel_setting_slider_shapekey_cb(bContext *C, void *key_poin, voi /* insert a keyframe for this F-Curve */ const AnimationEvalContext remapped_anim_eval_context = BKE_animsys_eval_context_construct_at( &anim_eval_context, remapped_frame); - done = blender::animrig::insert_keyframe_direct(reports, - ptr, - prop, - fcu, - &remapped_anim_eval_context, - eBezTriple_KeyframeType(ts->keyframe_type), - nla_context, - flag); + done = insert_keyframe_direct(reports, + ptr, + prop, + fcu, + &remapped_anim_eval_context, + eBezTriple_KeyframeType(ts->keyframe_type), + nla_context, + flag); if (done) { WM_event_add_notifier(C, NC_ANIMATION | ND_ANIMCHAN | NA_EDITED, nullptr); @@ -5163,14 +5161,14 @@ static void achannel_setting_slider_nla_curve_cb(bContext *C, void * /*id_poin*/ Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); const AnimationEvalContext anim_eval_context = BKE_animsys_eval_context_construct(depsgraph, cfra); - done = blender::animrig::insert_keyframe_direct(reports, - ptr, - prop, - fcu, - &anim_eval_context, - eBezTriple_KeyframeType(ts->keyframe_type), - nullptr, - flag); + done = insert_keyframe_direct(reports, + ptr, + prop, + fcu, + &anim_eval_context, + eBezTriple_KeyframeType(ts->keyframe_type), + nullptr, + flag); if (done) { WM_event_add_notifier(C, NC_ANIMATION | ND_ANIMCHAN | NA_EDITED, nullptr); diff --git a/source/blender/editors/animation/drivers.cc b/source/blender/editors/animation/drivers.cc index 5712c7ebaaf..e649369ec12 100644 --- a/source/blender/editors/animation/drivers.cc +++ b/source/blender/editors/animation/drivers.cc @@ -177,7 +177,7 @@ static int add_driver_with_target(ReportList * /*reports*/, if ((RNA_property_unit(dst_prop) == PROP_UNIT_ROTATION) && (RNA_property_unit(src_prop) != PROP_UNIT_ROTATION)) { - /* Rotation Destination: normal -> radians, so convert src to radians + /* Rotation Destination: normal -> radians, so convert src to radians * (However, if both input and output is a rotation, don't apply such corrections) */ STRNCPY(driver->expression, "radians(var)"); diff --git a/source/blender/editors/animation/keyframes_general.cc b/source/blender/editors/animation/keyframes_general.cc index 9b550859b49..e6796d2c365 100644 --- a/source/blender/editors/animation/keyframes_general.cc +++ b/source/blender/editors/animation/keyframes_general.cc @@ -15,7 +15,7 @@ #include "BLI_blenlib.h" #include "BLI_math_vector.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_utildefines.h" #include "DNA_anim_types.h" @@ -684,7 +684,7 @@ void ease_fcurve_segment(FCurve *fcu, FCurveSegment *segment, const float factor const float exponent = 1 + fabs(factor) * 4; for (int i = segment->start_index; i < segment->start_index + segment->length; i++) { - /* For easy calculation of the curve, the values are normalized. */ + /* For easy calculation of the curve, the values are normalized. */ const float normalized_x = (fcu->bezt[i].vec[1][0] - left_x) / key_x_range; float normalized_y = 0; diff --git a/source/blender/editors/animation/keyframing.cc b/source/blender/editors/animation/keyframing.cc index e8c4063a758..f362e03162e 100644 --- a/source/blender/editors/animation/keyframing.cc +++ b/source/blender/editors/animation/keyframing.cc @@ -6,12 +6,20 @@ * \ingroup edanimation */ +#include +#include #include #include +#include #include "MEM_guardedalloc.h" #include "BLI_blenlib.h" +#include "BLI_dynstr.h" +#include "BLI_math_matrix.h" +#include "BLI_math_rotation.h" +#include "BLI_math_vector.h" +#include "BLI_utildefines.h" #include "BLT_translation.h" @@ -22,6 +30,7 @@ #include "DNA_key_types.h" #include "DNA_material_types.h" #include "DNA_object_types.h" +#include "DNA_rigidbody_types.h" #include "DNA_scene_types.h" #include "BKE_action.h" @@ -30,23 +39,28 @@ #include "BKE_armature.h" #include "BKE_context.h" #include "BKE_fcurve.h" +#include "BKE_fcurve_driver.h" #include "BKE_global.h" #include "BKE_idtype.h" +#include "BKE_key.h" +#include "BKE_lib_id.h" +#include "BKE_main.h" +#include "BKE_material.h" #include "BKE_nla.h" #include "BKE_report.h" #include "BKE_scene.h" #include "DEG_depsgraph.hh" #include "DEG_depsgraph_build.hh" +#include "DEG_depsgraph_query.hh" +#include "ED_anim_api.hh" #include "ED_keyframes_edit.hh" #include "ED_keyframing.hh" #include "ED_object.hh" #include "ED_screen.hh" #include "ANIM_bone_collections.h" -#include "ANIM_fcurve.hh" -#include "ANIM_keyframing.hh" #include "UI_interface.hh" #include "UI_resources.hh" @@ -73,23 +87,22 @@ static int delete_key_using_keying_set(bContext *C, wmOperator *op, KeyingSet *k eInsertKeyFlags ANIM_get_keyframing_flags(Scene *scene, const bool use_autokey_mode) { - using namespace blender::animrig; eInsertKeyFlags flag = INSERTKEY_NOFLAGS; /* standard flags */ { /* visual keying */ - if (is_autokey_flag(scene, AUTOKEY_FLAG_AUTOMATKEY)) { + if (IS_AUTOKEY_FLAG(scene, AUTOMATKEY)) { flag |= INSERTKEY_MATRIX; } /* only needed */ - if (is_autokey_flag(scene, AUTOKEY_FLAG_INSERTNEEDED)) { + if (IS_AUTOKEY_FLAG(scene, INSERTNEEDED)) { flag |= INSERTKEY_NEEDED; } /* default F-Curve color mode - RGB from XYZ indices */ - if (is_autokey_flag(scene, AUTOKEY_FLAG_XYZ2RGB)) { + if (IS_AUTOKEY_FLAG(scene, XYZ2RGB)) { flag |= INSERTKEY_XYZ2RGB; } } @@ -97,12 +110,12 @@ eInsertKeyFlags ANIM_get_keyframing_flags(Scene *scene, const bool use_autokey_m /* only if including settings from the autokeying mode... */ if (use_autokey_mode) { /* keyframing mode - only replace existing keyframes */ - if (is_autokey_mode(scene, AUTOKEY_MODE_EDITKEYS)) { + if (IS_AUTOKEY_MODE(scene, EDITKEYS)) { flag |= INSERTKEY_REPLACE; } /* cycle-aware keyframe insertion - preserve cycle period and flow */ - if (is_autokey_flag(scene, AUTOKEY_FLAG_CYCLEAWARE)) { + if (IS_AUTOKEY_FLAG(scene, CYCLEAWARE)) { flag |= INSERTKEY_CYCLE_AWARE; } } @@ -233,7 +246,7 @@ FCurve *ED_action_fcurve_ensure(Main *bmain, } /** Helper for #update_autoflags_fcurve(). */ -void update_autoflags_fcurve_direct(FCurve *fcu, PropertyRNA *prop) +static void update_autoflags_fcurve_direct(FCurve *fcu, PropertyRNA *prop) { /* set additional flags for the F-Curve (i.e. only integer values) */ fcu->flag &= ~(FCURVE_INT_VALUES | FCURVE_DISCRETE_VALUES); @@ -292,6 +305,85 @@ void update_autoflags_fcurve(FCurve *fcu, bContext *C, ReportList *reports, Poin /* ************************************************** */ /* KEYFRAME INSERTION */ +/** + * Move the point where a key is about to be inserted to be inside the main cycle range. + * Returns the type of the cycle if it is enabled and valid. + */ +static eFCU_Cycle_Type remap_cyclic_keyframe_location(FCurve *fcu, float *px, float *py) +{ + if (fcu->totvert < 2 || !fcu->bezt) { + return FCU_CYCLE_NONE; + } + + eFCU_Cycle_Type type = BKE_fcurve_get_cycle_type(fcu); + + if (type == FCU_CYCLE_NONE) { + return FCU_CYCLE_NONE; + } + + BezTriple *first = &fcu->bezt[0], *last = &fcu->bezt[fcu->totvert - 1]; + float start = first->vec[1][0], end = last->vec[1][0]; + + if (start >= end) { + return FCU_CYCLE_NONE; + } + + if (*px < start || *px > end) { + float period = end - start; + float step = floorf((*px - start) / period); + *px -= step * period; + + if (type == FCU_CYCLE_OFFSET) { + /* Nasty check to handle the case when the modes are different better. */ + FMod_Cycles *data = static_cast(((FModifier *)fcu->modifiers.first)->data); + short mode = (step >= 0) ? data->after_mode : data->before_mode; + + if (mode == FCM_EXTRAPOLATE_CYCLIC_OFFSET) { + *py -= step * (last->vec[1][1] - first->vec[1][1]); + } + } + } + + return type; +} + +/** Used to make curves newly added to a cyclic Action cycle with the correct period. */ +static void make_new_fcurve_cyclic(const bAction *act, FCurve *fcu) +{ + /* The curve must contain one (newly-added) keyframe. */ + if (fcu->totvert != 1 || !fcu->bezt) { + return; + } + + const float period = act->frame_end - act->frame_start; + + if (period < 0.1f) { + return; + } + + /* Move the keyframe into the range. */ + const float frame_offset = fcu->bezt[0].vec[1][0] - act->frame_start; + const float fix = floorf(frame_offset / period) * period; + + fcu->bezt[0].vec[0][0] -= fix; + fcu->bezt[0].vec[1][0] -= fix; + fcu->bezt[0].vec[2][0] -= fix; + + /* Duplicate and offset the keyframe. */ + fcu->bezt = static_cast(MEM_reallocN(fcu->bezt, sizeof(BezTriple) * 2)); + fcu->totvert = 2; + + fcu->bezt[1] = fcu->bezt[0]; + fcu->bezt[1].vec[0][0] += period; + fcu->bezt[1].vec[1][0] += period; + fcu->bezt[1].vec[2][0] += period; + + /* Add the cycles modifier. */ + if (!fcu->modifiers.first) { + add_fmodifier(&fcu->modifiers, FMODIFIER_TYPE_CYCLES, fcu); + } +} + /* -------------- BezTriple Insertion -------------------- */ /* Change the Y position of a keyframe to match the input, adjusting handles. */ @@ -554,68 +646,1025 @@ int insert_vert_fcurve( return a; } +/* -------------- 'Smarter' Keyframing Functions -------------------- */ +/* return codes for new_key_needed */ +enum { + KEYNEEDED_DONTADD = 0, + KEYNEEDED_JUSTADD, + KEYNEEDED_DELPREV, + KEYNEEDED_DELNEXT, +} /*eKeyNeededStatus*/; + +/** + * This helper function determines whether a new keyframe is needed. + * + * Cases where keyframes should not be added: + * 1. Keyframe to be added between two keyframes with similar values. + * 2. Keyframe to be added on frame where two keyframes are already situated. + * 3. Keyframe lies at point that intersects the linear line between two keyframes. + */ +static short new_key_needed(FCurve *fcu, float cFrame, float nValue) +{ + /* safety checking */ + if (fcu == nullptr) { + return KEYNEEDED_JUSTADD; + } + int totCount = fcu->totvert; + if (totCount == 0) { + return KEYNEEDED_JUSTADD; + } + + /* loop through checking if any are the same */ + BezTriple *bezt = fcu->bezt; + BezTriple *prev = nullptr; + for (int i = 0; i < totCount; i++) { + float prevPosi = 0.0f, prevVal = 0.0f; + float beztPosi = 0.0f, beztVal = 0.0f; + + /* get current time+value */ + beztPosi = bezt->vec[1][0]; + beztVal = bezt->vec[1][1]; + + if (prev) { + /* there is a keyframe before the one currently being examined */ + + /* get previous time+value */ + prevPosi = prev->vec[1][0]; + prevVal = prev->vec[1][1]; + + /* keyframe to be added at point where there are already two similar points? */ + if (IS_EQF(prevPosi, cFrame) && IS_EQF(beztPosi, cFrame) && IS_EQF(beztPosi, prevPosi)) { + return KEYNEEDED_DONTADD; + } + + /* keyframe between prev+current points ? */ + if ((prevPosi <= cFrame) && (cFrame <= beztPosi)) { + /* is the value of keyframe to be added the same as keyframes on either side ? */ + if (IS_EQF(prevVal, nValue) && IS_EQF(beztVal, nValue) && IS_EQF(prevVal, beztVal)) { + return KEYNEEDED_DONTADD; + } + + float realVal; + + /* get real value of curve at that point */ + realVal = evaluate_fcurve(fcu, cFrame); + + /* compare whether it's the same as proposed */ + if (IS_EQF(realVal, nValue)) { + return KEYNEEDED_DONTADD; + } + return KEYNEEDED_JUSTADD; + } + + /* new keyframe before prev beztriple? */ + if (cFrame < prevPosi) { + /* A new keyframe will be added. However, whether the previous beztriple + * stays around or not depends on whether the values of previous/current + * beztriples and new keyframe are the same. + */ + if (IS_EQF(prevVal, nValue) && IS_EQF(beztVal, nValue) && IS_EQF(prevVal, beztVal)) { + return KEYNEEDED_DELNEXT; + } + + return KEYNEEDED_JUSTADD; + } + } + else { + /* just add a keyframe if there's only one keyframe + * and the new one occurs before the existing one does. + */ + if ((cFrame < beztPosi) && (totCount == 1)) { + return KEYNEEDED_JUSTADD; + } + } + + /* continue. frame to do not yet passed (or other conditions not met) */ + if (i < (totCount - 1)) { + prev = bezt; + bezt++; + } + else { + break; + } + } + + /* Frame in which to add a new-keyframe occurs after all other keys + * -> If there are at least two existing keyframes, then if the values of the + * last two keyframes and the new-keyframe match, the last existing keyframe + * gets deleted as it is no longer required. + * -> Otherwise, a keyframe is just added. 1.0 is added so that fake-2nd-to-last + * keyframe is not equal to last keyframe. + */ + bezt = (fcu->bezt + (fcu->totvert - 1)); + float valA = bezt->vec[1][1]; + float valB; + if (prev) { + valB = prev->vec[1][1]; + } + else { + valB = bezt->vec[1][1] + 1.0f; + } + + if (IS_EQF(valA, nValue) && IS_EQF(valA, valB)) { + return KEYNEEDED_DELPREV; + } + + return KEYNEEDED_JUSTADD; +} + /* ------------------ RNA Data-Access Functions ------------------ */ /** Try to read value using RNA-properties obtained already. */ -blender::Vector ANIM_setting_get_rna_values(PointerRNA *ptr, PropertyRNA *prop) +static float *setting_get_rna_values( + PointerRNA *ptr, PropertyRNA *prop, float *buffer, int buffer_size, int *r_count) { - blender::Vector values; + BLI_assert(buffer_size >= 1); + + float *values = buffer; + if (RNA_property_array_check(prop)) { - const int length = RNA_property_array_length(ptr, prop); + int length = *r_count = RNA_property_array_length(ptr, prop); + bool *tmp_bool; + int *tmp_int; + + if (length > buffer_size) { + values = static_cast(MEM_malloc_arrayN(length, sizeof(float), __func__)); + } switch (RNA_property_type(prop)) { - case PROP_BOOLEAN: { - bool *tmp_bool = static_cast(MEM_malloc_arrayN(length, sizeof(bool), __func__)); + case PROP_BOOLEAN: + tmp_bool = static_cast(MEM_malloc_arrayN(length, sizeof(*tmp_bool), __func__)); RNA_property_boolean_get_array(ptr, prop, tmp_bool); for (int i = 0; i < length; i++) { - values.append(float(tmp_bool[i])); + values[i] = float(tmp_bool[i]); } MEM_freeN(tmp_bool); break; - } - case PROP_INT: { - int *tmp_int = static_cast(MEM_malloc_arrayN(length, sizeof(int), __func__)); + case PROP_INT: + tmp_int = static_cast(MEM_malloc_arrayN(length, sizeof(*tmp_int), __func__)); RNA_property_int_get_array(ptr, prop, tmp_int); for (int i = 0; i < length; i++) { - values.append(float(tmp_int[i])); + values[i] = float(tmp_int[i]); } MEM_freeN(tmp_int); break; - } - case PROP_FLOAT: { - values.reinitialize(length); - RNA_property_float_get_array(ptr, prop, &values[0]); + case PROP_FLOAT: + RNA_property_float_get_array(ptr, prop, values); break; - } default: - values.reinitialize(length); - break; + memset(values, 0, sizeof(float) * length); } } else { + *r_count = 1; + switch (RNA_property_type(prop)) { case PROP_BOOLEAN: - values.append(float(RNA_property_boolean_get(ptr, prop))); + *values = float(RNA_property_boolean_get(ptr, prop)); break; case PROP_INT: - values.append(float(RNA_property_int_get(ptr, prop))); + *values = float(RNA_property_int_get(ptr, prop)); break; case PROP_FLOAT: - values.append(RNA_property_float_get(ptr, prop)); + *values = RNA_property_float_get(ptr, prop); break; case PROP_ENUM: - values.append(float(RNA_property_enum_get(ptr, prop))); + *values = float(RNA_property_enum_get(ptr, prop)); break; default: - values.append(0.0f); + *values = 0.0f; } } return values; } +/* ------------------ 'Visual' Keyframing Functions ------------------ */ + +/* internal status codes for visualkey_can_use */ +enum { + VISUALKEY_NONE = 0, + VISUALKEY_LOC, + VISUALKEY_ROT, + VISUALKEY_SCA, +}; + +/** + * This helper function determines if visual-keyframing should be used when + * inserting keyframes for the given channel. As visual-keyframing only works + * on Object and Pose-Channel blocks, this should only get called for those + * block-types, when using "standard" keying but 'Visual Keying' option in Auto-Keying + * settings is on. + */ +static bool visualkey_can_use(PointerRNA *ptr, PropertyRNA *prop) +{ + bConstraint *con = nullptr; + bool has_rigidbody = false; + bool has_parent = false; + + /* validate data */ + if (ELEM(nullptr, ptr, ptr->data, prop)) { + return false; + } + + /* get first constraint and determine type of keyframe constraints to check for + * - constraints can be on either Objects or PoseChannels, so we only check if the + * ptr->type is RNA_Object or RNA_PoseBone, which are the RNA wrapping-info for + * those structs, allowing us to identify the owner of the data + */ + if (ptr->type == &RNA_Object) { + /* Object */ + Object *ob = static_cast(ptr->data); + RigidBodyOb *rbo = ob->rigidbody_object; + + con = static_cast(ob->constraints.first); + has_parent = (ob->parent != nullptr); + + /* active rigidbody objects only, as only those are affected by sim */ + has_rigidbody = ((rbo) && (rbo->type == RBO_TYPE_ACTIVE)); + } + else if (ptr->type == &RNA_PoseBone) { + /* Pose Channel */ + bPoseChannel *pchan = static_cast(ptr->data); + + if (pchan->constflag & (PCHAN_HAS_IK | PCHAN_INFLUENCED_BY_IK)) { + /* Spline IK cannot generally be keyed visually, because (at least with the default + * constraint settings) it requires non-uniform scaling that causes shearing in child bones, + * which cannot be represented by the bone's loc/rot/scale properties. */ + return true; + } + + con = static_cast(pchan->constraints.first); + has_parent = (pchan->parent != nullptr); + } + else { + BLI_assert(!"visualkey_can_use called for data-block that is not an Object or PoseBone."); + return false; + } + + /* Parent or rigidbody are always matching, no need to check further. */ + if (has_parent || has_rigidbody) { + return true; + } + + /* Only do visual keying on transforms. */ + const char *identifier = RNA_property_identifier(prop); + if (identifier == nullptr) { + printf("%s failed: nullptr identifier\n", __func__); + return false; + } + + short searchtype = VISUALKEY_NONE; + if (strstr(identifier, "location")) { + searchtype = VISUALKEY_LOC; + } + else if (strstr(identifier, "rotation")) { + searchtype = VISUALKEY_ROT; + } + else if (strstr(identifier, "scale")) { + searchtype = VISUALKEY_SCA; + } + else { + printf("%s failed: identifier - '%s'\n", __func__, identifier); + return false; + } + + /* Check constraints. */ + for (; con; con = con->next) { + /* only consider constraint if it is not disabled, and has influence */ + if (con->flag & CONSTRAINT_DISABLE) { + continue; + } + if (con->enforce == 0.0f) { + continue; + } + + /* some constraints may alter these transforms */ + switch (con->type) { + /* multi-transform constraints */ + case CONSTRAINT_TYPE_CHILDOF: + case CONSTRAINT_TYPE_ARMATURE: + return true; + case CONSTRAINT_TYPE_TRANSFORM: + case CONSTRAINT_TYPE_TRANSLIKE: + return true; + case CONSTRAINT_TYPE_FOLLOWPATH: + return true; + case CONSTRAINT_TYPE_KINEMATIC: + return true; + + /* Single-transform constraints. */ + case CONSTRAINT_TYPE_TRACKTO: + if (searchtype == VISUALKEY_ROT) { + return true; + } + break; + case CONSTRAINT_TYPE_DAMPTRACK: + if (searchtype == VISUALKEY_ROT) { + return true; + } + break; + case CONSTRAINT_TYPE_ROTLIMIT: + if (searchtype == VISUALKEY_ROT) { + return true; + } + break; + case CONSTRAINT_TYPE_LOCLIMIT: + if (searchtype == VISUALKEY_LOC) { + return true; + } + break; + case CONSTRAINT_TYPE_SIZELIMIT: + if (searchtype == VISUALKEY_SCA) { + return true; + } + break; + case CONSTRAINT_TYPE_DISTLIMIT: + if (searchtype == VISUALKEY_LOC) { + return true; + } + break; + case CONSTRAINT_TYPE_ROTLIKE: + if (searchtype == VISUALKEY_ROT) { + return true; + } + break; + case CONSTRAINT_TYPE_LOCLIKE: + if (searchtype == VISUALKEY_LOC) { + return true; + } + break; + case CONSTRAINT_TYPE_SIZELIKE: + if (searchtype == VISUALKEY_SCA) { + return true; + } + break; + case CONSTRAINT_TYPE_LOCKTRACK: + if (searchtype == VISUALKEY_ROT) { + return true; + } + break; + case CONSTRAINT_TYPE_MINMAX: + if (searchtype == VISUALKEY_LOC) { + return true; + } + break; + + default: + break; + } + } + + return false; +} + +/** + * This helper function extracts the value to use for visual-keyframing + * In the event that it is not possible to perform visual keying, try to fall-back + * to using the default method. Assumes that all data it has been passed is valid. + */ +static float *visualkey_get_values( + PointerRNA *ptr, PropertyRNA *prop, float *buffer, int buffer_size, int *r_count) +{ + BLI_assert(buffer_size >= 4); + + const char *identifier = RNA_property_identifier(prop); + float tmat[4][4]; + int rotmode; + + /* handle for Objects or PoseChannels only + * - only Location, Rotation or Scale keyframes are supported currently + * - constraints can be on either Objects or PoseChannels, so we only check if the + * ptr->type is RNA_Object or RNA_PoseBone, which are the RNA wrapping-info for + * those structs, allowing us to identify the owner of the data + * - assume that array_index will be sane + */ + if (ptr->type == &RNA_Object) { + Object *ob = static_cast(ptr->data); + /* Loc code is specific... */ + if (strstr(identifier, "location")) { + copy_v3_v3(buffer, ob->object_to_world[3]); + *r_count = 3; + return buffer; + } + + copy_m4_m4(tmat, ob->object_to_world); + rotmode = ob->rotmode; + } + else if (ptr->type == &RNA_PoseBone) { + bPoseChannel *pchan = static_cast(ptr->data); + + BKE_armature_mat_pose_to_bone(pchan, pchan->pose_mat, tmat); + rotmode = pchan->rotmode; + + /* Loc code is specific... */ + if (strstr(identifier, "location")) { + /* only use for non-connected bones */ + if ((pchan->bone->parent == nullptr) || !(pchan->bone->flag & BONE_CONNECTED)) { + copy_v3_v3(buffer, tmat[3]); + *r_count = 3; + return buffer; + } + } + } + else { + return setting_get_rna_values(ptr, prop, buffer, buffer_size, r_count); + } + + /* Rot/Scale code are common! */ + if (strstr(identifier, "rotation_euler")) { + mat4_to_eulO(buffer, rotmode, tmat); + + *r_count = 3; + return buffer; + } + + if (strstr(identifier, "rotation_quaternion")) { + mat4_to_quat(buffer, tmat); + + *r_count = 4; + return buffer; + } + + if (strstr(identifier, "rotation_axis_angle")) { + /* w = 0, x,y,z = 1,2,3 */ + mat4_to_axis_angle(buffer + 1, buffer, tmat); + + *r_count = 4; + return buffer; + } + + if (strstr(identifier, "scale")) { + mat4_to_size(buffer, tmat); + + *r_count = 3; + return buffer; + } + + /* as the function hasn't returned yet, read value from system in the default way */ + return setting_get_rna_values(ptr, prop, buffer, buffer_size, r_count); +} + /* ------------------------- Insert Key API ------------------------- */ +/* Check indices that were intended to be remapped and report any failed remaps. */ +static void get_keyframe_values_create_reports(ReportList *reports, + PointerRNA ptr, + PropertyRNA *prop, + const int index, + const int count, + const bool force_all, + const BLI_bitmap *successful_remaps) +{ + + DynStr *ds_failed_indices = BLI_dynstr_new(); + + int total_failed = 0; + for (int i = 0; i < count; i++) { + const bool cur_index_evaluated = ELEM(index, i, -1) || force_all; + if (!cur_index_evaluated) { + /* `values[i]` was never intended to be remapped. */ + continue; + } + + if (BLI_BITMAP_TEST_BOOL(successful_remaps, i)) { + /* `values[i]` successfully remapped. */ + continue; + } + + total_failed++; + /* Report that `values[i]` were intended to be remapped but failed remapping process. */ + BLI_dynstr_appendf(ds_failed_indices, "%d, ", i); + } + + if (total_failed == 0) { + BLI_dynstr_free(ds_failed_indices); + return; + } + + char *str_failed_indices = BLI_dynstr_get_cstring(ds_failed_indices); + BLI_dynstr_free(ds_failed_indices); + + BKE_reportf(reports, + RPT_WARNING, + "Could not insert %i keyframe(s) due to zero NLA influence, base value, or value " + "remapping failed: %s.%s for indices [%s]", + total_failed, + ptr.owner_id->name, + RNA_property_ui_name(prop), + str_failed_indices); + + MEM_freeN(str_failed_indices); +} + +/** + * Retrieve current property values to keyframe, + * possibly applying NLA correction when necessary. + * + * \param r_successful_remaps: Enables bits for indices which are both intended to be remapped and + * were successfully remapped. Bitmap allocated so it must be freed afterward. + */ +static float *get_keyframe_values(ReportList *reports, + PointerRNA ptr, + PropertyRNA *prop, + int index, + NlaKeyframingContext *nla_context, + eInsertKeyFlags flag, + float *buffer, + int buffer_size, + const AnimationEvalContext *anim_eval_context, + int *r_count, + bool *r_force_all, + BLI_bitmap **r_successful_remaps) +{ + float *values; + + if ((flag & INSERTKEY_MATRIX) && visualkey_can_use(&ptr, prop)) { + /* visual-keying is only available for object and pchan datablocks, as + * it works by keyframing using a value extracted from the final matrix + * instead of using the kt system to extract a value. + */ + values = visualkey_get_values(&ptr, prop, buffer, buffer_size, r_count); + } + else { + /* read value from system */ + values = setting_get_rna_values(&ptr, prop, buffer, buffer_size, r_count); + } + + *r_successful_remaps = BLI_BITMAP_NEW(*r_count, __func__); + + /* adjust the value for NLA factors */ + BKE_animsys_nla_remap_keyframe_values(nla_context, + &ptr, + prop, + values, + *r_count, + index, + anim_eval_context, + r_force_all, + *r_successful_remaps); + get_keyframe_values_create_reports(reports, + ptr, + prop, + index, + *r_count, + r_force_all ? *r_force_all : false, + *r_successful_remaps); + + return values; +} + +/* Insert the specified keyframe value into a single F-Curve. */ +static bool insert_keyframe_value(ReportList *reports, + PointerRNA *ptr, + PropertyRNA *prop, + FCurve *fcu, + const AnimationEvalContext *anim_eval_context, + float curval, + eBezTriple_KeyframeType keytype, + eInsertKeyFlags flag) +{ + /* F-Curve not editable? */ + if (BKE_fcurve_is_keyframable(fcu) == 0) { + BKE_reportf( + reports, + RPT_ERROR, + "F-Curve with path '%s[%d]' cannot be keyframed, ensure that it is not locked or sampled, " + "and try removing F-Modifiers", + fcu->rna_path, + fcu->array_index); + return false; + } + + float cfra = anim_eval_context->eval_time; + + /* adjust frame on which to add keyframe */ + if ((flag & INSERTKEY_DRIVER) && (fcu->driver)) { + PathResolvedRNA anim_rna; + + if (RNA_path_resolved_create(ptr, prop, fcu->array_index, &anim_rna)) { + /* for making it easier to add corrective drivers... */ + cfra = evaluate_driver(&anim_rna, fcu->driver, fcu->driver, anim_eval_context); + } + else { + cfra = 0.0f; + } + } + + /* adjust coordinates for cycle aware insertion */ + if (flag & INSERTKEY_CYCLE_AWARE) { + if (remap_cyclic_keyframe_location(fcu, &cfra, &curval) != FCU_CYCLE_PERFECT) { + /* inhibit action from insert_vert_fcurve unless it's a perfect cycle */ + flag &= ~INSERTKEY_CYCLE_AWARE; + } + } + + /* only insert keyframes where they are needed */ + if (flag & INSERTKEY_NEEDED) { + short insert_mode; + + /* check whether this curve really needs a new keyframe */ + insert_mode = new_key_needed(fcu, cfra, curval); + + /* only return success if keyframe added */ + if (insert_mode == KEYNEEDED_DONTADD) { + return false; + } + + /* insert new keyframe at current frame */ + if (insert_vert_fcurve(fcu, cfra, curval, keytype, flag) < 0) { + return false; + } + + /* delete keyframe immediately before/after newly added */ + switch (insert_mode) { + case KEYNEEDED_DELPREV: + BKE_fcurve_delete_key(fcu, fcu->totvert - 2); + BKE_fcurve_handles_recalc(fcu); + break; + case KEYNEEDED_DELNEXT: + BKE_fcurve_delete_key(fcu, 1); + BKE_fcurve_handles_recalc(fcu); + break; + } + + return true; + } + + /* just insert keyframe */ + return insert_vert_fcurve(fcu, cfra, curval, keytype, flag) >= 0; +} + +bool insert_keyframe_direct(ReportList *reports, + PointerRNA ptr, + PropertyRNA *prop, + FCurve *fcu, + const AnimationEvalContext *anim_eval_context, + eBezTriple_KeyframeType keytype, + NlaKeyframingContext *nla_context, + eInsertKeyFlags flag) +{ + float curval = 0.0f; + + /* no F-Curve to add keyframe to? */ + if (fcu == nullptr) { + BKE_report(reports, RPT_ERROR, "No F-Curve to add keyframes to"); + return false; + } + + /* if no property given yet, try to validate from F-Curve info */ + if ((ptr.owner_id == nullptr) && (ptr.data == nullptr)) { + BKE_report( + reports, RPT_ERROR, "No RNA pointer available to retrieve values for keyframing from"); + return false; + } + if (prop == nullptr) { + PointerRNA tmp_ptr; + + /* try to get property we should be affecting */ + if (RNA_path_resolve_property(&ptr, fcu->rna_path, &tmp_ptr, &prop) == false) { + /* property not found... */ + const char *idname = (ptr.owner_id) ? ptr.owner_id->name : TIP_(""); + + BKE_reportf(reports, + RPT_ERROR, + "Could not insert keyframe, as RNA path is invalid for the given ID (ID = %s, " + "path = %s)", + idname, + fcu->rna_path); + return false; + } + + /* property found, so overwrite 'ptr' to make later code easier */ + ptr = tmp_ptr; + } + + /* update F-Curve flags to ensure proper behavior for property type */ + update_autoflags_fcurve_direct(fcu, prop); + + /* Obtain the value to insert. */ + float value_buffer[RNA_MAX_ARRAY_LENGTH]; + int value_count; + int index = fcu->array_index; + + BLI_bitmap *successful_remaps = nullptr; + float *values = get_keyframe_values(reports, + ptr, + prop, + index, + nla_context, + flag, + value_buffer, + RNA_MAX_ARRAY_LENGTH, + anim_eval_context, + &value_count, + nullptr, + &successful_remaps); + + if (index >= 0 && index < value_count) { + curval = values[index]; + } + + if (values != value_buffer) { + MEM_freeN(values); + } + + const bool curval_valid = BLI_BITMAP_TEST_BOOL(successful_remaps, index); + MEM_freeN(successful_remaps); + + /* This happens if NLA rejects this insertion. */ + if (!curval_valid) { + return false; + } + + return insert_keyframe_value(reports, &ptr, prop, fcu, anim_eval_context, curval, keytype, flag); +} + +/** Find or create the #FCurve based on the given path, and insert the specified value into it. */ +static bool insert_keyframe_fcurve_value(Main *bmain, + ReportList *reports, + PointerRNA *ptr, + PropertyRNA *prop, + bAction *act, + const char group[], + const char rna_path[], + int array_index, + const AnimationEvalContext *anim_eval_context, + float curval, + eBezTriple_KeyframeType keytype, + eInsertKeyFlags flag) +{ + /* make sure the F-Curve exists + * - if we're replacing keyframes only, DO NOT create new F-Curves if they do not exist yet + * but still try to get the F-Curve if it exists... + */ + bool can_create_curve = (flag & (INSERTKEY_REPLACE | INSERTKEY_AVAILABLE)) == 0; + FCurve *fcu = can_create_curve ? + ED_action_fcurve_ensure(bmain, act, group, ptr, rna_path, array_index) : + ED_action_fcurve_find(act, rna_path, array_index); + + /* we may not have a F-Curve when we're replacing only... */ + if (fcu) { + const bool is_new_curve = (fcu->totvert == 0); + + /* set color mode if the F-Curve is new (i.e. without any keyframes) */ + if (is_new_curve && (flag & INSERTKEY_XYZ2RGB)) { + /* for Loc/Rot/Scale and also Color F-Curves, the color of the F-Curve in the Graph Editor, + * is determined by the array index for the F-Curve + */ + PropertySubType prop_subtype = RNA_property_subtype(prop); + if (ELEM(prop_subtype, PROP_TRANSLATION, PROP_XYZ, PROP_EULER, PROP_COLOR, PROP_COORDS)) { + fcu->color_mode = FCURVE_COLOR_AUTO_RGB; + } + else if (ELEM(prop_subtype, PROP_QUATERNION)) { + fcu->color_mode = FCURVE_COLOR_AUTO_YRGB; + } + } + + /* If the curve has only one key, make it cyclic if appropriate. */ + const bool is_cyclic_action = (flag & INSERTKEY_CYCLE_AWARE) && BKE_action_is_cyclic(act); + + if (is_cyclic_action && fcu->totvert == 1) { + make_new_fcurve_cyclic(act, fcu); + } + + /* update F-Curve flags to ensure proper behavior for property type */ + update_autoflags_fcurve_direct(fcu, prop); + + /* insert keyframe */ + const bool success = insert_keyframe_value( + reports, ptr, prop, fcu, anim_eval_context, curval, keytype, flag); + + /* If the curve is new, make it cyclic if appropriate. */ + if (is_cyclic_action && is_new_curve) { + make_new_fcurve_cyclic(act, fcu); + } + + return success; + } + + return false; +} + +static AnimationEvalContext nla_time_remap(const AnimationEvalContext *anim_eval_context, + PointerRNA *id_ptr, + AnimData *adt, + bAction *act, + ListBase *nla_cache, + NlaKeyframingContext **r_nla_context) +{ + if (adt && adt->action == act) { + /* Get NLA context for value remapping. */ + *r_nla_context = BKE_animsys_get_nla_keyframing_context( + nla_cache, id_ptr, adt, anim_eval_context); + + /* Apply NLA-mapping to frame. */ + const float remapped_frame = BKE_nla_tweakedit_remap( + adt, anim_eval_context->eval_time, NLATIME_CONVERT_UNMAP); + return BKE_animsys_eval_context_construct_at(anim_eval_context, remapped_frame); + } + + *r_nla_context = nullptr; + return *anim_eval_context; +} + +int insert_keyframe(Main *bmain, + ReportList *reports, + ID *id, + bAction *act, + const char group[], + const char rna_path[], + int array_index, + const AnimationEvalContext *anim_eval_context, + eBezTriple_KeyframeType keytype, + ListBase *nla_cache, + eInsertKeyFlags flag) +{ + PointerRNA ptr; + PropertyRNA *prop = nullptr; + AnimData *adt; + ListBase tmp_nla_cache = {nullptr, nullptr}; + NlaKeyframingContext *nla_context = nullptr; + int ret = 0; + + /* validate pointer first - exit if failure */ + if (id == nullptr) { + BKE_reportf(reports, RPT_ERROR, "No ID block to insert keyframe in (path = %s)", rna_path); + return 0; + } + + if (!BKE_id_is_editable(bmain, id)) { + BKE_reportf(reports, RPT_ERROR, "'%s' on %s is not editable", rna_path, id->name + 2); + return 0; + } + + PointerRNA id_ptr = RNA_id_pointer_create(id); + if (RNA_path_resolve_property(&id_ptr, rna_path, &ptr, &prop) == false) { + BKE_reportf( + reports, + RPT_ERROR, + "Could not insert keyframe, as RNA path is invalid for the given ID (ID = %s, path = %s)", + (id) ? id->name : TIP_(""), + rna_path); + return 0; + } + + /* if no action is provided, keyframe to the default one attached to this ID-block */ + if (act == nullptr) { + /* get action to add F-Curve+keyframe to */ + act = ED_id_action_ensure(bmain, id); + + if (act == nullptr) { + BKE_reportf(reports, + RPT_ERROR, + "Could not insert keyframe, as this type does not support animation data (ID = " + "%s, path = %s)", + id->name, + rna_path); + return 0; + } + } + + /* apply NLA-mapping to frame to use (if applicable) */ + adt = BKE_animdata_from_id(id); + const AnimationEvalContext remapped_context = nla_time_remap( + anim_eval_context, &id_ptr, adt, act, nla_cache ? nla_cache : &tmp_nla_cache, &nla_context); + + /* Obtain values to insert. */ + float value_buffer[RNA_MAX_ARRAY_LENGTH]; + int value_count; + bool force_all; + + BLI_bitmap *successful_remaps = nullptr; + float *values = get_keyframe_values(reports, + ptr, + prop, + array_index, + nla_context, + flag, + value_buffer, + RNA_MAX_ARRAY_LENGTH, + anim_eval_context, + &value_count, + &force_all, + &successful_remaps); + + /* Key the entire array. */ + if (array_index == -1 || force_all) { + /* In force mode, if any of the curves succeeds, drop the replace mode and restart. */ + if (force_all && (flag & (INSERTKEY_REPLACE | INSERTKEY_AVAILABLE)) != 0) { + int exclude = -1; + + for (array_index = 0; array_index < value_count; array_index++) { + if (!BLI_BITMAP_TEST_BOOL(successful_remaps, array_index)) { + continue; + } + + if (insert_keyframe_fcurve_value(bmain, + reports, + &ptr, + prop, + act, + group, + rna_path, + array_index, + &remapped_context, + values[array_index], + keytype, + flag)) + { + ret++; + exclude = array_index; + break; + } + } + + if (exclude != -1) { + flag &= ~(INSERTKEY_REPLACE | INSERTKEY_AVAILABLE); + + for (array_index = 0; array_index < value_count; array_index++) { + if (!BLI_BITMAP_TEST_BOOL(successful_remaps, array_index)) { + continue; + } + + if (array_index != exclude) { + ret += insert_keyframe_fcurve_value(bmain, + reports, + &ptr, + prop, + act, + group, + rna_path, + array_index, + &remapped_context, + values[array_index], + keytype, + flag); + } + } + } + } + /* Simply insert all channels. */ + else { + for (array_index = 0; array_index < value_count; array_index++) { + if (!BLI_BITMAP_TEST_BOOL(successful_remaps, array_index)) { + continue; + } + + ret += insert_keyframe_fcurve_value(bmain, + reports, + &ptr, + prop, + act, + group, + rna_path, + array_index, + &remapped_context, + values[array_index], + keytype, + flag); + } + } + } + /* Key a single index. */ + else { + if (array_index >= 0 && array_index < value_count && + BLI_BITMAP_TEST_BOOL(successful_remaps, array_index)) + { + ret += insert_keyframe_fcurve_value(bmain, + reports, + &ptr, + prop, + act, + group, + rna_path, + array_index, + &remapped_context, + values[array_index], + keytype, + flag); + } + } + + if (values != value_buffer) { + MEM_freeN(values); + } + + MEM_freeN(successful_remaps); + BKE_animsys_free_nla_keyframing_context_cache(&tmp_nla_cache); + + if (ret) { + if (act != nullptr) { + DEG_id_tag_update(&act->id, ID_RECALC_ANIMATION_NO_FLUSH); + } + if (adt != nullptr && adt->action != nullptr && adt->action != act) { + DEG_id_tag_update(&adt->action->id, ID_RECALC_ANIMATION_NO_FLUSH); + } + } + + return ret; +} + void ED_keyframes_add(FCurve *fcu, int num_keys_to_add) { BLI_assert_msg(num_keys_to_add >= 0, "cannot remove keyframes with this function"); @@ -640,6 +1689,259 @@ void ED_keyframes_add(FCurve *fcu, int num_keys_to_add) } } +/* ************************************************** */ +/* KEYFRAME DELETION */ + +/* Main Keyframing API call: + * Use this when validation of necessary animation data isn't necessary as it + * already exists. It will delete a keyframe at the current frame. + * + * The flag argument is used for special settings that alter the behavior of + * the keyframe deletion. These include the quick refresh options. + */ + +/** + * \note caller needs to run #BKE_nla_tweakedit_remap to get NLA relative frame. + * caller should also check #BKE_fcurve_is_protected before keying. + */ +static bool delete_keyframe_fcurve(AnimData *adt, FCurve *fcu, float cfra) +{ + bool found; + int i; + + /* try to find index of beztriple to get rid of */ + i = BKE_fcurve_bezt_binarysearch_index(fcu->bezt, cfra, fcu->totvert, &found); + if (found) { + /* delete the key at the index (will sanity check + do recalc afterwards) */ + BKE_fcurve_delete_key(fcu, i); + BKE_fcurve_handles_recalc(fcu); + + /* Only delete curve too if it won't be doing anything anymore */ + if (BKE_fcurve_is_empty(fcu)) { + ANIM_fcurve_delete_from_animdata(nullptr, adt, fcu); + } + + /* return success */ + return true; + } + return false; +} + +static void deg_tag_after_keyframe_delete(Main *bmain, ID *id, AnimData *adt) +{ + if (adt->action == nullptr) { + /* In the case last f-curve was removed need to inform dependency graph + * about relations update, since it needs to get rid of animation operation + * for this data-block. */ + DEG_id_tag_update_ex(bmain, id, ID_RECALC_ANIMATION_NO_FLUSH); + DEG_relations_tag_update(bmain); + } + else { + DEG_id_tag_update_ex(bmain, &adt->action->id, ID_RECALC_ANIMATION_NO_FLUSH); + } +} + +int delete_keyframe(Main *bmain, + ReportList *reports, + ID *id, + bAction *act, + const char rna_path[], + int array_index, + float cfra) +{ + AnimData *adt = BKE_animdata_from_id(id); + PointerRNA ptr; + PropertyRNA *prop; + int array_index_max = array_index + 1; + int ret = 0; + + /* sanity checks */ + if (ELEM(nullptr, id, adt)) { + BKE_report(reports, RPT_ERROR, "No ID block and/or AnimData to delete keyframe from"); + return 0; + } + + /* validate pointer first - exit if failure */ + PointerRNA id_ptr = RNA_id_pointer_create(id); + if (RNA_path_resolve_property(&id_ptr, rna_path, &ptr, &prop) == false) { + BKE_reportf( + reports, + RPT_ERROR, + "Could not delete keyframe, as RNA path is invalid for the given ID (ID = %s, path = %s)", + id->name, + rna_path); + return 0; + } + + /* get F-Curve + * NOTE: here is one of the places where we don't want new Action + F-Curve added! + * so 'add' var must be 0 + */ + if (act == nullptr) { + /* if no action is provided, use the default one attached to this ID-block + * - if it doesn't exist, then we're out of options... + */ + if (adt->action) { + act = adt->action; + + /* apply NLA-mapping to frame to use (if applicable) */ + cfra = BKE_nla_tweakedit_remap(adt, cfra, NLATIME_CONVERT_UNMAP); + } + else { + BKE_reportf(reports, RPT_ERROR, "No action to delete keyframes from for ID = %s", id->name); + return 0; + } + } + + /* key entire array convenience method */ + if (array_index == -1) { + array_index = 0; + array_index_max = RNA_property_array_length(&ptr, prop); + + /* for single properties, increase max_index so that the property itself gets included, + * but don't do this for standard arrays since that can cause corruption issues + * (extra unused curves) + */ + if (array_index_max == array_index) { + array_index_max++; + } + } + + /* will only loop once unless the array index was -1 */ + for (; array_index < array_index_max; array_index++) { + FCurve *fcu = ED_action_fcurve_find(act, rna_path, array_index); + + /* check if F-Curve exists and/or whether it can be edited */ + if (fcu == nullptr) { + continue; + } + + if (BKE_fcurve_is_protected(fcu)) { + BKE_reportf(reports, + RPT_WARNING, + "Not deleting keyframe for locked F-Curve '%s' for %s '%s'", + fcu->rna_path, + BKE_idtype_idcode_to_name(GS(id->name)), + id->name + 2); + continue; + } + + ret += delete_keyframe_fcurve(adt, fcu, cfra); + } + if (ret) { + deg_tag_after_keyframe_delete(bmain, id, adt); + } + /* return success/failure */ + return ret; +} + +/* ************************************************** */ +/* KEYFRAME CLEAR */ + +/** + * Main Keyframing API call: + * Use this when validation of necessary animation data isn't necessary as it + * already exists. It will clear the current buttons fcurve(s). + * + * The flag argument is used for special settings that alter the behavior of + * the keyframe deletion. These include the quick refresh options. + * + * \return The number of f-curves removed. + */ +static int clear_keyframe(Main *bmain, + ReportList *reports, + ID *id, + bAction *act, + const char rna_path[], + int array_index, + eInsertKeyFlags /*flag*/) +{ + AnimData *adt = BKE_animdata_from_id(id); + PointerRNA ptr; + PropertyRNA *prop; + int array_index_max = array_index + 1; + int ret = 0; + + /* sanity checks */ + if (ELEM(nullptr, id, adt)) { + BKE_report(reports, RPT_ERROR, "No ID block and/or AnimData to delete keyframe from"); + return 0; + } + + /* validate pointer first - exit if failure */ + PointerRNA id_ptr = RNA_id_pointer_create(id); + if (RNA_path_resolve_property(&id_ptr, rna_path, &ptr, &prop) == false) { + BKE_reportf( + reports, + RPT_ERROR, + "Could not clear keyframe, as RNA path is invalid for the given ID (ID = %s, path = %s)", + id->name, + rna_path); + return 0; + } + + /* get F-Curve + * NOTE: here is one of the places where we don't want new Action + F-Curve added! + * so 'add' var must be 0 + */ + if (act == nullptr) { + /* if no action is provided, use the default one attached to this ID-block + * - if it doesn't exist, then we're out of options... + */ + if (adt->action) { + act = adt->action; + } + else { + BKE_reportf(reports, RPT_ERROR, "No action to delete keyframes from for ID = %s", id->name); + return 0; + } + } + + /* key entire array convenience method */ + if (array_index == -1) { + array_index = 0; + array_index_max = RNA_property_array_length(&ptr, prop); + + /* for single properties, increase max_index so that the property itself gets included, + * but don't do this for standard arrays since that can cause corruption issues + * (extra unused curves) + */ + if (array_index_max == array_index) { + array_index_max++; + } + } + + /* will only loop once unless the array index was -1 */ + for (; array_index < array_index_max; array_index++) { + FCurve *fcu = ED_action_fcurve_find(act, rna_path, array_index); + + /* check if F-Curve exists and/or whether it can be edited */ + if (fcu == nullptr) { + continue; + } + + if (BKE_fcurve_is_protected(fcu)) { + BKE_reportf(reports, + RPT_WARNING, + "Not clearing all keyframes from locked F-Curve '%s' for %s '%s'", + fcu->rna_path, + BKE_idtype_idcode_to_name(GS(id->name)), + id->name + 2); + continue; + } + + ANIM_fcurve_delete_from_animdata(nullptr, adt, fcu); + + /* return success */ + ret++; + } + if (ret) { + deg_tag_after_keyframe_delete(bmain, id, adt); + } + /* return success/failure */ + return ret; +} + /* ******************************************* */ /* KEYFRAME MODIFICATION */ @@ -694,7 +1996,7 @@ static int insert_key_exec(bContext *C, wmOperator *op) } /* try to insert keyframes for the channels specified by KeyingSet */ - num_channels = ANIM_apply_keyingset(C, nullptr, ks, MODIFYKEY_MODE_INSERT, cfra); + num_channels = ANIM_apply_keyingset(C, nullptr, nullptr, ks, MODIFYKEY_MODE_INSERT, cfra); if (G.debug & G_DEBUG) { BKE_reportf(op->reports, RPT_INFO, @@ -903,7 +2205,7 @@ static int delete_key_using_keying_set(bContext *C, wmOperator *op, KeyingSet *k const bool confirm = op->flag & OP_IS_INVOKE; /* try to delete keyframes for the channels specified by KeyingSet */ - num_channels = ANIM_apply_keyingset(C, nullptr, ks, MODIFYKEY_MODE_DELETE, cfra); + num_channels = ANIM_apply_keyingset(C, nullptr, nullptr, ks, MODIFYKEY_MODE_DELETE, cfra); if (G.debug & G_DEBUG) { printf("KeyingSet '%s' - Successfully removed %d Keyframes\n", ks->name, num_channels); } @@ -1147,7 +2449,7 @@ static int delete_key_v3d_without_keying_set(bContext *C, wmOperator *op) /* delete keyframes on current frame * WARNING: this can delete the next F-Curve, hence the "fcn" copying */ - success += blender::animrig::delete_keyframe_fcurve(adt, fcu, cfra_unmap); + success += delete_keyframe_fcurve(adt, fcu, cfra_unmap); } DEG_id_tag_update(&ob->adt->action->id, ID_RECALC_ANIMATION_NO_FLUSH); } @@ -1250,15 +2552,14 @@ static int insert_key_button_exec(bContext *C, wmOperator *op) FCurve *fcu = BKE_fcurve_find(&strip->fcurves, RNA_property_identifier(prop), index); if (fcu) { - changed = blender::animrig::insert_keyframe_direct( - op->reports, - ptr, - prop, - fcu, - &anim_eval_context, - eBezTriple_KeyframeType(ts->keyframe_type), - nullptr, - eInsertKeyFlags(0)); + changed = insert_keyframe_direct(op->reports, + ptr, + prop, + fcu, + &anim_eval_context, + eBezTriple_KeyframeType(ts->keyframe_type), + nullptr, + eInsertKeyFlags(0)); } else { BKE_report(op->reports, @@ -1275,15 +2576,14 @@ static int insert_key_button_exec(bContext *C, wmOperator *op) C, &ptr, prop, index, nullptr, nullptr, &driven, &special); if (fcu && driven) { - changed = blender::animrig::insert_keyframe_direct( - op->reports, - ptr, - prop, - fcu, - &anim_eval_context, - eBezTriple_KeyframeType(ts->keyframe_type), - nullptr, - INSERTKEY_DRIVER); + changed = insert_keyframe_direct(op->reports, + ptr, + prop, + fcu, + &anim_eval_context, + eBezTriple_KeyframeType(ts->keyframe_type), + nullptr, + INSERTKEY_DRIVER); } } else { @@ -1320,16 +2620,17 @@ static int insert_key_button_exec(bContext *C, wmOperator *op) index = -1; } - changed = (blender::animrig::insert_keyframe(bmain, - op->reports, - ptr.owner_id, - nullptr, - group, - path, - index, - &anim_eval_context, - eBezTriple_KeyframeType(ts->keyframe_type), - flag) != 0); + changed = (insert_keyframe(bmain, + op->reports, + ptr.owner_id, + nullptr, + group, + path, + index, + &anim_eval_context, + eBezTriple_KeyframeType(ts->keyframe_type), + nullptr, + flag) != 0); MEM_freeN(path); } @@ -1462,8 +2763,8 @@ static int delete_key_button_exec(bContext *C, wmOperator *op) index = -1; } - changed = blender::animrig::delete_keyframe( - bmain, op->reports, ptr.owner_id, nullptr, path, index, cfra) != 0; + changed = delete_keyframe(bmain, op->reports, ptr.owner_id, nullptr, path, index, cfra) != + 0; MEM_freeN(path); } else if (G.debug & G_DEBUG) { @@ -1531,7 +2832,7 @@ static int clear_key_button_exec(bContext *C, wmOperator *op) } changed |= - (blender::animrig::clear_keyframe( + (clear_keyframe( bmain, op->reports, ptr.owner_id, nullptr, path, index, eInsertKeyFlags(0)) != 0); MEM_freeN(path); } @@ -1572,6 +2873,37 @@ void ANIM_OT_keyframe_clear_button(wmOperatorType *ot) RNA_def_boolean(ot->srna, "all", true, "All", "Clear keyframes from all elements of the array"); } +/* ******************************************* */ +/* AUTO KEYFRAME */ + +bool autokeyframe_cfra_can_key(const Scene *scene, ID *id) +{ + const float cfra = BKE_scene_frame_get(scene); + + /* only filter if auto-key mode requires this */ + if (IS_AUTOKEY_ON(scene) == 0) { + return false; + } + + if (IS_AUTOKEY_MODE(scene, EDITKEYS)) { + /* Replace Mode: + * For whole block, only key if there's a keyframe on that frame already + * This is a valid assumption when we're blocking + tweaking + */ + return id_frame_has_keyframe(id, cfra); + } + + /* Normal Mode (or treat as being normal mode): + * + * Just in case the flags aren't set properly (i.e. only on/off is set, without a mode) + * let's set the "normal" flag too, so that it will all be sane everywhere... + */ + scene->toolsettings->autokey_mode = AUTOKEY_MODE_NORMAL; + + /* Can insert anytime we like... */ + return true; +} + /* ******************************************* */ /* KEYFRAME DETECTION */ @@ -1612,11 +2944,16 @@ bool fcurve_is_changed(PointerRNA ptr, anim_rna.prop = prop; anim_rna.prop_index = fcu->array_index; - int index = fcu->array_index; - blender::Vector values = ANIM_setting_get_rna_values(&ptr, prop); + float buffer[RNA_MAX_ARRAY_LENGTH]; + int count, index = fcu->array_index; + float *values = setting_get_rna_values(&ptr, prop, buffer, RNA_MAX_ARRAY_LENGTH, &count); float fcurve_val = calculate_fcurve(&anim_rna, fcu, anim_eval_context); - float cur_val = (index >= 0 && index < values.size()) ? values[index] : 0.0f; + float cur_val = (index >= 0 && index < count) ? values[index] : 0.0f; + + if (values != buffer) { + MEM_freeN(values); + } return !compare_ff_relative(fcurve_val, cur_val, FLT_EPSILON, 64); } @@ -1712,6 +3049,154 @@ bool id_frame_has_keyframe(ID *id, float frame) return false; } +/* ************************************************** */ + +bool ED_autokeyframe_object(bContext *C, Scene *scene, Object *ob, KeyingSet *ks) +{ + /* auto keyframing */ + if (autokeyframe_cfra_can_key(scene, &ob->id)) { + ListBase dsources = {nullptr, nullptr}; + + /* Now insert the key-frame(s) using the Keying Set: + * 1) Add data-source override for the Object. + * 2) Insert key-frames. + * 3) Free the extra info. + */ + ANIM_relative_keyingset_add_source(&dsources, &ob->id, nullptr, nullptr); + ANIM_apply_keyingset( + C, &dsources, nullptr, ks, MODIFYKEY_MODE_INSERT, BKE_scene_frame_get(scene)); + BLI_freelistN(&dsources); + + return true; + } + return false; +} + +bool ED_autokeyframe_pchan( + bContext *C, Scene *scene, Object *ob, bPoseChannel *pchan, KeyingSet *ks) +{ + if (autokeyframe_cfra_can_key(scene, &ob->id)) { + ListBase dsources = {nullptr, nullptr}; + + /* Now insert the keyframe(s) using the Keying Set: + * 1) Add data-source override for the pose-channel. + * 2) Insert key-frames. + * 3) Free the extra info. + */ + ANIM_relative_keyingset_add_source(&dsources, &ob->id, &RNA_PoseBone, pchan); + ANIM_apply_keyingset( + C, &dsources, nullptr, ks, MODIFYKEY_MODE_INSERT, BKE_scene_frame_get(scene)); + BLI_freelistN(&dsources); + + return true; + } + + return false; +} + +bool ED_autokeyframe_property(bContext *C, + Scene *scene, + PointerRNA *ptr, + PropertyRNA *prop, + int rnaindex, + float cfra, + const bool only_if_property_keyed) +{ + Main *bmain = CTX_data_main(C); + Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); + const AnimationEvalContext anim_eval_context = BKE_animsys_eval_context_construct(depsgraph, + cfra); + ID *id; + bAction *action; + FCurve *fcu; + bool driven; + bool special; + bool changed = false; + + /* for entire array buttons we check the first component, it's not perfect + * but works well enough in typical cases */ + const int rnaindex_check = (rnaindex == -1) ? 0 : rnaindex; + fcu = BKE_fcurve_find_by_rna_context_ui( + C, ptr, prop, rnaindex_check, nullptr, &action, &driven, &special); + + /* Only early out when we actually want an existing F-curve already + * (e.g. auto-keyframing from buttons). */ + if (fcu == nullptr && (driven || special || only_if_property_keyed)) { + return changed; + } + + if (special) { + /* NLA Strip property */ + if (IS_AUTOKEY_ON(scene)) { + ReportList *reports = CTX_wm_reports(C); + ToolSettings *ts = scene->toolsettings; + + changed = insert_keyframe_direct(reports, + *ptr, + prop, + fcu, + &anim_eval_context, + eBezTriple_KeyframeType(ts->keyframe_type), + nullptr, + eInsertKeyFlags(0)); + WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, nullptr); + } + } + else if (driven) { + /* Driver - Try to insert keyframe using the driver's input as the frame, + * making it easier to set up corrective drivers + */ + if (IS_AUTOKEY_ON(scene)) { + ReportList *reports = CTX_wm_reports(C); + ToolSettings *ts = scene->toolsettings; + + changed = insert_keyframe_direct(reports, + *ptr, + prop, + fcu, + &anim_eval_context, + eBezTriple_KeyframeType(ts->keyframe_type), + nullptr, + INSERTKEY_DRIVER); + WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, nullptr); + } + } + else { + id = ptr->owner_id; + + /* TODO: this should probably respect the keyingset only option for anim */ + if (autokeyframe_cfra_can_key(scene, id)) { + ReportList *reports = CTX_wm_reports(C); + ToolSettings *ts = scene->toolsettings; + const eInsertKeyFlags flag = ANIM_get_keyframing_flags(scene, true); + char *path = RNA_path_from_ID_to_property(ptr, prop); + + if (only_if_property_keyed) { + /* NOTE: We use rnaindex instead of fcu->array_index, + * because a button may control all items of an array at once. + * E.g., color wheels (see #42567). */ + BLI_assert((fcu->array_index == rnaindex) || (rnaindex == -1)); + } + changed = insert_keyframe(bmain, + reports, + id, + action, + (fcu && fcu->grp) ? fcu->grp->name : nullptr, + fcu ? fcu->rna_path : path, + rnaindex, + &anim_eval_context, + eBezTriple_KeyframeType(ts->keyframe_type), + nullptr, + flag) != 0; + if (path) { + MEM_freeN(path); + } + WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, nullptr); + } + } + return changed; +} + /* -------------------------------------------------------------------- */ /** \name Internal Utilities * \{ */ diff --git a/source/blender/editors/animation/keyingsets.cc b/source/blender/editors/animation/keyingsets.cc index e63aa354c1e..1e351a092d5 100644 --- a/source/blender/editors/animation/keyingsets.cc +++ b/source/blender/editors/animation/keyingsets.cc @@ -28,7 +28,6 @@ #include "DEG_depsgraph.hh" -#include "ANIM_keyframing.hh" #include "ED_keyframing.hh" #include "ED_screen.hh" @@ -153,7 +152,7 @@ static int remove_active_keyingset_exec(bContext *C, wmOperator *op) ks = static_cast(BLI_findlink(&scene->keyingsets, scene->active_keyingset - 1)); /* free KeyingSet's data, then remove it from the scene */ - BKE_keyingset_free_paths(ks); + BKE_keyingset_free(ks); BLI_freelinkN(&scene->keyingsets, ks); /* the active one should now be the previously second-to-last one */ @@ -229,23 +228,26 @@ static int remove_active_ks_path_exec(bContext *C, wmOperator *op) BLI_findlink(&scene->keyingsets, scene->active_keyingset - 1)); /* if there is a KeyingSet, find the nominated path to remove */ - if (!ks) { + if (ks) { + KS_Path *ksp = static_cast(BLI_findlink(&ks->paths, ks->active_path - 1)); + + if (ksp) { + /* remove the active path from the KeyingSet */ + BKE_keyingset_free_path(ks, ksp); + + /* the active path should now be the previously second-to-last active one */ + ks->active_path--; + } + else { + BKE_report(op->reports, RPT_ERROR, "No active Keying Set path to remove"); + return OPERATOR_CANCELLED; + } + } + else { BKE_report(op->reports, RPT_ERROR, "No active Keying Set to remove a path from"); return OPERATOR_CANCELLED; } - KS_Path *ksp = static_cast(BLI_findlink(&ks->paths, ks->active_path - 1)); - if (!ksp) { - BKE_report(op->reports, RPT_ERROR, "No active Keying Set path to remove"); - return OPERATOR_CANCELLED; - } - - /* remove the active path from the KeyingSet */ - BKE_keyingset_free_path(ks, ksp); - - /* the active path should now be the previously second-to-last active one */ - ks->active_path--; - return OPERATOR_FINISHED; } @@ -268,9 +270,14 @@ void ANIM_OT_keying_set_path_remove(wmOperatorType *ot) static int add_keyingset_button_exec(bContext *C, wmOperator *op) { + Scene *scene = CTX_data_scene(C); + KeyingSet *ks = nullptr; PropertyRNA *prop = nullptr; PointerRNA ptr = {nullptr}; + char *path = nullptr; + bool changed = false; int index = 0, pflag = 0; + const bool all = RNA_boolean_get(op->ptr, "all"); if (!UI_context_active_but_prop_get(C, &ptr, &prop, &index)) { /* pass event on if no active button found */ @@ -281,8 +288,6 @@ static int add_keyingset_button_exec(bContext *C, wmOperator *op) * - use the active one for now (more control over this can be added later) * - add a new one if it doesn't exist */ - KeyingSet *ks = nullptr; - Scene *scene = CTX_data_scene(C); if (scene->active_keyingset == 0) { eKS_Settings flag = eKS_Settings(0); eInsertKeyFlags keyingflag = eInsertKeyFlags(0); @@ -294,7 +299,7 @@ static int add_keyingset_button_exec(bContext *C, wmOperator *op) keyingflag |= ANIM_get_keyframing_flags(scene, false); - if (blender::animrig::is_autokey_flag(scene, AUTOKEY_FLAG_XYZ2RGB)) { + if (IS_AUTOKEY_FLAG(scene, XYZ2RGB)) { keyingflag |= INSERTKEY_XYZ2RGB; } @@ -313,9 +318,6 @@ static int add_keyingset_button_exec(bContext *C, wmOperator *op) } /* check if property is able to be added */ - const bool all = RNA_boolean_get(op->ptr, "all"); - char *path = nullptr; - bool changed = false; if (ptr.owner_id && ptr.data && prop && RNA_property_animateable(&ptr, prop)) { path = RNA_path_from_ID_to_property(&ptr, prop); @@ -374,8 +376,12 @@ void ANIM_OT_keyingset_button_add(wmOperatorType *ot) static int remove_keyingset_button_exec(bContext *C, wmOperator *op) { + Scene *scene = CTX_data_scene(C); + KeyingSet *ks = nullptr; PropertyRNA *prop = nullptr; PointerRNA ptr = {nullptr}; + char *path = nullptr; + bool changed = false; int index = 0; if (!UI_context_active_but_prop_get(C, &ptr, &prop, &index)) { @@ -387,7 +393,6 @@ static int remove_keyingset_button_exec(bContext *C, wmOperator *op) * - use the active one for now (more control over this can be added later) * - return error if it doesn't exist */ - Scene *scene = CTX_data_scene(C); if (scene->active_keyingset == 0) { BKE_report(op->reports, RPT_ERROR, "No active Keying Set to remove property from"); return OPERATOR_CANCELLED; @@ -398,11 +403,8 @@ static int remove_keyingset_button_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } - KeyingSet *ks = static_cast( - BLI_findlink(&scene->keyingsets, scene->active_keyingset - 1)); + ks = static_cast(BLI_findlink(&scene->keyingsets, scene->active_keyingset - 1)); - bool changed = false; - char *path = nullptr; if (ptr.owner_id && ptr.data && prop) { path = RNA_path_from_ID_to_property(&ptr, prop); @@ -528,15 +530,25 @@ KeyingSetInfo *ANIM_keyingset_info_find_name(const char name[]) BLI_findstring(&keyingset_type_infos, name, offsetof(KeyingSetInfo, idname))); } -KeyingSet *ANIM_builtin_keyingset_get_named(const char name[]) +KeyingSet *ANIM_builtin_keyingset_get_named(KeyingSet *prevKS, const char name[]) { + KeyingSet *ks, *first = nullptr; + /* sanity checks any name to check? */ if (name[0] == 0) { return nullptr; } + /* get first KeyingSet to use */ + if (prevKS && prevKS->next) { + first = prevKS->next; + } + else { + first = static_cast(builtin_keyingsets.first); + } + /* loop over KeyingSets checking names */ - LISTBASE_FOREACH (KeyingSet *, ks, &builtin_keyingsets) { + for (ks = first; ks; ks = ks->next) { if (STREQ(name, ks->idname)) { return ks; } @@ -583,20 +595,19 @@ void ANIM_keyingset_info_unregister(Main *bmain, KeyingSetInfo *ksi) ksn = ks->next; /* remove if matching typeinfo name */ - if (!STREQ(ks->typeinfo, ksi->idname)) { - continue; - } - Scene *scene; - BKE_keyingset_free_paths(ks); - BLI_remlink(&builtin_keyingsets, ks); + if (STREQ(ks->typeinfo, ksi->idname)) { + Scene *scene; + BKE_keyingset_free(ks); + BLI_remlink(&builtin_keyingsets, ks); - for (scene = static_cast(bmain->scenes.first); scene; - scene = static_cast(scene->id.next)) - { - BLI_remlink_safe(&scene->keyingsets, ks); - } + for (scene = static_cast(bmain->scenes.first); scene; + scene = static_cast(scene->id.next)) + { + BLI_remlink_safe(&scene->keyingsets, ks); + } - MEM_freeN(ks); + MEM_freeN(ks); + } } /* free the type info */ @@ -693,17 +704,13 @@ KeyingSet *ANIM_get_keyingset_for_autokeying(const Scene *scene, const char *tra * - use the active KeyingSet if defined (and user wants to use it for all autokeying), * or otherwise key transforms only */ - if (blender::animrig::is_autokey_flag(scene, AUTOKEY_FLAG_ONLYKEYINGSET) && - (scene->active_keyingset)) - { + if (IS_AUTOKEY_FLAG(scene, ONLYKEYINGSET) && (scene->active_keyingset)) { return ANIM_scene_get_active_keyingset(scene); } - - if (blender::animrig::is_autokey_flag(scene, AUTOKEY_FLAG_INSERTAVAIL)) { - return ANIM_builtin_keyingset_get_named(ANIM_KS_AVAILABLE_ID); + if (IS_AUTOKEY_FLAG(scene, INSERTAVAIL)) { + return ANIM_builtin_keyingset_get_named(nullptr, ANIM_KS_AVAILABLE_ID); } - - return ANIM_builtin_keyingset_get_named(transformKSName); + return ANIM_builtin_keyingset_get_named(nullptr, transformKSName); } static void anim_keyingset_visit_for_search_impl(const bContext *C, @@ -778,7 +785,11 @@ const EnumPropertyItem *ANIM_keying_sets_enum_itemf(bContext *C, PropertyRNA * /*prop*/, bool *r_free) { - int enum_index = 0; + Scene *scene = CTX_data_scene(C); + KeyingSet *ks; + EnumPropertyItem *item = nullptr, item_tmp = {0}; + int totitem = 0; + int i = 0; if (C == nullptr) { return rna_enum_dummy_DEFAULT_items; @@ -787,33 +798,29 @@ const EnumPropertyItem *ANIM_keying_sets_enum_itemf(bContext *C, /* active Keying Set * - only include entry if it exists */ - Scene *scene = CTX_data_scene(C); - EnumPropertyItem *item = nullptr, item_tmp = {0}; - int totitem = 0; if (scene->active_keyingset) { /* active Keying Set */ item_tmp.identifier = "__ACTIVE__"; item_tmp.name = "Active Keying Set"; - item_tmp.value = enum_index; + item_tmp.value = i; RNA_enum_item_add(&item, &totitem, &item_tmp); /* separator */ RNA_enum_item_add_separator(&item, &totitem); } - enum_index++; + i++; /* user-defined Keying Sets * - these are listed in the order in which they were defined for the active scene */ - KeyingSet *ks; if (scene->keyingsets.first) { - for (ks = static_cast(scene->keyingsets.first); ks; ks = ks->next, enum_index++) { + for (ks = static_cast(scene->keyingsets.first); ks; ks = ks->next, i++) { if (ANIM_keyingset_context_ok_poll(C, ks)) { item_tmp.identifier = ks->idname; item_tmp.name = ks->name; item_tmp.description = ks->description; - item_tmp.value = enum_index; + item_tmp.value = i; RNA_enum_item_add(&item, &totitem, &item_tmp); } } @@ -823,14 +830,14 @@ const EnumPropertyItem *ANIM_keying_sets_enum_itemf(bContext *C, } /* builtin Keying Sets */ - enum_index = -1; - for (ks = static_cast(builtin_keyingsets.first); ks; ks = ks->next, enum_index--) { + i = -1; + for (ks = static_cast(builtin_keyingsets.first); ks; ks = ks->next, i--) { /* only show KeyingSet if context is suitable */ if (ANIM_keyingset_context_ok_poll(C, ks)) { item_tmp.identifier = ks->idname; item_tmp.name = ks->name; item_tmp.description = ks->description; - item_tmp.value = enum_index; + item_tmp.value = i; RNA_enum_item_add(&item, &totitem, &item_tmp); } } @@ -876,24 +883,33 @@ KeyingSet *ANIM_keyingset_get_from_idname(Scene *scene, const char *idname) bool ANIM_keyingset_context_ok_poll(bContext *C, KeyingSet *ks) { - if ((ks->flag & KEYINGSET_ABSOLUTE)) { - return true; + if ((ks->flag & KEYINGSET_ABSOLUTE) == 0) { + KeyingSetInfo *ksi = ANIM_keyingset_info_find_name(ks->typeinfo); + + /* get the associated 'type info' for this KeyingSet */ + if (ksi == nullptr) { + return false; + } + /* TODO: check for missing callbacks! */ + + /* check if it can be used in the current context */ + return ksi->poll(ksi, C); } - KeyingSetInfo *ksi = ANIM_keyingset_info_find_name(ks->typeinfo); - - /* get the associated 'type info' for this KeyingSet */ - if (ksi == nullptr) { - return false; - } - /* TODO: check for missing callbacks! */ - - /* check if it can be used in the current context */ - return ksi->poll(ksi, C); + return true; } /* Special 'Overrides' Iterator for Relative KeyingSets ------ */ +/* 'Data Sources' for relative Keying Set 'overrides' + * - this is basically a wrapper for PointerRNA's in a linked list + * - do not allow this to be accessed from outside for now + */ +struct tRKS_DSource { + tRKS_DSource *next, *prev; + PointerRNA ptr; /* the whole point of this exercise! */ +}; + /* Iterator used for overriding the behavior of iterators defined for * relative Keying Sets, with the main usage of this being operators * requiring Auto Keyframing. Internal Use Only! @@ -901,89 +917,94 @@ bool ANIM_keyingset_context_ok_poll(bContext *C, KeyingSet *ks) static void RKS_ITER_overrides_list(KeyingSetInfo *ksi, bContext *C, KeyingSet *ks, - blender::Vector &sources) + ListBase *dsources) { - for (PointerRNA ptr : sources) { - /* Run generate callback on this data. */ - ksi->generate(ksi, C, ks, &ptr); + LISTBASE_FOREACH (tRKS_DSource *, ds, dsources) { + /* run generate callback on this data */ + ksi->generate(ksi, C, ks, &ds->ptr); } } -void ANIM_relative_keyingset_add_source(blender::Vector &sources, - ID *id, - StructRNA *srna, - void *data) +void ANIM_relative_keyingset_add_source(ListBase *dsources, ID *id, StructRNA *srna, void *data) { - if (ELEM(nullptr, srna, data, id)) { - return; - } - sources.append(RNA_pointer_create(id, srna, data)); -} + tRKS_DSource *ds; -void ANIM_relative_keyingset_add_source(blender::Vector &sources, ID *id) -{ - if (id == nullptr) { + /* sanity checks + * - we must have somewhere to output the data + * - we must have both srna+data (and with id too optionally), or id by itself only + */ + if (dsources == nullptr) { return; } - sources.append(RNA_id_pointer_create(id)); + if (ELEM(nullptr, srna, data) && (id == nullptr)) { + return; + } + + /* allocate new elem, and add to the list */ + ds = static_cast(MEM_callocN(sizeof(tRKS_DSource), "tRKS_DSource")); + BLI_addtail(dsources, ds); + + /* depending on what data we have, create using ID or full pointer call */ + if (srna && data) { + ds->ptr = RNA_pointer_create(id, srna, data); + } + else { + ds->ptr = RNA_id_pointer_create(id); + } } /* KeyingSet Operations (Insert/Delete Keyframes) ------------ */ -eModifyKey_Returns ANIM_validate_keyingset(bContext *C, - blender::Vector *sources, - KeyingSet *ks) +eModifyKey_Returns ANIM_validate_keyingset(bContext *C, ListBase *dsources, KeyingSet *ks) { /* sanity check */ if (ks == nullptr) { - return MODIFYKEY_SUCCESS; + return eModifyKey_Returns(0); } /* if relative Keying Sets, poll and build up the paths */ - if ((ks->flag & KEYINGSET_ABSOLUTE)) { - return MODIFYKEY_SUCCESS; - } + if ((ks->flag & KEYINGSET_ABSOLUTE) == 0) { + KeyingSetInfo *ksi = ANIM_keyingset_info_find_name(ks->typeinfo); - KeyingSetInfo *ksi = ANIM_keyingset_info_find_name(ks->typeinfo); + /* clear all existing paths + * NOTE: BKE_keyingset_free() frees all of the paths for the KeyingSet, but not the set itself + */ + BKE_keyingset_free(ks); - /* clear all existing paths - * NOTE: BKE_keyingset_free_paths() frees all of the paths for the KeyingSet, but not the set - * itself - */ - BKE_keyingset_free_paths(ks); + /* get the associated 'type info' for this KeyingSet */ + if (ksi == nullptr) { + return MODIFYKEY_MISSING_TYPEINFO; + } + /* TODO: check for missing callbacks! */ - /* get the associated 'type info' for this KeyingSet */ - if (ksi == nullptr) { - return MODIFYKEY_MISSING_TYPEINFO; - } - /* TODO: check for missing callbacks! */ + /* check if it can be used in the current context */ + if (ksi->poll(ksi, C)) { + /* if a list of data sources are provided, run a special iterator over them, + * otherwise, just continue per normal + */ + if (dsources) { + RKS_ITER_overrides_list(ksi, C, ks, dsources); + } + else { + ksi->iter(ksi, C, ks); + } - /* check if it can be used in the current context */ - if (!ksi->poll(ksi, C)) { - /* poll callback tells us that KeyingSet is useless in current context */ - /* FIXME: the poll callback needs to give us more info why */ - return MODIFYKEY_INVALID_CONTEXT; - } - - /* if a list of data sources are provided, run a special iterator over them, - * otherwise, just continue per normal - */ - if (sources != nullptr) { - RKS_ITER_overrides_list(ksi, C, ks, *sources); - } - else { - ksi->iter(ksi, C, ks); - } - - /* if we don't have any paths now, then this still qualifies as invalid context */ - /* FIXME: we need some error conditions (to be retrieved from the iterator why this failed!) - */ - if (BLI_listbase_is_empty(&ks->paths)) { - return MODIFYKEY_INVALID_CONTEXT; + /* if we don't have any paths now, then this still qualifies as invalid context */ + /* FIXME: we need some error conditions (to be retrieved from the iterator why this failed!) + */ + if (BLI_listbase_is_empty(&ks->paths)) { + return MODIFYKEY_INVALID_CONTEXT; + } + } + else { + /* poll callback tells us that KeyingSet is useless in current context */ + /* FIXME: the poll callback needs to give us more info why */ + return MODIFYKEY_INVALID_CONTEXT; + } } /* succeeded; return 0 to tag error free */ - return MODIFYKEY_SUCCESS; + return eModifyKey_Returns(0); } /* Determine which keying flags apply based on the override flags */ @@ -1019,42 +1040,43 @@ static eInsertKeyFlags keyingset_apply_keying_flags(const eInsertKeyFlags base_f } int ANIM_apply_keyingset( - bContext *C, blender::Vector *sources, KeyingSet *ks, short mode, float cfra) + bContext *C, ListBase *dsources, bAction *act, KeyingSet *ks, short mode, float cfra) { + Main *bmain = CTX_data_main(C); + Scene *scene = CTX_data_scene(C); + ReportList *reports = CTX_wm_reports(C); + ListBase nla_cache = {nullptr, nullptr}; + const eInsertKeyFlags base_kflags = ANIM_get_keyframing_flags(scene, true); + const char *groupname = nullptr; + eInsertKeyFlags kflag = eInsertKeyFlags(0); + int num_channels = 0; + char keytype = scene->toolsettings->keyframe_type; + /* sanity checks */ if (ks == nullptr) { return 0; } /* get flags to use */ - Scene *scene = CTX_data_scene(C); - const eInsertKeyFlags base_kflags = ANIM_get_keyframing_flags(scene, true); - eInsertKeyFlags kflag = INSERTKEY_NOFLAGS; if (mode == MODIFYKEY_MODE_INSERT) { /* use context settings as base */ kflag = keyingset_apply_keying_flags( base_kflags, eInsertKeyFlags(ks->keyingoverride), eInsertKeyFlags(ks->keyingflag)); } else if (mode == MODIFYKEY_MODE_DELETE) { - kflag = INSERTKEY_NOFLAGS; + kflag = eInsertKeyFlags(0); } /* if relative Keying Sets, poll and build up the paths */ { - const eModifyKey_Returns error = ANIM_validate_keyingset(C, sources, ks); - if (error != MODIFYKEY_SUCCESS) { + const eModifyKey_Returns error = ANIM_validate_keyingset(C, dsources, ks); + if (error != 0) { BLI_assert(error < 0); /* return error code if failed */ return error; } } - Main *bmain = CTX_data_main(C); - ReportList *reports = CTX_wm_reports(C); - char keytype = scene->toolsettings->keyframe_type; - int num_channels = 0; - const char *groupname = nullptr; - /* apply the paths as specified in the KeyingSet now */ LISTBASE_FOREACH (KS_Path *, ksp, &ks->paths) { int arraylen, i; @@ -1120,20 +1142,20 @@ int ANIM_apply_keyingset( for (; i < arraylen; i++) { /* action to take depends on mode */ if (mode == MODIFYKEY_MODE_INSERT) { - num_channels += blender::animrig::insert_keyframe(bmain, - reports, - ksp->id, - nullptr, - groupname, - ksp->rna_path, - i, - &anim_eval_context, - eBezTriple_KeyframeType(keytype), - kflag2); + num_channels += insert_keyframe(bmain, + reports, + ksp->id, + act, + groupname, + ksp->rna_path, + i, + &anim_eval_context, + eBezTriple_KeyframeType(keytype), + &nla_cache, + kflag2); } else if (mode == MODIFYKEY_MODE_DELETE) { - num_channels += blender::animrig::delete_keyframe( - bmain, reports, ksp->id, nullptr, ksp->rna_path, i, cfra); + num_channels += delete_keyframe(bmain, reports, ksp->id, act, ksp->rna_path, i, cfra); } } @@ -1156,6 +1178,8 @@ int ANIM_apply_keyingset( WM_main_add_notifier(NC_ANIMATION | ND_KEYFRAME | NA_ADDED, nullptr); } + BKE_animsys_free_nla_keyframing_context_cache(&nla_cache); + /* return the number of channels successfully affected */ BLI_assert(num_channels >= 0); return num_channels; diff --git a/source/blender/editors/armature/armature_add.cc b/source/blender/editors/armature/armature_add.cc index 3a4615d3625..da2eba48157 100644 --- a/source/blender/editors/armature/armature_add.cc +++ b/source/blender/editors/armature/armature_add.cc @@ -20,7 +20,7 @@ #include "BLI_math_matrix.h" #include "BLI_math_rotation.h" #include "BLI_math_vector.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLT_translation.h" diff --git a/source/blender/editors/armature/armature_naming.cc b/source/blender/editors/armature/armature_naming.cc index 07fef0943e7..141b18db8b6 100644 --- a/source/blender/editors/armature/armature_naming.cc +++ b/source/blender/editors/armature/armature_naming.cc @@ -22,7 +22,7 @@ #include "BLI_blenlib.h" #include "BLI_ghash.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_utildefines.h" #include "BLT_translation.h" diff --git a/source/blender/editors/armature/armature_select.cc b/source/blender/editors/armature/armature_select.cc index b715ba264ec..c59583dbba8 100644 --- a/source/blender/editors/armature/armature_select.cc +++ b/source/blender/editors/armature/armature_select.cc @@ -17,7 +17,7 @@ #include "BLI_math_matrix.h" #include "BLI_math_vector.h" #include "BLI_rect.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BKE_action.h" #include "BKE_armature.h" @@ -316,11 +316,12 @@ static void *ed_armature_pick_bone_impl( const bool is_editmode, bContext *C, const int xy[2], bool findunsel, Base **r_base) { Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + ViewContext vc; rcti rect; GPUSelectResult buffer[MAXPICKELEMS]; short hits; - ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph); + ED_view3d_viewcontext_init(C, &vc, depsgraph); BLI_assert((vc.obedit != nullptr) == is_editmode); BLI_rcti_init_pt_radius(&rect, xy, 0); @@ -949,7 +950,8 @@ bool ED_armature_edit_deselect_all_visible_multi_ex(Base **bases, uint bases_len bool ED_armature_edit_deselect_all_visible_multi(bContext *C) { Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph); + ViewContext vc; + ED_view3d_viewcontext_init(C, &vc, depsgraph); uint bases_len = 0; Base **bases = BKE_view_layer_array_from_bases_in_edit_mode_unique_data( vc.scene, vc.view_layer, vc.v3d, &bases_len); @@ -1141,11 +1143,12 @@ bool ED_armature_edit_select_pick(bContext *C, const int mval[2], const SelectPi { Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + ViewContext vc; EditBone *nearBone = nullptr; int selmask; Base *basact = nullptr; - ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph); + ED_view3d_viewcontext_init(C, &vc, depsgraph); vc.mval[0] = mval[0]; vc.mval[1] = mval[1]; diff --git a/source/blender/editors/armature/armature_skinning.cc b/source/blender/editors/armature/armature_skinning.cc index 42cded81591..26682b26888 100644 --- a/source/blender/editors/armature/armature_skinning.cc +++ b/source/blender/editors/armature/armature_skinning.cc @@ -18,7 +18,7 @@ #include "BLI_math_matrix.h" #include "BLI_math_vector.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BKE_action.h" #include "BKE_armature.h" diff --git a/source/blender/editors/armature/armature_utils.cc b/source/blender/editors/armature/armature_utils.cc index 129e60296da..5b6a404af84 100644 --- a/source/blender/editors/armature/armature_utils.cc +++ b/source/blender/editors/armature/armature_utils.cc @@ -14,7 +14,7 @@ #include "BLI_blenlib.h" #include "BLI_math_matrix.h" #include "BLI_math_vector.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BKE_armature.h" #include "BKE_context.h" diff --git a/source/blender/editors/armature/pose_edit.cc b/source/blender/editors/armature/pose_edit.cc index ff03a151f3c..63ec12d74b3 100644 --- a/source/blender/editors/armature/pose_edit.cc +++ b/source/blender/editors/armature/pose_edit.cc @@ -51,7 +51,6 @@ #include "ED_view3d.hh" #include "ANIM_bone_collections.h" -#include "ANIM_keyframing.hh" #include "UI_interface.hh" @@ -809,7 +808,7 @@ void POSE_OT_reveal(wmOperatorType *ot) static int pose_flip_quats_exec(bContext *C, wmOperator * /*op*/) { Scene *scene = CTX_data_scene(C); - KeyingSet *ks = ANIM_builtin_keyingset_get_named(ANIM_KS_LOC_ROT_SCALE_ID); + KeyingSet *ks = ANIM_builtin_keyingset_get_named(nullptr, ANIM_KS_LOC_ROT_SCALE_ID); bool changed_multi = false; @@ -825,7 +824,7 @@ static int pose_flip_quats_exec(bContext *C, wmOperator * /*op*/) /* quaternions have 720 degree range */ negate_v4(pchan->quat); - blender::animrig::autokeyframe_pchan(C, scene, ob_iter, pchan, ks); + ED_autokeyframe_pchan(C, scene, ob_iter, pchan, ks); } } FOREACH_PCHAN_SELECTED_IN_OBJECT_END; diff --git a/source/blender/editors/armature/pose_lib_2.cc b/source/blender/editors/armature/pose_lib_2.cc index b96202a817f..a7ed7932d99 100644 --- a/source/blender/editors/armature/pose_lib_2.cc +++ b/source/blender/editors/armature/pose_lib_2.cc @@ -46,7 +46,6 @@ #include "ED_util.hh" #include "ANIM_bone_collections.h" -#include "ANIM_keyframing.hh" #include "armature_intern.h" @@ -117,7 +116,7 @@ static void poselib_backup_posecopy(PoseBlendData *pbd) /* Auto-key/tag bones affected by the pose Action. */ static void poselib_keytag_pose(bContext *C, Scene *scene, PoseBlendData *pbd) { - if (!blender::animrig::autokeyframe_cfra_can_key(scene, &pbd->ob->id)) { + if (!autokeyframe_cfra_can_key(scene, &pbd->ob->id)) { return; } @@ -133,7 +132,7 @@ static void poselib_keytag_pose(bContext *C, Scene *scene, PoseBlendData *pbd) bAction *act = poselib_action_to_blend(pbd); KeyingSet *ks = ANIM_get_keyingset_for_autokeying(scene, ANIM_KS_WHOLE_CHARACTER_ID); - blender::Vector sources; + ListBase dsources = {nullptr, nullptr}; /* start tagging/keying */ const bArmature *armature = static_cast(pbd->ob->data); @@ -151,11 +150,12 @@ static void poselib_keytag_pose(bContext *C, Scene *scene, PoseBlendData *pbd) } /* Add data-source override for the PoseChannel, to be used later. */ - ANIM_relative_keyingset_add_source(sources, &pbd->ob->id, &RNA_PoseBone, pchan); + ANIM_relative_keyingset_add_source(&dsources, &pbd->ob->id, &RNA_PoseBone, pchan); } /* Perform actual auto-keying. */ - ANIM_apply_keyingset(C, &sources, ks, MODIFYKEY_MODE_INSERT, float(scene->r.cfra)); + ANIM_apply_keyingset(C, &dsources, nullptr, ks, MODIFYKEY_MODE_INSERT, float(scene->r.cfra)); + BLI_freelistN(&dsources); /* send notifiers for this */ WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, nullptr); diff --git a/source/blender/editors/armature/pose_select.cc b/source/blender/editors/armature/pose_select.cc index 1f9f6b37d2f..63631773790 100644 --- a/source/blender/editors/armature/pose_select.cc +++ b/source/blender/editors/armature/pose_select.cc @@ -411,7 +411,8 @@ bool ED_pose_deselect_all_multi_ex(Base **bases, bool ED_pose_deselect_all_multi(bContext *C, int select_mode, const bool ignore_visibility) { Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph); + ViewContext vc; + ED_view3d_viewcontext_init(C, &vc, depsgraph); uint bases_len = 0; Base **bases = BKE_object_pose_base_array_get_unique( diff --git a/source/blender/editors/armature/pose_transform.cc b/source/blender/editors/armature/pose_transform.cc index 9fcaedc290f..fe720415ae2 100644 --- a/source/blender/editors/armature/pose_transform.cc +++ b/source/blender/editors/armature/pose_transform.cc @@ -18,7 +18,7 @@ #include "BLI_math_matrix.h" #include "BLI_math_rotation.h" #include "BLI_math_vector.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BKE_action.h" #include "BKE_animsys.h" @@ -49,7 +49,6 @@ #include "ED_util.hh" #include "ANIM_bone_collections.h" -#include "ANIM_keyframing.hh" #include "UI_interface.hh" #include "UI_resources.hh" @@ -910,7 +909,7 @@ static int pose_paste_exec(bContext *C, wmOperator *op) bPoseChannel *pchan = pose_bone_do_paste(ob, chan, selOnly, flip); if (pchan != nullptr) { /* Keyframing tagging for successful paste, */ - blender::animrig::autokeyframe_pchan(C, scene, ob, pchan, ks); + ED_autokeyframe_pchan(C, scene, ob, pchan, ks); } } } @@ -1198,7 +1197,7 @@ static int pose_clear_transform_generic_exec(bContext *C, FOREACH_OBJECT_IN_MODE_BEGIN (scene, view_layer, v3d, OB_ARMATURE, OB_MODE_POSE, ob_iter) { /* XXX: UGLY HACK (for auto-key + clear transforms). */ Object *ob_eval = DEG_get_evaluated_object(depsgraph, ob_iter); - blender::Vector sources; + ListBase dsources = {nullptr, nullptr}; bool changed = false; FOREACH_PCHAN_SELECTED_IN_OBJECT_BEGIN (ob_iter, pchan) { @@ -1207,9 +1206,9 @@ static int pose_clear_transform_generic_exec(bContext *C, changed = true; /* do auto-keyframing as appropriate */ - if (blender::animrig::autokeyframe_cfra_can_key(scene, &ob_iter->id)) { + if (autokeyframe_cfra_can_key(scene, &ob_iter->id)) { /* tag for autokeying later */ - ANIM_relative_keyingset_add_source(sources, &ob_iter->id, &RNA_PoseBone, pchan); + ANIM_relative_keyingset_add_source(&dsources, &ob_iter->id, &RNA_PoseBone, pchan); #if 1 /* XXX: Ugly Hack - Run clearing function on evaluated copy of pchan */ bPoseChannel *pchan_eval = BKE_pose_channel_find_name(ob_eval->pose, pchan->name); @@ -1223,17 +1222,20 @@ static int pose_clear_transform_generic_exec(bContext *C, changed_multi = true; /* perform autokeying on the bones if needed */ - if (!sources.is_empty()) { + if (!BLI_listbase_is_empty(&dsources)) { /* get KeyingSet to use */ KeyingSet *ks = ANIM_get_keyingset_for_autokeying(scene, default_ksName); /* insert keyframes */ - ANIM_apply_keyingset(C, &sources, ks, MODIFYKEY_MODE_INSERT, float(scene->r.cfra)); + ANIM_apply_keyingset( + C, &dsources, nullptr, ks, MODIFYKEY_MODE_INSERT, float(scene->r.cfra)); /* now recalculate paths */ if (ob_iter->pose->avs.path_bakeflag & MOTIONPATH_BAKE_HAS_PATHS) { ED_pose_recalculate_paths(C, scene, ob_iter, POSE_PATH_CALC_RANGE_FULL); } + + BLI_freelistN(&dsources); } DEG_id_tag_update(&ob_iter->id, ID_RECALC_GEOMETRY); diff --git a/source/blender/editors/armature/pose_utils.cc b/source/blender/editors/armature/pose_utils.cc index 85ef1eeef76..58ef15a76f3 100644 --- a/source/blender/editors/armature/pose_utils.cc +++ b/source/blender/editors/armature/pose_utils.cc @@ -37,8 +37,6 @@ #include "ED_armature.hh" #include "ED_keyframing.hh" -#include "ANIM_keyframing.hh" - #include "armature_intern.h" /* *********************************************** */ @@ -263,7 +261,7 @@ void poseAnim_mapping_autoKeyframe(bContext *C, Scene *scene, ListBase *pfLinks, continue; } - if (blender::animrig::autokeyframe_cfra_can_key(scene, &ob->id)) { + if (autokeyframe_cfra_can_key(scene, &ob->id)) { ob->id.tag |= LIB_TAG_DOIT; skip = false; } @@ -276,7 +274,7 @@ void poseAnim_mapping_autoKeyframe(bContext *C, Scene *scene, ListBase *pfLinks, /* Insert keyframes as necessary if auto-key-framing. */ KeyingSet *ks = ANIM_get_keyingset_for_autokeying(scene, ANIM_KS_WHOLE_CHARACTER_ID); - blender::Vector sources; + ListBase dsources = {nullptr, nullptr}; /* iterate over each pose-channel affected, tagging bones to be keyed */ /* XXX: here we already have the information about what transforms exist, though @@ -290,11 +288,12 @@ void poseAnim_mapping_autoKeyframe(bContext *C, Scene *scene, ListBase *pfLinks, } /* Add data-source override for the PoseChannel, to be used later. */ - ANIM_relative_keyingset_add_source(sources, &pfl->ob->id, &RNA_PoseBone, pchan); + ANIM_relative_keyingset_add_source(&dsources, &pfl->ob->id, &RNA_PoseBone, pchan); } /* insert keyframes for all relevant bones in one go */ - ANIM_apply_keyingset(C, &sources, ks, MODIFYKEY_MODE_INSERT, cframe); + ANIM_apply_keyingset(C, &dsources, nullptr, ks, MODIFYKEY_MODE_INSERT, cframe); + BLI_freelistN(&dsources); /* do the bone paths * - only do this if keyframes should have been added diff --git a/source/blender/editors/asset/intern/asset_catalog.cc b/source/blender/editors/asset/intern/asset_catalog.cc index 7576a5c0084..5a139ccb6a9 100644 --- a/source/blender/editors/asset/intern/asset_catalog.cc +++ b/source/blender/editors/asset/intern/asset_catalog.cc @@ -13,7 +13,7 @@ #include "BKE_main.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "RNA_access.hh" #include "RNA_prototypes.h" diff --git a/source/blender/editors/curve/editcurve.cc b/source/blender/editors/curve/editcurve.cc index b6ddeb1650c..8e698ffd752 100644 --- a/source/blender/editors/curve/editcurve.cc +++ b/source/blender/editors/curve/editcurve.cc @@ -4792,6 +4792,7 @@ bool ED_curve_editnurb_select_pick(bContext *C, const SelectPick_Params *params) { Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + ViewContext vc; Nurb *nu; BezTriple *bezt = nullptr; BPoint *bp = nullptr; @@ -4800,7 +4801,7 @@ bool ED_curve_editnurb_select_pick(bContext *C, bool changed = false; view3d_operator_needs_opengl(C); - ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph); + ED_view3d_viewcontext_init(C, &vc, depsgraph); copy_v2_v2_int(vc.mval, mval); const bool use_handle_select = (vc.v3d->overlay.handle_display != CURVE_HANDLE_NONE); @@ -5615,7 +5616,9 @@ static int add_vertex_exec(bContext *C, wmOperator *op) static int add_vertex_invoke(bContext *C, wmOperator *op, const wmEvent *event) { Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph); + ViewContext vc; + + ED_view3d_viewcontext_init(C, &vc, depsgraph); if (vc.rv3d && !RNA_struct_property_is_set(op->ptr, "location")) { Curve *cu; diff --git a/source/blender/editors/curve/editcurve_paint.cc b/source/blender/editors/curve/editcurve_paint.cc index 25290c10d36..134ccfb6562 100644 --- a/source/blender/editors/curve/editcurve_paint.cc +++ b/source/blender/editors/curve/editcurve_paint.cc @@ -574,7 +574,7 @@ static bool curve_draw_init(bContext *C, wmOperator *op, bool is_invoke) Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); if (is_invoke) { - cdd->vc = ED_view3d_viewcontext_init(C, depsgraph); + ED_view3d_viewcontext_init(C, &cdd->vc, depsgraph); if (ELEM(nullptr, cdd->vc.region, cdd->vc.rv3d, cdd->vc.v3d, cdd->vc.win, cdd->vc.scene)) { MEM_freeN(cdd); BKE_report(op->reports, RPT_ERROR, "Unable to access 3D viewport"); diff --git a/source/blender/editors/curve/editcurve_pen.cc b/source/blender/editors/curve/editcurve_pen.cc index 1bcec4e29c9..49cb622d362 100644 --- a/source/blender/editors/curve/editcurve_pen.cc +++ b/source/blender/editors/curve/editcurve_pen.cc @@ -1559,9 +1559,10 @@ wmKeyMap *curve_pen_modal_keymap(wmKeyConfig *keyconf) static int curve_pen_modal(bContext *C, wmOperator *op, const wmEvent *event) { Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + ViewContext vc; Object *obedit = CTX_data_edit_object(C); - ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph); + ED_view3d_viewcontext_init(C, &vc, depsgraph); Curve *cu = static_cast(vc.obedit->data); ListBase *nurbs = &cu->editnurb->nurbs; const float threshold_dist_px = ED_view3d_select_dist_px() * SEL_DIST_FACTOR; @@ -1748,8 +1749,8 @@ static int curve_pen_modal(bContext *C, wmOperator *op, const wmEvent *event) static int curve_pen_invoke(bContext *C, wmOperator *op, const wmEvent *event) { - Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph); + ViewContext vc; + ED_view3d_viewcontext_init(C, &vc, CTX_data_ensure_evaluated_depsgraph(C)); Curve *cu = static_cast(vc.obedit->data); ListBase *nurbs = &cu->editnurb->nurbs; diff --git a/source/blender/editors/curve/editcurve_select.cc b/source/blender/editors/curve/editcurve_select.cc index 0beb79d1678..9e659bc0444 100644 --- a/source/blender/editors/curve/editcurve_select.cc +++ b/source/blender/editors/curve/editcurve_select.cc @@ -256,7 +256,8 @@ bool ED_curve_deselect_all_multi_ex(Base **bases, int bases_len) bool ED_curve_deselect_all_multi(bContext *C) { Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph); + ViewContext vc; + ED_view3d_viewcontext_init(C, &vc, depsgraph); uint bases_len = 0; Base **bases = BKE_view_layer_array_from_bases_in_edit_mode_unique_data( vc.scene, vc.view_layer, vc.v3d, &bases_len); @@ -701,6 +702,7 @@ void CURVE_OT_select_linked(wmOperatorType *ot) static int select_linked_pick_invoke(bContext *C, wmOperator *op, const wmEvent *event) { Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + ViewContext vc; Nurb *nu; BezTriple *bezt; BPoint *bp; @@ -709,7 +711,7 @@ static int select_linked_pick_invoke(bContext *C, wmOperator *op, const wmEvent Base *basact = nullptr; view3d_operator_needs_opengl(C); - ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph); + ED_view3d_viewcontext_init(C, &vc, depsgraph); copy_v2_v2_int(vc.mval, event->mval); if (!ED_curve_pick_vert(&vc, 1, &nu, &bezt, &bp, nullptr, &basact)) { @@ -2026,6 +2028,7 @@ static void curve_select_shortest_path_surf(Nurb *nu, int vert_src, int vert_dst static int edcu_shortest_path_pick_invoke(bContext *C, wmOperator *op, const wmEvent *event) { Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + ViewContext vc; Nurb *nu_dst; BezTriple *bezt_dst; BPoint *bp_dst; @@ -2034,7 +2037,7 @@ static int edcu_shortest_path_pick_invoke(bContext *C, wmOperator *op, const wmE Base *basact = nullptr; view3d_operator_needs_opengl(C); - ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph); + ED_view3d_viewcontext_init(C, &vc, depsgraph); copy_v2_v2_int(vc.mval, event->mval); if (!ED_curve_pick_vert(&vc, 1, &nu_dst, &bezt_dst, &bp_dst, nullptr, &basact)) { diff --git a/source/blender/editors/curve/editfont.cc b/source/blender/editors/curve/editfont.cc index fe420d00cad..62ceb2cebef 100644 --- a/source/blender/editors/curve/editfont.cc +++ b/source/blender/editors/curve/editfont.cc @@ -2386,6 +2386,7 @@ bool ED_curve_editfont_select_pick( Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); Object *obedit = CTX_data_edit_object(C); Curve *cu = static_cast(obedit->data); + ViewContext vc; /* bias against the active, in pixels, allows cycling */ const float active_bias_px = 4.0f; const float mval_fl[2] = {float(mval[0]), float(mval[1])}; @@ -2394,7 +2395,7 @@ bool ED_curve_editfont_select_pick( const float dist = ED_view3d_select_dist_px(); float dist_sq_best = dist * dist; - ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph); + ED_view3d_viewcontext_init(C, &vc, depsgraph); ED_view3d_init_mats_rv3d(vc.obedit, vc.rv3d); diff --git a/source/blender/editors/curves/CMakeLists.txt b/source/blender/editors/curves/CMakeLists.txt index 6f2531f45bb..d5fcf758a33 100644 --- a/source/blender/editors/curves/CMakeLists.txt +++ b/source/blender/editors/curves/CMakeLists.txt @@ -24,7 +24,6 @@ set(INC_SYS set(SRC intern/curves_add.cc - intern/curves_attribute_set.cc intern/curves_data.cc intern/curves_edit.cc intern/curves_masks.cc diff --git a/source/blender/editors/curves/intern/curves_attribute_set.cc b/source/blender/editors/curves/intern/curves_attribute_set.cc deleted file mode 100644 index 51b73d5532b..00000000000 --- a/source/blender/editors/curves/intern/curves_attribute_set.cc +++ /dev/null @@ -1,229 +0,0 @@ -/* SPDX-FileCopyrightText: 2023 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -/** \file - * \ingroup edmesh - */ - -#include "BLI_generic_pointer.hh" - -#include "BKE_attribute.h" -#include "BKE_attribute_math.hh" -#include "BKE_context.h" -#include "BKE_report.h" -#include "BKE_type_conversions.hh" - -#include "WM_api.hh" -#include "WM_types.hh" - -#include "ED_curves.hh" -#include "ED_geometry.hh" -#include "ED_object.hh" -#include "ED_screen.hh" -#include "ED_transform.hh" -#include "ED_view3d.hh" - -#include "RNA_access.hh" - -#include "BLT_translation.h" - -#include "UI_interface.hh" -#include "UI_resources.hh" - -#include "DNA_object_types.h" - -#include "DEG_depsgraph.hh" -#include "DEG_depsgraph_query.hh" - -/* -------------------------------------------------------------------- */ -/** \name Delete Operator - * \{ */ - -namespace blender::ed::curves { - -static bool active_attribute_poll(bContext *C) -{ - if (!editable_curves_in_edit_mode_poll(C)) { - return false; - } - Object *object = CTX_data_active_object(C); - Curves &curves_id = *static_cast(object->data); - const CustomDataLayer *layer = BKE_id_attributes_active_get(&const_cast(curves_id.id)); - if (!layer) { - CTX_wm_operator_poll_msg_set(C, "No active attribute"); - return false; - } - if (layer->type == CD_PROP_STRING) { - CTX_wm_operator_poll_msg_set(C, "Active string attribute not supported"); - return false; - } - return true; -} - -static IndexMask retrieve_selected_elements(const Curves &curves_id, - const eAttrDomain domain, - IndexMaskMemory &memory) -{ - switch (domain) { - case ATTR_DOMAIN_POINT: - return retrieve_selected_points(curves_id, memory); - case ATTR_DOMAIN_CURVE: - return retrieve_selected_curves(curves_id, memory); - default: - BLI_assert_unreachable(); - return {}; - } -} - -static void validate_value(const bke::AttributeAccessor attributes, - const StringRef name, - const CPPType &type, - void *buffer) -{ - const bke::AttributeValidator validator = attributes.lookup_validator(name); - if (!validator) { - return; - } - BUFFER_FOR_CPP_TYPE_VALUE(type, validated_buffer); - BLI_SCOPED_DEFER([&]() { type.destruct(validated_buffer); }); - - const IndexMask single_mask(1); - mf::ParamsBuilder params(*validator.function, &single_mask); - params.add_readonly_single_input(GPointer(type, buffer)); - params.add_uninitialized_single_output({type, validated_buffer, 1}); - mf::ContextBuilder context; - validator.function->call(single_mask, params, context); - - type.copy_assign(validated_buffer, buffer); -} - -static int set_attribute_exec(bContext *C, wmOperator *op) -{ - Object *active_object = CTX_data_active_object(C); - Curves &active_curves_id = *static_cast(active_object->data); - - CustomDataLayer *active_attribute = BKE_id_attributes_active_get(&active_curves_id.id); - const eCustomDataType active_type = eCustomDataType(active_attribute->type); - const CPPType &type = *bke::custom_data_type_to_cpp_type(active_type); - - BUFFER_FOR_CPP_TYPE_VALUE(type, buffer); - BLI_SCOPED_DEFER([&]() { type.destruct(buffer); }); - const GPointer value = geometry::rna_property_for_attribute_type_retrieve_value( - *op->ptr, active_type, buffer); - - const bke::DataTypeConversions &conversions = bke::get_implicit_type_conversions(); - - for (Curves *curves_id : get_unique_editable_curves(*C)) { - bke::CurvesGeometry &curves = curves_id->geometry.wrap(); - CustomDataLayer *layer = BKE_id_attributes_active_get(&curves_id->id); - if (!layer) { - continue; - } - bke::MutableAttributeAccessor attributes = curves.attributes_for_write(); - bke::GSpanAttributeWriter attribute = attributes.lookup_for_write_span(layer->name); - - /* Use implicit conversions to try to handle the case where the active attribute has a - * different type on multiple objects. */ - const CPPType &dst_type = attribute.span.type(); - if (&type != &dst_type && !conversions.is_convertible(type, dst_type)) { - continue; - } - BUFFER_FOR_CPP_TYPE_VALUE(dst_type, dst_buffer); - BLI_SCOPED_DEFER([&]() { dst_type.destruct(dst_buffer); }); - conversions.convert_to_uninitialized(type, dst_type, value.get(), dst_buffer); - - validate_value(attributes, layer->name, dst_type, dst_buffer); - const GPointer dst_value(type, dst_buffer); - - IndexMaskMemory memory; - const IndexMask selection = retrieve_selected_elements(*curves_id, attribute.domain, memory); - if (selection.is_empty()) { - attribute.finish(); - continue; - } - dst_type.fill_assign_indices(dst_value.get(), attribute.span.data(), selection); - attribute.finish(); - - DEG_id_tag_update(&curves_id->id, ID_RECALC_GEOMETRY); - WM_event_add_notifier(C, NC_GEOM | ND_DATA, curves_id); - } - - return OPERATOR_FINISHED; -} - -static int set_attribute_invoke(bContext *C, wmOperator *op, const wmEvent *event) -{ - Object *active_object = CTX_data_active_object(C); - Curves &active_curves_id = *static_cast(active_object->data); - - CustomDataLayer *active_attribute = BKE_id_attributes_active_get(&active_curves_id.id); - const bke::CurvesGeometry &curves = active_curves_id.geometry.wrap(); - const bke::AttributeAccessor attributes = curves.attributes(); - const bke::GAttributeReader attribute = attributes.lookup(active_attribute->name); - const eAttrDomain domain = attribute.domain; - - IndexMaskMemory memory; - const IndexMask selection = retrieve_selected_elements(active_curves_id, domain, memory); - - const CPPType &type = attribute.varray.type(); - - PropertyRNA *prop = geometry::rna_property_for_type(*op->ptr, - bke::cpp_type_to_custom_data_type(type)); - if (RNA_property_is_set(op->ptr, prop)) { - return WM_operator_props_popup(C, op, event); - } - - BUFFER_FOR_CPP_TYPE_VALUE(type, buffer); - BLI_SCOPED_DEFER([&]() { type.destruct(buffer); }); - - bke::attribute_math::convert_to_static_type(type, [&](auto dummy) { - using T = decltype(dummy); - const VArray values_typed = attribute.varray.typed(); - bke::attribute_math::DefaultMixer mixer{MutableSpan(static_cast(buffer), 1)}; - selection.foreach_index([&](const int i) { mixer.mix_in(0, values_typed[i]); }); - mixer.finalize(); - }); - - geometry::rna_property_for_attribute_type_set_value(*op->ptr, *prop, GPointer(type, buffer)); - - return WM_operator_props_popup(C, op, event); -} - -static void set_attribute_ui(bContext *C, wmOperator *op) -{ - uiLayout *layout = uiLayoutColumn(op->layout, true); - uiLayoutSetPropSep(layout, true); - uiLayoutSetPropDecorate(layout, false); - - Object *object = CTX_data_active_object(C); - Curves &curves_id = *static_cast(object->data); - - CustomDataLayer *active_attribute = BKE_id_attributes_active_get(&curves_id.id); - const eCustomDataType active_type = eCustomDataType(active_attribute->type); - const StringRefNull prop_name = geometry::rna_property_name_for_type(active_type); - const char *name = active_attribute->name; - uiItemR(layout, op->ptr, prop_name.c_str(), UI_ITEM_NONE, name, ICON_NONE); -} - -void CURVES_OT_attribute_set(wmOperatorType *ot) -{ - using namespace blender::ed; - using namespace blender::ed::curves; - ot->name = "Set Attribute"; - ot->description = "Set values of the active attribute for selected elements"; - ot->idname = "CURVES_OT_attribute_set"; - - ot->exec = set_attribute_exec; - ot->invoke = set_attribute_invoke; - ot->poll = active_attribute_poll; - ot->ui = set_attribute_ui; - - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - geometry::register_rna_properties_for_attribute_types(*ot->srna); -} - -} // namespace blender::ed::curves - -/** \} */ diff --git a/source/blender/editors/curves/intern/curves_ops.cc b/source/blender/editors/curves/intern/curves_ops.cc index 98fe6435d0d..c707915856f 100644 --- a/source/blender/editors/curves/intern/curves_ops.cc +++ b/source/blender/editors/curves/intern/curves_ops.cc @@ -1248,7 +1248,6 @@ static void CURVES_OT_delete(wmOperatorType *ot) void ED_operatortypes_curves() { using namespace blender::ed::curves; - WM_operatortype_append(CURVES_OT_attribute_set); WM_operatortype_append(CURVES_OT_convert_to_particle_system); WM_operatortype_append(CURVES_OT_convert_from_particle_system); WM_operatortype_append(CURVES_OT_snap_curves_to_surface); diff --git a/source/blender/editors/curves/intern/curves_selection.cc b/source/blender/editors/curves/intern/curves_selection.cc index bb08915bfa2..c0af4f50795 100644 --- a/source/blender/editors/curves/intern/curves_selection.cc +++ b/source/blender/editors/curves/intern/curves_selection.cc @@ -23,7 +23,8 @@ namespace blender::ed::curves { -IndexMask retrieve_selected_curves(const bke::CurvesGeometry &curves, IndexMaskMemory &memory) +static IndexMask retrieve_selected_curves(const bke::CurvesGeometry &curves, + IndexMaskMemory &memory) { const IndexRange curves_range = curves.curves_range(); const bke::AttributeAccessor attributes = curves.attributes(); diff --git a/source/blender/editors/geometry/geometry_attributes.cc b/source/blender/editors/geometry/geometry_attributes.cc index 708403ab679..f01385f219a 100644 --- a/source/blender/editors/geometry/geometry_attributes.cc +++ b/source/blender/editors/geometry/geometry_attributes.cc @@ -12,8 +12,6 @@ #include "DNA_meshdata_types.h" #include "DNA_scene_types.h" -#include "BLI_color.hh" - #include "BKE_attribute.h" #include "BKE_context.h" #include "BKE_deform.h" @@ -44,137 +42,6 @@ namespace blender::ed::geometry { -StringRefNull rna_property_name_for_type(const eCustomDataType type) -{ - switch (type) { - case CD_PROP_FLOAT: - return "value_float"; - case CD_PROP_FLOAT2: - return "value_float_vector_2d"; - case CD_PROP_FLOAT3: - return "value_float_vector_3d"; - case CD_PROP_COLOR: - case CD_PROP_BYTE_COLOR: - return "value_color"; - case CD_PROP_BOOL: - return "value_bool"; - case CD_PROP_INT8: - case CD_PROP_INT32: - return "value_int"; - default: - BLI_assert_unreachable(); - return ""; - } -} - -PropertyRNA *rna_property_for_type(PointerRNA &ptr, const eCustomDataType type) -{ - return RNA_struct_find_property(&ptr, rna_property_name_for_type(type).c_str()); -} - -void register_rna_properties_for_attribute_types(StructRNA &srna) -{ - static blender::float4 color_default(1); - - RNA_def_float(&srna, "value_float", 0.0f, -FLT_MAX, FLT_MAX, "Value", "", -FLT_MAX, FLT_MAX); - RNA_def_float_array(&srna, - "value_float_vector_2d", - 2, - nullptr, - -FLT_MAX, - FLT_MAX, - "Value", - "", - -FLT_MAX, - FLT_MAX); - RNA_def_float_array(&srna, - "value_float_vector_3d", - 3, - nullptr, - -FLT_MAX, - FLT_MAX, - "Value", - "", - -FLT_MAX, - FLT_MAX); - RNA_def_int(&srna, "value_int", 0, INT_MIN, INT_MAX, "Value", "", INT_MIN, INT_MAX); - RNA_def_float_color( - &srna, "value_color", 4, color_default, -FLT_MAX, FLT_MAX, "Value", "", 0.0f, 1.0f); - RNA_def_boolean(&srna, "value_bool", false, "Value", ""); -} - -GPointer rna_property_for_attribute_type_retrieve_value(PointerRNA &ptr, - const eCustomDataType type, - void *buffer) -{ - const StringRefNull prop_name = rna_property_name_for_type(type); - switch (type) { - case CD_PROP_FLOAT: - *static_cast(buffer) = RNA_float_get(&ptr, prop_name.c_str()); - break; - case CD_PROP_FLOAT2: - RNA_float_get_array(&ptr, prop_name.c_str(), static_cast(buffer)); - break; - case CD_PROP_FLOAT3: - RNA_float_get_array(&ptr, prop_name.c_str(), static_cast(buffer)); - break; - case CD_PROP_COLOR: - RNA_float_get_array(&ptr, prop_name.c_str(), static_cast(buffer)); - break; - case CD_PROP_BYTE_COLOR: - ColorGeometry4f value; - RNA_float_get_array(&ptr, prop_name.c_str(), value); - *static_cast(buffer) = value.encode(); - break; - case CD_PROP_BOOL: - *static_cast(buffer) = RNA_boolean_get(&ptr, prop_name.c_str()); - break; - case CD_PROP_INT8: - *static_cast(buffer) = RNA_int_get(&ptr, prop_name.c_str()); - break; - case CD_PROP_INT32: - *static_cast(buffer) = RNA_int_get(&ptr, prop_name.c_str()); - break; - default: - BLI_assert_unreachable(); - } - return GPointer(bke::custom_data_type_to_cpp_type(type), buffer); -} - -void rna_property_for_attribute_type_set_value(PointerRNA &ptr, - PropertyRNA &prop, - const GPointer value) -{ - switch (bke::cpp_type_to_custom_data_type(*value.type())) { - case CD_PROP_FLOAT: - RNA_property_float_set(&ptr, &prop, *value.get()); - break; - case CD_PROP_FLOAT2: - RNA_property_float_set_array(&ptr, &prop, *value.get()); - break; - case CD_PROP_FLOAT3: - RNA_property_float_set_array(&ptr, &prop, *value.get()); - break; - case CD_PROP_BYTE_COLOR: - RNA_property_float_set_array(&ptr, &prop, value.get()->decode()); - break; - case CD_PROP_COLOR: - RNA_property_float_set_array(&ptr, &prop, *value.get()); - break; - case CD_PROP_BOOL: - RNA_property_boolean_set(&ptr, &prop, *value.get()); - break; - case CD_PROP_INT8: - RNA_property_int_set(&ptr, &prop, *value.get()); - break; - case CD_PROP_INT32: - RNA_property_int_set(&ptr, &prop, *value.get()); - break; - default: - BLI_assert_unreachable(); - } -} - /*********************** Attribute Operators ************************/ static bool geometry_attributes_poll(bContext *C) diff --git a/source/blender/editors/geometry/node_group_operator.cc b/source/blender/editors/geometry/node_group_operator.cc index 0fe2ecb8246..3006377d9ce 100644 --- a/source/blender/editors/geometry/node_group_operator.cc +++ b/source/blender/editors/geometry/node_group_operator.cc @@ -450,7 +450,7 @@ static void run_node_group_ui(bContext *C, wmOperator *op) node_tree->ensure_interface_cache(); int input_index = 0; - for (const bNodeTreeInterfaceSocket *io_socket : node_tree->interface_inputs()) { + for (bNodeTreeInterfaceSocket *io_socket : node_tree->interface_inputs()) { draw_property_for_socket( *node_tree, layout, op->properties, &bmain_ptr, op->ptr, *io_socket, input_index); ++input_index; diff --git a/source/blender/editors/gizmo_library/gizmo_types/snap3d_gizmo.cc b/source/blender/editors/gizmo_library/gizmo_types/snap3d_gizmo.cc index 7e204d0ac3d..ad8c113de83 100644 --- a/source/blender/editors/gizmo_library/gizmo_types/snap3d_gizmo.cc +++ b/source/blender/editors/gizmo_library/gizmo_types/snap3d_gizmo.cc @@ -391,10 +391,8 @@ static void GIZMO_GT_snap_3d(wmGizmoType *gzt) SCE_SNAP_TO_NONE, "Snap Source Type", "Snap Source type (influences drawing)"); - RNA_def_property_enum_funcs_runtime(prop, - gizmo_snap_rna_snap_srouce_type_get_fn, - gizmo_snap_rna_snap_srouce_type_set_fn, - nullptr); + RNA_def_property_enum_funcs_runtime( + prop, gizmo_snap_rna_snap_srouce_type_get_fn, gizmo_snap_rna_snap_srouce_type_set_fn, NULL); } void ED_gizmotypes_snap_3d() diff --git a/source/blender/editors/gpencil_legacy/gpencil_convert.cc b/source/blender/editors/gpencil_legacy/gpencil_convert.cc index 3ba89a34cf6..f6f3bf0af4c 100644 --- a/source/blender/editors/gpencil_legacy/gpencil_convert.cc +++ b/source/blender/editors/gpencil_legacy/gpencil_convert.cc @@ -66,7 +66,6 @@ #include "UI_resources.hh" #include "UI_view2d.hh" -#include "ANIM_keyframing.hh" #include "ED_clip.hh" #include "ED_gpencil_legacy.hh" #include "ED_keyframing.hh" @@ -437,14 +436,14 @@ static void gpencil_stroke_path_animation_add_keyframes(ReportList *reports, } const AnimationEvalContext anim_eval_context = BKE_animsys_eval_context_construct( depsgraph, cfra); - blender::animrig::insert_keyframe_direct(reports, - ptr, - prop, - fcu, - &anim_eval_context, - BEZT_KEYTYPE_KEYFRAME, - nullptr, - INSERTKEY_FAST); + insert_keyframe_direct(reports, + ptr, + prop, + fcu, + &anim_eval_context, + BEZT_KEYTYPE_KEYFRAME, + nullptr, + INSERTKEY_FAST); last_valid_time = cfra; } } @@ -455,14 +454,14 @@ static void gpencil_stroke_path_animation_add_keyframes(ReportList *reports, } const AnimationEvalContext anim_eval_context = BKE_animsys_eval_context_construct(depsgraph, cfra); - blender::animrig::insert_keyframe_direct(reports, - ptr, - prop, - fcu, - &anim_eval_context, - BEZT_KEYTYPE_KEYFRAME, - nullptr, - INSERTKEY_FAST); + insert_keyframe_direct(reports, + ptr, + prop, + fcu, + &anim_eval_context, + BEZT_KEYTYPE_KEYFRAME, + nullptr, + INSERTKEY_FAST); last_valid_time = cfra; } else { @@ -472,14 +471,14 @@ static void gpencil_stroke_path_animation_add_keyframes(ReportList *reports, if ((cfra - last_valid_time) > MIN_TIME_DELTA && (end_stroke_time - cfra) > MIN_TIME_DELTA) { const AnimationEvalContext anim_eval_context = BKE_animsys_eval_context_construct( depsgraph, cfra); - blender::animrig::insert_keyframe_direct(reports, - ptr, - prop, - fcu, - &anim_eval_context, - BEZT_KEYTYPE_BREAKDOWN, - nullptr, - INSERTKEY_FAST); + insert_keyframe_direct(reports, + ptr, + prop, + fcu, + &anim_eval_context, + BEZT_KEYTYPE_BREAKDOWN, + nullptr, + INSERTKEY_FAST); last_valid_time = cfra; } else if (G.debug & G_DEBUG) { @@ -535,14 +534,14 @@ static void gpencil_stroke_path_animation(bContext *C, cfra = float(gtd->start_frame); AnimationEvalContext anim_eval_context_start = BKE_animsys_eval_context_construct(depsgraph, cfra); - blender::animrig::insert_keyframe_direct(reports, - ptr, - prop, - fcu, - &anim_eval_context_start, - BEZT_KEYTYPE_KEYFRAME, - nullptr, - INSERTKEY_FAST); + insert_keyframe_direct(reports, + ptr, + prop, + fcu, + &anim_eval_context_start, + BEZT_KEYTYPE_KEYFRAME, + nullptr, + INSERTKEY_FAST); cu->ctime = cu->pathlen; if (gtd->realtime) { @@ -553,14 +552,14 @@ static void gpencil_stroke_path_animation(bContext *C, } AnimationEvalContext anim_eval_context_end = BKE_animsys_eval_context_construct(depsgraph, cfra); - blender::animrig::insert_keyframe_direct(reports, - ptr, - prop, - fcu, - &anim_eval_context_end, - BEZT_KEYTYPE_KEYFRAME, - nullptr, - INSERTKEY_FAST); + insert_keyframe_direct(reports, + ptr, + prop, + fcu, + &anim_eval_context_end, + BEZT_KEYTYPE_KEYFRAME, + nullptr, + INSERTKEY_FAST); } else { /* Use actual recorded timing! */ diff --git a/source/blender/editors/gpencil_legacy/gpencil_data.cc b/source/blender/editors/gpencil_legacy/gpencil_data.cc index 056b864dc7f..3159c3c0f0e 100644 --- a/source/blender/editors/gpencil_legacy/gpencil_data.cc +++ b/source/blender/editors/gpencil_legacy/gpencil_data.cc @@ -21,7 +21,7 @@ #include "BLI_math_geom.h" #include "BLI_math_matrix.h" #include "BLI_math_vector.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_utildefines.h" #include "BLT_translation.h" diff --git a/source/blender/editors/gpencil_legacy/gpencil_edit.cc b/source/blender/editors/gpencil_legacy/gpencil_edit.cc index 0441f998901..545a1e43041 100644 --- a/source/blender/editors/gpencil_legacy/gpencil_edit.cc +++ b/source/blender/editors/gpencil_legacy/gpencil_edit.cc @@ -69,6 +69,7 @@ #include "ED_armature.hh" #include "ED_gpencil_legacy.hh" +#include "ED_keyframing.hh" #include "ED_object.hh" #include "ED_outliner.hh" #include "ED_screen.hh" @@ -77,8 +78,6 @@ #include "ED_transform_snap_object_context.hh" #include "ED_view3d.hh" -#include "ANIM_keyframing.hh" - #include "DEG_depsgraph.hh" #include "DEG_depsgraph_build.hh" #include "DEG_depsgraph_query.hh" @@ -1762,7 +1761,7 @@ static int gpencil_strokes_paste_exec(bContext *C, wmOperator *op) } bGPDframe *gpf; - if (blender::animrig::is_autokey_on(scene) || (gpl->actframe == nullptr)) { + if (IS_AUTOKEY_ON(scene) || (gpl->actframe == nullptr)) { gpf = BKE_gpencil_layer_frame_get(gpl, scene->r.cfra, GP_GETFRAME_ADD_NEW); } else { diff --git a/source/blender/editors/gpencil_legacy/gpencil_fill.cc b/source/blender/editors/gpencil_legacy/gpencil_fill.cc index 2a29cf7dec1..0c86c0864e3 100644 --- a/source/blender/editors/gpencil_legacy/gpencil_fill.cc +++ b/source/blender/editors/gpencil_legacy/gpencil_fill.cc @@ -40,12 +40,11 @@ #include "BKE_screen.hh" #include "ED_gpencil_legacy.hh" +#include "ED_keyframing.hh" #include "ED_screen.hh" #include "ED_space_api.hh" #include "ED_view3d.hh" -#include "ANIM_keyframing.hh" - #include "RNA_access.hh" #include "RNA_define.hh" @@ -1125,7 +1124,7 @@ static void gpencil_draw_datablock(tGPDfill *tgpf, const float ink[4]) if (gpl == tgpf->gpl) { if ((gpl->actframe == nullptr) || (gpl->actframe->framenum != tgpf->active_cfra)) { short add_frame_mode; - if (blender::animrig::is_autokey_on(tgpf->scene)) { + if (IS_AUTOKEY_ON(tgpf->scene)) { if (ts->gpencil_flags & GP_TOOL_FLAG_RETAIN_LAST) { add_frame_mode = GP_GETFRAME_ADD_COPY; } @@ -2161,10 +2160,10 @@ static void gpencil_stroke_from_buffer(tGPDfill *tgpf) tgpf->done = true; /* Get frame or create a new one. */ - tgpf->gpf = BKE_gpencil_layer_frame_get( - tgpf->gpl, - tgpf->active_cfra, - blender::animrig::is_autokey_on(tgpf->scene) ? GP_GETFRAME_ADD_NEW : GP_GETFRAME_USE_PREV); + tgpf->gpf = BKE_gpencil_layer_frame_get(tgpf->gpl, + tgpf->active_cfra, + IS_AUTOKEY_ON(tgpf->scene) ? GP_GETFRAME_ADD_NEW : + GP_GETFRAME_USE_PREV); /* Set frame as selected. */ tgpf->gpf->flag |= GP_FRAME_SELECT; @@ -2836,9 +2835,7 @@ static int gpencil_fill_modal(bContext *C, wmOperator *op, const wmEvent *event) estate = OPERATOR_CANCELLED; break; case LEFTMOUSE: - if (!blender::animrig::is_autokey_on(tgpf->scene) && (!is_multiedit) && - (tgpf->gpl->actframe == nullptr)) - { + if (!IS_AUTOKEY_ON(tgpf->scene) && (!is_multiedit) && (tgpf->gpl->actframe == nullptr)) { BKE_report(op->reports, RPT_INFO, "No available frame for creating stroke"); estate = OPERATOR_CANCELLED; break; @@ -2882,8 +2879,7 @@ static int gpencil_fill_modal(bContext *C, wmOperator *op, const wmEvent *event) tgpf->gpf = BKE_gpencil_layer_frame_get( tgpf->gpl, tgpf->active_cfra, - blender::animrig::is_autokey_on(tgpf->scene) ? GP_GETFRAME_ADD_NEW : - GP_GETFRAME_USE_PREV); + IS_AUTOKEY_ON(tgpf->scene) ? GP_GETFRAME_ADD_NEW : GP_GETFRAME_USE_PREV); tgpf->gpf->flag |= GP_FRAME_SELECT; BLI_ghash_insert( diff --git a/source/blender/editors/gpencil_legacy/gpencil_paint.cc b/source/blender/editors/gpencil_legacy/gpencil_paint.cc index 5bbb9fc8068..673769d7fe9 100644 --- a/source/blender/editors/gpencil_legacy/gpencil_paint.cc +++ b/source/blender/editors/gpencil_legacy/gpencil_paint.cc @@ -55,12 +55,11 @@ #include "ED_clip.hh" #include "ED_gpencil_legacy.hh" +#include "ED_keyframing.hh" #include "ED_object.hh" #include "ED_screen.hh" #include "ED_view3d.hh" -#include "ANIM_keyframing.hh" - #include "GPU_immediate.h" #include "GPU_immediate_util.h" #include "GPU_state.h" @@ -2256,7 +2255,7 @@ static void gpencil_paint_initstroke(tGPsdata *p, continue; } - if (!blender::animrig::is_autokey_on(scene) && (gpl->actframe == nullptr)) { + if (!IS_AUTOKEY_ON(scene) && (gpl->actframe == nullptr)) { continue; } @@ -2268,8 +2267,7 @@ static void gpencil_paint_initstroke(tGPsdata *p, * -> If there are no strokes in that frame, don't add a new empty frame */ if (gpl->actframe && gpl->actframe->strokes.first) { - short frame_mode = blender::animrig::is_autokey_on(scene) ? GP_GETFRAME_ADD_COPY : - GP_GETFRAME_USE_PREV; + short frame_mode = IS_AUTOKEY_ON(scene) ? GP_GETFRAME_ADD_COPY : GP_GETFRAME_USE_PREV; gpl->actframe = BKE_gpencil_layer_frame_get( gpl, scene->r.cfra, eGP_GetFrame_Mode(frame_mode)); has_layer_to_erase = true; @@ -2293,7 +2291,7 @@ static void gpencil_paint_initstroke(tGPsdata *p, /* Drawing Modes - Add a new frame if needed on the active layer */ short add_frame_mode; - if (blender::animrig::is_autokey_on(scene)) { + if (IS_AUTOKEY_ON(scene)) { if (ts->gpencil_flags & GP_TOOL_FLAG_RETAIN_LAST) { add_frame_mode = GP_GETFRAME_ADD_COPY; } @@ -2319,7 +2317,7 @@ static void gpencil_paint_initstroke(tGPsdata *p, if (p->gpf == nullptr) { p->status = GP_STATUS_ERROR; - if (!blender::animrig::is_autokey_on(scene)) { + if (!IS_AUTOKEY_ON(scene)) { BKE_report(p->reports, RPT_INFO, "No available frame for creating stroke"); } diff --git a/source/blender/editors/gpencil_legacy/gpencil_primitive.cc b/source/blender/editors/gpencil_legacy/gpencil_primitive.cc index 74571074ef2..4ca7f30fda2 100644 --- a/source/blender/editors/gpencil_legacy/gpencil_primitive.cc +++ b/source/blender/editors/gpencil_legacy/gpencil_primitive.cc @@ -58,13 +58,12 @@ #include "RNA_enum_types.hh" #include "ED_gpencil_legacy.hh" +#include "ED_keyframing.hh" #include "ED_object.hh" #include "ED_screen.hh" #include "ED_space_api.hh" #include "ED_view3d.hh" -#include "ANIM_keyframing.hh" - #include "DEG_depsgraph.hh" #include "DEG_depsgraph_query.hh" @@ -1265,7 +1264,7 @@ static int gpencil_primitive_invoke(bContext *C, wmOperator *op, const wmEvent * bGPdata *gpd = CTX_data_gpencil_data(C); tGPDprimitive *tgpi = nullptr; - if (!blender::animrig::is_autokey_on(scene)) { + if (!IS_AUTOKEY_ON(scene)) { bGPDlayer *gpl = BKE_gpencil_layer_active_get(gpd); if ((gpl == nullptr) || (gpl->actframe == nullptr)) { BKE_report(op->reports, RPT_INFO, "No available frame for creating stroke"); @@ -1327,7 +1326,7 @@ static void gpencil_primitive_interaction_end(bContext *C, /* insert keyframes as required... */ short add_frame_mode; - if (blender::animrig::is_autokey_on(tgpi->scene)) { + if (IS_AUTOKEY_ON(tgpi->scene)) { if (ts->gpencil_flags & GP_TOOL_FLAG_RETAIN_LAST) { add_frame_mode = GP_GETFRAME_ADD_COPY; } diff --git a/source/blender/editors/gpencil_legacy/gpencil_sculpt_paint.cc b/source/blender/editors/gpencil_legacy/gpencil_sculpt_paint.cc index 51968942bc2..f88ff0930ef 100644 --- a/source/blender/editors/gpencil_legacy/gpencil_sculpt_paint.cc +++ b/source/blender/editors/gpencil_legacy/gpencil_sculpt_paint.cc @@ -63,11 +63,10 @@ #include "UI_view2d.hh" #include "ED_gpencil_legacy.hh" +#include "ED_keyframing.hh" #include "ED_screen.hh" #include "ED_view3d.hh" -#include "ANIM_keyframing.hh" - #include "DEG_depsgraph.hh" #include "DEG_depsgraph_query.hh" @@ -1027,9 +1026,7 @@ static void gpencil_brush_clone_add(bContext *C, tGP_BrushEditData *gso) gpl = CTX_data_active_gpencil_layer(C); } bGPDframe *gpf = BKE_gpencil_layer_frame_get( - gpl, - scene->r.cfra, - blender::animrig::is_autokey_on(scene) ? GP_GETFRAME_ADD_NEW : GP_GETFRAME_USE_PREV); + gpl, scene->r.cfra, IS_AUTOKEY_ON(scene) ? GP_GETFRAME_ADD_NEW : GP_GETFRAME_USE_PREV); if (gpf == nullptr) { continue; } @@ -1377,7 +1374,7 @@ static void gpencil_sculpt_brush_init_stroke(bContext *C, tGP_BrushEditData *gso /* go through each layer, and ensure that we've got a valid frame to use */ LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { - if (!blender::animrig::is_autokey_on(scene) && (gpl->actframe == nullptr)) { + if (!IS_AUTOKEY_ON(scene) && (gpl->actframe == nullptr)) { continue; } @@ -1390,7 +1387,7 @@ static void gpencil_sculpt_brush_init_stroke(bContext *C, tGP_BrushEditData *gso * - This is useful when animating as it saves that "uh-oh" moment when you realize you've * spent too much time editing the wrong frame. */ - if (blender::animrig::is_autokey_on(scene) && (gpf->framenum != cfra)) { + if (IS_AUTOKEY_ON(scene) && (gpf->framenum != cfra)) { BKE_gpencil_frame_addcopy(gpl, cfra); BKE_gpencil_tag_full_update(gpd, gpl, nullptr, nullptr); /* Need tag to recalculate evaluated data to avoid crashes. */ @@ -1639,6 +1636,9 @@ static bool gpencil_sculpt_brush_do_frame(bContext *C, GP_SCULPT_SETT_FLAG_AUTOMASK_MATERIAL_STROKE | GP_SCULPT_SETT_FLAG_AUTOMASK_LAYER_ACTIVE | GP_SCULPT_SETT_FLAG_AUTOMASK_MATERIAL_ACTIVE)) != 0; + /* Calc bound box matrix. */ + float bound_mat[4][4]; + BKE_gpencil_layer_transform_matrix_get(gso->depsgraph, gso->object, gpl, bound_mat); LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { /* skip strokes that are invalid for current view */ @@ -1659,7 +1659,7 @@ static bool gpencil_sculpt_brush_do_frame(bContext *C, /* Check if the stroke collide with brush. */ if ((gps->totpoints > 1) && - !ED_gpencil_stroke_check_collision(gsc, gps, gso->mval, radius, diff_mat)) + !ED_gpencil_stroke_check_collision(gsc, gps, gso->mval, radius, bound_mat)) { continue; } @@ -1697,7 +1697,7 @@ static bool gpencil_sculpt_brush_do_frame(bContext *C, */ gpencil_brush_grab_stroke_init(gso, gps_active); changed |= gpencil_sculpt_brush_do_stroke( - gso, gps, diff_mat, gpencil_brush_grab_store_points); + gso, gps, bound_mat, gpencil_brush_grab_store_points); } else { /* Apply effect to the stored points */ diff --git a/source/blender/editors/gpencil_legacy/gpencil_trace_ops.cc b/source/blender/editors/gpencil_legacy/gpencil_trace_ops.cc index ef54db5a3dc..0ff331b9eff 100644 --- a/source/blender/editors/gpencil_legacy/gpencil_trace_ops.cc +++ b/source/blender/editors/gpencil_legacy/gpencil_trace_ops.cc @@ -200,13 +200,13 @@ static void trace_initialize_job_data(TraceJob *trace_job) } } -static void trace_start_job(void *customdata, wmJobWorkerStatus *worker_status) +static void trace_start_job(void *customdata, bool *stop, bool *do_update, float *progress) { TraceJob *trace_job = static_cast(customdata); - trace_job->stop = &worker_status->stop; - trace_job->do_update = &worker_status->do_update; - trace_job->progress = &worker_status->progress; + trace_job->stop = stop; + trace_job->do_update = do_update; + trace_job->progress = progress; trace_job->was_canceled = false; const int init_frame = max_ii((trace_job->use_current_frame) ? trace_job->frame_target : 0, 0); @@ -241,7 +241,7 @@ static void trace_start_job(void *customdata, wmJobWorkerStatus *worker_status) } *(trace_job->progress) = float(i) / float(iuser->frames); - worker_status->do_update = true; + *do_update = true; iuser->framenr = i + 1; @@ -258,8 +258,8 @@ static void trace_start_job(void *customdata, wmJobWorkerStatus *worker_status) } trace_job->success = !trace_job->was_canceled; - worker_status->do_update = true; - worker_status->stop = false; + *do_update = true; + *stop = false; } static void trace_end_job(void *customdata) @@ -341,8 +341,9 @@ static int gpencil_trace_image_exec(bContext *C, wmOperator *op) ED_object_base_activate(job->C, job->base_active); if ((job->image->source == IMA_SRC_FILE) || (job->frame_num > 0)) { - wmJobWorkerStatus worker_status = {}; - trace_start_job(job, &worker_status); + bool stop = false, do_update = true; + float progress; + trace_start_job(job, &stop, &do_update, &progress); trace_end_job(job); trace_free_job(job); } diff --git a/source/blender/editors/grease_pencil/CMakeLists.txt b/source/blender/editors/grease_pencil/CMakeLists.txt index 0b21eafd02b..8c7aefd2414 100644 --- a/source/blender/editors/grease_pencil/CMakeLists.txt +++ b/source/blender/editors/grease_pencil/CMakeLists.txt @@ -6,13 +6,11 @@ set(INC ../include ../../blenkernel ../../blentranslation - ../../bmesh ../../depsgraph ../../gpu ../../imbuf ../../makesrna ../../windowmanager - ../../../../extern/curve_fit_nd # RNA_prototypes.h ${CMAKE_BINARY_DIR}/source/blender/makesrna ) @@ -24,11 +22,9 @@ set(SRC intern/grease_pencil_add.cc intern/grease_pencil_edit.cc intern/grease_pencil_frames.cc - intern/grease_pencil_geom.cc intern/grease_pencil_layers.cc intern/grease_pencil_ops.cc intern/grease_pencil_select.cc - intern/grease_pencil_utils.cc ) set(LIB @@ -36,7 +32,6 @@ set(LIB PRIVATE bf::blenlib PRIVATE bf::dna PRIVATE bf::intern::guardedalloc - extern_curve_fit_nd ) blender_add_lib(bf_editor_grease_pencil "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") diff --git a/source/blender/editors/grease_pencil/intern/grease_pencil_add.cc b/source/blender/editors/grease_pencil/intern/grease_pencil_add.cc index 526d75505d5..446d1ada28c 100644 --- a/source/blender/editors/grease_pencil/intern/grease_pencil_add.cc +++ b/source/blender/editors/grease_pencil/intern/grease_pencil_add.cc @@ -7,7 +7,6 @@ */ #include -#include #include "BKE_curves.hh" #include "BKE_grease_pencil.hh" @@ -190,24 +189,21 @@ static std::array stroke_positions({ }); static constexpr std::array stroke_radii({ - 0.00143, 0.00259, 0.00334, 0.00420, 0.00503, 0.00581, 0.00656, 0.00728, 0.00791, 0.00851, - 0.00907, 0.00960, 0.01005, 0.01050, 0.01087, 0.01121, 0.01151, 0.01181, 0.01208, 0.01234, - 0.01256, 0.01279, 0.01297, 0.01316, 0.01331, 0.01350, 0.01365, 0.01380, 0.01391, 0.01399, - 0.01410, 0.01414, 0.01418, 0.01421, 0.01421, 0.01421, 0.01425, 0.01425, 0.01429, 0.01433, - 0.01440, 0.01448, 0.01455, 0.01462, 0.01474, 0.01485, 0.01496, 0.01511, 0.01526, 0.01541, - 0.01556, 0.01575, 0.01594, 0.01616, 0.01639, 0.01661, 0.01688, 0.01714, 0.01740, 0.01766, - 0.01796, 0.01826, 0.01856, 0.01886, 0.01920, 0.01950, 0.01980, 0.02014, 0.02044, 0.02074, - 0.02107, 0.02138, 0.02171, 0.02205, 0.02239, 0.02273, 0.02306, 0.02344, 0.02381, 0.02415, - 0.02452, 0.02490, 0.02531, 0.02569, 0.02610, 0.02651, 0.02693, 0.02734, 0.02775, 0.02816, - 0.02854, 0.02895, 0.02933, 0.02974, 0.03015, 0.03056, 0.03105, 0.03161, 0.03225, 0.03296, - 0.03364, 0.03431, 0.03495, 0.03551, 0.03608, 0.03652, 0.03694, 0.03731, 0.03765, 0.03791, - 0.03818, 0.03840, 0.03859, 0.03874, 0.03885, 0.03889, 0.03889, 0.03881, 0.03870, 0.03859, - 0.03848, 0.03836, 0.03829, 0.03821, 0.03814, 0.03810, 0.03810, 0.03810, 0.03810, 0.03814, - 0.03814, 0.03818, 0.03814, 0.03814, 0.03810, 0.03806, 0.03799, 0.03784, 0.03769, 0.03743, - 0.03713, 0.03675, 0.03630, 0.03581, 0.03521, 0.03461, 0.03405, 0.03356, 0.03308, 0.03263, - 0.03218, 0.03165, 0.03105, 0.03038, 0.02963, 0.02884, 0.02801, 0.02715, 0.02625, 0.02535, - 0.02441, 0.02344, 0.02246, 0.02149, 0.02048, 0.01935, 0.01811, 0.01673, 0.01526, 0.01369, - 0.01208, 0.01050, 0.00885, 0.00758, 0.00581, + 0.038f, 0.069f, 0.089f, 0.112f, 0.134f, 0.155f, 0.175f, 0.194f, 0.211f, 0.227f, 0.242f, 0.256f, + 0.268f, 0.28f, 0.29f, 0.299f, 0.307f, 0.315f, 0.322f, 0.329f, 0.335f, 0.341f, 0.346f, 0.351f, + 0.355f, 0.36f, 0.364f, 0.368f, 0.371f, 0.373f, 0.376f, 0.377f, 0.378f, 0.379f, 0.379f, 0.379f, + 0.38f, 0.38f, 0.381f, 0.382f, 0.384f, 0.386f, 0.388f, 0.39f, 0.393f, 0.396f, 0.399f, 0.403f, + 0.407f, 0.411f, 0.415f, 0.42f, 0.425f, 0.431f, 0.437f, 0.443f, 0.45f, 0.457f, 0.464f, 0.471f, + 0.479f, 0.487f, 0.495f, 0.503f, 0.512f, 0.52f, 0.528f, 0.537f, 0.545f, 0.553f, 0.562f, 0.57f, + 0.579f, 0.588f, 0.597f, 0.606f, 0.615f, 0.625f, 0.635f, 0.644f, 0.654f, 0.664f, 0.675f, 0.685f, + 0.696f, 0.707f, 0.718f, 0.729f, 0.74f, 0.751f, 0.761f, 0.772f, 0.782f, 0.793f, 0.804f, 0.815f, + 0.828f, 0.843f, 0.86f, 0.879f, 0.897f, 0.915f, 0.932f, 0.947f, 0.962f, 0.974f, 0.985f, 0.995f, + 1.004f, 1.011f, 1.018f, 1.024f, 1.029f, 1.033f, 1.036f, 1.037f, 1.037f, 1.035f, 1.032f, 1.029f, + 1.026f, 1.023f, 1.021f, 1.019f, 1.017f, 1.016f, 1.016f, 1.016f, 1.016f, 1.017f, 1.017f, 1.018f, + 1.017f, 1.017f, 1.016f, 1.015f, 1.013f, 1.009f, 1.005f, 0.998f, 0.99f, 0.98f, 0.968f, 0.955f, + 0.939f, 0.923f, 0.908f, 0.895f, 0.882f, 0.87f, 0.858f, 0.844f, 0.828f, 0.81f, 0.79f, 0.769f, + 0.747f, 0.724f, 0.7f, 0.676f, 0.651f, 0.625f, 0.599f, 0.573f, 0.546f, 0.516f, 0.483f, 0.446f, + 0.407f, 0.365f, 0.322f, 0.28f, 0.236f, 0.202f, 0.155f, }); static constexpr std::array stroke_opacities({ @@ -640,76 +636,65 @@ static constexpr std::array monkey_fill_opacities({ }); static constexpr std::array monkey_fill_radii({ - 0.01001, 0.01163, 0.01425, 0.01624, 0.01766, 0.01860, 0.01916, 0.01954, 0.01976, 0.01991, - 0.02002, 0.02006, 0.02010, 0.02010, 0.02010, 0.02006, 0.02002, 0.02002, 0.01999, 0.01999, - 0.01995, 0.01991, 0.01991, 0.01995, 0.02006, 0.02025, 0.02033, 0.02036, 0.02036, 0.02048, - 0.02059, 0.02059, 0.02059, 0.02059, 0.02066, 0.02074, 0.02078, 0.02078, 0.02081, 0.02085, - 0.02089, 0.02089, 0.02089, 0.02089, 0.02093, 0.02093, 0.02089, 0.02085, 0.02078, 0.02070, - 0.02066, 0.02063, 0.02059, 0.02059, 0.02059, 0.02063, 0.02070, 0.02085, 0.02111, 0.02145, - 0.02183, 0.02216, 0.02239, 0.02258, 0.02269, 0.02276, 0.02284, 0.02295, 0.02310, 0.02321, - 0.02336, 0.02348, 0.02363, 0.02389, 0.02423, 0.02452, 0.02471, 0.02490, 0.02501, 0.02516, - 0.02528, 0.02539, 0.02543, 0.02550, 0.02550, 0.02554, 0.02558, 0.02561, 0.02569, 0.02576, - 0.02588, 0.02599, 0.02614, 0.02625, 0.02640, 0.02651, 0.02659, 0.02666, 0.02670, 0.02678, - 0.02681, 0.02681, 0.02685, 0.02689, 0.02693, 0.02693, 0.02693, 0.02696, 0.02696, 0.02696, - 0.02696, 0.02696, 0.02693, 0.02689, 0.02689, 0.02693, 0.02689, 0.02681, 0.02674, 0.02693, - 0.02745, 0.02824, 0.02903, 0.02966, 0.03008, 0.03034, 0.03045, 0.03053, 0.03053, 0.03045, - 0.03038, 0.03023, 0.03000, 0.02978, 0.02955, 0.02936, 0.02929, 0.02921, 0.02918, 0.02914, - 0.02914, 0.02910, 0.02906, 0.02903, 0.02903, 0.02899, 0.02895, 0.02891, 0.02888, 0.02880, - 0.02876, 0.02873, 0.02869, 0.02865, 0.02854, 0.02835, 0.02816, 0.02794, 0.02771, 0.02749, - 0.02730, 0.02711, 0.02693, 0.02674, 0.02659, 0.02640, 0.02625, 0.02606, 0.02591, 0.02573, - 0.02554, 0.02535, 0.02516, 0.02498, 0.02486, 0.02479, 0.02475, 0.02471, 0.02468, 0.02468, - 0.02468, 0.02464, 0.02460, 0.02449, 0.02423, 0.02389, 0.02355, 0.02325, 0.02303, 0.02288, - 0.02280, 0.02276, 0.02273, 0.02269, 0.02261, 0.02242, 0.02216, 0.02186, 0.02153, 0.02115, - 0.02081, 0.02051, 0.02029, 0.02010, 0.01995, 0.01984, 0.01969, 0.01954, 0.01935, 0.01912, - 0.01894, 0.01871, 0.01856, 0.01849, 0.01841, 0.01841, 0.01841, 0.01845, 0.01845, 0.01845, - 0.01849, 0.01849, 0.01849, 0.01845, 0.01841, 0.01841, 0.01838, 0.01838, 0.01834, 0.01834, - 0.01834, 0.01834, 0.01834, 0.01838, 0.01838, 0.01841, 0.01845, 0.01853, 0.01864, 0.01879, - 0.01894, 0.01912, 0.01931, 0.01950, 0.01969, 0.01987, 0.02006, 0.02021, 0.02033, 0.02048, - 0.02055, 0.02063, 0.02066, 0.02070, 0.02074, 0.02078, 0.02078, 0.02081, 0.02085, 0.02085, - 0.02089, 0.02093, 0.02093, 0.02093, 0.02096, 0.02096, 0.02096, 0.02093, 0.02093, 0.02081, - 0.00300, 0.00660, 0.01384, 0.01806, 0.02094, 0.02307, 0.02471, 0.02598, 0.02701, 0.02782, - 0.02845, 0.02896, 0.02934, 0.02963, 0.02983, 0.02995, 0.02999, 0.02997, 0.02989, 0.02974, - 0.02950, 0.02917, 0.02873, 0.02815, 0.02743, 0.02652, 0.02538, 0.02394, 0.02208, 0.01962, - 0.01619, 0.01080, 0.00300, 0.00300, 0.00660, 0.01806, 0.02307, 0.02598, 0.02782, 0.02896, - 0.02963, 0.02995, 0.02997, 0.02974, 0.02917, 0.02815, 0.02652, 0.02394, 0.01962, 0.01080, - 0.00300, 0.00300, 0.00660, 0.01080, 0.01384, 0.01619, 0.01806, 0.01962, 0.02094, 0.02208, - 0.02307, 0.02394, 0.02471, 0.02538, 0.02598, 0.02652, 0.02701, 0.02743, 0.02782, 0.02815, - 0.02845, 0.02873, 0.02896, 0.02917, 0.02934, 0.02950, 0.02963, 0.02974, 0.02983, 0.02989, - 0.02995, 0.02997, 0.02999, 0.02999, 0.02997, 0.02995, 0.02989, 0.02983, 0.02974, 0.02963, - 0.02950, 0.02934, 0.02917, 0.02896, 0.02873, 0.02845, 0.02815, 0.02782, 0.02743, 0.02701, - 0.02652, 0.02598, 0.02538, 0.02471, 0.02394, 0.02307, 0.02208, 0.02094, 0.01962, 0.01806, - 0.01619, 0.01384, 0.01080, 0.00660, 0.00300, 0.00300, 0.00660, 0.01384, 0.01806, 0.02094, - 0.02307, 0.02471, 0.02598, 0.02701, 0.02782, 0.02845, 0.02896, 0.02934, 0.02963, 0.02983, - 0.02995, 0.02999, 0.02997, 0.02989, 0.02974, 0.02950, 0.02917, 0.02873, 0.02815, 0.02743, - 0.02652, 0.02538, 0.02394, 0.02208, 0.01962, 0.01619, 0.01080, 0.00300, 0.00300, 0.00660, - 0.01080, 0.01384, 0.01619, 0.01806, 0.01962, 0.02094, 0.02208, 0.02307, 0.02394, 0.02471, - 0.02538, 0.02598, 0.02652, 0.02701, 0.02743, 0.02782, 0.02815, 0.02845, 0.02873, 0.02896, - 0.02917, 0.02934, 0.02950, 0.02963, 0.02974, 0.02983, 0.02989, 0.02995, 0.02997, 0.02999, - 0.02999, 0.02997, 0.02995, 0.02989, 0.02983, 0.02974, 0.02963, 0.02950, 0.02934, 0.02917, - 0.02896, 0.02873, 0.02845, 0.02815, 0.02782, 0.02743, 0.02701, 0.02652, 0.02598, 0.02538, - 0.02471, 0.02394, 0.02307, 0.02208, 0.02094, 0.01962, 0.01806, 0.01619, 0.01384, 0.01080, - 0.00660, 0.00300, 0.00300, 0.00660, 0.01384, 0.01806, 0.02094, 0.02307, 0.02471, 0.02598, - 0.02701, 0.02782, 0.02845, 0.02896, 0.02934, 0.02963, 0.02983, 0.02995, 0.02999, 0.02997, - 0.02989, 0.02974, 0.02950, 0.02917, 0.02873, 0.02815, 0.02743, 0.02652, 0.02538, 0.02394, - 0.02208, 0.01962, 0.01619, 0.01080, 0.00300, 0.00200, 0.00440, 0.01204, 0.01538, 0.01732, - 0.01854, 0.01931, 0.01975, 0.01997, 0.01998, 0.01982, 0.01945, 0.01877, 0.01768, 0.01596, - 0.01308, 0.00720, 0.00200, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, - 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, - 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, - 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, - 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, - 0.03000, 0.03000, 0.00300, 0.00660, 0.01384, 0.01806, 0.02094, 0.02307, 0.02471, 0.02598, - 0.02701, 0.02782, 0.02845, 0.02896, 0.02934, 0.02963, 0.02983, 0.02995, 0.02999, 0.02997, - 0.02989, 0.02974, 0.02950, 0.02917, 0.02873, 0.02815, 0.02743, 0.02652, 0.02538, 0.02394, - 0.02208, 0.01962, 0.01619, 0.01080, 0.00300, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, - 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, - 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, - 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, - 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, - 0.03000, 0.03000, 0.03000, 0.03000, 0.00200, 0.00440, 0.01204, 0.01538, 0.01732, 0.01854, - 0.01931, 0.01975, 0.01997, 0.01998, 0.01982, 0.01945, 0.01877, 0.01768, 0.01596, 0.01308, - 0.00720, 0.00200, 0.00200, 0.00440, 0.01204, 0.01538, 0.01732, 0.01854, 0.01931, 0.01975, - 0.01997, 0.01998, 0.01982, 0.01945, 0.01877, 0.01768, 0.01596, 0.01308, 0.00720, 0.00200, + 0.2670, 0.3100, 0.3800, 0.4330, 0.4710, 0.4960, 0.5110, 0.5210, 0.5270, 0.5310, 0.5340, 0.5350, + 0.5360, 0.5360, 0.5360, 0.5350, 0.5340, 0.5340, 0.5330, 0.5330, 0.5320, 0.5310, 0.5310, 0.5320, + 0.5350, 0.5400, 0.5420, 0.5430, 0.5430, 0.5460, 0.5490, 0.5490, 0.5490, 0.5490, 0.5510, 0.5530, + 0.5540, 0.5540, 0.5550, 0.5560, 0.5570, 0.5570, 0.5570, 0.5570, 0.5580, 0.5580, 0.5570, 0.5560, + 0.5540, 0.5520, 0.5510, 0.5500, 0.5490, 0.5490, 0.5490, 0.5500, 0.5520, 0.5560, 0.5630, 0.5720, + 0.5820, 0.5910, 0.5970, 0.6020, 0.6050, 0.6070, 0.6090, 0.6120, 0.6160, 0.6190, 0.6230, 0.6260, + 0.6300, 0.6370, 0.6460, 0.6540, 0.6590, 0.6640, 0.6670, 0.6710, 0.6740, 0.6770, 0.6780, 0.6800, + 0.6800, 0.6810, 0.6820, 0.6830, 0.6850, 0.6870, 0.6900, 0.6930, 0.6970, 0.7000, 0.7040, 0.7070, + 0.7090, 0.7110, 0.7120, 0.7140, 0.7150, 0.7150, 0.7160, 0.7170, 0.7180, 0.7180, 0.7180, 0.7190, + 0.7190, 0.7190, 0.7190, 0.7190, 0.7180, 0.7170, 0.7170, 0.7180, 0.7170, 0.7150, 0.7130, 0.7180, + 0.7320, 0.7530, 0.7740, 0.7910, 0.8020, 0.8090, 0.8120, 0.8140, 0.8140, 0.8120, 0.8100, 0.8060, + 0.8000, 0.7940, 0.7880, 0.7830, 0.7810, 0.7790, 0.7780, 0.7770, 0.7770, 0.7760, 0.7750, 0.7740, + 0.7740, 0.7730, 0.7720, 0.7710, 0.7700, 0.7680, 0.7670, 0.7660, 0.7650, 0.7640, 0.7610, 0.7560, + 0.7510, 0.7450, 0.7390, 0.7330, 0.7280, 0.7230, 0.7180, 0.7130, 0.7090, 0.7040, 0.7000, 0.6950, + 0.6910, 0.6860, 0.6810, 0.6760, 0.6710, 0.6660, 0.6630, 0.6610, 0.6600, 0.6590, 0.6580, 0.6580, + 0.6580, 0.6570, 0.6560, 0.6530, 0.6460, 0.6370, 0.6280, 0.6200, 0.6140, 0.6100, 0.6080, 0.6070, + 0.6060, 0.6050, 0.6030, 0.5980, 0.5910, 0.5830, 0.5740, 0.5640, 0.5550, 0.5470, 0.5410, 0.5360, + 0.5320, 0.5290, 0.5250, 0.5210, 0.5160, 0.5100, 0.5050, 0.4990, 0.4950, 0.4930, 0.4910, 0.4910, + 0.4910, 0.4920, 0.4920, 0.4920, 0.4930, 0.4930, 0.4930, 0.4920, 0.4910, 0.4910, 0.4900, 0.4900, + 0.4890, 0.4890, 0.4890, 0.4890, 0.4890, 0.4900, 0.4900, 0.4910, 0.4920, 0.4940, 0.4970, 0.5010, + 0.5050, 0.5100, 0.5150, 0.5200, 0.5250, 0.5300, 0.5350, 0.5390, 0.5420, 0.5460, 0.5480, 0.5500, + 0.5510, 0.5520, 0.5530, 0.5540, 0.5540, 0.5550, 0.5560, 0.5560, 0.5570, 0.5580, 0.5580, 0.5580, + 0.5590, 0.5590, 0.5590, 0.5580, 0.5580, 0.5550, 0.1000, 0.2199, 0.4615, 0.6019, 0.6981, 0.7689, + 0.8236, 0.8660, 0.9003, 0.9272, 0.9485, 0.9653, 0.9781, 0.9876, 0.9942, 0.9983, 0.9997, 0.9990, + 0.9963, 0.9912, 0.9834, 0.9724, 0.9576, 0.9385, 0.9143, 0.8841, 0.8461, 0.7979, 0.7359, 0.6541, + 0.5396, 0.3600, 0.1000, 0.1000, 0.2199, 0.6019, 0.7689, 0.8660, 0.9272, 0.9653, 0.9876, 0.9983, + 0.9990, 0.9912, 0.9724, 0.9385, 0.8841, 0.7979, 0.6541, 0.3600, 0.1000, 0.1000, 0.2199, 0.3600, + 0.4615, 0.5396, 0.6019, 0.6541, 0.6981, 0.7359, 0.7689, 0.7979, 0.8236, 0.8461, 0.8660, 0.8841, + 0.9003, 0.9143, 0.9272, 0.9385, 0.9485, 0.9576, 0.9653, 0.9724, 0.9781, 0.9834, 0.9876, 0.9912, + 0.9942, 0.9963, 0.9983, 0.9990, 0.9997, 0.9997, 0.9990, 0.9983, 0.9963, 0.9942, 0.9912, 0.9876, + 0.9834, 0.9781, 0.9724, 0.9653, 0.9576, 0.9485, 0.9385, 0.9272, 0.9143, 0.9003, 0.8841, 0.8660, + 0.8461, 0.8236, 0.7979, 0.7689, 0.7359, 0.6981, 0.6541, 0.6019, 0.5396, 0.4615, 0.3600, 0.2199, + 0.1000, 0.1000, 0.2199, 0.4615, 0.6019, 0.6981, 0.7689, 0.8236, 0.8660, 0.9003, 0.9272, 0.9485, + 0.9653, 0.9781, 0.9876, 0.9942, 0.9983, 0.9997, 0.9990, 0.9963, 0.9912, 0.9834, 0.9724, 0.9576, + 0.9385, 0.9143, 0.8841, 0.8461, 0.7979, 0.7359, 0.6541, 0.5396, 0.3600, 0.1000, 0.1000, 0.2199, + 0.3600, 0.4615, 0.5396, 0.6019, 0.6541, 0.6981, 0.7359, 0.7689, 0.7979, 0.8236, 0.8461, 0.8660, + 0.8841, 0.9003, 0.9143, 0.9272, 0.9385, 0.9485, 0.9576, 0.9653, 0.9724, 0.9781, 0.9834, 0.9876, + 0.9912, 0.9942, 0.9963, 0.9983, 0.9990, 0.9997, 0.9997, 0.9990, 0.9983, 0.9963, 0.9942, 0.9912, + 0.9876, 0.9834, 0.9781, 0.9724, 0.9653, 0.9576, 0.9485, 0.9385, 0.9272, 0.9143, 0.9003, 0.8841, + 0.8660, 0.8461, 0.8236, 0.7979, 0.7689, 0.7359, 0.6981, 0.6541, 0.6019, 0.5396, 0.4615, 0.3600, + 0.2199, 0.1000, 0.1000, 0.2199, 0.4615, 0.6019, 0.6981, 0.7689, 0.8236, 0.8660, 0.9003, 0.9272, + 0.9485, 0.9653, 0.9781, 0.9876, 0.9942, 0.9983, 0.9997, 0.9990, 0.9963, 0.9912, 0.9834, 0.9724, + 0.9576, 0.9385, 0.9143, 0.8841, 0.8461, 0.7979, 0.7359, 0.6541, 0.5396, 0.3600, 0.1000, 0.1000, + 0.2199, 0.6019, 0.7689, 0.8660, 0.9272, 0.9653, 0.9876, 0.9983, 0.9990, 0.9912, 0.9724, 0.9385, + 0.8841, 0.7979, 0.6541, 0.3600, 0.1000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, + 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, + 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, + 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, + 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 0.1000, 0.2199, 0.4615, 0.6019, 0.6981, 0.7689, + 0.8236, 0.8660, 0.9003, 0.9272, 0.9485, 0.9653, 0.9781, 0.9876, 0.9942, 0.9983, 0.9997, 0.9990, + 0.9963, 0.9912, 0.9834, 0.9724, 0.9576, 0.9385, 0.9143, 0.8841, 0.8461, 0.7979, 0.7359, 0.6541, + 0.5396, 0.3600, 0.1000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, + 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, + 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, + 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, + 1.0000, 1.0000, 1.0000, 1.0000, 0.1000, 0.2199, 0.6019, 0.7689, 0.8660, 0.9272, 0.9653, 0.9876, + 0.9983, 0.9990, 0.9912, 0.9724, 0.9385, 0.8841, 0.7979, 0.6541, 0.3600, 0.1000, 0.1000, 0.2199, + 0.6019, 0.7689, 0.8660, 0.9272, 0.9653, 0.9876, 0.9983, 0.9990, 0.9912, 0.9724, 0.9385, 0.8841, + 0.7979, 0.6541, 0.3600, 0.1000, }); static constexpr std::array monkey_fill_offsets({ @@ -1037,58 +1022,49 @@ static constexpr std::array monkey_line_opacities({ }); static constexpr std::array monkey_line_radii({ - 0.00300, 0.00660, 0.01384, 0.01806, 0.02094, 0.02307, 0.02471, 0.02598, 0.02701, 0.02782, - 0.02845, 0.02896, 0.02934, 0.02963, 0.02983, 0.02995, 0.02999, 0.02997, 0.02989, 0.02974, - 0.02950, 0.02917, 0.02873, 0.02815, 0.02743, 0.02652, 0.02538, 0.02394, 0.02208, 0.01962, - 0.01619, 0.01080, 0.00300, 0.00300, 0.00660, 0.01384, 0.01806, 0.02094, 0.02307, 0.02471, - 0.02598, 0.02701, 0.02782, 0.02845, 0.02896, 0.02934, 0.02963, 0.02983, 0.02995, 0.02999, - 0.02997, 0.02989, 0.02974, 0.02950, 0.02917, 0.02873, 0.02815, 0.02743, 0.02652, 0.02538, - 0.02394, 0.02208, 0.01962, 0.01619, 0.01080, 0.00300, 0.00300, 0.00386, 0.00889, 0.01244, - 0.01508, 0.01717, 0.01887, 0.02030, 0.02153, 0.02259, 0.02351, 0.02433, 0.02505, 0.02569, - 0.02627, 0.02677, 0.02722, 0.02763, 0.02799, 0.02832, 0.02859, 0.02885, 0.02906, 0.02926, - 0.02942, 0.02957, 0.02968, 0.02979, 0.02986, 0.02992, 0.02996, 0.02998, 0.03000, 0.02998, - 0.02996, 0.02992, 0.02986, 0.02979, 0.02968, 0.02957, 0.02942, 0.02926, 0.02906, 0.02885, - 0.02859, 0.02832, 0.02799, 0.02763, 0.02722, 0.02677, 0.02627, 0.02569, 0.02505, 0.02433, - 0.02351, 0.02259, 0.02153, 0.02030, 0.01887, 0.01717, 0.01508, 0.01244, 0.00889, 0.00386, - 0.00300, 0.00300, 0.00386, 0.01244, 0.01717, 0.02030, 0.02259, 0.02433, 0.02569, 0.02677, - 0.02763, 0.02832, 0.02885, 0.02926, 0.02957, 0.02979, 0.02992, 0.02998, 0.02998, 0.02992, - 0.02979, 0.02957, 0.02926, 0.02885, 0.02832, 0.02763, 0.02677, 0.02569, 0.02433, 0.02259, - 0.02030, 0.01717, 0.01244, 0.00386, 0.00300, 0.00300, 0.00660, 0.01384, 0.01806, 0.02094, - 0.02307, 0.02471, 0.02598, 0.02701, 0.02782, 0.02845, 0.02896, 0.02934, 0.02963, 0.02983, - 0.02995, 0.02999, 0.02997, 0.02989, 0.02974, 0.02950, 0.02917, 0.02873, 0.02815, 0.02743, - 0.02652, 0.02538, 0.02394, 0.02208, 0.01962, 0.01619, 0.01080, 0.00300, 0.00200, 0.00440, - 0.00923, 0.01204, 0.01396, 0.01538, 0.01647, 0.01732, 0.01801, 0.01854, 0.01897, 0.01931, - 0.01956, 0.01975, 0.01988, 0.01997, 0.01999, 0.01998, 0.01993, 0.01982, 0.01967, 0.01945, - 0.01915, 0.01877, 0.01829, 0.01768, 0.01692, 0.01596, 0.01472, 0.01308, 0.01079, 0.00720, - 0.00200, 0.00200, 0.00258, 0.00829, 0.01145, 0.01354, 0.01506, 0.01622, 0.01713, 0.01784, - 0.01842, 0.01888, 0.01923, 0.01951, 0.01972, 0.01986, 0.01995, 0.01999, 0.01999, 0.01995, - 0.01986, 0.01972, 0.01951, 0.01923, 0.01888, 0.01842, 0.01784, 0.01713, 0.01622, 0.01506, - 0.01354, 0.01145, 0.00829, 0.00258, 0.00200, 0.00300, 0.00660, 0.01384, 0.01806, 0.02094, - 0.02307, 0.02471, 0.02598, 0.02701, 0.02782, 0.02845, 0.02896, 0.02934, 0.02963, 0.02983, - 0.02995, 0.02999, 0.02997, 0.02989, 0.02974, 0.02950, 0.02917, 0.02873, 0.02815, 0.02743, - 0.02652, 0.02538, 0.02394, 0.02208, 0.01962, 0.01619, 0.01080, 0.00300, 0.00300, 0.00660, - 0.01080, 0.01384, 0.01619, 0.01806, 0.01962, 0.02094, 0.02208, 0.02307, 0.02394, 0.02471, - 0.02538, 0.02598, 0.02652, 0.02701, 0.02743, 0.02782, 0.02815, 0.02845, 0.02873, 0.02896, - 0.02917, 0.02934, 0.02950, 0.02963, 0.02974, 0.02983, 0.02989, 0.02995, 0.02997, 0.02999, - 0.02999, 0.02997, 0.02995, 0.02989, 0.02983, 0.02974, 0.02963, 0.02950, 0.02934, 0.02917, - 0.02896, 0.02873, 0.02845, 0.02815, 0.02782, 0.02743, 0.02701, 0.02652, 0.02598, 0.02538, - 0.02471, 0.02394, 0.02307, 0.02208, 0.02094, 0.01962, 0.01806, 0.01619, 0.01384, 0.01080, - 0.00660, 0.00300, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, - 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, - 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, - 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, - 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, 0.03000, - 0.03000, 0.03000, 0.03000, 0.03000, 0.00300, 0.00660, 0.01384, 0.01806, 0.02094, 0.02307, - 0.02471, 0.02598, 0.02701, 0.02782, 0.02845, 0.02896, 0.02934, 0.02963, 0.02983, 0.02995, - 0.02999, 0.02997, 0.02989, 0.02974, 0.02950, 0.02917, 0.02873, 0.02815, 0.02743, 0.02652, - 0.02538, 0.02394, 0.02208, 0.01962, 0.01619, 0.01080, 0.00300, 0.00200, 0.00440, 0.01204, - 0.01538, 0.01732, 0.01854, 0.01931, 0.01975, 0.01997, 0.01998, 0.01982, 0.01945, 0.01877, - 0.01768, 0.01596, 0.01308, 0.00720, 0.00200, 0.00200, 0.00440, 0.01204, 0.01538, 0.01732, - 0.01854, 0.01931, 0.01975, 0.01997, 0.01998, 0.01982, 0.01945, 0.01877, 0.01768, 0.01596, - 0.01308, 0.00720, 0.00200, 0.00300, 0.00660, 0.01384, 0.01806, 0.02094, 0.02307, 0.02471, - 0.02598, 0.02701, 0.02782, 0.02845, 0.02896, 0.02934, 0.02963, 0.02983, 0.02995, 0.02999, - 0.02997, 0.02989, 0.02974, 0.02950, 0.02917, 0.02873, 0.02815, 0.02743, 0.02652, 0.02538, - 0.02394, 0.02208, 0.01962, 0.01619, 0.01080, 0.00300, + 0.1000, 0.2199, 0.4615, 0.6019, 0.6981, 0.7689, 0.8236, 0.8660, 0.9003, 0.9272, 0.9485, 0.9653, + 0.9781, 0.9876, 0.9942, 0.9983, 0.9997, 0.9990, 0.9963, 0.9912, 0.9834, 0.9724, 0.9576, 0.9385, + 0.9143, 0.8841, 0.8461, 0.7979, 0.7359, 0.6541, 0.5396, 0.3600, 0.1000, 0.1000, 0.2199, 0.4615, + 0.6019, 0.6981, 0.7689, 0.8236, 0.8660, 0.9003, 0.9272, 0.9485, 0.9653, 0.9781, 0.9876, 0.9942, + 0.9983, 0.9997, 0.9990, 0.9963, 0.9912, 0.9834, 0.9724, 0.9576, 0.9385, 0.9143, 0.8841, 0.8461, + 0.7979, 0.7359, 0.6541, 0.5396, 0.3600, 0.1000, 0.1000, 0.1288, 0.2962, 0.4147, 0.5028, 0.5723, + 0.6291, 0.6768, 0.7177, 0.7530, 0.7838, 0.8109, 0.8349, 0.8564, 0.8756, 0.8922, 0.9074, 0.9211, + 0.9329, 0.9440, 0.9531, 0.9617, 0.9688, 0.9755, 0.9808, 0.9858, 0.9894, 0.9930, 0.9952, 0.9973, + 0.9987, 0.9993, 1.0000, 0.9993, 0.9987, 0.9973, 0.9952, 0.9930, 0.9894, 0.9858, 0.9808, 0.9755, + 0.9688, 0.9617, 0.9531, 0.9440, 0.9329, 0.9211, 0.9074, 0.8922, 0.8756, 0.8564, 0.8349, 0.8109, + 0.7838, 0.7530, 0.7177, 0.6768, 0.6291, 0.5723, 0.5028, 0.4147, 0.2962, 0.1288, 0.1000, 0.1000, + 0.1288, 0.4147, 0.5723, 0.6768, 0.7530, 0.8109, 0.8564, 0.8922, 0.9211, 0.9440, 0.9617, 0.9755, + 0.9858, 0.9930, 0.9973, 0.9993, 0.9993, 0.9973, 0.9930, 0.9858, 0.9755, 0.9617, 0.9440, 0.9211, + 0.8922, 0.8564, 0.8109, 0.7530, 0.6768, 0.5723, 0.4147, 0.1288, 0.1000, 0.1000, 0.2199, 0.4615, + 0.6019, 0.6981, 0.7689, 0.8236, 0.8660, 0.9003, 0.9272, 0.9485, 0.9653, 0.9781, 0.9876, 0.9942, + 0.9983, 0.9997, 0.9990, 0.9963, 0.9912, 0.9834, 0.9724, 0.9576, 0.9385, 0.9143, 0.8841, 0.8461, + 0.7979, 0.7359, 0.6541, 0.5396, 0.3600, 0.1000, 0.1000, 0.2199, 0.4615, 0.6019, 0.6981, 0.7689, + 0.8236, 0.8660, 0.9003, 0.9272, 0.9485, 0.9653, 0.9781, 0.9876, 0.9942, 0.9983, 0.9997, 0.9990, + 0.9963, 0.9912, 0.9834, 0.9724, 0.9576, 0.9385, 0.9143, 0.8841, 0.8461, 0.7979, 0.7359, 0.6541, + 0.5396, 0.3600, 0.1000, 0.1000, 0.1288, 0.4147, 0.5723, 0.6768, 0.7530, 0.8109, 0.8564, 0.8922, + 0.9211, 0.9440, 0.9617, 0.9755, 0.9858, 0.9930, 0.9973, 0.9993, 0.9993, 0.9973, 0.9930, 0.9858, + 0.9755, 0.9617, 0.9440, 0.9211, 0.8922, 0.8564, 0.8109, 0.7530, 0.6768, 0.5723, 0.4147, 0.1288, + 0.1000, 0.1000, 0.2199, 0.4615, 0.6019, 0.6981, 0.7689, 0.8236, 0.8660, 0.9003, 0.9272, 0.9485, + 0.9653, 0.9781, 0.9876, 0.9942, 0.9983, 0.9997, 0.9990, 0.9963, 0.9912, 0.9834, 0.9724, 0.9576, + 0.9385, 0.9143, 0.8841, 0.8461, 0.7979, 0.7359, 0.6541, 0.5396, 0.3600, 0.1000, 0.1000, 0.2199, + 0.3600, 0.4615, 0.5396, 0.6019, 0.6541, 0.6981, 0.7359, 0.7689, 0.7979, 0.8236, 0.8461, 0.8660, + 0.8841, 0.9003, 0.9143, 0.9272, 0.9385, 0.9485, 0.9576, 0.9653, 0.9724, 0.9781, 0.9834, 0.9876, + 0.9912, 0.9942, 0.9963, 0.9983, 0.9990, 0.9997, 0.9997, 0.9990, 0.9983, 0.9963, 0.9942, 0.9912, + 0.9876, 0.9834, 0.9781, 0.9724, 0.9653, 0.9576, 0.9485, 0.9385, 0.9272, 0.9143, 0.9003, 0.8841, + 0.8660, 0.8461, 0.8236, 0.7979, 0.7689, 0.7359, 0.6981, 0.6541, 0.6019, 0.5396, 0.4615, 0.3600, + 0.2199, 0.1000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, + 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, + 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, + 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, + 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 0.1000, 0.2199, 0.4615, 0.6019, 0.6981, 0.7689, + 0.8236, 0.8660, 0.9003, 0.9272, 0.9485, 0.9653, 0.9781, 0.9876, 0.9942, 0.9983, 0.9997, 0.9990, + 0.9963, 0.9912, 0.9834, 0.9724, 0.9576, 0.9385, 0.9143, 0.8841, 0.8461, 0.7979, 0.7359, 0.6541, + 0.5396, 0.3600, 0.1000, 0.1000, 0.2199, 0.6019, 0.7689, 0.8660, 0.9272, 0.9653, 0.9876, 0.9983, + 0.9990, 0.9912, 0.9724, 0.9385, 0.8841, 0.7979, 0.6541, 0.3600, 0.1000, 0.1000, 0.2199, 0.6019, + 0.7689, 0.8660, 0.9272, 0.9653, 0.9876, 0.9983, 0.9990, 0.9912, 0.9724, 0.9385, 0.8841, 0.7979, + 0.6541, 0.3600, 0.1000, 0.1000, 0.2199, 0.4615, 0.6019, 0.6981, 0.7689, 0.8236, 0.8660, 0.9003, + 0.9272, 0.9485, 0.9653, 0.9781, 0.9876, 0.9942, 0.9983, 0.9997, 0.9990, 0.9963, 0.9912, 0.9834, + 0.9724, 0.9576, 0.9385, 0.9143, 0.8841, 0.8461, 0.7979, 0.7359, 0.6541, 0.5396, 0.3600, 0.1000, }); static constexpr std::array monkey_line_offsets({ @@ -1110,6 +1086,40 @@ static constexpr std::array monkey_line_offsets({ 516, }); +static const std::array monkey_line_radii_factors({ + 60, + 60, + 60, + 60, + 60, + 40, + 40, + 60, + 60, + 60, + 60, + 60, + 40, + 40, + 60, +}); + +static const std::array monkey_fill_radii_factors({ + 75, + 60, + 60, + 60, + 60, + 60, + 60, + 40, + 60, + 60, + 60, + 40, + 40, +}); + static int add_material_from_template(Main &bmain, Object &ob, const ColorTemplate &pct) { int index; @@ -1133,6 +1143,7 @@ static bke::CurvesGeometry create_drawing_data(const Span positions, const Span opacities, const Span offsets, const Span materials, + const Span radii_factor, const float4x4 &matrix) { using namespace blender::bke; @@ -1145,8 +1156,6 @@ static bke::CurvesGeometry create_drawing_data(const Span positions, MutableSpan point_positions = curves.positions_for_write(); point_positions.copy_from(positions); - curves.transform(matrix); - SpanAttributeWriter point_radii = attributes.lookup_or_add_for_write_only_span( "radius", ATTR_DOMAIN_POINT); point_radii.span.copy_from(radii); @@ -1163,6 +1172,15 @@ static bke::CurvesGeometry create_drawing_data(const Span positions, "material_index", ATTR_DOMAIN_CURVE); stroke_materials.span.copy_from(materials); + const OffsetIndices points_by_curve = curves.points_by_curve(); + for (const int curve_i : curves.curves_range()) { + const IndexRange points = points_by_curve[curve_i]; + for (const int point_i : points) { + point_positions[point_i] = math::transform_point(matrix, point_positions[point_i]); + point_radii.span[point_i] *= radii_factor[curve_i]; + } + } + point_radii.finish(); point_opacities.finish(); @@ -1180,7 +1198,7 @@ void create_blank(Main &bmain, Object &object, const int frame_number) int material_index = add_material_from_template(bmain, object, gp_stroke_material_black); object.actcol = material_index + 1; - Layer &new_layer = grease_pencil.add_layer(DATA_("GP_Layer")); + Layer &new_layer = grease_pencil.add_layer(grease_pencil.root_group(), DATA_("GP_Layer")); grease_pencil.set_active_layer(&new_layer); grease_pencil.insert_blank_frame(new_layer, frame_number, 0, BEZT_KEYTYPE_KEYFRAME); } @@ -1199,18 +1217,17 @@ void create_stroke(Main &bmain, Object &object, float4x4 matrix, const int frame add_material_from_template(bmain, object, gp_fill_material_grey); object.actcol = material_index + 1; - Layer &layer_lines = grease_pencil.add_layer(DATA_("Lines")); - Layer &layer_color = grease_pencil.add_layer(DATA_("Color")); + Layer &layer_lines = grease_pencil.add_layer(grease_pencil.root_group(), DATA_("Lines")); + Layer &layer_color = grease_pencil.add_layer(grease_pencil.root_group(), DATA_("Color")); grease_pencil.set_active_layer(&layer_lines); grease_pencil.insert_blank_frame(layer_lines, frame_number, 0, BEZT_KEYTYPE_KEYFRAME); grease_pencil.insert_blank_frame(layer_color, frame_number, 0, BEZT_KEYTYPE_KEYFRAME); - Drawing &drawing_lines = - reinterpret_cast(grease_pencil.drawing(0))->wrap(); - drawing_lines.strokes_for_write() = create_drawing_data( - stroke_positions, stroke_radii, stroke_opacities, {0, 175}, {material_index}, matrix); - drawing_lines.tag_topology_changed(); + GreasePencilDrawing &drawing_lines = *reinterpret_cast( + grease_pencil.drawing(0)); + drawing_lines.geometry.wrap() = create_drawing_data( + stroke_positions, stroke_radii, stroke_opacities, {0, 175}, {material_index}, {75}, matrix); } void create_suzanne(Main &bmain, Object &object, float4x4 matrix, const int frame_number) @@ -1226,7 +1243,7 @@ void create_suzanne(Main &bmain, Object &object, float4x4 matrix, const int fram int color_skin_light = add_material_from_template(bmain, object, gp_monkey_material_skin_light); int color_skin_shadow = add_material_from_template( bmain, object, gp_monkey_material_skin_shadow); - object.actcol = color_black + 1; + object.actcol = color_black; const std::array monkey_line_materials({ color_black, @@ -1261,32 +1278,32 @@ void create_suzanne(Main &bmain, Object &object, float4x4 matrix, const int fram color_skin_shadow, }); - Layer &layer_fills = grease_pencil.add_layer(DATA_("Fills")); - Layer &layer_lines = grease_pencil.add_layer(DATA_("Lines")); + Layer &layer_fills = grease_pencil.add_layer(grease_pencil.root_group(), DATA_("Fills")); + Layer &layer_lines = grease_pencil.add_layer(grease_pencil.root_group(), DATA_("Lines")); grease_pencil.set_active_layer(&layer_lines); grease_pencil.insert_blank_frame(layer_lines, frame_number, 0, BEZT_KEYTYPE_KEYFRAME); grease_pencil.insert_blank_frame(layer_fills, frame_number, 0, BEZT_KEYTYPE_KEYFRAME); - Drawing &drawing_lines = - reinterpret_cast(grease_pencil.drawing(0))->wrap(); - drawing_lines.strokes_for_write() = create_drawing_data(monkey_line_positions, - monkey_line_radii, - monkey_line_opacities, - monkey_line_offsets, - monkey_line_materials, - matrix); - drawing_lines.tag_topology_changed(); + GreasePencilDrawing *drawing_lines = reinterpret_cast( + grease_pencil.drawing(0)); + drawing_lines->geometry.wrap() = create_drawing_data(monkey_line_positions, + monkey_line_radii, + monkey_line_opacities, + monkey_line_offsets, + monkey_line_materials, + monkey_line_radii_factors, + matrix); - Drawing &drawing_fills = - reinterpret_cast(grease_pencil.drawing(1))->wrap(); - drawing_fills.strokes_for_write() = create_drawing_data(monkey_fill_positions, - monkey_fill_radii, - monkey_fill_opacities, - monkey_fill_offsets, - monkey_fill_materials, - matrix); - drawing_fills.tag_topology_changed(); + GreasePencilDrawing *drawing_fills = reinterpret_cast( + grease_pencil.drawing(1)); + drawing_fills->geometry.wrap() = create_drawing_data(monkey_fill_positions, + monkey_fill_radii, + monkey_fill_opacities, + monkey_fill_offsets, + monkey_fill_materials, + monkey_fill_radii_factors, + matrix); } } // namespace blender::ed::greasepencil diff --git a/source/blender/editors/grease_pencil/intern/grease_pencil_edit.cc b/source/blender/editors/grease_pencil/intern/grease_pencil_edit.cc index 00369dfbf47..54db7c8eacd 100644 --- a/source/blender/editors/grease_pencil/intern/grease_pencil_edit.cc +++ b/source/blender/editors/grease_pencil/intern/grease_pencil_edit.cc @@ -237,8 +237,7 @@ void gaussian_blur_1D(const GSpan src, bke::attribute_math::convert_to_static_type(src.type(), [&](auto dummy) { using T = decltype(dummy); /* Reduces unnecessary code generation. */ - if constexpr (std::is_same_v || std::is_same_v || - std::is_same_v) { + if constexpr (std::is_same_v || std::is_same_v) { gaussian_blur_1D(src.typed(), iterations, influence, @@ -306,7 +305,7 @@ static int grease_pencil_stroke_smooth_exec(bContext *C, wmOperator *op) } grease_pencil.foreach_editable_drawing( - scene->r.cfra, [&](const int /*layer_index*/, bke::greasepencil::Drawing &drawing) { + scene->r.cfra, [&](int /*drawing_index*/, bke::greasepencil::Drawing &drawing) { bke::CurvesGeometry &curves = drawing.strokes_for_write(); if (curves.points_num() == 0) { return; @@ -395,52 +394,56 @@ static void GREASE_PENCIL_OT_stroke_smooth(wmOperatorType *ot) /** \name Simplify Stroke Operator * \{ */ -static float dist_to_interpolated( - float3 pos, float3 posA, float3 posB, float val, float valA, float valB) +/** + * An implementation of the Ramer-Douglas-Peucker algorithm. + * + * \param range: The range to simplify. + * \param epsilon: The threshold distance from the coord between two points for when a point + * in-between needs to be kept. + * \param dist_function: A function that computes the distance to a point at an index in the range. + * The IndexRange is a subrange of \a range and the index is an index relative to the subrange. + * \param points_to_delete: Writes true to the indecies for which the points should be removed. + * \returns the total number of points to remove. + */ +int64_t ramer_douglas_peucker_simplify(const IndexRange range, + const float epsilon, + const FunctionRef dist_function, + MutableSpan points_to_delete) { - float dist1 = math::distance_squared(posA, pos); - float dist2 = math::distance_squared(posB, pos); + /* Mark all points to not be removed. */ + points_to_delete.slice(range).fill(false); + int64_t total_points_to_remove = 0; - if (dist1 + dist2 > 0) { - float interpolated_val = interpf(valB, valA, dist1 / (dist1 + dist2)); - return math::distance(interpolated_val, val); - } - return 0.0f; -} + Stack stack; + stack.push(range); + while (!stack.is_empty()) { + const IndexRange sub_range = stack.pop(); -static int64_t stroke_simplify(const IndexRange points, - const bool cyclic, - const float epsilon, - const FunctionRef dist_function, - MutableSpan points_to_delete) -{ - int64_t total_points_to_delete = 0; - const Span curve_selection = points_to_delete.slice(points); - if (!curve_selection.contains(true)) { - return total_points_to_delete; - } + /* Compute the maximum distance and the corresponding distance. */ + float max_dist = -1.0f; + int max_index = -1; + for (const int64_t sub_index : sub_range.index_range().drop_front(1).drop_back(1)) { + const float dist = dist_function(sub_range, sub_index); + if (dist > max_dist) { + max_dist = dist; + max_index = sub_index; + } + } - const bool is_last_segment_selected = (curve_selection.first() && curve_selection.last()); - - const Vector selection_ranges = array_utils::find_all_ranges(curve_selection, true); - threading::parallel_for( - selection_ranges.index_range(), 1024, [&](const IndexRange range_of_ranges) { - for (const IndexRange range : selection_ranges.as_span().slice(range_of_ranges)) { - total_points_to_delete += ramer_douglas_peucker_simplify( - range.shift(points.start()), epsilon, dist_function, points_to_delete); - } - }); - - /* For cyclic curves, simplify the last segment. */ - if (cyclic && points.size() > 2 && is_last_segment_selected) { - const float dist = dist_function(points.last(1), points.first(), points.last()); - if (dist <= epsilon) { - points_to_delete[points.last()] = true; - total_points_to_delete++; + if (max_dist > epsilon) { + /* Found point outside the epsilon-sized strip. Repeat the search on the left & right side. + */ + stack.push(sub_range.slice(IndexRange(max_index + 1))); + stack.push(sub_range.slice(IndexRange(max_index, sub_range.size() - max_index))); + } + else { + /* Points in `sub_range` are inside the epsilon-sized strip. Mark them to be deleted. */ + const IndexRange inside_range = sub_range.drop_front(1).drop_back(1); + total_points_to_remove += inside_range.size(); + points_to_delete.slice(inside_range).fill(true); } } - - return total_points_to_delete; + return total_points_to_remove; } static int grease_pencil_stroke_simplify_exec(bContext *C, wmOperator *op) @@ -454,41 +457,25 @@ static int grease_pencil_stroke_simplify_exec(bContext *C, wmOperator *op) bool changed = false; grease_pencil.foreach_editable_drawing( - scene->r.cfra, [&](const int /*layer_index*/, bke::greasepencil::Drawing &drawing) { + scene->r.cfra, [&](int /*drawing_index*/, bke::greasepencil::Drawing &drawing) { bke::CurvesGeometry &curves = drawing.strokes_for_write(); if (curves.points_num() == 0) { return; } + if (!ed::curves::has_anything_selected(curves)) { return; } const Span positions = curves.positions(); - const VArray radii = drawing.radii(); - /* Distance functions for `ramer_douglas_peucker_simplify`. */ - const auto dist_function_positions = - [positions](int64_t first_index, int64_t last_index, int64_t index) { - const float dist_position = dist_to_line_v3( - positions[index], positions[first_index], positions[last_index]); - return dist_position; - }; - const auto dist_function_positions_and_radii = - [positions, radii](int64_t first_index, int64_t last_index, int64_t index) { - const float dist_position = dist_to_line_v3( - positions[index], positions[first_index], positions[last_index]); - /* We divide the distance by 2000.0f to convert from "pixels" to an actual distance. - * For some reason, grease pencil strokes the thickness of strokes in pixels rather - * than object space distance. */ - const float dist_radii = dist_to_interpolated(positions[index], - positions[first_index], - positions[last_index], - radii[index], - radii[first_index], - radii[last_index]) / - 2000.0f; - return math::max(dist_position, dist_radii); - }; + /* Distance function for `ramer_douglas_peucker_simplify`. */ + auto dist_func = [&](IndexRange range, int64_t index_in_range) { + const Span position_slice = positions.slice(range); + const float dist_position = dist_to_line_v3( + position_slice[index_in_range], position_slice.first(), position_slice.last()); + return dist_position; + }; const VArray cyclic = curves.cyclic(); const OffsetIndices points_by_curve = curves.points_by_curve(); @@ -499,30 +486,42 @@ static int grease_pencil_stroke_simplify_exec(bContext *C, wmOperator *op) selection.materialize(points_to_delete); std::atomic total_points_to_delete = 0; - if (radii.is_single()) { - threading::parallel_for(curves.curves_range(), 128, [&](const IndexRange range) { - for (const int curve_i : range) { - const IndexRange points = points_by_curve[curve_i]; - total_points_to_delete += stroke_simplify(points, - cyclic[curve_i], - epsilon, - dist_function_positions, - points_to_delete.as_mutable_span()); + threading::parallel_for(curves.curves_range(), 128, [&](const IndexRange range) { + for (const int curve_i : range) { + const IndexRange points = points_by_curve[curve_i]; + const Span curve_selection = points_to_delete.as_span().slice(points); + if (!curve_selection.contains(true)) { + continue; } - }); - } - else if (radii.is_span()) { - threading::parallel_for(curves.curves_range(), 128, [&](const IndexRange range) { - for (const int curve_i : range) { - const IndexRange points = points_by_curve[curve_i]; - total_points_to_delete += stroke_simplify(points, - cyclic[curve_i], - epsilon, - dist_function_positions_and_radii, - points_to_delete.as_mutable_span()); + + const bool is_last_segment_selected = (curve_selection.first() && + curve_selection.last()); + + const Vector selection_ranges = array_utils::find_all_ranges( + curve_selection, true); + threading::parallel_for( + selection_ranges.index_range(), 1024, [&](const IndexRange range_of_ranges) { + for (const IndexRange range : selection_ranges.as_span().slice(range_of_ranges)) + { + total_points_to_delete += ramer_douglas_peucker_simplify( + range.shift(points.start()), + epsilon, + dist_func, + points_to_delete.as_mutable_span()); + } + }); + + /* For cyclic curves, simplify the last segment. */ + if (cyclic[curve_i] && curves.points_num() > 2 && is_last_segment_selected) { + const float dist = dist_to_line_v3( + positions[points.last()], positions[points.last(1)], positions[points.first()]); + if (dist <= epsilon) { + points_to_delete[points.last()] = true; + total_points_to_delete++; + } } - }); - } + } + }); if (total_points_to_delete > 0) { IndexMaskMemory memory; @@ -555,7 +554,7 @@ static void GREASE_PENCIL_OT_stroke_simplify(wmOperatorType *ot) ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* Simplify parameters. */ - prop = RNA_def_float(ot->srna, "factor", 0.01f, 0.0f, 100.0f, "Factor", "", 0.0f, 100.0f); + prop = RNA_def_float(ot->srna, "factor", 0.001f, 0.0f, 100.0f, "Factor", "", 0.0f, 100.0f); RNA_def_property_flag(prop, PROP_SKIP_SAVE); } @@ -660,11 +659,12 @@ static int grease_pencil_dissolve_exec(bContext *C, wmOperator *op) bool changed = false; grease_pencil.foreach_editable_drawing( - scene->r.cfra, [&](const int /*layer_index*/, bke::greasepencil::Drawing &drawing) { + scene->r.cfra, [&](int /*drawing_index*/, bke::greasepencil::Drawing &drawing) { bke::CurvesGeometry &curves = drawing.strokes_for_write(); if (curves.points_num() == 0) { return; } + if (!ed::curves::has_anything_selected(curves)) { return; } @@ -767,7 +767,6 @@ static int grease_pencil_delete_frame_exec(bContext *C, wmOperator *op) if (changed) { DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY); WM_event_add_notifier(C, NC_GEOM | ND_DATA | NA_EDITED, &grease_pencil); - WM_event_add_notifier(C, NC_GPENCIL | NA_EDITED, nullptr); } return OPERATOR_FINISHED; @@ -798,154 +797,6 @@ static void GREASE_PENCIL_OT_delete_frame(wmOperatorType *ot) RNA_def_property_flag(prop, PROP_SKIP_SAVE); } -static int grease_pencil_stroke_change_color_exec(bContext *C, wmOperator * /*op*/) -{ - using namespace blender; - const Scene *scene = CTX_data_scene(C); - Object *object = CTX_data_active_object(C); - GreasePencil &grease_pencil = *static_cast(object->data); - const int material_index = object->actcol - 1; - - if (material_index == -1) { - return OPERATOR_CANCELLED; - } - - grease_pencil.foreach_editable_drawing( - scene->r.cfra, [&](int /*layer_index*/, bke::greasepencil::Drawing &drawing) { - bke::CurvesGeometry &curves = drawing.strokes_for_write(); - - if (curves.points_num() == 0) { - return; - } - - bke::SpanAttributeWriter materials = - curves.attributes_for_write().lookup_or_add_for_write_span("material_index", - ATTR_DOMAIN_CURVE); - - IndexMaskMemory memory; - IndexMask selected_curves = ed::curves::retrieve_selected_curves(curves, memory); - - selected_curves.foreach_index( - [&](const int curve_index) { materials.span[curve_index] = material_index; }); - - materials.finish(); - }); - - DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY); - WM_event_add_notifier(C, NC_GEOM | ND_DATA | NA_EDITED, &grease_pencil); - - return OPERATOR_FINISHED; -} - -static void GREASE_PENCIL_OT_stroke_change_color(wmOperatorType *ot) -{ - ot->name = "Change Stroke color"; - ot->idname = "GREASE_PENCIL_OT_stroke_change_color"; - ot->description = "Change Stroke color with selected material"; - - ot->exec = grease_pencil_stroke_change_color_exec; - ot->poll = editable_grease_pencil_poll; - - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; -} -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Cyclical Set Operator - * \{ */ - -enum class CyclicalMode : int8_t { - /* Sets all strokes to cycle. */ - CLOSE, - /* Sets all strokes to not cycle. */ - OPEN, - /* Switches the cyclic state of the strokes. */ - TOGGLE, -}; - -static const EnumPropertyItem prop_cyclical_types[] = { - {int(CyclicalMode::CLOSE), "CLOSE", 0, "Close All", ""}, - {int(CyclicalMode::OPEN), "OPEN", 0, "Open All", ""}, - {int(CyclicalMode::TOGGLE), "TOGGLE", 0, "Toggle", ""}, - {0, nullptr, 0, nullptr, nullptr}, -}; - -static int grease_pencil_cyclical_set_exec(bContext *C, wmOperator *op) -{ - const Scene *scene = CTX_data_scene(C); - Object *object = CTX_data_active_object(C); - GreasePencil &grease_pencil = *static_cast(object->data); - - const CyclicalMode mode = CyclicalMode(RNA_enum_get(op->ptr, "type")); - - bool changed = false; - grease_pencil.foreach_editable_drawing( - scene->r.cfra, [&](int /*layer_index*/, bke::greasepencil::Drawing &drawing) { - bke::CurvesGeometry &curves = drawing.strokes_for_write(); - - if (mode == CyclicalMode::OPEN && !curves.attributes().contains("cyclic")) { - /* Avoid creating unneeded attribute. */ - return; - } - - IndexMaskMemory memory; - const IndexMask curve_selection = ed::curves::retrieve_selected_curves(curves, memory); - if (curve_selection.is_empty()) { - return; - } - - MutableSpan cyclic = curves.cyclic_for_write(); - - switch (mode) { - case CyclicalMode::CLOSE: - index_mask::masked_fill(cyclic, true, curve_selection); - break; - case CyclicalMode::OPEN: - index_mask::masked_fill(cyclic, false, curve_selection); - break; - case CyclicalMode::TOGGLE: - array_utils::invert_booleans(cyclic, curve_selection); - break; - } - - /* Remove the attribute if it is empty. */ - if (mode != CyclicalMode::CLOSE) { - if (array_utils::booleans_mix_calc(curves.cyclic()) == array_utils::BooleanMix::AllFalse) - { - curves.attributes_for_write().remove("cyclic"); - } - } - - changed = true; - }); - - if (changed) { - DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY); - WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil); - } - - return OPERATOR_FINISHED; -} - -static void GREASE_PENCIL_OT_cyclical_set(wmOperatorType *ot) -{ - /* Identifiers. */ - ot->name = "Set Cyclical State"; - ot->idname = "GREASE_PENCIL_OT_cyclical_set"; - ot->description = "Close or open the selected stroke adding a segment from last to first point"; - - /* Callbacks. */ - ot->invoke = WM_menu_invoke; - ot->exec = grease_pencil_cyclical_set_exec; - ot->poll = editable_grease_pencil_poll; - - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - /* Simplify parameters. */ - ot->prop = RNA_def_enum( - ot->srna, "type", prop_cyclical_types, int(CyclicalMode::TOGGLE), "Type", ""); -} - /** \} */ } // namespace blender::ed::greasepencil @@ -957,8 +808,6 @@ void ED_operatortypes_grease_pencil_edit() WM_operatortype_append(GREASE_PENCIL_OT_stroke_simplify); WM_operatortype_append(GREASE_PENCIL_OT_dissolve); WM_operatortype_append(GREASE_PENCIL_OT_delete_frame); - WM_operatortype_append(GREASE_PENCIL_OT_stroke_change_color); - WM_operatortype_append(GREASE_PENCIL_OT_cyclical_set); } void ED_keymap_grease_pencil(wmKeyConfig *keyconf) diff --git a/source/blender/editors/grease_pencil/intern/grease_pencil_frames.cc b/source/blender/editors/grease_pencil/intern/grease_pencil_frames.cc index fade7076c90..10793bd6811 100644 --- a/source/blender/editors/grease_pencil/intern/grease_pencil_frames.cc +++ b/source/blender/editors/grease_pencil/intern/grease_pencil_frames.cc @@ -352,7 +352,6 @@ static int insert_blank_frame_exec(bContext *C, wmOperator *op) if (changed) { DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY); WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil); - WM_event_add_notifier(C, NC_GPENCIL | NA_EDITED, nullptr); } return OPERATOR_FINISHED; @@ -377,7 +376,7 @@ static void GREASE_PENCIL_OT_insert_blank_frame(wmOperatorType *ot) prop = RNA_def_boolean( ot->srna, "all_layers", false, "All Layers", "Insert a blank frame in all editable layers"); RNA_def_property_flag(prop, PROP_SKIP_SAVE); - RNA_def_int(ot->srna, "duration", 0, 0, MAXFRAME, "Duration", "", 0, 100); + RNA_def_int(ot->srna, "duration", 1, 1, MAXFRAME, "Duration", "", 1, 100); } } // namespace blender::ed::greasepencil diff --git a/source/blender/editors/grease_pencil/intern/grease_pencil_geom.cc b/source/blender/editors/grease_pencil/intern/grease_pencil_geom.cc deleted file mode 100644 index 6e219be13bb..00000000000 --- a/source/blender/editors/grease_pencil/intern/grease_pencil_geom.cc +++ /dev/null @@ -1,160 +0,0 @@ -/* SPDX-FileCopyrightText: 2023 Blender Foundation - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -/** \file - * \ingroup edgreasepencil - */ - -#include - -#include "BLI_math_vector.hh" -#include "BLI_stack.hh" - -#include "BKE_grease_pencil.hh" - -#include "ED_grease_pencil.hh" - -extern "C" { -#include "curve_fit_nd.h" -} - -namespace blender::ed::greasepencil { - -/** - * An implementation of the Ramer-Douglas-Peucker algorithm. - * - * \param range: The range to simplify. - * \param epsilon: The threshold distance from the coord between two points for when a point - * in-between needs to be kept. - * \param dist_function: A function that computes the distance to a point at an index in the range. - * The IndexRange is a subrange of \a range and the index is an index relative to the subrange. - * \param points_to_delete: Writes true to the indices for which the points should be removed. - * \returns the total number of points to remove. - */ -int64_t ramer_douglas_peucker_simplify( - const IndexRange range, - const float epsilon, - const FunctionRef dist_function, - MutableSpan points_to_delete) -{ - /* Mark all points to not be removed. */ - points_to_delete.slice(range).fill(false); - int64_t total_points_to_remove = 0; - - Stack stack; - stack.push(range); - while (!stack.is_empty()) { - const IndexRange sub_range = stack.pop(); - /* Skip ranges with less than 3 points. All points are kept. */ - if (sub_range.size() < 3) { - continue; - } - const IndexRange inside_range = sub_range.drop_front(1).drop_back(1); - /* Compute the maximum distance and the corresponding index. */ - float max_dist = -1.0f; - int max_index = -1; - for (const int64_t index : inside_range) { - const float dist = dist_function(sub_range.first(), sub_range.last(), index); - if (dist > max_dist) { - max_dist = dist; - max_index = index - sub_range.first(); - } - } - - if (max_dist > epsilon) { - /* Found point outside the epsilon-sized strip. The point at `max_index` will be kept, repeat - * the search on the left & right side. */ - stack.push(sub_range.slice(0, max_index + 1)); - stack.push(sub_range.slice(max_index, sub_range.size() - max_index)); - } - else { - /* Points in `sub_range` are inside the epsilon-sized strip. Mark them to be deleted. */ - total_points_to_remove += inside_range.size(); - points_to_delete.slice(inside_range).fill(true); - } - } - return total_points_to_remove; -} - -Array polyline_fit_curve(Span points, - const float error_threshold, - const IndexMask &corner_mask) -{ - if (points.is_empty()) { - return {}; - } - double total_length = 0.0; - for (const int point_i : points.index_range().drop_front(1)) { - total_length += math::distance(points[point_i - 1], points[point_i]); - } - /* Just return a dot. */ - if (total_length < 1e-8) { - return Array({points[0], points[0], points[0]}); - } - - Array indices(corner_mask.size()); - corner_mask.to_indices(indices.as_mutable_span()); - uint *indicies_ptr = corner_mask.is_empty() ? nullptr : reinterpret_cast(indices.data()); - - float *r_cubic_array; - uint r_cubic_array_len; - int error = curve_fit_cubic_to_points_fl(*points.data(), - points.size(), - 2, - error_threshold, - CURVE_FIT_CALC_HIGH_QUALIY, - indicies_ptr, - indices.size(), - &r_cubic_array, - &r_cubic_array_len, - nullptr, - nullptr, - nullptr); - - if (error != 0) { - /* Some error occurred. Return. */ - return {}; - } - - Span r_cubic_array_span(reinterpret_cast(r_cubic_array), - r_cubic_array_len * 3); - - Array curve_positions(r_cubic_array_span); - return curve_positions; -} - -IndexMask polyline_detect_corners(Span points, - const float radius_min, - const float radius_max, - const int samples_max, - const float angle_threshold, - IndexMaskMemory &memory) -{ - if (points.is_empty()) { - return {}; - } - if (points.size() == 1) { - return IndexMask::from_indices({0}, memory); - } - uint *r_corners; - uint r_corner_len; - const int error = curve_fit_corners_detect_fl(*points.data(), - points.size(), - float2::type_length, - radius_min, - radius_max, - samples_max, - angle_threshold, - &r_corners, - &r_corner_len); - if (error != 0) { - /* Error occurred, return. */ - return IndexMask(); - } - BLI_assert(samples_max < std::numeric_limits::max()); - Span indices(reinterpret_cast(r_corners), r_corner_len); - return IndexMask::from_indices(indices, memory); -} - -} // namespace blender::ed::greasepencil diff --git a/source/blender/editors/grease_pencil/intern/grease_pencil_layers.cc b/source/blender/editors/grease_pencil/intern/grease_pencil_layers.cc index 70b4f4b80ff..91309693438 100644 --- a/source/blender/editors/grease_pencil/intern/grease_pencil_layers.cc +++ b/source/blender/editors/grease_pencil/intern/grease_pencil_layers.cc @@ -48,14 +48,15 @@ static int grease_pencil_layer_add_exec(bContext *C, wmOperator *op) op->ptr, "new_layer_name", nullptr, 0, &new_layer_name_length); if (grease_pencil.has_active_layer()) { - Layer &new_layer = grease_pencil.add_layer(new_layer_name); + LayerGroup &active_group = grease_pencil.get_active_layer()->parent_group(); + Layer &new_layer = grease_pencil.add_layer(active_group, new_layer_name); grease_pencil.move_node_after(new_layer.as_node(), grease_pencil.get_active_layer_for_write()->as_node()); grease_pencil.set_active_layer(&new_layer); grease_pencil.insert_blank_frame(new_layer, scene->r.cfra, 0, BEZT_KEYTYPE_KEYFRAME); } else { - Layer &new_layer = grease_pencil.add_layer(new_layer_name); + Layer &new_layer = grease_pencil.add_layer(grease_pencil.root_group(), new_layer_name); grease_pencil.set_active_layer(&new_layer); grease_pencil.insert_blank_frame(new_layer, scene->r.cfra, 0, BEZT_KEYTYPE_KEYFRAME); } @@ -140,8 +141,8 @@ static int grease_pencil_layer_reorder_exec(bContext *C, wmOperator *op) op->ptr, "target_layer_name", nullptr, 0, &target_layer_name_length); const int reorder_location = RNA_enum_get(op->ptr, "location"); - TreeNode *target_node = grease_pencil.find_node_by_name(target_layer_name); - if (!target_node || !target_node->is_layer()) { + Layer *target_layer = grease_pencil.find_layer_by_name(target_layer_name); + if (!target_layer) { MEM_SAFE_FREE(target_layer_name); return OPERATOR_CANCELLED; } @@ -151,13 +152,13 @@ static int grease_pencil_layer_reorder_exec(bContext *C, wmOperator *op) case LAYER_REORDER_ABOVE: { /* NOTE: The layers are stored from bottom to top, so inserting above (visually), means * inserting the link after the target. */ - grease_pencil.move_node_after(active_layer.as_node(), *target_node); + grease_pencil.move_node_after(active_layer.as_node(), target_layer->as_node()); break; } case LAYER_REORDER_BELOW: { /* NOTE: The layers are stored from bottom to top, so inserting below (visually), means * inserting the link before the target. */ - grease_pencil.move_node_before(active_layer.as_node(), *target_node); + grease_pencil.move_node_before(active_layer.as_node(), target_layer->as_node()); break; } default: diff --git a/source/blender/editors/grease_pencil/intern/grease_pencil_select.cc b/source/blender/editors/grease_pencil/intern/grease_pencil_select.cc index 54fd2325778..eaeb43e54fd 100644 --- a/source/blender/editors/grease_pencil/intern/grease_pencil_select.cc +++ b/source/blender/editors/grease_pencil/intern/grease_pencil_select.cc @@ -34,10 +34,10 @@ static int select_all_exec(bContext *C, wmOperator *op) Scene *scene = CTX_data_scene(C); Object *object = CTX_data_active_object(C); GreasePencil &grease_pencil = *static_cast(object->data); - eAttrDomain selection_domain = ED_grease_pencil_selection_domain_get(scene->toolsettings); + eAttrDomain selection_domain = ED_grease_pencil_selection_domain_get(C); grease_pencil.foreach_editable_drawing( - scene->r.cfra, [&](const int /*layer_index*/, blender::bke::greasepencil::Drawing &drawing) { + scene->r.cfra, [&](int /*drawing_index*/, blender::bke::greasepencil::Drawing &drawing) { blender::ed::curves::select_all(drawing.strokes_for_write(), selection_domain, action); }); @@ -70,7 +70,7 @@ static int select_more_exec(bContext *C, wmOperator * /*op*/) GreasePencil &grease_pencil = *static_cast(object->data); grease_pencil.foreach_editable_drawing( - scene->r.cfra, [](const int /*layer_index*/, blender::bke::greasepencil::Drawing &drawing) { + scene->r.cfra, [](int /*drawing_index*/, blender::bke::greasepencil::Drawing &drawing) { blender::ed::curves::select_adjacent(drawing.strokes_for_write(), false); }); @@ -101,7 +101,7 @@ static int select_less_exec(bContext *C, wmOperator * /*op*/) GreasePencil &grease_pencil = *static_cast(object->data); grease_pencil.foreach_editable_drawing( - scene->r.cfra, [](const int /*layer_index*/, blender::bke::greasepencil::Drawing &drawing) { + scene->r.cfra, [](int /*drawing_index*/, blender::bke::greasepencil::Drawing &drawing) { blender::ed::curves::select_adjacent(drawing.strokes_for_write(), true); }); @@ -132,7 +132,7 @@ static int select_linked_exec(bContext *C, wmOperator * /*op*/) GreasePencil &grease_pencil = *static_cast(object->data); grease_pencil.foreach_editable_drawing( - scene->r.cfra, [](const int /*layer_index*/, blender::bke::greasepencil::Drawing &drawing) { + scene->r.cfra, [](int /*drawing_index*/, blender::bke::greasepencil::Drawing &drawing) { blender::ed::curves::select_linked(drawing.strokes_for_write()); }); @@ -164,17 +164,17 @@ static int select_random_exec(bContext *C, wmOperator *op) Scene *scene = CTX_data_scene(C); Object *object = CTX_data_active_object(C); GreasePencil &grease_pencil = *static_cast(object->data); - eAttrDomain selection_domain = ED_grease_pencil_selection_domain_get(scene->toolsettings); + eAttrDomain selection_domain = ED_grease_pencil_selection_domain_get(C); grease_pencil.foreach_editable_drawing( - scene->r.cfra, [&](const int layer_index, bke::greasepencil::Drawing &drawing) { + scene->r.cfra, [&](int drawing_index, bke::greasepencil::Drawing &drawing) { bke::CurvesGeometry &curves = drawing.strokes_for_write(); IndexMaskMemory memory; const IndexMask random_elements = ed::curves::random_mask( curves, selection_domain, - blender::get_default_hash_2(seed, layer_index), + blender::get_default_hash_2(seed, drawing_index), ratio, memory); @@ -219,7 +219,7 @@ static int select_alternate_exec(bContext *C, wmOperator *op) GreasePencil &grease_pencil = *static_cast(object->data); grease_pencil.foreach_editable_drawing( - scene->r.cfra, [&](const int /*layer_index*/, blender::bke::greasepencil::Drawing &drawing) { + scene->r.cfra, [&](int /*drawing_index*/, blender::bke::greasepencil::Drawing &drawing) { blender::ed::curves::select_alternate(drawing.strokes_for_write(), deselect_ends); }); @@ -258,7 +258,7 @@ static int select_ends_exec(bContext *C, wmOperator *op) GreasePencil &grease_pencil = *static_cast(object->data); grease_pencil.foreach_editable_drawing( - scene->r.cfra, [&](const int /*layer_index*/, blender::bke::greasepencil::Drawing &drawing) { + scene->r.cfra, [&](int /*drawing_index*/, blender::bke::greasepencil::Drawing &drawing) { bke::CurvesGeometry &curves = drawing.strokes_for_write(); IndexMaskMemory memory; @@ -327,15 +327,14 @@ static int select_set_mode_exec(bContext *C, wmOperator *op) /* Set new selection mode. */ const int mode_new = RNA_enum_get(op->ptr, "mode"); ToolSettings *ts = CTX_data_tool_settings(C); - - bool changed = (mode_new != ts->gpencil_selectmode_edit); ts->gpencil_selectmode_edit = mode_new; /* Convert all drawings of the active GP to the new selection domain. */ - const eAttrDomain domain = ED_grease_pencil_selection_domain_get(ts); + const eAttrDomain domain = ED_grease_pencil_selection_domain_get(C); Object *object = CTX_data_active_object(C); GreasePencil &grease_pencil = *static_cast(object->data); Span drawings = grease_pencil.drawings(); + bool changed = false; for (const int index : drawings.index_range()) { GreasePencilDrawingBase *drawing_base = drawings[index]; @@ -418,9 +417,11 @@ static void GREASE_PENCIL_OT_set_selection_mode(wmOperatorType *ot) } // namespace blender::ed::greasepencil -eAttrDomain ED_grease_pencil_selection_domain_get(const ToolSettings *tool_settings) +eAttrDomain ED_grease_pencil_selection_domain_get(bContext *C) { - switch (tool_settings->gpencil_selectmode_edit) { + ToolSettings *ts = CTX_data_tool_settings(C); + + switch (ts->gpencil_selectmode_edit) { case GP_SELECTMODE_POINT: return ATTR_DOMAIN_POINT; break; diff --git a/source/blender/editors/grease_pencil/intern/grease_pencil_utils.cc b/source/blender/editors/grease_pencil/intern/grease_pencil_utils.cc deleted file mode 100644 index 145b9f78d37..00000000000 --- a/source/blender/editors/grease_pencil/intern/grease_pencil_utils.cc +++ /dev/null @@ -1,79 +0,0 @@ -/* SPDX-FileCopyrightText: 2023 Blender Foundation - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -/** \file - * \ingroup edgreasepencil - */ - -#include "BKE_brush.hh" -#include "BKE_context.h" -#include "BKE_grease_pencil.hh" - -#include "BLI_math_vector.hh" - -#include "DNA_brush_types.h" -#include "DNA_object_types.h" -#include "DNA_scene_types.h" - -#include "ED_grease_pencil.hh" -#include "ED_view3d.hh" - -namespace blender::ed::greasepencil { - -static float3 drawing_origin(const Scene *scene, const Object *object, char align_flag) -{ - BLI_assert(object != nullptr && object->type == OB_GREASE_PENCIL); - if (align_flag & GP_PROJECT_VIEWSPACE) { - if (align_flag & GP_PROJECT_CURSOR) { - return float3(scene->cursor.location); - } - /* Use the object location. */ - return float3(object->object_to_world[3]); - } - return float3(scene->cursor.location); -} - -static float3 screen_space_to_3d( - const Scene *scene, const ARegion *region, const View3D *v3d, const Object *object, float2 co) -{ - float3 origin = drawing_origin(scene, object, scene->toolsettings->gpencil_v3d_align); - float3 r_co; - ED_view3d_win_to_3d(v3d, region, origin, co, r_co); - return r_co; -} - -float brush_radius_world_space(bContext &C, int x, int y) -{ - ARegion *region = CTX_wm_region(&C); - View3D *v3d = CTX_wm_view3d(&C); - Scene *scene = CTX_data_scene(&C); - Object *object = CTX_data_active_object(&C); - Brush *brush = scene->toolsettings->gp_paint->paint.brush; - - /* Default radius. */ - float radius = 2.0f; - if (brush == nullptr || object->type != OB_GREASE_PENCIL) { - return radius; - } - - /* Use an (arbitrary) screen space offset in the x direction to measure the size. */ - const int x_offest = 64; - const float brush_size = float(BKE_brush_size_get(scene, brush)); - - /* Get two 3d coordinates to measure the distance from. */ - const float2 screen1(x, y); - const float2 screen2(x + x_offest, y); - const float3 pos1 = screen_space_to_3d(scene, region, v3d, object, screen1); - const float3 pos2 = screen_space_to_3d(scene, region, v3d, object, screen2); - - /* Clip extreme zoom level (and avoid division by zero). */ - const float distance = math::max(math::distance(pos1, pos2), 0.001f); - - /* Calculate the radius of the brush in world space. */ - radius = (1.0f / distance) * (brush_size / 64.0f); - - return radius; -} - -} // namespace blender::ed::greasepencil diff --git a/source/blender/editors/include/ED_clip.hh b/source/blender/editors/include/ED_clip.hh index fa00e1debcc..9c52dbd45a5 100644 --- a/source/blender/editors/include/ED_clip.hh +++ b/source/blender/editors/include/ED_clip.hh @@ -88,8 +88,8 @@ ImBuf *ED_space_clip_get_stable_buffer(const SpaceClip *sc, bool ED_space_clip_get_position(const SpaceClip *sc, const ARegion *region, - const int mval[2], - float r_fpos[2]); + int mval[2], + float fpos[2]); /** * Returns color in linear space, matching #ED_space_image_color_sample(). */ @@ -118,10 +118,7 @@ void ED_clip_point_stable_pos__reverse(const SpaceClip *sc, /** * Takes `event->mval`. */ -void ED_clip_mouse_pos(const SpaceClip *sc, - const ARegion *region, - const int mval[2], - float r_co[2]); +void ED_clip_mouse_pos(const SpaceClip *sc, const ARegion *region, const int mval[2], float co[2]); bool ED_space_clip_check_show_trackedit(const SpaceClip *sc); bool ED_space_clip_check_show_maskedit(const SpaceClip *sc); diff --git a/source/blender/editors/include/ED_curves.hh b/source/blender/editors/include/ED_curves.hh index f9a560db512..524c54dcc83 100644 --- a/source/blender/editors/include/ED_curves.hh +++ b/source/blender/editors/include/ED_curves.hh @@ -70,14 +70,6 @@ bool curves_poll(bContext *C); /** \} */ -/* -------------------------------------------------------------------- */ -/** \name Operators - * \{ */ - -void CURVES_OT_attribute_set(wmOperatorType *ot); - -/** \} */ - /* -------------------------------------------------------------------- */ /** \name Mask Functions * \{ */ @@ -142,7 +134,6 @@ bool has_anything_selected(const VArray &varray, IndexRange range_to_check * Find curves that have any point selected (a selection factor greater than zero), * or curves that have their own selection factor greater than zero. */ -IndexMask retrieve_selected_curves(const bke::CurvesGeometry &curves, IndexMaskMemory &memory); IndexMask retrieve_selected_curves(const Curves &curves_id, IndexMaskMemory &memory); /** diff --git a/source/blender/editors/include/ED_geometry.hh b/source/blender/editors/include/ED_geometry.hh index 72172d50f3c..fb9ebb256f6 100644 --- a/source/blender/editors/include/ED_geometry.hh +++ b/source/blender/editors/include/ED_geometry.hh @@ -18,33 +18,6 @@ struct Mesh; struct ReportList; -#include "BLI_generic_pointer.hh" -#include "BLI_string_ref.hh" - -struct PointerRNA; -struct PropertyRNA; - -namespace blender::ed::geometry { - -/* -------------------------------------------------------------------- */ -/** \name Attribute Value RNA Property Helpers - * - * Functions to make it easier to register RNA properties for the various attribute types and - * retrieve/set their values. - * \{ */ - -StringRefNull rna_property_name_for_type(eCustomDataType type); -PropertyRNA *rna_property_for_type(PointerRNA &ptr, const eCustomDataType type); -void register_rna_properties_for_attribute_types(StructRNA &srna); -GPointer rna_property_for_attribute_type_retrieve_value(PointerRNA &ptr, - const eCustomDataType type, - void *buffer); -void rna_property_for_attribute_type_set_value(PointerRNA &ptr, PropertyRNA &prop, GPointer value); - -/** \} */ - -} // namespace blender::ed::geometry - void ED_operatortypes_geometry(); /** diff --git a/source/blender/editors/include/ED_grease_pencil.hh b/source/blender/editors/include/ED_grease_pencil.hh index 2771e2ed8bf..ac96d3b0f87 100644 --- a/source/blender/editors/include/ED_grease_pencil.hh +++ b/source/blender/editors/include/ED_grease_pencil.hh @@ -12,7 +12,6 @@ #include "BKE_grease_pencil.hh" #include "BLI_generic_span.hh" -#include "BLI_index_mask.hh" #include "BLI_math_matrix_types.hh" #include "ED_keyframes_edit.hh" @@ -22,7 +21,6 @@ struct Main; struct Object; struct KeyframeEditData; struct wmKeyConfig; -struct ToolSettings; enum { LAYER_REORDER_ABOVE, @@ -43,7 +41,7 @@ void ED_keymap_grease_pencil(wmKeyConfig *keyconf); /** * Get the selection mode for Grease Pencil selection operators: point, stroke, segment. */ -eAttrDomain ED_grease_pencil_selection_domain_get(const ToolSettings *tool_settings); +eAttrDomain ED_grease_pencil_selection_domain_get(bContext *C); /** \} */ @@ -103,8 +101,6 @@ bool has_any_frame_selected(const bke::greasepencil::Layer &layer); void create_keyframe_edit_data_selected_frames_list(KeyframeEditData *ked, const bke::greasepencil::Layer &layer); -float brush_radius_world_space(bContext &C, int x, int y); - bool active_grease_pencil_poll(bContext *C); bool editable_grease_pencil_poll(bContext *C); bool editable_grease_pencil_point_selection_poll(bContext *C); @@ -124,18 +120,7 @@ void gaussian_blur_1D(const GSpan src, int64_t ramer_douglas_peucker_simplify(IndexRange range, float epsilon, - FunctionRef dist_function, + FunctionRef dist_function, MutableSpan dst); -Array polyline_fit_curve(Span points, - float error_threshold, - const IndexMask &corner_mask); - -IndexMask polyline_detect_corners(Span points, - float radius_min, - float radius_max, - int samples_max, - float angle_threshold, - IndexMaskMemory &memory); - } // namespace blender::ed::greasepencil diff --git a/source/blender/editors/include/ED_image.hh b/source/blender/editors/include/ED_image.hh index 672185490af..66ff3fa834c 100644 --- a/source/blender/editors/include/ED_image.hh +++ b/source/blender/editors/include/ED_image.hh @@ -53,10 +53,7 @@ void ED_space_image_set_mask(bContext *C, SpaceImage *sima, Mask *mask); /** * Returns mouse position in image space. */ -bool ED_space_image_get_position(SpaceImage *sima, - ARegion *region, - const int mval[2], - float r_fpos[2]); +bool ED_space_image_get_position(SpaceImage *sima, ARegion *region, int mval[2], float fpos[2]); /** * Returns color in linear space, matching #ED_space_node_color_sample(). */ diff --git a/source/blender/editors/include/ED_keyframing.hh b/source/blender/editors/include/ED_keyframing.hh index b1fe7d0789c..921e75b6795 100644 --- a/source/blender/editors/include/ED_keyframing.hh +++ b/source/blender/editors/include/ED_keyframing.hh @@ -8,7 +8,6 @@ #pragma once -#include "BLI_vector.hh" #include "DNA_anim_types.h" #include "RNA_types.hh" @@ -39,8 +38,6 @@ struct NlaKeyframingContext; /** \name Key-Framing Management * \{ */ -blender::Vector ANIM_setting_get_rna_values(PointerRNA *ptr, PropertyRNA *prop); - /** * Get the active settings for key-framing settings from context (specifically the given scene) * \param use_autokey_mode: include settings from key-framing mode in the result @@ -82,7 +79,6 @@ FCurve *ED_action_fcurve_find(bAction *act, const char rna_path[], int array_ind * but also through RNA when editing an ID prop, see #37103). */ void update_autoflags_fcurve(FCurve *fcu, bContext *C, ReportList *reports, PointerRNA *ptr); -void update_autoflags_fcurve_direct(FCurve *fcu, PropertyRNA *prop); /* -------- */ @@ -129,6 +125,74 @@ int insert_vert_fcurve( */ void ED_keyframes_add(FCurve *fcu, int num_keys_to_add); +/* -------- */ + +/** + * \brief Secondary Insert Key-framing API call. + * + * Use this when validation of necessary animation data is not necessary, + * since an RNA-pointer to the necessary data being keyframed, + * and a pointer to the F-Curve to use have both been provided. + * + * This function can't keyframe quaternion channels on some NLA strip types. + * + * \param keytype: The "keyframe type" (eBezTriple_KeyframeType), as shown in the Dope Sheet. + * + * \param flag: Used for special settings that alter the behavior of the keyframe insertion. + * These include the 'visual' key-framing modes, quick refresh, + * and extra keyframe filtering. + * \return Success. + */ +bool insert_keyframe_direct(ReportList *reports, + PointerRNA ptr, + PropertyRNA *prop, + FCurve *fcu, + const AnimationEvalContext *anim_eval_context, + eBezTriple_KeyframeType keytype, + NlaKeyframingContext *nla, + eInsertKeyFlags flag); + +/* -------- */ + +/** + * \brief Main Insert Key-framing API call. + * + * Use this to create any necessary animation data, and then insert a keyframe + * using the current value being keyframed, in the relevant place. + * + * \param flag: Used for special settings that alter the behavior of the keyframe insertion. + * These include the 'visual' key-framing modes, quick refresh, and extra keyframe filtering. + * + * \param array_index: The index to key or -1 keys all array indices. + * \return The number of key-frames inserted. + */ +int insert_keyframe(Main *bmain, + ReportList *reports, + ID *id, + bAction *act, + const char group[], + const char rna_path[], + int array_index, + const AnimationEvalContext *anim_eval_context, + eBezTriple_KeyframeType keytype, + ListBase *nla_cache, + eInsertKeyFlags flag); + +/** + * \brief Main Delete Key-Framing API call. + * + * Use this to delete keyframe on current frame for relevant channel. + * Will perform checks just in case. + * \return The number of key-frames deleted. + */ +int delete_keyframe(Main *bmain, + ReportList *reports, + ID *id, + bAction *act, + const char rna_path[], + int array_index, + float cfra); + /** \} */ /* -------------------------------------------------------------------- */ @@ -185,11 +249,7 @@ struct KeyingSetInfo { /** * Add another data source for Relative Keying Sets to be evaluated with. */ -void ANIM_relative_keyingset_add_source(blender::Vector &sources, - ID *id, - StructRNA *srna, - void *data); -void ANIM_relative_keyingset_add_source(blender::Vector &sources, ID *id); +void ANIM_relative_keyingset_add_source(ListBase *dsources, ID *id, StructRNA *srna, void *data); /* mode for modify_keyframes */ enum eModifyKey_Modes { @@ -199,7 +259,6 @@ enum eModifyKey_Modes { /* return codes for errors (with Relative KeyingSets) */ enum eModifyKey_Returns { - MODIFYKEY_SUCCESS = 0, /** Context info was invalid for using the Keying Set. */ MODIFYKEY_INVALID_CONTEXT = -1, /** There isn't any type-info for generating paths from context. */ @@ -212,13 +271,9 @@ enum eModifyKey_Returns { * where their list of paths is dynamically generated based on the * current context info. * - * \note Passing sources as pointer because it can be a nullptr. - * * \return 0 if succeeded, otherwise an error code: #eModifyKey_Returns. */ -eModifyKey_Returns ANIM_validate_keyingset(bContext *C, - blender::Vector *sources, - KeyingSet *ks); +eModifyKey_Returns ANIM_validate_keyingset(bContext *C, ListBase *dsources, KeyingSet *ks); /** * Use the specified #KeyingSet and context info (if required) @@ -231,16 +286,17 @@ eModifyKey_Returns ANIM_validate_keyingset(bContext *C, * an #eModifyKey_Returns value (always a negative number). */ int ANIM_apply_keyingset( - bContext *C, blender::Vector *sources, KeyingSet *ks, short mode, float cfra); + bContext *C, ListBase *dsources, bAction *act, KeyingSet *ks, short mode, float cfra); /* -------- */ /** * Find builtin #KeyingSet by name. * - * \return The first builtin #KeyingSet with the given name + * \return The first builtin #KeyingSet with the given name, which occurs after the given one + * (or start of list if none given). */ -KeyingSet *ANIM_builtin_keyingset_get_named(const char name[]); +KeyingSet *ANIM_builtin_keyingset_get_named(KeyingSet *prevKS, const char name[]); /** * Find KeyingSet type info given a name. @@ -494,6 +550,39 @@ void ANIM_copy_as_driver(ID *target_id, const char *target_path, const char *var /** \} */ +/* -------------------------------------------------------------------- */ +/** \name Auto-Key-Framing + * + * Notes: + * - All the defines for this (User-Pref settings and Per-Scene settings) + * are defined in DNA_userdef_types.h + * - Scene settings take precedence over those for user-preferences, with old files + * inheriting user-preferences settings for the scene settings + * - "On/Off + Mode" are stored per Scene, but "settings" are currently stored as user-preferences. + * \{ */ + +/* Auto-Keying macros for use by various tools. */ + +/** Check if auto-key-framing is enabled (per scene takes precedence). */ +#define IS_AUTOKEY_ON(scene) \ + ((scene) ? ((scene)->toolsettings->autokey_mode & AUTOKEY_ON) : (U.autokey_mode & AUTOKEY_ON)) +/** Check the mode for auto-keyframing (per scene takes precedence). */ +#define IS_AUTOKEY_MODE(scene, mode) \ + ((scene) ? ((scene)->toolsettings->autokey_mode == AUTOKEY_MODE_##mode) : \ + (U.autokey_mode == AUTOKEY_MODE_##mode)) +/** Check if a flag is set for auto-key-framing (per scene takes precedence). */ +#define IS_AUTOKEY_FLAG(scene, flag) \ + ((scene) ? (((scene)->toolsettings->autokey_flag & AUTOKEY_FLAG_##flag) || \ + (U.autokey_flag & AUTOKEY_FLAG_##flag)) : \ + (U.autokey_flag & AUTOKEY_FLAG_##flag)) + +/** + * Auto-keyframing feature - checks for whether anything should be done for the current frame. + */ +bool autokeyframe_cfra_can_key(const Scene *scene, ID *id); + +/** \} */ + /* -------------------------------------------------------------------- */ /** \name Keyframe Checking * \{ */ @@ -527,6 +616,26 @@ bool fcurve_is_changed(PointerRNA ptr, */ bool id_frame_has_keyframe(ID *id, float frame); +/* Utility functions for auto key-frame. */ + +bool ED_autokeyframe_object(bContext *C, Scene *scene, Object *ob, KeyingSet *ks); +bool ED_autokeyframe_pchan( + bContext *C, Scene *scene, Object *ob, bPoseChannel *pchan, KeyingSet *ks); + +/** + * Use for auto-key-framing. + * \param only_if_property_keyed: if true, auto-key-framing only creates keyframes on already keyed + * properties. This is by design when using buttons. For other callers such as gizmos or sequencer + * preview transform, creating new animation/keyframes also on non-keyed properties is desired. + */ +bool ED_autokeyframe_property(bContext *C, + Scene *scene, + PointerRNA *ptr, + PropertyRNA *prop, + int rnaindex, + float cfra, + bool only_if_property_keyed); + /* Names for builtin keying sets so we don't confuse these with labels/text, * defined in python script: `keyingsets_builtins.py`. */ diff --git a/source/blender/editors/include/ED_mesh.hh b/source/blender/editors/include/ED_mesh.hh index 36fb74b0205..38f411810f6 100644 --- a/source/blender/editors/include/ED_mesh.hh +++ b/source/blender/editors/include/ED_mesh.hh @@ -317,7 +317,7 @@ void EDBM_select_toggle_all(BMEditMesh *em); void EDBM_select_swap(BMEditMesh *em); /* exported for UV */ bool EDBM_select_interior_faces(BMEditMesh *em); -ViewContext em_setup_viewcontext(bContext *C); /* rename? */ +void em_setup_viewcontext(bContext *C, ViewContext *vc); /* rename? */ bool EDBM_mesh_deselect_all_multi_ex(Base **bases, uint bases_len); bool EDBM_mesh_deselect_all_multi(bContext *C); diff --git a/source/blender/editors/include/ED_screen.hh b/source/blender/editors/include/ED_screen.hh index a67d4ab8b93..e05111fea6d 100644 --- a/source/blender/editors/include/ED_screen.hh +++ b/source/blender/editors/include/ED_screen.hh @@ -543,6 +543,7 @@ bool ED_operator_editable_mesh(bContext *C); bool ED_operator_editmesh(bContext *C); bool ED_operator_editmesh_view3d(bContext *C); bool ED_operator_editmesh_region_view3d(bContext *C); +bool ED_operator_editmesh_auto_smooth(bContext *C); bool ED_operator_editarmature(bContext *C); bool ED_operator_editcurve(bContext *C); bool ED_operator_editcurve_3d(bContext *C); diff --git a/source/blender/editors/include/ED_view3d.hh b/source/blender/editors/include/ED_view3d.hh index 9d3e58073a0..e7ad7761388 100644 --- a/source/blender/editors/include/ED_view3d.hh +++ b/source/blender/editors/include/ED_view3d.hh @@ -944,7 +944,7 @@ int view3d_opengl_select_with_id_filter(ViewContext *vc, /* view3d_select.cc */ float ED_view3d_select_dist_px(); -ViewContext ED_view3d_viewcontext_init(bContext *C, Depsgraph *depsgraph); +void ED_view3d_viewcontext_init(bContext *C, ViewContext *vc, Depsgraph *depsgraph); /** * Re-initialize `vc` with `obact` as if it's active object (with some differences). diff --git a/source/blender/editors/include/ED_viewer_path.hh b/source/blender/editors/include/ED_viewer_path.hh index fb4ebb47453..c068c03bdc4 100644 --- a/source/blender/editors/include/ED_viewer_path.hh +++ b/source/blender/editors/include/ED_viewer_path.hh @@ -6,7 +6,6 @@ #include -#include "BLI_compute_context.hh" #include "BLI_string_ref.hh" #include "BLI_vector.hh" @@ -77,13 +76,4 @@ enum class UpdateActiveGeometryNodesViewerResult { UpdateActiveGeometryNodesViewerResult update_active_geometry_nodes_viewer(const bContext &C, ViewerPath &viewer_path); -/** - * Some viewer path elements correspond to compute-contexts. This function converts from the viewer - * path element to the corresponding compute context if possible. - * - * \return False, there is no matching compute context. - */ -[[nodiscard]] bool add_compute_context_for_viewer_path_elem( - const ViewerPathElem &elem, ComputeContextBuilder &compute_context_builder); - } // namespace blender::ed::viewer_path diff --git a/source/blender/editors/include/UI_icons.hh b/source/blender/editors/include/UI_icons.hh index 7cad22a1c93..b5374cf7a0e 100644 --- a/source/blender/editors/include/UI_icons.hh +++ b/source/blender/editors/include/UI_icons.hh @@ -387,9 +387,9 @@ DEF_ICON(CURVE_BEZCIRCLE) DEF_ICON(CURVE_NCURVE) DEF_ICON(CURVE_NCIRCLE) DEF_ICON(CURVE_PATH) -DEF_ICON_OBJECT_DATA(LIGHTPROBE_SPHERE) -DEF_ICON_OBJECT_DATA(LIGHTPROBE_PLANE) -DEF_ICON_OBJECT_DATA(LIGHTPROBE_VOLUME) +DEF_ICON_OBJECT_DATA(LIGHTPROBE_CUBEMAP) +DEF_ICON_OBJECT_DATA(LIGHTPROBE_PLANAR) +DEF_ICON_OBJECT_DATA(LIGHTPROBE_GRID) DEF_ICON_BLANK(406) DEF_ICON_BLANK(407) DEF_ICON(COLOR_RED) diff --git a/source/blender/editors/include/UI_interface_c.hh b/source/blender/editors/include/UI_interface_c.hh index bd54b2b123e..61feb011bcc 100644 --- a/source/blender/editors/include/UI_interface_c.hh +++ b/source/blender/editors/include/UI_interface_c.hh @@ -343,10 +343,10 @@ enum { /** This but is "inside" a box item (currently used to change theme colors). */ UI_BUT_BOX_ITEM = 1 << 20, - /** Mouse is hovering left part of number button */ - UI_BUT_HOVER_LEFT = 1 << 21, - /** Mouse is hovering right part of number button */ - UI_BUT_HOVER_RIGHT = 1 << 22, + /** Active left part of number button */ + UI_BUT_ACTIVE_LEFT = 1 << 21, + /** Active right part of number button */ + UI_BUT_ACTIVE_RIGHT = 1 << 22, /** Reverse order of consecutive off/on icons */ UI_BUT_ICON_REVERSE = 1 << 23, @@ -937,11 +937,6 @@ void UI_but_disable(uiBut *but, const char *disabled_hint); void UI_but_type_set_menu_from_pulldown(uiBut *but); -/** - * Set at hint that describes the expected value when empty. - */ -void UI_but_placeholder_set(uiBut *but, const char *placeholder_text) ATTR_NONNULL(1); - /** * Special button case, only draw it when used actively, for outliner etc. * @@ -1790,7 +1785,6 @@ typedef enum uiTooltipStyle { UI_TIP_STYLE_HEADER, /* Header text. */ UI_TIP_STYLE_MONO, /* Mono-spaced text. */ UI_TIP_STYLE_IMAGE, /* Image field. */ - UI_TIP_STYLE_SPACER, /* Padding to separate sections. */ } uiTooltipStyle; typedef enum uiTooltipColorID { @@ -2785,8 +2779,7 @@ void uiItemFullR(uiLayout *layout, int value, eUI_Item_Flag flag, const char *name, - int icon, - const char *placeholder = nullptr); + int icon); /** * Use a wrapper function since re-implementing all the logic in this function would be messy. */ diff --git a/source/blender/editors/include/UI_tree_view.hh b/source/blender/editors/include/UI_tree_view.hh index 46f3b31d840..d37c2f07a5d 100644 --- a/source/blender/editors/include/UI_tree_view.hh +++ b/source/blender/editors/include/UI_tree_view.hh @@ -322,12 +322,11 @@ class BasicTreeViewItem : public AbstractTreeViewItem { */ class TreeViewItemDropTarget : public DropTargetInterface { protected: - AbstractTreeViewItem &view_item_; + AbstractTreeView &view_; const DropBehavior behavior_; public: - TreeViewItemDropTarget(AbstractTreeViewItem &view_item, - DropBehavior behavior = DropBehavior::Insert); + TreeViewItemDropTarget(AbstractTreeView &view, DropBehavior behavior = DropBehavior::Insert); std::optional choose_drop_location(const ARegion ®ion, const wmEvent &event) const override; @@ -369,7 +368,7 @@ template ViewType &TreeViewItemDropTarget::get_view() const { static_assert(std::is_base_of::value, "Type must derive from and implement the ui::AbstractTreeView interface"); - return dynamic_cast(view_item_.get_tree_view()); + return dynamic_cast(view_); } } // namespace blender::ui diff --git a/source/blender/editors/interface/CMakeLists.txt b/source/blender/editors/interface/CMakeLists.txt index f36e4b817ba..4c0aa54b7de 100644 --- a/source/blender/editors/interface/CMakeLists.txt +++ b/source/blender/editors/interface/CMakeLists.txt @@ -102,7 +102,6 @@ set(LIB PRIVATE bf::dna bf_editor_datafiles PRIVATE bf::intern::guardedalloc - PRIVATE bf::animrig ) if(WITH_INTERNATIONAL) @@ -117,8 +116,10 @@ if(WITH_PYTHON) add_definitions(-DWITH_PYTHON) endif() -if(WITH_INPUT_IME) - add_definitions(-DWITH_INPUT_IME) +if(WIN32 OR APPLE) + if(WITH_INPUT_IME) + add_definitions(-DWITH_INPUT_IME) + endif() endif() if(WITH_EXPERIMENTAL_FEATURES) diff --git a/source/blender/editors/interface/eyedroppers/eyedropper_color.cc b/source/blender/editors/interface/eyedroppers/eyedropper_color.cc index 56581273d7a..11e2961ac94 100644 --- a/source/blender/editors/interface/eyedroppers/eyedropper_color.cc +++ b/source/blender/editors/interface/eyedroppers/eyedropper_color.cc @@ -27,7 +27,7 @@ #include "BKE_node.hh" #include "BKE_screen.hh" -#include "NOD_composite.hh" +#include "NOD_composite.h" #include "RNA_access.hh" #include "RNA_prototypes.h" @@ -68,8 +68,6 @@ struct Eyedropper { float accum_col[3]; int accum_tot; - wmWindow *cb_win; - int cb_win_event_xy[2]; void *draw_handle_sample_text; char sample_text[MAX_NAME]; @@ -77,10 +75,10 @@ struct Eyedropper { CryptomatteSession *cryptomatte_session; }; -static void eyedropper_draw_cb(const wmWindow * /*window*/, void *arg) +static void eyedropper_draw_cb(const wmWindow *window, void *arg) { Eyedropper *eye = static_cast(arg); - eyedropper_draw_cursor_text_region(eye->cb_win_event_xy, eye->sample_text); + eyedropper_draw_cursor_text_window(window, eye->sample_text); } static bool eyedropper_init(bContext *C, wmOperator *op) @@ -110,8 +108,7 @@ static bool eyedropper_init(bContext *C, wmOperator *op) eye->crypto_node = (bNode *)eye->ptr.data; eye->cryptomatte_session = ntreeCompositCryptomatteSession(CTX_data_scene(C), eye->crypto_node); - eye->cb_win = CTX_wm_window(C); - eye->draw_handle_sample_text = WM_draw_cb_activate(eye->cb_win, eyedropper_draw_cb, eye); + eye->draw_handle_sample_text = WM_draw_cb_activate(CTX_wm_window(C), eyedropper_draw_cb, eye); } if (prop_subtype != PROP_COLOR) { @@ -138,7 +135,7 @@ static void eyedropper_exit(bContext *C, wmOperator *op) WM_cursor_modal_restore(window); if (eye->draw_handle_sample_text) { - WM_draw_cb_exit(eye->cb_win, eye->draw_handle_sample_text); + WM_draw_cb_exit(window, eye->draw_handle_sample_text); eye->draw_handle_sample_text = nullptr; } @@ -256,39 +253,18 @@ static bool eyedropper_cryptomatte_sample_fl(bContext *C, return false; } - ScrArea *area = nullptr; - - int event_xy_win[2]; - wmWindow *win = WM_window_find_under_cursor(CTX_wm_window(C), event_xy, event_xy_win); - if (win) { - bScreen *screen = WM_window_get_active_screen(win); - area = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, event_xy_win); - } - - eye->cb_win_event_xy[0] = event_xy_win[0]; - eye->cb_win_event_xy[1] = event_xy_win[1]; - - if (win && win != eye->cb_win && eye->draw_handle_sample_text) { - WM_draw_cb_exit(eye->cb_win, eye->draw_handle_sample_text); - eye->cb_win = win; - eye->draw_handle_sample_text = WM_draw_cb_activate(eye->cb_win, eyedropper_draw_cb, eye); - ED_region_tag_redraw(CTX_wm_region(C)); - } - + bScreen *screen = CTX_wm_screen(C); + ScrArea *area = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, event_xy); if (!area || !ELEM(area->spacetype, SPACE_IMAGE, SPACE_NODE, SPACE_CLIP)) { return false; } - ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_WINDOW, event_xy_win); - + ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_WINDOW, event_xy); if (!region) { return false; } - const int mval[2] = { - event_xy_win[0] - region->winrct.xmin, - event_xy_win[1] - region->winrct.ymin, - }; + int mval[2] = {event_xy[0] - region->winrct.xmin, event_xy[1] - region->winrct.ymin}; float fpos[2] = {-1.0f, -1.0}; switch (area->spacetype) { case SPACE_IMAGE: { @@ -322,8 +298,6 @@ static bool eyedropper_cryptomatte_sample_fl(bContext *C, return false; } - ED_region_tag_redraw(region); - /* TODO(jbakker): Migrate this file to cc and use std::string as return param. */ char prefix[MAX_NAME + 1]; const Scene *scene = CTX_data_scene(C); @@ -343,36 +317,33 @@ void eyedropper_color_sample_fl(bContext *C, const int event_xy[2], float r_col[ { ScrArea *area = nullptr; - int event_xy_win[2]; - wmWindow *win = WM_window_find_under_cursor(CTX_wm_window(C), event_xy, event_xy_win); + int mval[2]; + wmWindow *win = WM_window_find_under_cursor(CTX_wm_window(C), event_xy, mval); if (win) { bScreen *screen = WM_window_get_active_screen(win); - area = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, event_xy_win); + area = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, mval); } if (area) { - ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_WINDOW, event_xy_win); + ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_WINDOW, mval); if (region) { - const int mval[2] = { - event_xy_win[0] - region->winrct.xmin, - event_xy_win[1] - region->winrct.ymin, - }; + const int region_mval[2] = {mval[0] - region->winrct.xmin, mval[1] - region->winrct.ymin}; if (area->spacetype == SPACE_IMAGE) { SpaceImage *sima = static_cast(area->spacedata.first); - if (ED_space_image_color_sample(sima, region, mval, r_col, nullptr)) { + if (ED_space_image_color_sample(sima, region, region_mval, r_col, nullptr)) { return; } } else if (area->spacetype == SPACE_NODE) { SpaceNode *snode = static_cast(area->spacedata.first); Main *bmain = CTX_data_main(C); - if (ED_space_node_color_sample(bmain, snode, region, mval, r_col)) { + if (ED_space_node_color_sample(bmain, snode, region, region_mval, r_col)) { return; } } else if (area->spacetype == SPACE_CLIP) { SpaceClip *sc = static_cast(area->spacedata.first); - if (ED_space_clip_color_sample(sc, region, mval, r_col)) { + if (ED_space_clip_color_sample(sc, region, region_mval, r_col)) { return; } } @@ -381,8 +352,8 @@ void eyedropper_color_sample_fl(bContext *C, const int event_xy[2], float r_col[ if (win) { /* Other areas within a Blender window. */ - if (!WM_window_pixels_read_sample(C, win, event_xy_win, r_col)) { - WM_window_pixels_read_sample_from_offscreen(C, win, event_xy_win, r_col); + if (!WM_window_pixels_read_sample(C, win, mval, r_col)) { + WM_window_pixels_read_sample_from_offscreen(C, win, mval, r_col); } const char *display_device = CTX_data_scene(C)->display_settings.display_device; ColorManagedDisplay *display = IMB_colormanagement_display_get_named(display_device); @@ -520,6 +491,7 @@ static int eyedropper_modal(bContext *C, wmOperator *op, const wmEvent *event) if (eye->draw_handle_sample_text) { eyedropper_color_sample_text_update(C, eye, event->xy); + ED_region_tag_redraw(CTX_wm_region(C)); } } diff --git a/source/blender/editors/interface/interface.cc b/source/blender/editors/interface/interface.cc index 8fc9166190f..afabb9a6a80 100644 --- a/source/blender/editors/interface/interface.cc +++ b/source/blender/editors/interface/interface.cc @@ -61,7 +61,6 @@ #include "WM_types.hh" #include "RNA_access.hh" -#include "RNA_enum_types.hh" #ifdef WITH_PYTHON # include "BPY_extern_run.h" @@ -1007,7 +1006,7 @@ static bool ui_but_update_from_old_block(const bContext *C, /* Stupid special case: The active button may be inside (as in, overlapped on top) a row * button which we also want to keep highlighted then. */ if (ELEM(but->type, UI_BTYPE_VIEW_ITEM, UI_BTYPE_LISTROW)) { - flag_copy |= UI_HOVER; + flag_copy |= UI_ACTIVE; } but->flag = (but->flag & ~flag_copy) | (oldbut->flag & flag_copy); @@ -3479,10 +3478,6 @@ static void ui_but_free(const bContext *C, uiBut *but) MEM_freeN(but->hold_argN); } - if (but->placeholder) { - MEM_freeN(but->placeholder); - } - ui_but_free_type_specific(but); if (but->active) { @@ -5912,36 +5907,6 @@ void UI_but_disable(uiBut *but, const char *disabled_hint) but->disabled_info = disabled_hint; } -void UI_but_placeholder_set(uiBut *but, const char *placeholder_text) -{ - MEM_SAFE_FREE(but->placeholder); - but->placeholder = BLI_strdup_null(placeholder_text); -} - -const char *ui_but_placeholder_get(uiBut *but) -{ - const char *placeholder = (but->placeholder) ? but->placeholder : nullptr; - - if (!placeholder && but->rnaprop) { - if (but->type == UI_BTYPE_SEARCH_MENU) { - StructRNA *type = RNA_property_pointer_type(&but->rnapoin, but->rnaprop); - const short idcode = RNA_type_to_ID_code(type); - if (idcode != 0) { - RNA_enum_name(rna_enum_id_type_items, idcode, &placeholder); - placeholder = CTX_IFACE_(BLT_I18NCONTEXT_ID_ID, placeholder); - } - else if (type && !STREQ(RNA_struct_identifier(type), "UnknownType")) { - placeholder = RNA_struct_ui_name(type); - } - } - else if (but->type == UI_BTYPE_TEXT && but->icon == ICON_VIEWZOOM) { - placeholder = CTX_IFACE_(BLT_I18NCONTEXT_ID_WINDOWMANAGER, "Search"); - } - } - - return placeholder; -} - void UI_but_type_set_menu_from_pulldown(uiBut *but) { BLI_assert(but->type == UI_BTYPE_PULLDOWN); diff --git a/source/blender/editors/interface/interface_anim.cc b/source/blender/editors/interface/interface_anim.cc index ff32a162c5e..222786b26d9 100644 --- a/source/blender/editors/interface/interface_anim.cc +++ b/source/blender/editors/interface/interface_anim.cc @@ -34,8 +34,6 @@ #include "ED_keyframing.hh" -#include "ANIM_keyframing.hh" - #include "UI_interface.hh" #include "RNA_access.hh" @@ -302,8 +300,7 @@ bool ui_but_anim_expression_create(uiBut *but, const char *str) void ui_but_anim_autokey(bContext *C, uiBut *but, Scene *scene, float cfra) { - blender::animrig::autokeyframe_property( - C, scene, &but->rnapoin, but->rnaprop, but->rnaindex, cfra, true); + ED_autokeyframe_property(C, scene, &but->rnapoin, but->rnaprop, but->rnaindex, cfra, true); } void ui_but_anim_copy_driver(bContext *C) diff --git a/source/blender/editors/interface/interface_draw.cc b/source/blender/editors/interface/interface_draw.cc index c470f47c61a..edf8102051f 100644 --- a/source/blender/editors/interface/interface_draw.cc +++ b/source/blender/editors/interface/interface_draw.cc @@ -619,7 +619,7 @@ void ui_draw_but_WAVEFORM(ARegion * /*region*/, int scissor[4]; float colors[3][3]; const float colorsycc[3][3] = {{1, 0, 1}, {1, 1, 0}, {0, 1, 1}}; - /* Colors pre-multiplied by alpha for speed up. */ + /* colors pre multiplied by alpha for speed up */ float colors_alpha[3][3], colorsycc_alpha[3][3]; float min, max; diff --git a/source/blender/editors/interface/interface_handlers.cc b/source/blender/editors/interface/interface_handlers.cc index b3d02351e3f..0284ef5b700 100644 --- a/source/blender/editors/interface/interface_handlers.cc +++ b/source/blender/editors/interface/interface_handlers.cc @@ -3415,7 +3415,7 @@ void ui_but_ime_reposition(uiBut *but, int x, int y, bool complete) wm_window_IME_begin(but->active->window, x, y - 4, 0, 0, complete); } -const wmIMEData *ui_but_ime_data_get(uiBut *but) +wmIMEData *ui_but_ime_data_get(uiBut *but) { if (but->active && but->active->window) { return but->active->window->ime_data; @@ -3604,11 +3604,7 @@ static void ui_textedit_end(bContext *C, uiBut *but, uiHandleButtonData *data) data->undo_stack_text = nullptr; #ifdef WITH_INPUT_IME - /* See #wm_window_IME_end code-comments for details. */ -# if defined(WIN32) || defined(__APPLE__) - if (win->ime_data) -# endif - { + if (win->ime_data) { ui_textedit_ime_end(win, but); } #endif @@ -3710,8 +3706,8 @@ static void ui_do_but_textedit( #ifdef WITH_INPUT_IME wmWindow *win = CTX_wm_window(C); - const wmIMEData *ime_data = win->ime_data; - const bool is_ime_composing = ime_data && win->ime_data_is_composing; + wmIMEData *ime_data = win->ime_data; + const bool is_ime_composing = ime_data && ime_data->is_ime_composing; #else const bool is_ime_composing = false; #endif @@ -4021,15 +4017,13 @@ static void ui_do_but_textedit( } #ifdef WITH_INPUT_IME - if (event->type == WM_IME_COMPOSITE_START) { + if (ELEM(event->type, WM_IME_COMPOSITE_START, WM_IME_COMPOSITE_EVENT)) { changed = true; - if (but->selend > but->selsta) { + + if (event->type == WM_IME_COMPOSITE_START && but->selend > but->selsta) { ui_textedit_delete_selection(but, data); } - } - else if (event->type == WM_IME_COMPOSITE_EVENT) { - changed = true; - if (ime_data->result_len) { + if (event->type == WM_IME_COMPOSITE_EVENT && ime_data->result_len) { if (ELEM(but->type, UI_BTYPE_NUM, UI_BTYPE_NUM_SLIDER) && STREQ(ime_data->str_result, "\xE3\x80\x82")) { @@ -4569,8 +4563,8 @@ static int ui_do_but_BUT(bContext *C, uiBut *but, uiHandleButtonData *data, cons return WM_UI_HANDLER_BREAK; } if (event->type == LEFTMOUSE && event->val == KM_RELEASE && but->block->handle) { - /* regular buttons will be 'UI_SELECT', menu items 'UI_HOVER' */ - if (!(but->flag & (UI_SELECT | UI_HOVER))) { + /* regular buttons will be 'UI_SELECT', menu items 'UI_ACTIVE' */ + if (!(but->flag & (UI_SELECT | UI_ACTIVE))) { data->cancel = true; } button_activate_state(C, but, BUTTON_STATE_EXIT); @@ -5347,7 +5341,7 @@ static bool ui_numedit_but_NUM(uiButNumber *but, static void ui_numedit_set_active(uiBut *but) { const int oldflag = but->drawflag; - but->drawflag &= ~(UI_BUT_HOVER_LEFT | UI_BUT_HOVER_RIGHT); + but->drawflag &= ~(UI_BUT_ACTIVE_LEFT | UI_BUT_ACTIVE_RIGHT); uiHandleButtonData *data = but->active; if (!data) { @@ -5365,16 +5359,16 @@ static void ui_numedit_set_active(uiBut *but) ui_window_to_block(data->region, but->block, &mx, &my); if (mx < (but->rect.xmin + handle_width)) { - but->drawflag |= UI_BUT_HOVER_LEFT; + but->drawflag |= UI_BUT_ACTIVE_LEFT; } else if (mx > (but->rect.xmax - handle_width)) { - but->drawflag |= UI_BUT_HOVER_RIGHT; + but->drawflag |= UI_BUT_ACTIVE_RIGHT; } } /* Don't change the cursor once pressed. */ if ((but->flag & UI_SELECT) == 0) { - if ((but->drawflag & UI_BUT_HOVER_LEFT) || (but->drawflag & UI_BUT_HOVER_RIGHT)) { + if ((but->drawflag & UI_BUT_ACTIVE_LEFT) || (but->drawflag & UI_BUT_ACTIVE_RIGHT)) { if (data->changed_cursor) { WM_cursor_modal_restore(data->window); data->changed_cursor = false; @@ -5425,14 +5419,14 @@ static int ui_do_but_NUM( } else if (type == WHEELDOWNMOUSE && (event->modifier & KM_CTRL)) { mx = but->rect.xmin; - but->drawflag &= ~UI_BUT_HOVER_RIGHT; - but->drawflag |= UI_BUT_HOVER_LEFT; + but->drawflag &= ~UI_BUT_ACTIVE_RIGHT; + but->drawflag |= UI_BUT_ACTIVE_LEFT; click = 1; } else if ((type == WHEELUPMOUSE) && (event->modifier & KM_CTRL)) { mx = but->rect.xmax; - but->drawflag &= ~UI_BUT_HOVER_LEFT; - but->drawflag |= UI_BUT_HOVER_RIGHT; + but->drawflag &= ~UI_BUT_ACTIVE_LEFT; + but->drawflag |= UI_BUT_ACTIVE_RIGHT; click = 1; } else if (event->val == KM_PRESS) { @@ -5534,14 +5528,14 @@ static int ui_do_but_NUM( if (!ui_but_is_float(but)) { /* Integer Value. */ - if (but->drawflag & (UI_BUT_HOVER_LEFT | UI_BUT_HOVER_RIGHT)) { + if (but->drawflag & (UI_BUT_ACTIVE_LEFT | UI_BUT_ACTIVE_RIGHT)) { button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); const int value_step = int(number_but->step_size); BLI_assert(value_step > 0); const int softmin = round_fl_to_int_clamp(but->softmin); const int softmax = round_fl_to_int_clamp(but->softmax); - const double value_test = (but->drawflag & UI_BUT_HOVER_LEFT) ? + const double value_test = (but->drawflag & UI_BUT_ACTIVE_LEFT) ? double(max_ii(softmin, int(data->value) - value_step)) : double(min_ii(softmax, int(data->value) + value_step)); if (value_test != data->value) { @@ -5558,7 +5552,7 @@ static int ui_do_but_NUM( } else { /* Float Value. */ - if (but->drawflag & (UI_BUT_HOVER_LEFT | UI_BUT_HOVER_RIGHT)) { + if (but->drawflag & (UI_BUT_ACTIVE_LEFT | UI_BUT_ACTIVE_RIGHT)) { const PropertyScaleType scale_type = ui_but_scale_type(but); button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); @@ -5578,7 +5572,7 @@ static int ui_do_but_NUM( } BLI_assert(value_step > 0.0f); const double value_test = - (but->drawflag & UI_BUT_HOVER_LEFT) ? + (but->drawflag & UI_BUT_ACTIVE_LEFT) ? double(max_ff(but->softmin, float(data->value - value_step))) : double(min_ff(but->softmax, float(data->value + value_step))); if (value_test != data->value) { @@ -8623,9 +8617,9 @@ static void button_activate_init(bContext *C, data->state = BUTTON_STATE_INIT; - /* Activate button. Sets the hover flag to enable button highlights, usually the button is - * initially activated because it's hovered. */ - but->flag |= UI_HOVER; + /* activate button */ + but->flag |= UI_ACTIVE; + but->active = data; /* we disable auto_open in the block after a threshold, because we still @@ -8813,7 +8807,7 @@ static void button_activate_exit( /* clean up button */ MEM_SAFE_FREE(but->active); - but->flag &= ~(UI_HOVER | UI_SELECT); + but->flag &= ~(UI_ACTIVE | UI_SELECT); but->flag |= UI_BUT_LAST_ACTIVE; if (!onfree) { ui_but_update(but); @@ -9411,14 +9405,14 @@ static int ui_handle_button_event(bContext *C, const wmEvent *event, uiBut *but) } if (!(but->flag & UI_SELECT)) { - but->flag |= (UI_SELECT | UI_HOVER); + but->flag |= (UI_SELECT | UI_ACTIVE); data->cancel = false; ED_region_tag_redraw_no_rebuild(data->region); } } else { if (but->flag & UI_SELECT) { - but->flag &= ~(UI_SELECT | UI_HOVER); + but->flag &= ~(UI_SELECT | UI_ACTIVE); data->cancel = true; ED_region_tag_redraw_no_rebuild(data->region); } @@ -9831,7 +9825,7 @@ static int ui_handle_viewlist_items_hover(const wmEvent *event, const ARegion *r LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { LISTBASE_FOREACH (uiBut *, but, &block->buttons) { if (ELEM(but->type, UI_BTYPE_VIEW_ITEM, UI_BTYPE_LISTROW)) { - but->flag &= ~UI_HOVER; + but->flag &= ~UI_ACTIVE; has_item = true; } } @@ -9848,7 +9842,7 @@ static int ui_handle_viewlist_items_hover(const wmEvent *event, const ARegion *r hovered_row_but = ui_list_row_find_mouse_over(region, event->xy); } if (hovered_row_but) { - hovered_row_but->flag |= UI_HOVER; + hovered_row_but->flag |= UI_ACTIVE; } return WM_UI_HANDLER_CONTINUE; @@ -10783,7 +10777,7 @@ static int ui_handle_menu_event(bContext *C, (event->flag & WM_EVENT_IS_REPEAT) == 0) { - /* Menu search if space-bar or #MenuTypeFlag::SearchOnKeyPress. */ + /* Menu search if spacebar or SearchOnKeyPress. */ MenuType *mt = WM_menutype_find(menu->menu_idname, true); if ((mt && bool(mt->flag & MenuTypeFlag::SearchOnKeyPress)) || event->type == EVT_SPACEKEY) { diff --git a/source/blender/editors/interface/interface_icons.cc b/source/blender/editors/interface/interface_icons.cc index 4c8f63eaed8..ab9a74122b4 100644 --- a/source/blender/editors/interface/interface_icons.cc +++ b/source/blender/editors/interface/interface_icons.cc @@ -1316,7 +1316,10 @@ static void icon_create_rect(PreviewImage *prv_img, enum eIconSizes size) static void ui_id_preview_image_render_size( const bContext *C, Scene *scene, ID *id, PreviewImage *pi, int size, const bool use_job); -static void ui_studiolight_icon_job_exec(void *customdata, wmJobWorkerStatus * /*worker_status*/) +static void ui_studiolight_icon_job_exec(void *customdata, + bool * /*stop*/, + bool * /*do_update*/, + float * /*progress*/) { Icon **tmp = (Icon **)customdata; Icon *icon = *tmp; diff --git a/source/blender/editors/interface/interface_intern.hh b/source/blender/editors/interface/interface_intern.hh index abf1facc7ec..aef2dc8a7fb 100644 --- a/source/blender/editors/interface/interface_intern.hh +++ b/source/blender/editors/interface/interface_intern.hh @@ -70,14 +70,7 @@ enum { UI_SELECT = (1 << 0), /** Temporarily hidden (scrolled out of the view). */ UI_SCROLLED = (1 << 1), - /** - * The button is hovered by the mouse and should be drawn with a hover highlight. Also set - * sometimes to highlight buttons without actually hovering it (e.g. for arrow navigation in - * menus). UI handling code manages this mostly and usually does this together with making the - * button active/focused (see #uiBut::active). This means events will be forwarded to it and - * further handlers/shortcuts can be used while hovering it. - */ - UI_HOVER = (1 << 2), + UI_ACTIVE = (1 << 2), UI_HAS_ICON = (1 << 3), UI_HIDDEN = (1 << 4), /** Display selected, doesn't impact interaction. */ @@ -182,8 +175,6 @@ struct uiBut { char strdata[UI_MAX_NAME_STR] = ""; char drawstr[UI_MAX_DRAW_STR] = ""; - char *placeholder = nullptr; - rctf rect = {}; /* block relative coords */ char *poin = nullptr; @@ -290,10 +281,7 @@ struct uiBut { const ImBuf *imb = nullptr; float imb_scale = 0; - /** - * Active button data, set when the user is hovering or interacting with a button (#UI_HOVER and - * #UI_SELECT state mostly). - */ + /** Active button data (set when the user is hovering or interacting with a button). */ uiHandleButtonData *active = nullptr; /** Custom button data (borrowed, not owned). */ @@ -754,11 +742,6 @@ void ui_but_active_string_clear_and_exit(bContext *C, uiBut *but) ATTR_NONNULL() void ui_but_set_string_interactive(bContext *C, uiBut *but, const char *value); uiBut *ui_but_drag_multi_edit_get(uiBut *but); -/** - * Get the hint that describes the expected value when empty. - */ -const char *ui_but_placeholder_get(uiBut *but); - void ui_def_but_icon(uiBut *but, int icon, int flag); /** * Avoid using this where possible since it's better not to ask for an icon in the first place. @@ -1152,7 +1135,7 @@ uiBut *ui_but_find_new(uiBlock *block_new, const uiBut *but_old); #ifdef WITH_INPUT_IME void ui_but_ime_reposition(uiBut *but, int x, int y, bool complete); -const wmIMEData *ui_but_ime_data_get(uiBut *but); +wmIMEData *ui_but_ime_data_get(uiBut *but); #endif /* interface_widgets.cc */ @@ -1225,7 +1208,7 @@ enum uiMenuItemSeparatorType { * Helper call to draw a menu item without a button. * * \param but_flag: Button flags (#uiBut.flag) indicating the state of the item, typically - * #UI_HOVER, #UI_BUT_DISABLED, #UI_BUT_INACTIVE. + * #UI_ACTIVE, #UI_BUT_DISABLED, #UI_BUT_INACTIVE. * \param separator_type: The kind of separator which controls if and how the string is clipped. * \param r_xmax: The right hand position of the text, this takes into the icon, padding and text * clipping when there is not enough room to display the full text. diff --git a/source/blender/editors/interface/interface_layout.cc b/source/blender/editors/interface/interface_layout.cc index c7e11106fbb..25c176b3680 100644 --- a/source/blender/editors/interface/interface_layout.cc +++ b/source/blender/editors/interface/interface_layout.cc @@ -2064,8 +2064,7 @@ void uiItemFullR(uiLayout *layout, int value, eUI_Item_Flag flag, const char *name, - int icon, - const char *placeholder) + int icon) { uiBlock *block = layout->root->block; char namestr[UI_MAX_NAME_STR]; @@ -2478,10 +2477,6 @@ void uiItemFullR(uiLayout *layout, UI_but_flag_enable(but, UI_BUT_LIST_ITEM); } - if (but && placeholder) { - UI_but_placeholder_set(but, placeholder); - } - #ifdef UI_PROP_DECORATE if (ui_decorate.use_prop_decorate) { uiBut *but_decorate = ui_decorate.but ? ui_decorate.but->next : @@ -3193,8 +3188,6 @@ void uiItemPopoverPanel_ptr( if (ok && (pt->draw_header != nullptr)) { layout = uiLayoutRow(layout, true); Panel panel{}; - Panel_Runtime panel_runtime{}; - panel.runtime = &panel_runtime; panel.type = pt; panel.layout = layout; panel.flag = PNL_POPOVER; @@ -6013,7 +6006,8 @@ static bool ui_layout_has_panel_label(const uiLayout *layout, const PanelType *p static void ui_paneltype_draw_impl(bContext *C, PanelType *pt, uiLayout *layout, bool show_header) { - Panel *panel = BKE_panel_new(pt); + Panel *panel = MEM_cnew(__func__); + panel->type = pt; panel->flag = PNL_POPOVER; if (pt->listener) { @@ -6042,9 +6036,9 @@ static void ui_paneltype_draw_impl(bContext *C, PanelType *pt, uiLayout *layout, panel->layout = layout; pt->draw(C, panel); panel->layout = nullptr; - BLI_assert(panel->runtime->custom_data_ptr == nullptr); + BLI_assert(panel->runtime.custom_data_ptr == nullptr); - BKE_panel_free(panel); + MEM_freeN(panel); /* Draw child panels. */ LISTBASE_FOREACH (LinkData *, link, &pt->children) { diff --git a/source/blender/editors/interface/interface_panel.cc b/source/blender/editors/interface/interface_panel.cc index 97cf855a3ae..d7ea15047da 100644 --- a/source/blender/editors/interface/interface_panel.cc +++ b/source/blender/editors/interface/interface_panel.cc @@ -222,9 +222,11 @@ static bool panels_need_realign(const ScrArea *area, ARegion *region, Panel **r_ static Panel *panel_add_instanced(ListBase *panels, PanelType *panel_type, PointerRNA *custom_data) { - Panel *panel = BKE_panel_new(panel_type); + Panel *panel = MEM_cnew(__func__); + panel->type = panel_type; + STRNCPY(panel->panelname, panel_type->idname); - panel->runtime->custom_data_ptr = custom_data; + panel->runtime.custom_data_ptr = custom_data; panel->runtime_flag |= PANEL_NEW_ADDED; /* Add the panel's children too. Although they aren't instanced panels, we can still use this @@ -299,7 +301,11 @@ static void panel_delete(const bContext *C, ARegion *region, ListBase *panels, P BLI_freelistN(&panel->children); BLI_remlink(panels, panel); - BKE_panel_free(panel); + if (panel->activedata) { + MEM_freeN(panel->activedata); + } + MEM_SAFE_FREE(panel->drawname); + MEM_freeN(panel); } void UI_panels_free_instanced(const bContext *C, ARegion *region) @@ -313,8 +319,8 @@ void UI_panels_free_instanced(const bContext *C, ARegion *region) } /* Free panel's custom data. */ - if (panel->runtime->custom_data_ptr != nullptr) { - MEM_freeN(panel->runtime->custom_data_ptr); + if (panel->runtime.custom_data_ptr != nullptr) { + MEM_freeN(panel->runtime.custom_data_ptr); } /* Free the panel and its sub-panels. */ @@ -440,7 +446,7 @@ static void reorder_instanced_panel_list(bContext *C, ARegion *region, Panel *dr /* Set the bit to tell the interface to instanced the list. */ drag_panel->flag |= PNL_INSTANCED_LIST_ORDER_CHANGED; - CTX_store_set(C, drag_panel->runtime->context); + CTX_store_set(C, drag_panel->runtime.context); /* Finally, move this panel's list item to the new index in its list. */ drag_panel->type->reorder(C, drag_panel, move_to_index); @@ -660,10 +666,13 @@ Panel *UI_panel_begin( { Panel *panel_last; const char *drawname = CTX_IFACE_(pt->translation_context, pt->label); + const char *idname = pt->idname; const bool newpanel = (panel == nullptr); if (newpanel) { - panel = BKE_panel_new(pt); + panel = MEM_cnew(__func__); + panel->type = pt; + STRNCPY(panel->panelname, idname); if (pt->flag & PANEL_TYPE_DEFAULT_CLOSED) { panel->flag |= PNL_CLOSED; @@ -685,7 +694,7 @@ Panel *UI_panel_begin( panel->type = pt; } - panel->runtime->block = block; + panel->runtime.block = block; UI_panel_drawname_set(panel, drawname); @@ -727,14 +736,14 @@ Panel *UI_panel_begin( void UI_panel_header_buttons_begin(Panel *panel) { - uiBlock *block = panel->runtime->block; + uiBlock *block = panel->runtime.block; ui_block_new_button_group(block, UI_BUTTON_GROUP_LOCK | UI_BUTTON_GROUP_PANEL_HEADER); } void UI_panel_header_buttons_end(Panel *panel) { - uiBlock *block = panel->runtime->block; + uiBlock *block = panel->runtime.block; /* A button group should always be created in #UI_panel_header_buttons_begin. */ BLI_assert(!block->button_groups.is_empty()); @@ -792,7 +801,7 @@ static void panel_calculate_size_recursive(ARegion *region, Panel *panel) } else { const int old_sizex = panel->sizex, old_sizey = panel->sizey; - const int old_region_ofsx = panel->runtime->region_ofsx; + const int old_region_ofsx = panel->runtime.region_ofsx; /* Update width/height if non-zero. */ if (width != 0) { @@ -808,8 +817,8 @@ static void panel_calculate_size_recursive(ARegion *region, Panel *panel) panel->ofsy += old_sizey - panel->sizey; } - panel->runtime->region_ofsx = panel_region_offset_x_get(region); - if (old_region_ofsx != panel->runtime->region_ofsx) { + panel->runtime.region_ofsx = panel_region_offset_x_get(region); + if (old_region_ofsx != panel->runtime.region_ofsx) { panel->runtime_flag |= PANEL_ANIM_ALIGN; } } @@ -913,7 +922,7 @@ static void region_panels_set_expansion_from_search_filter(const bContext *C, */ static void panel_remove_invisible_layouts_recursive(Panel *panel, const Panel *parent_panel) { - uiBlock *block = panel->runtime->block; + uiBlock *block = panel->runtime.block; BLI_assert(block != nullptr); BLI_assert(block->active); if (parent_panel != nullptr && UI_panel_is_closed(parent_panel)) { @@ -939,7 +948,7 @@ static void panel_remove_invisible_layouts_recursive(Panel *panel, const Panel * LISTBASE_FOREACH (Panel *, child_panel, &panel->children) { if (child_panel->runtime_flag & PANEL_ACTIVE) { - BLI_assert(child_panel->runtime->block != nullptr); + BLI_assert(child_panel->runtime.block != nullptr); panel_remove_invisible_layouts_recursive(child_panel, panel); } } @@ -949,7 +958,7 @@ static void region_panels_remove_invisible_layouts(ARegion *region) { LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { if (panel->runtime_flag & PANEL_ACTIVE) { - BLI_assert(panel->runtime->block != nullptr); + BLI_assert(panel->runtime.block != nullptr); panel_remove_invisible_layouts_recursive(panel, nullptr); } } @@ -1049,7 +1058,7 @@ static void panel_draw_highlight_border(const Panel *panel, } const bTheme *btheme = UI_GetTheme(); - const float aspect = panel->runtime->block->aspect; + const float aspect = panel->runtime.block->aspect; const float radius = (btheme->tui.panel_roundness * U.widget_unit * 0.5f) / aspect; UI_draw_roundbox_corner_set(UI_CNR_ALL); @@ -1168,7 +1177,7 @@ static void panel_draw_aligned_backdrop(const Panel *panel, } const bTheme *btheme = UI_GetTheme(); - const float aspect = panel->runtime->block->aspect; + const float aspect = panel->runtime.block->aspect; const float radius = btheme->tui.panel_roundness * U.widget_unit * 0.5f / aspect; immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); @@ -1660,7 +1669,7 @@ static bool uiAlignPanelStep(ARegion *region, const float factor, const bool dra for (int i = 0; i < active_panels_len; i++) { PanelSort *ps = &panel_sort[i]; const bool show_background = UI_panel_should_show_background(region, ps->panel->type); - ps->panel->runtime->region_ofsx = region_offset_x; + ps->panel->runtime.region_ofsx = region_offset_x; ps->new_offset_x = region_offset_x + (show_background ? UI_PANEL_MARGIN_X : 0); } @@ -1819,7 +1828,7 @@ void UI_panels_end(const bContext *C, ARegion *region, int *r_x, int *r_y) LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { if (panel->runtime_flag & PANEL_ACTIVE) { - BLI_assert(panel->runtime->block != nullptr); + BLI_assert(panel->runtime.block != nullptr); panel_calculate_size_recursive(region, panel); } } @@ -2391,7 +2400,7 @@ int ui_handler_panel_region(bContext *C, static void ui_panel_custom_data_set_recursive(Panel *panel, PointerRNA *custom_data) { - panel->runtime->custom_data_ptr = custom_data; + panel->runtime.custom_data_ptr = custom_data; LISTBASE_FOREACH (Panel *, child_panel, &panel->children) { ui_panel_custom_data_set_recursive(child_panel, custom_data); @@ -2401,7 +2410,7 @@ static void ui_panel_custom_data_set_recursive(Panel *panel, PointerRNA *custom_ void UI_panel_context_pointer_set(Panel *panel, const char *name, PointerRNA *ptr) { uiLayoutSetContextPointer(panel->layout, name, ptr); - panel->runtime->context = uiLayoutGetContextStore(panel->layout); + panel->runtime.context = uiLayoutGetContextStore(panel->layout); } void UI_panel_custom_data_set(Panel *panel, PointerRNA *custom_data) @@ -2409,8 +2418,8 @@ void UI_panel_custom_data_set(Panel *panel, PointerRNA *custom_data) BLI_assert(panel->type != nullptr); /* Free the old custom data, which should be shared among all of the panel's sub-panels. */ - if (panel->runtime->custom_data_ptr != nullptr) { - MEM_freeN(panel->runtime->custom_data_ptr); + if (panel->runtime.custom_data_ptr != nullptr) { + MEM_freeN(panel->runtime.custom_data_ptr); } ui_panel_custom_data_set_recursive(panel, custom_data); @@ -2418,7 +2427,7 @@ void UI_panel_custom_data_set(Panel *panel, PointerRNA *custom_data) PointerRNA *UI_panel_custom_data_get(const Panel *panel) { - return panel->runtime->custom_data_ptr; + return panel->runtime.custom_data_ptr; } PointerRNA *UI_region_panel_custom_data_under_cursor(const bContext *C, const wmEvent *event) diff --git a/source/blender/editors/interface/interface_region_color_picker.cc b/source/blender/editors/interface/interface_region_color_picker.cc index 2ec5144a27a..d3233165a4e 100644 --- a/source/blender/editors/interface/interface_region_color_picker.cc +++ b/source/blender/editors/interface/interface_region_color_picker.cc @@ -740,7 +740,7 @@ static void ui_block_colorpicker(uiBlock *block, } UI_but_flag_disable(bt, UI_BUT_UNDO); - bt->hardmax = hardmax; /* Not common but RGB may be over 1.0. */ + bt->hardmax = hardmax; /* not common but rgb may be over 1.0 */ UI_but_func_set(bt, ui_colorpicker_hsv_update_cb, bt, nullptr); bt->custom_data = cpicker; diff --git a/source/blender/editors/interface/interface_region_menu_popup.cc b/source/blender/editors/interface/interface_region_menu_popup.cc index 6df2fe203d2..8cb86514d81 100644 --- a/source/blender/editors/interface/interface_region_menu_popup.cc +++ b/source/blender/editors/interface/interface_region_menu_popup.cc @@ -566,8 +566,6 @@ void UI_popup_menu_reports(bContext *C, ReportList *reports) return; } - BKE_reports_lock(reports); - LISTBASE_FOREACH (Report *, report, &reports->list) { int icon; const char *msg, *msg_next; @@ -603,8 +601,6 @@ void UI_popup_menu_reports(bContext *C, ReportList *reports) } while ((msg = msg_next) && *msg); } - BKE_reports_unlock(reports); - if (pup) { UI_popup_menu_end(C, pup); } diff --git a/source/blender/editors/interface/interface_region_search.cc b/source/blender/editors/interface/interface_region_search.cc index cbdf88179c7..f26a3bceec9 100644 --- a/source/blender/editors/interface/interface_region_search.cc +++ b/source/blender/editors/interface/interface_region_search.cc @@ -564,7 +564,7 @@ static void ui_searchbox_region_draw_fn(const bContext *C, ARegion *region) if (data->preview) { /* draw items */ for (int a = 0; a < data->items.totitem; a++) { - const int but_flag = ((a == data->active) ? UI_HOVER : 0) | data->items.but_flags[a]; + const int but_flag = ((a == data->active) ? UI_ACTIVE : 0) | data->items.but_flags[a]; /* ensure icon is up-to-date */ ui_icon_ensure_deferred(C, data->items.icons[a], data->preview); @@ -598,7 +598,7 @@ static void ui_searchbox_region_draw_fn(const bContext *C, ARegion *region) const int search_sep_len = data->sep_string ? strlen(data->sep_string) : 0; /* draw items */ for (int a = 0; a < data->items.totitem; a++) { - const int but_flag = ((a == data->active) ? UI_HOVER : 0) | data->items.but_flags[a]; + const int but_flag = ((a == data->active) ? UI_ACTIVE : 0) | data->items.but_flags[a]; char *name = data->items.names[a]; int icon = data->items.icons[a]; char *name_sep_test = nullptr; @@ -975,7 +975,7 @@ static void ui_searchbox_region_draw_cb__operator(const bContext * /*C*/, ARegio /* widget itself */ /* NOTE: i18n messages extracting tool does the same, please keep it in sync. */ { - const int but_flag = ((a == data->active) ? UI_HOVER : 0) | data->items.but_flags[a]; + const int but_flag = ((a == data->active) ? UI_ACTIVE : 0) | data->items.but_flags[a]; wmOperatorType *ot = static_cast(data->items.pointers[a]); char text_pre[128]; diff --git a/source/blender/editors/interface/interface_region_tooltip.cc b/source/blender/editors/interface/interface_region_tooltip.cc index 6b3dfa9c047..4f86861003c 100644 --- a/source/blender/editors/interface/interface_region_tooltip.cc +++ b/source/blender/editors/interface/interface_region_tooltip.cc @@ -66,8 +66,8 @@ #include "interface_intern.hh" #include "interface_regions_intern.hh" -#define UI_TIP_SPACER 0.3f -#define UI_TIP_PADDING int(1.3f * UI_UNIT_Y) +#define UI_TIP_PAD_FAC 1.3f +#define UI_TIP_PADDING int(UI_TIP_PAD_FAC * UI_UNIT_Y) #define UI_TIP_MAXWIDTH 600 #define UI_TIP_MAXIMAGEWIDTH 500 #define UI_TIP_MAXIMAGEHEIGHT 300 @@ -76,6 +76,7 @@ struct uiTooltipFormat { uiTooltipStyle style; uiTooltipColorID color_id; + bool is_pad; }; struct uiTooltipField { @@ -120,16 +121,11 @@ void UI_tooltip_text_field_add(uiTooltipData *data, const uiTooltipColorID color_id, const bool is_pad) { - if (is_pad) { - /* Add a spacer field before this one. */ - UI_tooltip_text_field_add( - data, nullptr, nullptr, UI_TIP_STYLE_SPACER, UI_TIP_LC_NORMAL, false); - } - uiTooltipField *field = text_field_add_only(data); field->format = {}; field->format.style = style; field->format.color_id = color_id; + field->format.is_pad = is_pad; field->text = text; field->text_suffix = suffix; } @@ -219,6 +215,8 @@ static void ui_tooltip_region_draw_cb(const bContext * /*C*/, ARegion *region) for (int i = 0; i < data->fields_len; i++) { const uiTooltipField *field = &data->fields[i]; + const uiTooltipField *field_next = (i + 1) != data->fields_len ? &data->fields[i + 1] : + nullptr; bbox.ymin = bbox.ymax - (data->lineh * field->geom.lines); if (field->format.style == UI_TIP_STYLE_HEADER) { @@ -282,9 +280,6 @@ static void ui_tooltip_region_draw_cb(const bContext * /*C*/, ARegion *region) GPU_blend(GPU_BLEND_ALPHA); } - else if (field->format.style == UI_TIP_STYLE_SPACER) { - bbox.ymax -= data->lineh * UI_TIP_SPACER; - } else { BLI_assert(field->format.style == UI_TIP_STYLE_NORMAL); uiFontStyleDraw_Params fs_params{}; @@ -298,6 +293,10 @@ static void ui_tooltip_region_draw_cb(const bContext * /*C*/, ARegion *region) } bbox.ymax -= data->lineh * field->geom.lines; + + if (field_next && field_next->format.is_pad) { + bbox.ymax -= data->lineh * (UI_TIP_PAD_FAC - 1); + } } BLF_disable(data->fstyle.uifont_id, BLF_WORD_WRAP); @@ -1224,6 +1223,8 @@ static ARegion *ui_tooltip_create_with_data(bContext *C, int i, fonth, fontw; for (i = 0, fontw = 0, fonth = 0; i < data->fields_len; i++) { uiTooltipField *field = &data->fields[i]; + uiTooltipField *field_next = (i + 1) != data->fields_len ? &data->fields[i + 1] : nullptr; + ResultBLF info = {0}; int w = 0; int x_pos = 0; @@ -1248,9 +1249,8 @@ static ARegion *ui_tooltip_create_with_data(bContext *C, } fonth += h * info.lines; - - if (field->format.style == UI_TIP_STYLE_SPACER) { - fonth += h * UI_TIP_SPACER; + if (field_next && field_next->format.is_pad) { + fonth += h * (UI_TIP_PAD_FAC - 1); } if (field->format.style == UI_TIP_STYLE_IMAGE) { diff --git a/source/blender/editors/interface/interface_style.cc b/source/blender/editors/interface/interface_style.cc index c7a602e7380..1b0bf8bb5ac 100644 --- a/source/blender/editors/interface/interface_style.cc +++ b/source/blender/editors/interface/interface_style.cc @@ -69,7 +69,6 @@ static uiStyle *ui_style_new(ListBase *styles, const char *name, short uifont_id style->paneltitle.uifont_id = uifont_id; style->paneltitle.points = UI_DEFAULT_TITLE_POINTS; - style->paneltitle.character_weight = 400; style->paneltitle.shadow = 3; style->paneltitle.shadx = 0; style->paneltitle.shady = -1; @@ -78,7 +77,6 @@ static uiStyle *ui_style_new(ListBase *styles, const char *name, short uifont_id style->grouplabel.uifont_id = uifont_id; style->grouplabel.points = UI_DEFAULT_TITLE_POINTS; - style->grouplabel.character_weight = 400; style->grouplabel.shadow = 3; style->grouplabel.shadx = 0; style->grouplabel.shady = -1; @@ -87,7 +85,6 @@ static uiStyle *ui_style_new(ListBase *styles, const char *name, short uifont_id style->widgetlabel.uifont_id = uifont_id; style->widgetlabel.points = UI_DEFAULT_TEXT_POINTS; - style->widgetlabel.character_weight = 400; style->widgetlabel.shadow = 3; style->widgetlabel.shadx = 0; style->widgetlabel.shady = -1; @@ -96,7 +93,6 @@ static uiStyle *ui_style_new(ListBase *styles, const char *name, short uifont_id style->widget.uifont_id = uifont_id; style->widget.points = UI_DEFAULT_TEXT_POINTS; - style->widget.character_weight = 400; style->widget.shadow = 1; style->widget.shady = -1; style->widget.shadowalpha = 0.5f; @@ -496,7 +492,6 @@ static void fontstyle_set_ex(const uiFontStyle *fs, const float dpi_fac) uiFont *font = uifont_to_blfont(fs->uifont_id); BLF_size(font->blf_id, fs->points * dpi_fac); - BLF_character_weight(fs->uifont_id, fs->character_weight); } void UI_fontstyle_set(const uiFontStyle *fs) diff --git a/source/blender/editors/interface/interface_template_grease_pencil_layer_tree.cc b/source/blender/editors/interface/interface_template_grease_pencil_layer_tree.cc index 369389cc3d9..a10d421545e 100644 --- a/source/blender/editors/interface/interface_template_grease_pencil_layer_tree.cc +++ b/source/blender/editors/interface/interface_template_grease_pencil_layer_tree.cc @@ -11,8 +11,6 @@ #include "BLT_translation.h" -#include "DEG_depsgraph.hh" - #include "UI_interface.hh" #include "UI_tree_view.hh" @@ -44,8 +42,8 @@ class LayerNodeDropTarget : public TreeViewItemDropTarget { TreeNode &drop_tree_node_; public: - LayerNodeDropTarget(AbstractTreeViewItem &item, TreeNode &drop_tree_node, DropBehavior behavior) - : TreeViewItemDropTarget(item, behavior), drop_tree_node_(drop_tree_node) + LayerNodeDropTarget(AbstractTreeView &view, TreeNode &drop_tree_node, DropBehavior behavior) + : TreeViewItemDropTarget(view, behavior), drop_tree_node_(drop_tree_node) { } @@ -120,9 +118,8 @@ class LayerNodeDropTarget : public TreeViewItemDropTarget { return false; } } - - DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY); WM_event_add_notifier(C, NC_GPENCIL | NA_EDITED, nullptr); + return true; } }; @@ -208,15 +205,9 @@ class LayerViewItem : public AbstractTreeViewItem { return true; } - bool rename(const bContext &C, StringRefNull new_name) override + bool rename(const bContext & /*C*/, StringRefNull new_name) override { - PointerRNA layer_ptr = RNA_pointer_create(&grease_pencil_.id, &RNA_GreasePencilLayer, &layer_); - PropertyRNA *prop = RNA_struct_find_property(&layer_ptr, "name"); - - RNA_property_string_set(&layer_ptr, prop, new_name.c_str()); - RNA_property_update(&const_cast(C), &layer_ptr, prop); - - ED_undo_push(&const_cast(C), "Rename Grease Pencil Layer"); + grease_pencil_.rename_node(layer_.as_node(), new_name); return true; } @@ -233,7 +224,8 @@ class LayerViewItem : public AbstractTreeViewItem { std::unique_ptr create_drop_target() override { - return std::make_unique(*this, layer_.as_node(), DropBehavior::Reorder); + return std::make_unique( + get_tree_view(), layer_.as_node(), DropBehavior::Reorder); } private: @@ -321,16 +313,9 @@ class LayerGroupViewItem : public AbstractTreeViewItem { return true; } - bool rename(const bContext &C, StringRefNull new_name) override + bool rename(const bContext & /*C*/, StringRefNull new_name) override { - PointerRNA group_ptr = RNA_pointer_create( - &grease_pencil_.id, &RNA_GreasePencilLayerGroup, &group_); - PropertyRNA *prop = RNA_struct_find_property(&group_ptr, "name"); - - RNA_property_string_set(&group_ptr, prop, new_name.c_str()); - RNA_property_update(&const_cast(C), &group_ptr, prop); - - ED_undo_push(&const_cast(C), "Rename Grease Pencil Layer Group"); + grease_pencil_.rename_node(group_.as_node(), new_name); return true; } @@ -342,7 +327,7 @@ class LayerGroupViewItem : public AbstractTreeViewItem { std::unique_ptr create_drop_target() override { return std::make_unique( - *this, group_.as_node(), DropBehavior::ReorderAndInsert); + get_tree_view(), group_.as_node(), DropBehavior::ReorderAndInsert); } private: diff --git a/source/blender/editors/interface/interface_template_light_linking.cc b/source/blender/editors/interface/interface_template_light_linking.cc index 7cb1050cdc4..b3116d784d6 100644 --- a/source/blender/editors/interface/interface_template_light_linking.cc +++ b/source/blender/editors/interface/interface_template_light_linking.cc @@ -117,10 +117,8 @@ class ReorderCollectionDropTarget : public TreeViewItemDropTarget { const ID &drop_id_; public: - ReorderCollectionDropTarget(AbstractTreeViewItem &item, - Collection &collection, - const ID &drop_id) - : TreeViewItemDropTarget(item, DropBehavior::Reorder), + ReorderCollectionDropTarget(AbstractTreeView &view, Collection &collection, const ID &drop_id) + : TreeViewItemDropTarget(view, DropBehavior::Reorder), collection_target_(collection), drop_id_(drop_id) { @@ -255,7 +253,7 @@ class CollectionViewItem : public BasicTreeViewItem { std::unique_ptr create_drop_target() override { - return std::make_unique(*this, collection_, id_); + return std::make_unique(get_tree_view(), collection_, id_); } private: diff --git a/source/blender/editors/interface/interface_template_node_tree_interface.cc b/source/blender/editors/interface/interface_template_node_tree_interface.cc index 32551388f5d..8a178a0cd6f 100644 --- a/source/blender/editors/interface/interface_template_node_tree_interface.cc +++ b/source/blender/editors/interface/interface_template_node_tree_interface.cc @@ -39,8 +39,6 @@ struct wmDragNodeTreeInterface { namespace { -class NodePanelViewItem; -class NodeSocketViewItem; class NodeTreeInterfaceView; class NodeTreeInterfaceDragController : public AbstractViewItemDragController { @@ -62,7 +60,7 @@ class NodeSocketDropTarget : public TreeViewItemDropTarget { bNodeTreeInterfaceSocket &socket_; public: - explicit NodeSocketDropTarget(NodeSocketViewItem &item, bNodeTreeInterfaceSocket &socket); + explicit NodeSocketDropTarget(NodeTreeInterfaceView &view, bNodeTreeInterfaceSocket &socket); bool can_drop(const wmDrag &drag, const char **r_disabled_hint) const override; std::string drop_tooltip(const DragInfo &drag_info) const override; @@ -77,7 +75,7 @@ class NodePanelDropTarget : public TreeViewItemDropTarget { bNodeTreeInterfacePanel &panel_; public: - explicit NodePanelDropTarget(NodePanelViewItem &item, bNodeTreeInterfacePanel &panel); + explicit NodePanelDropTarget(NodeTreeInterfaceView &view, bNodeTreeInterfacePanel &panel); bool can_drop(const wmDrag &drag, const char **r_disabled_hint) const override; std::string drop_tooltip(const DragInfo &drag_info) const override; @@ -291,7 +289,8 @@ std::unique_ptr NodeSocketViewItem::create_drag_ std::unique_ptr NodeSocketViewItem::create_drop_target() { - return std::make_unique(*this, socket_); + return std::make_unique( + static_cast(this->get_tree_view()), socket_); } std::unique_ptr NodePanelViewItem::create_drag_controller() const @@ -302,7 +301,8 @@ std::unique_ptr NodePanelViewItem::create_drag_c std::unique_ptr NodePanelViewItem::create_drop_target() { - return std::make_unique(*this, panel_); + return std::make_unique( + static_cast(this->get_tree_view()), panel_); } NodeTreeInterfaceDragController::NodeTreeInterfaceDragController(NodeTreeInterfaceView &view, @@ -323,9 +323,9 @@ void *NodeTreeInterfaceDragController::create_drag_data() const return drag_data; } -NodeSocketDropTarget::NodeSocketDropTarget(NodeSocketViewItem &item, +NodeSocketDropTarget::NodeSocketDropTarget(NodeTreeInterfaceView &view, bNodeTreeInterfaceSocket &socket) - : TreeViewItemDropTarget(item, DropBehavior::Reorder), socket_(socket) + : TreeViewItemDropTarget(view, DropBehavior::Reorder), socket_(socket) { } @@ -406,8 +406,9 @@ wmDragNodeTreeInterface *NodeSocketDropTarget::get_drag_node_tree_declaration( return static_cast(drag.poin); } -NodePanelDropTarget::NodePanelDropTarget(NodePanelViewItem &item, bNodeTreeInterfacePanel &panel) - : TreeViewItemDropTarget(item, DropBehavior::ReorderAndInsert), panel_(panel) +NodePanelDropTarget::NodePanelDropTarget(NodeTreeInterfaceView &view, + bNodeTreeInterfacePanel &panel) + : TreeViewItemDropTarget(view, DropBehavior::ReorderAndInsert), panel_(panel) { } diff --git a/source/blender/editors/interface/interface_template_search_menu.cc b/source/blender/editors/interface/interface_template_search_menu.cc index 42c0b50721a..3b3f56c8c3d 100644 --- a/source/blender/editors/interface/interface_template_search_menu.cc +++ b/source/blender/editors/interface/interface_template_search_menu.cc @@ -31,7 +31,7 @@ #include "BLI_set.hh" #include "BLI_stack.hh" #include "BLI_string.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_utildefines.h" #include "BLT_translation.h" diff --git a/source/blender/editors/interface/interface_templates.cc b/source/blender/editors/interface/interface_templates.cc index 16717b611eb..5d6ffcc54a2 100644 --- a/source/blender/editors/interface/interface_templates.cc +++ b/source/blender/editors/interface/interface_templates.cc @@ -34,7 +34,7 @@ #include "BLI_path_util.h" #include "BLI_rect.h" #include "BLI_string.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_timecode.h" #include "BLI_utildefines.h" diff --git a/source/blender/editors/interface/interface_widgets.cc b/source/blender/editors/interface/interface_widgets.cc index 99640f5cf14..66bb844e52a 100644 --- a/source/blender/editors/interface/interface_widgets.cc +++ b/source/blender/editors/interface/interface_widgets.cc @@ -1393,7 +1393,7 @@ static void widget_draw_icon( if (but->flag & UI_SELECT) { /* pass */ } - else if (but->flag & UI_HOVER) { + else if (but->flag & UI_ACTIVE) { /* pass */ } else { @@ -1449,11 +1449,11 @@ static void widget_draw_icon( const bool has_theme = UI_icon_get_theme_color(icon, color); /* to indicate draggable */ - if (ui_but_drag_is_draggable(but) && (but->flag & UI_HOVER)) { + if (ui_but_drag_is_draggable(but) && (but->flag & UI_ACTIVE)) { UI_icon_draw_ex( xs, ys, icon, aspect, 1.25f, 0.0f, color, has_theme, &but->icon_overlay_text); } - else if (but->flag & (UI_HOVER | UI_SELECT | UI_SELECT_DRAW)) { + else if (but->flag & (UI_ACTIVE | UI_SELECT | UI_SELECT_DRAW)) { UI_icon_draw_ex( xs, ys, icon, aspect, alpha, 0.0f, color, has_theme, &but->icon_overlay_text); } @@ -1659,9 +1659,10 @@ float UI_text_clip_middle_ex(const uiFontStyle *fstyle, /* The following assert is meant to catch code changes that break this function's result, but * some wriggle room is fine and needed. Just a couple pixels for large sizes and with some * settings like "Full" hinting which can move features both left and right a pixel. We could - * probably reduce this to one pixel if we consolidate text output with length measuring. But + * probably reduce this to one pixel if we consolodate text output with length measuring. But * our text string lengths include the last character's right-side bearing anyway, so a string - * can be longer by that amount and still fit visibly in the required space. */ + * can be longer by that amount and still fit visibly in the required space. + */ BLI_assert((strwidth <= (okwidth + 2)) || (okwidth <= 0.0f)); @@ -2236,22 +2237,6 @@ static void widget_draw_text(const uiFontStyle *fstyle, } } - /* Show placeholder text if the input is empty and not being edited. */ - if (!drawstr[0] && !but->editstr && ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU)) { - const char *placeholder = ui_but_placeholder_get(but); - if (placeholder && placeholder[0]) { - uiFontStyleDraw_Params params{}; - params.align = align; - uiFontStyle style = *fstyle; - style.shadow = 0; - uchar col[4]; - copy_v4_v4_uchar(col, wcol->text); - col[3] *= 0.33f; - UI_fontstyle_draw_ex( - &style, rect, placeholder, strlen(placeholder), col, ¶ms, nullptr, nullptr, nullptr); - } - } - /* part text right aligned */ if (drawstr_right) { uchar col[4]; @@ -2624,8 +2609,8 @@ static void widget_state(uiWidgetType *wt, const uiWidgetStateInfo *state, eUIEm /* Add "hover" highlight. Ideally this could apply in all cases, * even if UI_SELECT. But currently this causes some flickering * as buttons can be created and updated without respect to mouse - * position and so can draw without UI_HOVER set. See D6503. */ - if (state->but_flag & UI_HOVER) { + * position and so can draw without UI_ACTIVE set. See D6503. */ + if (state->but_flag & UI_ACTIVE) { widget_active_color(&wt->wcol); } } @@ -2747,7 +2732,8 @@ static void widget_state_pie_menu_item(uiWidgetType *wt, { wt->wcol = *(wt->wcol_theme); - if ((state->but_flag & UI_BUT_DISABLED) && (state->but_flag & UI_HOVER)) { + /* active and disabled (not so common) */ + if ((state->but_flag & UI_BUT_DISABLED) && (state->but_flag & UI_ACTIVE)) { color_blend_v3_v3(wt->wcol.text, wt->wcol.text_sel, 0.5f); /* draw the backdrop at low alpha, helps navigating with keys * when disabled items are active */ @@ -2756,7 +2742,7 @@ static void widget_state_pie_menu_item(uiWidgetType *wt, } else { /* regular active */ - if (state->but_flag & (UI_SELECT | UI_HOVER)) { + if (state->but_flag & (UI_SELECT | UI_ACTIVE)) { copy_v3_v3_uchar(wt->wcol.text, wt->wcol.text_sel); } else if (state->but_flag & (UI_BUT_DISABLED | UI_BUT_INACTIVE)) { @@ -2767,7 +2753,7 @@ static void widget_state_pie_menu_item(uiWidgetType *wt, if (state->but_flag & UI_SELECT) { copy_v4_v4_uchar(wt->wcol.inner, wt->wcol.inner_sel); } - else if (state->but_flag & UI_HOVER) { + else if (state->but_flag & UI_ACTIVE) { copy_v4_v4_uchar(wt->wcol.inner, wt->wcol.item); } } @@ -2780,7 +2766,7 @@ static void widget_state_menu_item(uiWidgetType *wt, { wt->wcol = *(wt->wcol_theme); - if ((state->but_flag & UI_BUT_DISABLED) && (state->but_flag & UI_HOVER)) { + if ((state->but_flag & UI_BUT_DISABLED) && (state->but_flag & UI_ACTIVE)) { /* Hovering over disabled item. */ wt->wcol.text[3] = 128; color_blend_v3_v3(wt->wcol.inner, wt->wcol.text, 0.5f); @@ -2792,7 +2778,7 @@ static void widget_state_menu_item(uiWidgetType *wt, } else if (state->but_flag & UI_BUT_INACTIVE) { /* Inactive. */ - if (state->but_flag & UI_HOVER) { + if (state->but_flag & UI_ACTIVE) { color_blend_v3_v3(wt->wcol.inner, wt->wcol.text, 0.2f); wt->wcol.inner[3] = 255; } @@ -2810,7 +2796,7 @@ static void widget_state_menu_item(uiWidgetType *wt, copy_v4_v4_uchar(wt->wcol.inner, wt->wcol.inner_sel); copy_v4_v4_uchar(wt->wcol.text, wt->wcol.text_sel); } - else if (state->but_flag & UI_HOVER) { + else if (state->but_flag & UI_ACTIVE) { /* Regular hover. */ color_blend_v3_v3(wt->wcol.inner, wt->wcol.text, 0.2f); copy_v3_v3_uchar(wt->wcol.text, wt->wcol.text_sel); @@ -3444,7 +3430,7 @@ static void widget_numbut_draw(uiWidgetColors *wcol, } /* decoration */ - if ((state->but_flag & UI_HOVER) && !state->is_text_input) { + if ((state->but_flag & UI_ACTIVE) && !state->is_text_input) { uiWidgetColors wcol_zone; uiWidgetBase wtb_zone; rcti rect_zone; @@ -3457,7 +3443,7 @@ static void widget_numbut_draw(uiWidgetColors *wcol, wcol_zone = *wcol; copy_v3_v3_uchar(wcol_zone.item, wcol->text); - if (state->but_drawflag & UI_BUT_HOVER_LEFT) { + if (state->but_drawflag & UI_BUT_ACTIVE_LEFT) { widget_active_color(&wcol_zone); } @@ -3477,7 +3463,7 @@ static void widget_numbut_draw(uiWidgetColors *wcol, wcol_zone = *wcol; copy_v3_v3_uchar(wcol_zone.item, wcol->text); - if (state->but_drawflag & UI_BUT_HOVER_RIGHT) { + if (state->but_drawflag & UI_BUT_ACTIVE_RIGHT) { widget_active_color(&wcol_zone); } @@ -3496,7 +3482,7 @@ static void widget_numbut_draw(uiWidgetColors *wcol, wcol_zone = *wcol; copy_v3_v3_uchar(wcol_zone.item, wcol->text); - if (!(state->but_drawflag & (UI_BUT_HOVER_LEFT | UI_BUT_HOVER_RIGHT))) { + if (!(state->but_drawflag & (UI_BUT_ACTIVE_LEFT | UI_BUT_ACTIVE_RIGHT))) { widget_active_color(&wcol_zone); } @@ -4149,11 +4135,11 @@ static void widget_pulldownbut(uiWidgetColors *wcol, float back[4]; UI_GetThemeColor4fv(TH_BACK, back); - if ((state->but_flag & UI_HOVER) || (back[3] < 1.0f)) { + if ((state->but_flag & UI_ACTIVE) || (back[3] < 1.0f)) { uiWidgetBase wtb; const float rad = widget_radius_from_zoom(zoom, wcol); - if (state->but_flag & UI_HOVER) { + if (state->but_flag & UI_ACTIVE) { copy_v4_v4_uchar(wcol->inner, wcol->inner_sel); copy_v3_v3_uchar(wcol->text, wcol->text_sel); copy_v3_v3_uchar(wcol->outline, wcol->inner); @@ -4271,7 +4257,7 @@ static void widget_list_itembut(uiBut *but, const float rad = widget_radius_from_zoom(zoom, wcol); round_box_edges(&wtb, UI_CNR_ALL, &draw_rect, rad); - if (state->but_flag & UI_HOVER && !(state->but_flag & UI_SELECT)) { + if (state->but_flag & UI_ACTIVE && !(state->but_flag & UI_SELECT)) { copy_v3_v3_uchar(wcol->inner, wcol->text); wcol->inner[3] = 20; } @@ -5155,7 +5141,7 @@ void ui_draw_but(const bContext *C, ARegion *region, uiStyle *style, uiBut *but, #ifdef USE_UI_POPOVER_ONCE if (but->block->flag & UI_BLOCK_POPOVER_ONCE) { - if ((but->flag & UI_HOVER) && ui_but_is_popover_once_compat(but)) { + if ((but->flag & UI_ACTIVE) && ui_but_is_popover_once_compat(but)) { state.but_flag |= UI_BUT_ACTIVE_DEFAULT; } } diff --git a/source/blender/editors/interface/view2d.cc b/source/blender/editors/interface/view2d.cc index ab40ad18b23..aa2d572a1fc 100644 --- a/source/blender/editors/interface/view2d.cc +++ b/source/blender/editors/interface/view2d.cc @@ -16,6 +16,7 @@ #include "DNA_scene_types.h" #include "DNA_userdef_types.h" +#include "BLI_array.h" #include "BLI_easing.h" #include "BLI_link_utils.h" #include "BLI_listbase.h" @@ -385,7 +386,7 @@ void UI_view2d_region_reinit(View2D *v2d, short type, int winx, int winy) * Ensure View2D rects remain in a viable configuration * 'cur' is not allowed to be: larger than max, smaller than min, or outside of 'tot' */ -/* XXX pre2.5 -> this used to be called #test_view2d() */ +/* XXX pre2.5 -> this used to be called test_view2d() */ static void ui_view2d_curRect_validate_resize(View2D *v2d, bool resize) { float totwidth, totheight, curwidth, curheight, width, height; diff --git a/source/blender/editors/interface/view2d_draw.cc b/source/blender/editors/interface/view2d_draw.cc index 6c5f42a45bd..7b3541613bb 100644 --- a/source/blender/editors/interface/view2d_draw.cc +++ b/source/blender/editors/interface/view2d_draw.cc @@ -196,7 +196,7 @@ static void draw_parallel_lines(const ParallelLinesSet *lines, immBindBuiltinProgram(GPU_SHADER_3D_POLYLINE_UNIFORM_COLOR); immUniform2fv("viewportSize", &viewport[2]); - /* -1.0f offset here is because the line is too fat due to the builtin anti-aliasing. + /* -1.0f offset here is because the line is too fat due to the builtin anti-aliasing. * TODO: make a variant or a uniform to toggle it off. */ immUniform1f("lineWidth", U.pixelsize - 1.0f); } diff --git a/source/blender/editors/interface/view2d_ops.cc b/source/blender/editors/interface/view2d_ops.cc index f91a2c387f9..a4f01f16f6e 100644 --- a/source/blender/editors/interface/view2d_ops.cc +++ b/source/blender/editors/interface/view2d_ops.cc @@ -394,8 +394,8 @@ static int view_edge_pan_modal(bContext *C, wmOperator *op, const wmEvent *event View2DEdgePanData *vpd = static_cast(op->customdata); wmWindow *source_win = CTX_wm_window(C); - int event_xy_target[2]; - wmWindow *target_win = WM_window_find_under_cursor(source_win, event->xy, &event_xy_target[0]); + int r_mval[2]; + wmWindow *target_win = WM_window_find_under_cursor(source_win, event->xy, &r_mval[0]); /* Exit if we release the mouse button, hit escape, or enter a different window. */ if (event->val == KM_RELEASE || event->type == EVT_ESCKEY || source_win != target_win) { diff --git a/source/blender/editors/interface/views/tree_view.cc b/source/blender/editors/interface/views/tree_view.cc index 1cee9e0bcf6..c1180d5e7cd 100644 --- a/source/blender/editors/interface/views/tree_view.cc +++ b/source/blender/editors/interface/views/tree_view.cc @@ -242,9 +242,8 @@ AbstractTreeViewItem *AbstractTreeView::find_matching_child( /* ---------------------------------------------------------------------- */ -TreeViewItemDropTarget::TreeViewItemDropTarget(AbstractTreeViewItem &view_item, - DropBehavior behavior) - : view_item_(view_item), behavior_(behavior) +TreeViewItemDropTarget::TreeViewItemDropTarget(AbstractTreeView &view, DropBehavior behavior) + : view_(view), behavior_(behavior) { } @@ -255,11 +254,12 @@ std::optional TreeViewItemDropTarget::choose_drop_location( return DropLocation::Into; } - std::optional win_rect = view_item_.get_win_rect(region); - if (!win_rect) { - BLI_assert_unreachable(); + const AbstractTreeViewItem *hovered_item = view_.find_hovered(region, event.xy); + if (!hovered_item) { return std::nullopt; } + std::optional win_rect = hovered_item->get_win_rect(region); + BLI_assert(win_rect.has_value()); const float item_height = BLI_rctf_size_y(&*win_rect); BLI_assert(ELEM(behavior_, DropBehavior::Reorder, DropBehavior::ReorderAndInsert)); @@ -276,8 +276,8 @@ std::optional TreeViewItemDropTarget::choose_drop_location( return DropLocation::Before; } if (event.xy[1] - win_rect->ymin <= segment_height) { - if (behavior_ == DropBehavior::ReorderAndInsert && view_item_.is_collapsible() && - !view_item_.is_collapsed()) + if (behavior_ == DropBehavior::ReorderAndInsert && hovered_item->is_collapsible() && + !hovered_item->is_collapsed()) { /* Special case: Dropping at the lower 3rd of an uncollapsed item should insert into it, not * after. */ @@ -493,7 +493,7 @@ bool AbstractTreeViewItem::is_hovered() const * Get the matching button from the previous redraw instead. */ uiButViewItem *old_item_but = ui_block_view_find_matching_view_item_but_in_old_block( view_item_but_->block, this_item_handle); - return old_item_but && (old_item_but->flag & UI_HOVER); + return old_item_but && (old_item_but->flag & UI_ACTIVE); } bool AbstractTreeViewItem::is_collapsed() const diff --git a/source/blender/editors/io/io_usd.cc b/source/blender/editors/io/io_usd.cc index 714dcc02a3c..e8810b5e7d4 100644 --- a/source/blender/editors/io/io_usd.cc +++ b/source/blender/editors/io/io_usd.cc @@ -111,13 +111,6 @@ static void process_prim_path(char *prim_path) if (prim_path[0] == '/' && strlen(prim_path) == 1) { prim_path[0] = '\0'; } - - /* If a prim path doesn't start with a "/" it - * is invalid when creating the prim. */ - if (prim_path[0] != '/') { - auto new_path = "/" + std::string(prim_path); - snprintf(prim_path, FILE_MAX, "%s", new_path.c_str()); - } } static int wm_usd_export_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/) @@ -186,7 +179,7 @@ static int wm_usd_export_exec(bContext *C, wmOperator *op) STRNCPY(params.root_prim_path, root_prim_path); - bool ok = USD_export(C, filepath, ¶ms, as_background_job, op->reports); + bool ok = USD_export(C, filepath, ¶ms, as_background_job); return as_background_job || ok ? OPERATOR_FINISHED : OPERATOR_CANCELLED; } @@ -378,7 +371,7 @@ void WM_OT_usd_export(wmOperatorType *ot) RNA_def_string(ot->srna, "root_prim_path", - "/root", + nullptr, FILE_MAX, "Root Prim", "If set, add a transform primitive with the given path to the stage " @@ -522,7 +515,7 @@ static int wm_usd_import_exec(bContext *C, wmOperator *op) STRNCPY(params.import_textures_dir, import_textures_dir); - const bool ok = USD_import(C, filepath, ¶ms, as_background_job, op->reports); + const bool ok = USD_import(C, filepath, ¶ms, as_background_job); return as_background_job || ok ? OPERATOR_FINISHED : OPERATOR_CANCELLED; } diff --git a/source/blender/editors/lattice/editlattice_select.cc b/source/blender/editors/lattice/editlattice_select.cc index c88118ef92d..9d02d7c8dfe 100644 --- a/source/blender/editors/lattice/editlattice_select.cc +++ b/source/blender/editors/lattice/editlattice_select.cc @@ -75,7 +75,8 @@ bool ED_lattice_deselect_all_multi_ex(Base **bases, const uint bases_len) bool ED_lattice_deselect_all_multi(bContext *C) { Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph); + ViewContext vc; + ED_view3d_viewcontext_init(C, &vc, depsgraph); uint bases_len = 0; Base **bases = BKE_view_layer_array_from_bases_in_edit_mode_unique_data( vc.scene, vc.view_layer, vc.v3d, &bases_len); @@ -619,11 +620,12 @@ static BPoint *findnearestLattvert(ViewContext *vc, bool select, Base **r_base) bool ED_lattice_select_pick(bContext *C, const int mval[2], const SelectPick_Params *params) { Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + ViewContext vc; BPoint *bp = nullptr; Base *basact = nullptr; bool changed = false; - ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph); + ED_view3d_viewcontext_init(C, &vc, depsgraph); vc.mval[0] = mval[0]; vc.mval[1] = mval[1]; diff --git a/source/blender/editors/mask/CMakeLists.txt b/source/blender/editors/mask/CMakeLists.txt index 49005421cce..1fbf5529a7d 100644 --- a/source/blender/editors/mask/CMakeLists.txt +++ b/source/blender/editors/mask/CMakeLists.txt @@ -32,7 +32,6 @@ set(LIB PRIVATE bf::blenlib PRIVATE bf::dna PRIVATE bf::intern::guardedalloc - PRIVATE bf::animrig ) diff --git a/source/blender/editors/mask/mask_ops.cc b/source/blender/editors/mask/mask_ops.cc index ffe07863d73..e544bc29e08 100644 --- a/source/blender/editors/mask/mask_ops.cc +++ b/source/blender/editors/mask/mask_ops.cc @@ -28,12 +28,11 @@ #include "ED_clip.hh" #include "ED_image.hh" +#include "ED_keyframing.hh" #include "ED_mask.hh" #include "ED_screen.hh" #include "ED_select_utils.hh" -#include "ANIM_keyframing.hh" - #include "RNA_access.hh" #include "RNA_define.hh" @@ -868,7 +867,7 @@ static int slide_point_modal(bContext *C, wmOperator *op, const wmEvent *event) /* Don't key sliding feather UW's. */ if ((data->action == SLIDE_ACTION_FEATHER && data->uw) == false) { - if (blender::animrig::is_autokey_on(scene)) { + if (IS_AUTOKEY_ON(scene)) { ED_mask_layer_shape_auto_key(data->mask_layer, scene->r.cfra); } } @@ -1278,7 +1277,7 @@ static int slide_spline_curvature_modal(bContext *C, wmOperator *op, const wmEve case RIGHTMOUSE: if (event->type == slide_data->event_invoke_type && event->val == KM_RELEASE) { /* Don't key sliding feather UW's. */ - if (blender::animrig::is_autokey_on(scene)) { + if (IS_AUTOKEY_ON(scene)) { ED_mask_layer_shape_auto_key(slide_data->mask_layer, scene->r.cfra); } @@ -1542,7 +1541,7 @@ static int mask_switch_direction_exec(bContext *C, wmOperator * /*op*/) } if (changed_layer) { - if (blender::animrig::is_autokey_on(scene)) { + if (IS_AUTOKEY_ON(scene)) { ED_mask_layer_shape_auto_key(mask_layer, scene->r.cfra); } } @@ -1604,7 +1603,7 @@ static int mask_normals_make_consistent_exec(bContext *C, wmOperator * /*op*/) } if (changed_layer) { - if (blender::animrig::is_autokey_on(scene)) { + if (IS_AUTOKEY_ON(scene)) { ED_mask_layer_shape_auto_key(mask_layer, scene->r.cfra); } } diff --git a/source/blender/editors/mesh/editface.cc b/source/blender/editors/mesh/editface.cc index 202488edc68..fd7c20354e7 100644 --- a/source/blender/editors/mesh/editface.cc +++ b/source/blender/editors/mesh/editface.cc @@ -456,7 +456,8 @@ void paintface_select_loop(bContext *C, Object *ob, const int mval[2], const boo using namespace blender; Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph); + ViewContext vc; + ED_view3d_viewcontext_init(C, &vc, depsgraph); ED_view3d_select_id_validate(&vc); Object *ob_eval = DEG_get_evaluated_object(depsgraph, ob); diff --git a/source/blender/editors/mesh/editmesh_attribute.cc b/source/blender/editors/mesh/editmesh_attribute.cc index 1e676712f47..09e5d981781 100644 --- a/source/blender/editors/mesh/editmesh_attribute.cc +++ b/source/blender/editors/mesh/editmesh_attribute.cc @@ -25,7 +25,6 @@ #include "RNA_define.hh" #include "RNA_enum_types.hh" -#include "ED_geometry.hh" #include "ED_mesh.hh" #include "ED_object.hh" #include "ED_screen.hh" @@ -89,6 +88,33 @@ static bool mesh_active_attribute_poll(bContext *C) namespace set_attribute { +static StringRefNull rna_property_name_for_type(const eCustomDataType type) +{ + switch (type) { + case CD_PROP_FLOAT: + return "value_float"; + case CD_PROP_FLOAT2: + return "value_float_vector_2d"; + case CD_PROP_FLOAT3: + return "value_float_vector_3d"; + case CD_PROP_COLOR: + case CD_PROP_BYTE_COLOR: + return "value_color"; + case CD_PROP_BOOL: + return "value_bool"; + case CD_PROP_INT8: + case CD_PROP_INT32: + return "value_int"; + case CD_PROP_INT32_2D: + return "value_int_vector_2d"; + case CD_PROP_QUATERNION: + return "value_quat"; + default: + BLI_assert_unreachable(); + return ""; + } +} + static void bmesh_vert_edge_face_layer_selected_values_set(BMesh &bm, const BMIterType iter_type, const GPointer value, @@ -160,9 +186,48 @@ static int mesh_set_attribute_exec(bContext *C, wmOperator *op) BUFFER_FOR_CPP_TYPE_VALUE(type, buffer); BLI_SCOPED_DEFER([&]() { type.destruct(buffer); }); - const GPointer value = geometry::rna_property_for_attribute_type_retrieve_value( - *op->ptr, active_type, buffer); + const StringRefNull prop_name = rna_property_name_for_type(active_type); + switch (active_type) { + case CD_PROP_FLOAT: + *static_cast(buffer) = RNA_float_get(op->ptr, prop_name.c_str()); + break; + case CD_PROP_FLOAT2: + RNA_float_get_array(op->ptr, prop_name.c_str(), static_cast(buffer)); + break; + case CD_PROP_FLOAT3: + RNA_float_get_array(op->ptr, prop_name.c_str(), static_cast(buffer)); + break; + case CD_PROP_COLOR: + RNA_float_get_array(op->ptr, prop_name.c_str(), static_cast(buffer)); + break; + case CD_PROP_QUATERNION: { + float4 value; + RNA_float_get_array(op->ptr, prop_name.c_str(), value); + *static_cast(buffer) = math::normalize(math::Quaternion(value)); + break; + } + case CD_PROP_BYTE_COLOR: + ColorGeometry4f value; + RNA_float_get_array(op->ptr, prop_name.c_str(), value); + *static_cast(buffer) = value.encode(); + break; + case CD_PROP_BOOL: + *static_cast(buffer) = RNA_boolean_get(op->ptr, prop_name.c_str()); + break; + case CD_PROP_INT8: + *static_cast(buffer) = RNA_int_get(op->ptr, prop_name.c_str()); + break; + case CD_PROP_INT32: + *static_cast(buffer) = RNA_int_get(op->ptr, prop_name.c_str()); + break; + case CD_PROP_INT32_2D: + RNA_int_get_array(op->ptr, prop_name.c_str(), static_cast(buffer)); + break; + default: + BLI_assert_unreachable(); + } + const GPointer value(type, buffer); const bke::DataTypeConversions &conversions = bke::get_implicit_type_conversions(); bool changed = false; @@ -240,12 +305,48 @@ static int mesh_set_attribute_invoke(bContext *C, wmOperator *op, const wmEvent return WM_operator_props_popup(C, op, event); } + const StringRefNull prop_name = rna_property_name_for_type(data_type); const CPPType &type = *bke::custom_data_type_to_cpp_type(data_type); const GPointer active_value(type, POINTER_OFFSET(active_elem->head.data, layer->offset)); - PropertyRNA *prop = geometry::rna_property_for_type(*op->ptr, data_type); + PropertyRNA *prop = RNA_struct_find_property(op->ptr, prop_name.c_str()); if (!RNA_property_is_set(op->ptr, prop)) { - geometry::rna_property_for_attribute_type_set_value(*op->ptr, *prop, active_value); + switch (data_type) { + case CD_PROP_FLOAT: + RNA_property_float_set(op->ptr, prop, *active_value.get()); + break; + case CD_PROP_FLOAT2: + RNA_property_float_set_array(op->ptr, prop, *active_value.get()); + break; + case CD_PROP_FLOAT3: + RNA_property_float_set_array(op->ptr, prop, *active_value.get()); + break; + case CD_PROP_BYTE_COLOR: + RNA_property_float_set_array(op->ptr, prop, active_value.get()->decode()); + break; + case CD_PROP_COLOR: + RNA_property_float_set_array(op->ptr, prop, *active_value.get()); + break; + case CD_PROP_BOOL: + RNA_property_boolean_set(op->ptr, prop, *active_value.get()); + break; + case CD_PROP_INT8: + RNA_property_int_set(op->ptr, prop, *active_value.get()); + break; + case CD_PROP_INT32: + RNA_property_int_set(op->ptr, prop, *active_value.get()); + break; + case CD_PROP_INT32_2D: + RNA_property_int_set_array(op->ptr, prop, *active_value.get()); + break; + case CD_PROP_QUATERNION: { + const math::Quaternion value = math::normalize(*active_value.get()); + RNA_property_float_set_array(op->ptr, prop, float4(value)); + break; + } + default: + BLI_assert_unreachable(); + } } return WM_operator_props_popup(C, op, event); @@ -260,7 +361,7 @@ static void mesh_set_attribute_ui(bContext *C, wmOperator *op) Mesh *mesh = ED_mesh_context(C); CustomDataLayer *active_attribute = BKE_id_attributes_active_get(&mesh->id); const eCustomDataType active_type = eCustomDataType(active_attribute->type); - const StringRefNull prop_name = geometry::rna_property_name_for_type(active_type); + const StringRefNull prop_name = rna_property_name_for_type(active_type); const char *name = active_attribute->name; uiItemR(layout, op->ptr, prop_name.c_str(), UI_ITEM_NONE, name, ICON_NONE); } @@ -284,7 +385,53 @@ void MESH_OT_attribute_set(wmOperatorType *ot) ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - blender::ed::geometry::register_rna_properties_for_attribute_types(*ot->srna); + static blender::float4 color_default(1); + + RNA_def_float(ot->srna, "value_float", 0.0f, -FLT_MAX, FLT_MAX, "Value", "", -FLT_MAX, FLT_MAX); + RNA_def_float_array(ot->srna, + "value_float_vector_2d", + 2, + nullptr, + -FLT_MAX, + FLT_MAX, + "Value", + "", + -FLT_MAX, + FLT_MAX); + RNA_def_float_array(ot->srna, + "value_float_vector_3d", + 3, + nullptr, + -FLT_MAX, + FLT_MAX, + "Value", + "", + -FLT_MAX, + FLT_MAX); + RNA_def_int(ot->srna, "value_int", 0, INT_MIN, INT_MAX, "Value", "", INT_MIN, INT_MAX); + RNA_def_int_array(ot->srna, + "value_int_vector_2d", + 2, + nullptr, + INT_MIN, + INT_MAX, + "Value", + "", + INT_MIN, + INT_MAX); + RNA_def_float_color( + ot->srna, "value_color", 4, color_default, -FLT_MAX, FLT_MAX, "Value", "", 0.0f, 1.0f); + RNA_def_boolean(ot->srna, "value_bool", false, "Value", ""); + RNA_def_float_array(ot->srna, + "value_quat", + 4, + rna_default_quaternion, + -FLT_MAX, + FLT_MAX, + "Value", + "", + FLT_MAX, + FLT_MAX); } /** \} */ diff --git a/source/blender/editors/mesh/editmesh_bevel.cc b/source/blender/editors/mesh/editmesh_bevel.cc index 4d79dd0abfe..1669483eb66 100644 --- a/source/blender/editors/mesh/editmesh_bevel.cc +++ b/source/blender/editors/mesh/editmesh_bevel.cc @@ -336,13 +336,20 @@ static bool edbm_bevel_calc(wmOperator *op) const int material = CLAMPIS(material_init, -1, obedit->totcol - 1); + Mesh *me = static_cast(obedit->data); + + if (harden_normals && !(me->flag & ME_AUTOSMOOTH)) { + /* `harden_normals` only has a visible effect if auto-smooth is on, so turn it on. */ + me->flag |= ME_AUTOSMOOTH; + } + EDBM_op_init(em, &bmop, op, "bevel geom=%hev offset=%f segments=%i affect=%i offset_type=%i " "profile_type=%i profile=%f clamp_overlap=%b material=%i loop_slide=%b " "mark_seam=%b mark_sharp=%b harden_normals=%b face_strength_mode=%i " - "miter_outer=%i miter_inner=%i spread=%f custom_profile=%p " + "miter_outer=%i miter_inner=%i spread=%f smoothresh=%f custom_profile=%p " "vmesh_method=%i", BM_ELEM_SELECT, offset, @@ -361,6 +368,7 @@ static bool edbm_bevel_calc(wmOperator *op) miter_outer, miter_inner, spread, + me->smoothresh, opdata->custom_profile, vmesh_method); diff --git a/source/blender/editors/mesh/editmesh_extrude.cc b/source/blender/editors/mesh/editmesh_extrude.cc index 1281b520d6b..f5d374c4cb9 100644 --- a/source/blender/editors/mesh/editmesh_extrude.cc +++ b/source/blender/editors/mesh/editmesh_extrude.cc @@ -703,12 +703,13 @@ void MESH_OT_extrude_faces_indiv(wmOperatorType *ot) static int edbm_dupli_extrude_cursor_invoke(bContext *C, wmOperator *op, const wmEvent *event) { Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + ViewContext vc; BMVert *v1; BMIter iter; float center[3]; uint verts_len; - ViewContext vc = em_setup_viewcontext(C); + em_setup_viewcontext(C, &vc); const Object *object_active = vc.obact; const bool rot_src = RNA_boolean_get(op->ptr, "rotate_source"); diff --git a/source/blender/editors/mesh/editmesh_inset.cc b/source/blender/editors/mesh/editmesh_inset.cc index 8936e62764b..39071f60ea2 100644 --- a/source/blender/editors/mesh/editmesh_inset.cc +++ b/source/blender/editors/mesh/editmesh_inset.cc @@ -262,7 +262,7 @@ static bool edbm_inset_calc(wmOperator *op) EDBM_op_init(em, &bmop, op, - "inset_individual faces=%hf use_even_offset=%b use_relative_offset=%b " + "inset_individual faces=%hf use_even_offset=%b use_relative_offset=%b " "use_interpolate=%b thickness=%f depth=%f", BM_ELEM_SELECT, use_even_offset, diff --git a/source/blender/editors/mesh/editmesh_knife.cc b/source/blender/editors/mesh/editmesh_knife.cc index dc755a97bbc..01fb3b41c3e 100644 --- a/source/blender/editors/mesh/editmesh_knife.cc +++ b/source/blender/editors/mesh/editmesh_knife.cc @@ -4810,9 +4810,10 @@ static int knifetool_invoke(bContext *C, wmOperator *op, const wmEvent *event) const float angle_snapping_increment = RAD2DEGF( RNA_float_get(op->ptr, "angle_snapping_increment")); + ViewContext vc; KnifeTool_OpData *kcd; - ViewContext vc = em_setup_viewcontext(C); + em_setup_viewcontext(C, &vc); /* alloc new customdata */ kcd = static_cast( diff --git a/source/blender/editors/mesh/editmesh_knife_project.cc b/source/blender/editors/mesh/editmesh_knife_project.cc index 4b375161bf9..d237bbeeb49 100644 --- a/source/blender/editors/mesh/editmesh_knife_project.cc +++ b/source/blender/editors/mesh/editmesh_knife_project.cc @@ -123,7 +123,8 @@ static int knifeproject_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } - ViewContext vc = em_setup_viewcontext(C); + ViewContext vc; + em_setup_viewcontext(C, &vc); uint objects_len; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( diff --git a/source/blender/editors/mesh/editmesh_loopcut.cc b/source/blender/editors/mesh/editmesh_loopcut.cc index 4b298035c3c..65e717faa0f 100644 --- a/source/blender/editors/mesh/editmesh_loopcut.cc +++ b/source/blender/editors/mesh/editmesh_loopcut.cc @@ -282,7 +282,7 @@ static int ringsel_init(bContext *C, wmOperator *op, bool do_cut) lcd = static_cast( op->customdata = MEM_callocN(sizeof(RingSelOpData), "ringsel Modal Op Data")); - lcd->vc = em_setup_viewcontext(C); + em_setup_viewcontext(C, &lcd->vc); lcd->depsgraph = CTX_data_ensure_evaluated_depsgraph(C); @@ -554,7 +554,7 @@ static int loopcut_modal(bContext *C, wmOperator *op, const wmEvent *event) bool show_cuts = false; const bool has_numinput = hasNumInput(&lcd->num); - lcd->vc = em_setup_viewcontext(C); + em_setup_viewcontext(C, &lcd->vc); lcd->region = lcd->vc.region; view3d_operator_needs_opengl(C); diff --git a/source/blender/editors/mesh/editmesh_path.cc b/source/blender/editors/mesh/editmesh_path.cc index f9b0feed2a3..aa8bfb7a61a 100644 --- a/source/blender/editors/mesh/editmesh_path.cc +++ b/source/blender/editors/mesh/editmesh_path.cc @@ -713,9 +713,10 @@ static int edbm_shortest_path_pick_invoke(bContext *C, wmOperator *op, const wmE BMEdge *eed = nullptr; BMFace *efa = nullptr; + ViewContext vc; bool track_active = true; - ViewContext vc = em_setup_viewcontext(C); + em_setup_viewcontext(C, &vc); copy_v2_v2_int(vc.mval, event->mval); BKE_view_layer_synced_ensure(vc.scene, vc.view_layer); Base *basact = BKE_view_layer_active_base_get(vc.view_layer); diff --git a/source/blender/editors/mesh/editmesh_polybuild.cc b/source/blender/editors/mesh/editmesh_polybuild.cc index c132ff0f90c..4db31cc18e9 100644 --- a/source/blender/editors/mesh/editmesh_polybuild.cc +++ b/source/blender/editors/mesh/editmesh_polybuild.cc @@ -104,25 +104,27 @@ static bool edbm_preselect_or_active(bContext *C, const View3D *v3d, Base **r_ba return (*r_ele != nullptr); } -static ViewContext edbm_preselect_or_active_init_viewcontext(bContext *C, - Base **r_base, - BMElem **r_ele) +static bool edbm_preselect_or_active_init_viewcontext(bContext *C, + ViewContext *vc, + Base **r_base, + BMElem **r_ele) { - ViewContext vc = em_setup_viewcontext(C); - bool ok = edbm_preselect_or_active(C, vc.v3d, r_base, r_ele); + em_setup_viewcontext(C, vc); + bool ok = edbm_preselect_or_active(C, vc->v3d, r_base, r_ele); if (ok) { - ED_view3d_viewcontext_init_object(&vc, (*r_base)->object); + ED_view3d_viewcontext_init_object(vc, (*r_base)->object); } - return vc; + return ok; } static int edbm_polybuild_transform_at_cursor_invoke(bContext *C, wmOperator * /*op*/, const wmEvent * /*event*/) { + ViewContext vc; Base *basact = nullptr; BMElem *ele_act = nullptr; - ViewContext vc = edbm_preselect_or_active_init_viewcontext(C, &basact, &ele_act); + edbm_preselect_or_active_init_viewcontext(C, &vc, &basact, &ele_act); BMEditMesh *em = vc.em; BMesh *bm = em->bm; @@ -185,9 +187,11 @@ static int edbm_polybuild_delete_at_cursor_invoke(bContext *C, const wmEvent * /*event*/) { bool changed = false; + + ViewContext vc; Base *basact = nullptr; BMElem *ele_act = nullptr; - ViewContext vc = edbm_preselect_or_active_init_viewcontext(C, &basact, &ele_act); + edbm_preselect_or_active_init_viewcontext(C, &vc, &basact, &ele_act); BMEditMesh *em = vc.em; BMesh *bm = em->bm; @@ -278,9 +282,10 @@ static int edbm_polybuild_face_at_cursor_invoke(bContext *C, wmOperator *op, con float center[3]; bool changed = false; + ViewContext vc; Base *basact = nullptr; BMElem *ele_act = nullptr; - ViewContext vc = edbm_preselect_or_active_init_viewcontext(C, &basact, &ele_act); + edbm_preselect_or_active_init_viewcontext(C, &vc, &basact, &ele_act); BMEditMesh *em = vc.em; BMesh *bm = em->bm; @@ -455,9 +460,10 @@ static int edbm_polybuild_split_at_cursor_invoke(bContext *C, float center[3]; bool changed = false; + ViewContext vc; Base *basact = nullptr; BMElem *ele_act = nullptr; - ViewContext vc = edbm_preselect_or_active_init_viewcontext(C, &basact, &ele_act); + edbm_preselect_or_active_init_viewcontext(C, &vc, &basact, &ele_act); BMEditMesh *em = vc.em; BMesh *bm = em->bm; @@ -538,9 +544,10 @@ static int edbm_polybuild_dissolve_at_cursor_invoke(bContext *C, { bool changed = false; + ViewContext vc; Base *basact = nullptr; BMElem *ele_act = nullptr; - ViewContext vc = edbm_preselect_or_active_init_viewcontext(C, &basact, &ele_act); + edbm_preselect_or_active_init_viewcontext(C, &vc, &basact, &ele_act); BMEditMesh *em = vc.em; BMesh *bm = em->bm; diff --git a/source/blender/editors/mesh/editmesh_rip.cc b/source/blender/editors/mesh/editmesh_rip.cc index 40801eb1795..7097eda25cb 100644 --- a/source/blender/editors/mesh/editmesh_rip.cc +++ b/source/blender/editors/mesh/editmesh_rip.cc @@ -10,10 +10,9 @@ #include "DNA_object_types.h" +#include "BLI_array.h" #include "BLI_math_geom.h" #include "BLI_math_vector.h" -#include "BLI_span.hh" -#include "BLI_vector.hh" #include "BKE_context.h" #include "BKE_editmesh.h" @@ -37,9 +36,6 @@ #include "mesh_intern.h" /* own include */ -using blender::Span; -using blender::Vector; - /* -------------------------------------------------------------------- */ /** \name Local Utilities * \{ */ @@ -225,7 +221,7 @@ struct EdgeLoopPair { BMLoop *l_b; }; -static Vector edbm_ripsel_looptag_helper(BMesh *bm) +static EdgeLoopPair *edbm_ripsel_looptag_helper(BMesh *bm) { BMIter fiter; BMIter liter; @@ -237,6 +233,10 @@ static Vector edbm_ripsel_looptag_helper(BMesh *bm) int uid_end; int uid = bm->totedge; /* can start anywhere */ + EdgeLoopPair *eloop_pairs = nullptr; + BLI_array_declare(eloop_pairs); + EdgeLoopPair *lp; + /* initialize loops with dummy invalid index values */ BM_ITER_MESH (f, &fiter, bm, BM_FACES_OF_MESH) { BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) { @@ -245,8 +245,6 @@ static Vector edbm_ripsel_looptag_helper(BMesh *bm) } bm->elem_index_dirty |= BM_LOOP; - Vector eloop_pairs; - /* set contiguous loops ordered 'uid' values for walking after split */ while (true) { int tot = 0; @@ -301,10 +299,9 @@ static Vector edbm_ripsel_looptag_helper(BMesh *bm) uid_start = uid; uid = uid_end + bm->totedge; + lp = BLI_array_append_ret(eloop_pairs); /* no need to check, we know this will be true */ - EdgeLoopPair lp; - BM_edge_loop_pair(e_last, &lp.l_a, &lp.l_b); - eloop_pairs.append(lp); + BM_edge_loop_pair(e_last, &lp->l_a, &lp->l_b); BLI_assert(tot == uid_end - uid_start); UNUSED_VARS_NDEBUG(tot); @@ -314,6 +311,10 @@ static Vector edbm_ripsel_looptag_helper(BMesh *bm) #endif } + /* null terminate */ + lp = BLI_array_append_ret(eloop_pairs); + lp->l_a = lp->l_b = nullptr; + return eloop_pairs; } @@ -347,30 +348,32 @@ static BMVert *edbm_ripsel_edloop_pair_start_vert(BMEdge *e) } static void edbm_ripsel_deselect_helper(BMesh *bm, - const Span eloop_pairs, + EdgeLoopPair *eloop_pairs, ARegion *region, float projectMat[4][4], const float fmval[2]) { - for (const EdgeLoopPair &lp : eloop_pairs) { + EdgeLoopPair *lp; + + for (lp = eloop_pairs; lp->l_a; lp++) { BMEdge *e; BMVert *v_prev; float score_a = 0.0f; float score_b = 0.0f; - e = lp.l_a->e; + e = lp->l_a->e; v_prev = edbm_ripsel_edloop_pair_start_vert(e); for (; e; e = edbm_ripsel_edge_uid_step(e, &v_prev)) { score_a += edbm_rip_edge_side_measure(e, e->l, region, projectMat, fmval); } - e = lp.l_b->e; + e = lp->l_b->e; v_prev = edbm_ripsel_edloop_pair_start_vert(e); for (; e; e = edbm_ripsel_edge_uid_step(e, &v_prev)) { score_b += edbm_rip_edge_side_measure(e, e->l, region, projectMat, fmval); } - e = (score_a > score_b) ? lp.l_a->e : lp.l_b->e; + e = (score_a > score_b) ? lp->l_a->e : lp->l_b->e; v_prev = edbm_ripsel_edloop_pair_start_vert(e); for (; e; e = edbm_ripsel_edge_uid_step(e, &v_prev)) { BM_edge_select_set(bm, e, false); @@ -900,10 +903,12 @@ static int edbm_rip_invoke__edge(bContext *C, const wmEvent *event, Object *obed const int totedge_orig = bm->totedge; float projectMat[4][4], fmval[3] = {float(event->mval[0]), float(event->mval[1])}; + EdgeLoopPair *eloop_pairs; + ED_view3d_ob_project_mat_get(rv3d, obedit, projectMat); /* important this runs on the original selection, before tampering with tagging */ - Vector eloop_pairs = edbm_ripsel_looptag_helper(bm); + eloop_pairs = edbm_ripsel_looptag_helper(bm); /* expand edge selection */ BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) { @@ -993,6 +998,7 @@ static int edbm_rip_invoke__edge(bContext *C, const wmEvent *event, Object *obed * as expected (but not crash), however there are checks to ensure * tagged edges will split. So far its not been an issue. */ edbm_ripsel_deselect_helper(bm, eloop_pairs, region, projectMat, fmval); + MEM_freeN(eloop_pairs); /* deselect loose verts */ BM_mesh_select_mode_clean_ex(bm, SCE_SELECT_EDGE); diff --git a/source/blender/editors/mesh/editmesh_select.cc b/source/blender/editors/mesh/editmesh_select.cc index 8000b16a7a6..511e7b287b3 100644 --- a/source/blender/editors/mesh/editmesh_select.cc +++ b/source/blender/editors/mesh/editmesh_select.cc @@ -60,7 +60,7 @@ #include "DEG_depsgraph.hh" #include "DEG_depsgraph_query.hh" -#include "DRW_select_buffer.hh" +#include "DRW_select_buffer.h" #include "mesh_intern.h" /* own include */ @@ -280,7 +280,7 @@ BMVert *EDBM_vert_find_nearest_ex(ViewContext *vc, /* No after-queue (yet), so we check it now, otherwise the bm_xxxofs indices are bad. */ { - DRW_select_buffer_context_create(vc->depsgraph, bases, bases_len, SCE_SELECT_VERTEX); + DRW_select_buffer_context_create(bases, bases_len, SCE_SELECT_VERTEX); index = DRW_select_buffer_find_nearest_to_point( vc->depsgraph, vc->region, vc->v3d, vc->mval, 1, UINT_MAX, &dist_px_manhattan_test); @@ -511,7 +511,7 @@ BMEdge *EDBM_edge_find_nearest_ex(ViewContext *vc, /* No after-queue (yet), so we check it now, otherwise the bm_xxxofs indices are bad. */ { - DRW_select_buffer_context_create(vc->depsgraph, bases, bases_len, SCE_SELECT_EDGE); + DRW_select_buffer_context_create(bases, bases_len, SCE_SELECT_EDGE); index = DRW_select_buffer_find_nearest_to_point( vc->depsgraph, vc->region, vc->v3d, vc->mval, 1, UINT_MAX, &dist_px_manhattan_test); @@ -731,7 +731,7 @@ BMFace *EDBM_face_find_nearest_ex(ViewContext *vc, ED_view3d_backbuf_sample_size_clamp(vc->region, *dist_px_manhattan_p)); } - DRW_select_buffer_context_create(vc->depsgraph, bases, bases_len, SCE_SELECT_FACE); + DRW_select_buffer_context_create(bases, bases_len, SCE_SELECT_FACE); if (dist_px_manhattan_test == 0) { index = DRW_select_buffer_sample_point(vc->depsgraph, vc->region, vc->v3d, vc->mval); @@ -1691,13 +1691,14 @@ static bool mouse_mesh_loop( BMEdge *eed = nullptr; BMFace *efa = nullptr; + ViewContext vc; BMEditMesh *em; bool select = true; bool select_clear = false; bool select_cycle = true; float mvalf[2]; - ViewContext vc = em_setup_viewcontext(C); + em_setup_viewcontext(C, &vc); mvalf[0] = float(vc.mval[0] = mval[0]); mvalf[1] = float(vc.mval[1] = mval[1]); @@ -2050,13 +2051,15 @@ void MESH_OT_select_interior_faces(wmOperatorType *ot) bool EDBM_select_pick(bContext *C, const int mval[2], const SelectPick_Params *params) { + ViewContext vc; + int base_index_active = -1; BMVert *eve = nullptr; BMEdge *eed = nullptr; BMFace *efa = nullptr; /* setup view context for argument to callbacks */ - ViewContext vc = em_setup_viewcontext(C); + em_setup_viewcontext(C, &vc); vc.mval[0] = mval[0]; vc.mval[1] = mval[1]; @@ -2736,7 +2739,8 @@ bool EDBM_mesh_deselect_all_multi_ex(Base **bases, const uint bases_len) bool EDBM_mesh_deselect_all_multi(bContext *C) { Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph); + ViewContext vc; + ED_view3d_viewcontext_init(C, &vc, depsgraph); uint bases_len = 0; Base **bases = BKE_view_layer_array_from_bases_in_edit_mode_unique_data( vc.scene, vc.view_layer, vc.v3d, &bases_len); @@ -2770,7 +2774,8 @@ bool EDBM_selectmode_disable_multi(bContext *C, { Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); Scene *scene = CTX_data_scene(C); - ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph); + ViewContext vc; + ED_view3d_viewcontext_init(C, &vc, depsgraph); uint bases_len = 0; Base **bases = BKE_view_layer_array_from_bases_in_edit_mode_unique_data( vc.scene, vc.view_layer, nullptr, &bases_len); @@ -3612,6 +3617,7 @@ static void edbm_select_linked_pick_ex(BMEditMesh *em, BMElem *ele, bool sel, in static int edbm_select_linked_pick_invoke(bContext *C, wmOperator *op, const wmEvent *event) { + ViewContext vc; Base *basact = nullptr; BMVert *eve; BMEdge *eed; @@ -3627,7 +3633,7 @@ static int edbm_select_linked_pick_invoke(bContext *C, wmOperator *op, const wmE view3d_operator_needs_opengl(C); /* setup view context for argument to callbacks */ - ViewContext vc = em_setup_viewcontext(C); + em_setup_viewcontext(C, &vc); uint bases_len; Base **bases = BKE_view_layer_array_from_bases_in_edit_mode( @@ -4382,15 +4388,14 @@ void MESH_OT_select_nth(wmOperatorType *ot) WM_operator_properties_checker_interval(ot, false); } -ViewContext em_setup_viewcontext(bContext *C) +void em_setup_viewcontext(bContext *C, ViewContext *vc) { Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph); + ED_view3d_viewcontext_init(C, vc, depsgraph); - if (vc.obedit) { - vc.em = BKE_editmesh_from_object(vc.obedit); + if (vc->obedit) { + vc->em = BKE_editmesh_from_object(vc->obedit); } - return vc; } /** \} */ diff --git a/source/blender/editors/mesh/editmesh_tools.cc b/source/blender/editors/mesh/editmesh_tools.cc index a9a88eba6c1..0cd5b4f6812 100644 --- a/source/blender/editors/mesh/editmesh_tools.cc +++ b/source/blender/editors/mesh/editmesh_tools.cc @@ -4210,9 +4210,9 @@ static float bm_edge_seg_isect(const float sco_a[2], y2max = max_ff(y21, y22) + 0.001f; y2min = min_ff(y21, y22) - 0.001f; - /* Found an intersect, calc intersect point. */ - if (m1 == m2) { /* Co-incident lines. */ - /* Cut at 50% of overlap area. */ + /* Found an intersect, calc intersect point */ + if (m1 == m2) { /* co-incident lines */ + /* cut at 50% of overlap area */ x1max = max_ff(x11, x12); x1min = min_ff(x11, x12); xi = (min_ff(x2max, x1max) + max_ff(x2min, x1min)) / 2.0f; @@ -8449,7 +8449,8 @@ static bool point_normals_init(bContext *C, wmOperator *op) BMEditMesh *em = BKE_editmesh_from_object(obedit); BMesh *bm = em->bm; - BKE_editmesh_lnorspace_update(em); + BKE_editmesh_ensure_autosmooth(em, static_cast(obedit->data)); + BKE_editmesh_lnorspace_update(em, static_cast(obedit->data)); BMLoopNorEditDataArray *lnors_ed_arr = BM_loop_normal_editdata_array_init(bm, false); op->customdata = lnors_ed_arr; @@ -9074,7 +9075,8 @@ static int normals_split_merge(bContext *C, const bool do_merge) BMEdge *e; BMIter eiter; - BKE_editmesh_lnorspace_update(em); + BKE_editmesh_ensure_autosmooth(em, static_cast(obedit->data)); + BKE_editmesh_lnorspace_update(em, static_cast(obedit->data)); /* Note that we need temp lnor editing data for all loops of all affected vertices, since by * setting some faces/edges as smooth we are going to change clnors spaces... See also #65809. @@ -9092,7 +9094,7 @@ static int normals_split_merge(bContext *C, const bool do_merge) } bm->spacearr_dirty |= BM_SPACEARR_DIRTY_ALL; - BKE_editmesh_lnorspace_update(em); + BKE_editmesh_lnorspace_update(em, static_cast(obedit->data)); if (do_merge) { normals_merge(bm, lnors_ed_arr); @@ -9212,8 +9214,9 @@ static int edbm_average_normals_exec(bContext *C, wmOperator *op) BMLoop *l, *l_curr, *l_first; BMIter fiter; + BKE_editmesh_ensure_autosmooth(em, static_cast(obedit->data)); bm->spacearr_dirty |= BM_SPACEARR_DIRTY_ALL; - BKE_editmesh_lnorspace_update(em); + BKE_editmesh_lnorspace_update(em, static_cast(obedit->data)); const int cd_clnors_offset = CustomData_get_offset(&bm->ldata, CD_CUSTOMLOOPNORMAL); @@ -9463,7 +9466,8 @@ static int edbm_normals_tools_exec(bContext *C, wmOperator *op) continue; } - BKE_editmesh_lnorspace_update(em); + BKE_editmesh_ensure_autosmooth(em, static_cast(obedit->data)); + BKE_editmesh_lnorspace_update(em, static_cast(obedit->data)); BMLoopNorEditDataArray *lnors_ed_arr = BM_loop_normal_editdata_array_init(bm, false); BMLoopNorEditData *lnor_ed = lnors_ed_arr->lnor_editdata; @@ -9686,7 +9690,8 @@ static int edbm_set_normals_from_faces_exec(bContext *C, wmOperator *op) const bool keep_sharp = RNA_boolean_get(op->ptr, "keep_sharp"); - BKE_editmesh_lnorspace_update(em); + BKE_editmesh_ensure_autosmooth(em, static_cast(obedit->data)); + BKE_editmesh_lnorspace_update(em, static_cast(obedit->data)); float(*vert_normals)[3] = static_cast( MEM_mallocN(sizeof(*vert_normals) * bm->totvert, __func__)); @@ -9794,7 +9799,8 @@ static int edbm_smooth_normals_exec(bContext *C, wmOperator *op) BMLoop *l; BMIter fiter, liter; - BKE_editmesh_lnorspace_update(em); + BKE_editmesh_ensure_autosmooth(em, static_cast(obedit->data)); + BKE_editmesh_lnorspace_update(em, static_cast(obedit->data)); BMLoopNorEditDataArray *lnors_ed_arr = BM_loop_normal_editdata_array_init(bm, false); float(*smooth_normal)[3] = static_cast( diff --git a/source/blender/editors/mesh/mesh_data.cc b/source/blender/editors/mesh/mesh_data.cc index e2a915248e8..b3157cd7d19 100644 --- a/source/blender/editors/mesh/mesh_data.cc +++ b/source/blender/editors/mesh/mesh_data.cc @@ -723,9 +723,34 @@ static int mesh_customdata_custom_splitnormals_add_exec(bContext *C, wmOperator if (me->edit_mesh) { BMesh &bm = *me->edit_mesh->bm; + /* Tag edges as sharp according to smooth threshold if needed, + * to preserve auto-smooth shading. */ + if (me->flag & ME_AUTOSMOOTH) { + BM_edges_sharp_from_angle_set(&bm, me->smoothresh); + } + BM_data_layer_add(&bm, &bm.ldata, CD_CUSTOMLOOPNORMAL); } else { + /* Tag edges as sharp according to smooth threshold if needed, + * to preserve auto-smooth shading. */ + if (me->flag & ME_AUTOSMOOTH) { + bke::MutableAttributeAccessor attributes = me->attributes_for_write(); + bke::SpanAttributeWriter sharp_edges = attributes.lookup_or_add_for_write_span( + "sharp_edge", ATTR_DOMAIN_EDGE); + const bool *sharp_faces = static_cast( + CustomData_get_layer_named(&me->face_data, CD_PROP_BOOL, "sharp_face")); + bke::mesh::edges_sharp_from_angle_set(me->faces(), + me->corner_verts(), + me->corner_edges(), + me->face_normals(), + me->corner_to_face_map(), + sharp_faces, + me->smoothresh, + sharp_edges.span); + sharp_edges.finish(); + } + CustomData_add_layer(&me->loop_data, CD_CUSTOMLOOPNORMAL, CD_SET_DEFAULT, me->totloop); } @@ -1129,7 +1154,9 @@ void ED_mesh_split_faces(Mesh *mesh) { using namespace blender; const OffsetIndices polys = mesh->faces(); + const Span corner_verts = mesh->corner_verts(); const Span corner_edges = mesh->corner_edges(); + const float split_angle = (mesh->flag & ME_AUTOSMOOTH) != 0 ? mesh->smoothresh : float(M_PI); const bke::AttributeAccessor attributes = mesh->attributes(); const VArray mesh_sharp_edges = *attributes.lookup_or_default( "sharp_edge", ATTR_DOMAIN_EDGE, false); @@ -1139,6 +1166,15 @@ void ED_mesh_split_faces(Mesh *mesh) Array sharp_edges(mesh->totedge); mesh_sharp_edges.materialize(sharp_edges); + bke::mesh::edges_sharp_from_angle_set(polys, + corner_verts, + corner_edges, + mesh->face_normals(), + mesh->corner_to_face_map(), + sharp_faces, + split_angle, + sharp_edges); + threading::parallel_for(polys.index_range(), 1024, [&](const IndexRange range) { for (const int face_i : range) { if (sharp_faces && sharp_faces[face_i]) { diff --git a/source/blender/editors/mesh/meshtools.cc b/source/blender/editors/mesh/meshtools.cc index 6f62c033554..dfeaa03db39 100644 --- a/source/blender/editors/mesh/meshtools.cc +++ b/source/blender/editors/mesh/meshtools.cc @@ -47,7 +47,7 @@ #include "DEG_depsgraph_build.hh" #include "DEG_depsgraph_query.hh" -#include "DRW_select_buffer.hh" +#include "DRW_select_buffer.h" #include "ED_mesh.hh" #include "ED_object.hh" @@ -1164,6 +1164,7 @@ int *mesh_get_x_mirror_faces(Object *ob, BMEditMesh *em, Mesh *me_eval) bool ED_mesh_pick_face(bContext *C, Object *ob, const int mval[2], uint dist_px, uint *r_index) { + ViewContext vc; Mesh *me = static_cast(ob->data); BLI_assert(me && GS(me->id.name) == ID_ME); @@ -1173,7 +1174,7 @@ bool ED_mesh_pick_face(bContext *C, Object *ob, const int mval[2], uint dist_px, } Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph); + ED_view3d_viewcontext_init(C, &vc, depsgraph); ED_view3d_select_id_validate(&vc); if (dist_px) { @@ -1335,6 +1336,7 @@ static void ed_mesh_pick_vert__mapFunc(void *user_data, bool ED_mesh_pick_vert( bContext *C, Object *ob, const int mval[2], uint dist_px, bool use_zbuf, uint *r_index) { + ViewContext vc; Mesh *me = static_cast(ob->data); BLI_assert(me && GS(me->id.name) == ID_ME); @@ -1344,7 +1346,7 @@ bool ED_mesh_pick_vert( } Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph); + ED_view3d_viewcontext_init(C, &vc, depsgraph); ED_view3d_select_id_validate(&vc); if (use_zbuf) { diff --git a/source/blender/editors/metaball/mball_edit.cc b/source/blender/editors/metaball/mball_edit.cc index ad1024d516a..60ecf286dd5 100644 --- a/source/blender/editors/metaball/mball_edit.cc +++ b/source/blender/editors/metaball/mball_edit.cc @@ -87,7 +87,8 @@ void ED_mball_editmball_load(Object * /*obedit*/) {} bool ED_mball_deselect_all_multi(bContext *C) { Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph); + ViewContext vc; + ED_view3d_viewcontext_init(C, &vc, depsgraph); uint bases_len = 0; Base **bases = BKE_view_layer_array_from_bases_in_edit_mode_unique_data( vc.scene, vc.view_layer, vc.v3d, &bases_len); @@ -770,12 +771,13 @@ static bool ed_mball_findnearest_metaelem(bContext *C, uint *r_selmask) { Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + ViewContext vc; int a, hits; GPUSelectResult buffer[MAXPICKELEMS]; rcti rect; bool found = false; - ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph); + ED_view3d_viewcontext_init(C, &vc, depsgraph); BLI_rcti_init_pt_radius(&rect, mval, 12); diff --git a/source/blender/editors/object/object_add.cc b/source/blender/editors/object/object_add.cc index 609ced7999b..5d17eff772d 100644 --- a/source/blender/editors/object/object_add.cc +++ b/source/blender/editors/object/object_add.cc @@ -164,21 +164,21 @@ static const EnumPropertyItem field_type_items[] = { }; static EnumPropertyItem lightprobe_type_items[] = { - {LIGHTPROBE_TYPE_SPHERE, - "SPHERE", - ICON_LIGHTPROBE_SPHERE, - "Sphere", - "Light probe that captures precise lighting from all directions at a single point in space"}, - {LIGHTPROBE_TYPE_PLANE, - "PLANE", - ICON_LIGHTPROBE_PLANE, - "Plane", - "Light probe that captures incoming light from a single direction on a plane"}, - {LIGHTPROBE_TYPE_VOLUME, - "VOLUME", - ICON_LIGHTPROBE_VOLUME, - "Volume", - "Light probe that captures low frequency lighting inside a volume"}, + {LIGHTPROBE_TYPE_CUBE, + "CUBEMAP", + ICON_LIGHTPROBE_CUBEMAP, + "Reflection Cubemap", + "Reflection probe with spherical or cubic attenuation"}, + {LIGHTPROBE_TYPE_PLANAR, + "PLANAR", + ICON_LIGHTPROBE_PLANAR, + "Reflection Plane", + "Planar reflection probe"}, + {LIGHTPROBE_TYPE_GRID, + "GRID", + ICON_LIGHTPROBE_GRID, + "Irradiance Volume", + "Irradiance probe to capture diffuse indirect lighting"}, {0, nullptr, 0, nullptr, nullptr}, }; @@ -746,12 +746,12 @@ void OBJECT_OT_add(wmOperatorType *ot) static const char *get_lightprobe_defname(int type) { switch (type) { - case LIGHTPROBE_TYPE_VOLUME: - return CTX_DATA_(BLT_I18NCONTEXT_ID_LIGHT, "Volume"); - case LIGHTPROBE_TYPE_PLANE: - return CTX_DATA_(BLT_I18NCONTEXT_ID_LIGHT, "Plane"); - case LIGHTPROBE_TYPE_SPHERE: - return CTX_DATA_(BLT_I18NCONTEXT_ID_LIGHT, "Sphere"); + case LIGHTPROBE_TYPE_GRID: + return CTX_DATA_(BLT_I18NCONTEXT_ID_LIGHT, "IrradianceVolume"); + case LIGHTPROBE_TYPE_PLANAR: + return CTX_DATA_(BLT_I18NCONTEXT_ID_LIGHT, "ReflectionPlane"); + case LIGHTPROBE_TYPE_CUBE: + return CTX_DATA_(BLT_I18NCONTEXT_ID_LIGHT, "ReflectionCubemap"); default: return CTX_DATA_(BLT_I18NCONTEXT_ID_LIGHT, "LightProbe"); } @@ -3683,8 +3683,8 @@ static int object_convert_exec(bContext *C, wmOperator *op) } } - // XXX: ED_object_editmode_enter(C, 0); - // XXX: exit_editmode(C, EM_FREEDATA|); /* free data, but no undo. */ + // XXX ED_object_editmode_enter(C, 0); + // XXX exit_editmode(C, EM_FREEDATA|); /* free data, but no undo */ if (basact) { /* active base was changed */ diff --git a/source/blender/editors/object/object_bake.cc b/source/blender/editors/object/object_bake.cc index 3554c971aa1..6fab3adb930 100644 --- a/source/blender/editors/object/object_bake.cc +++ b/source/blender/editors/object/object_bake.cc @@ -464,7 +464,7 @@ static void init_multiresbake_job(bContext *C, MultiresBakeJob *bkj) CTX_DATA_END; } -static void multiresbake_startjob(void *bkv, wmJobWorkerStatus *worker_status) +static void multiresbake_startjob(void *bkv, bool *stop, bool *do_update, float *progress) { MultiresBakeJob *bkj = static_cast(bkv); int baked_objects = 0, tot_obj; @@ -510,9 +510,9 @@ static void multiresbake_startjob(void *bkv, wmJobWorkerStatus *worker_status) bkr.tot_obj = tot_obj; bkr.baked_objects = baked_objects; - bkr.stop = &worker_status->stop; - bkr.do_update = &worker_status->do_update; - bkr.progress = &worker_status->progress; + bkr.stop = stop; + bkr.do_update = do_update; + bkr.progress = progress; bkr.bias = bkj->bias; bkr.number_of_rays = bkj->number_of_rays; diff --git a/source/blender/editors/object/object_bake_api.cc b/source/blender/editors/object/object_bake_api.cc index 3503f9785c2..22aa21401ab 100644 --- a/source/blender/editors/object/object_bake_api.cc +++ b/source/blender/editors/object/object_bake_api.cc @@ -712,8 +712,9 @@ static Mesh *bake_mesh_new_from_object(Depsgraph *depsgraph, { Mesh *me = BKE_mesh_new_from_object(depsgraph, object, false, preserve_origindex); - if (me->normals_domain() == blender::bke::MeshNormalDomain::Corner) { + if (me->flag & ME_AUTOSMOOTH) { ED_mesh_split_faces(me); + CustomData_free_layers(&me->loop_data, CD_NORMAL, me->totloop); } return me; @@ -1915,13 +1916,13 @@ finally: return result; } -static void bake_startjob(void *bkv, wmJobWorkerStatus *worker_status) +static void bake_startjob(void *bkv, bool * /*stop*/, bool *do_update, float *progress) { BakeAPIRender *bkr = (BakeAPIRender *)bkv; /* setup new render */ - bkr->do_update = &worker_status->do_update; - bkr->progress = &worker_status->progress; + bkr->do_update = do_update; + bkr->progress = progress; RE_SetReports(bkr->render, bkr->reports); diff --git a/source/blender/editors/object/object_bake_simulation.cc b/source/blender/editors/object/object_bake_simulation.cc index 8444629fccc..9289fa17a68 100644 --- a/source/blender/editors/object/object_bake_simulation.cc +++ b/source/blender/editors/object/object_bake_simulation.cc @@ -12,7 +12,7 @@ #include "BLI_path_util.h" #include "BLI_serialize.hh" #include "BLI_string.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_vector.hh" #include "PIL_time.h" @@ -82,7 +82,10 @@ struct CalculateSimulationJob { int end_frame; }; -static void calculate_simulation_job_startjob(void *customdata, wmJobWorkerStatus *worker_status) +static void calculate_simulation_job_startjob(void *customdata, + bool *stop, + bool *do_update, + float *progress) { CalculateSimulationJob &job = *static_cast(customdata); G.is_rendering = true; @@ -111,8 +114,8 @@ static void calculate_simulation_job_startjob(void *customdata, wmJobWorkerStatu objects_to_calc.append(object); } - worker_status->progress = 0.0f; - worker_status->do_update = true; + *progress = 0.0f; + *do_update = true; const float frame_step_size = 1.0f; const float progress_per_frame = 1.0f / @@ -122,7 +125,7 @@ static void calculate_simulation_job_startjob(void *customdata, wmJobWorkerStatu for (float frame_f = job.start_frame; frame_f <= job.end_frame; frame_f += frame_step_size) { const SubFrame frame{frame_f}; - if (G.is_break || worker_status->stop) { + if (G.is_break || (stop != nullptr && *stop)) { break; } @@ -131,15 +134,15 @@ static void calculate_simulation_job_startjob(void *customdata, wmJobWorkerStatu BKE_scene_graph_update_for_newframe(job.depsgraph); - worker_status->progress += progress_per_frame; - worker_status->do_update = true; + *progress += progress_per_frame; + *do_update = true; } job.scene->r.cfra = old_frame; DEG_time_tag_update(job.bmain); - worker_status->progress = 1.0f; - worker_status->do_update = true; + *progress = 1.0f; + *do_update = true; } static void calculate_simulation_job_endjob(void *customdata) @@ -247,7 +250,10 @@ struct BakeSimulationJob { Vector objects; }; -static void bake_simulation_job_startjob(void *customdata, wmJobWorkerStatus *worker_status) +static void bake_simulation_job_startjob(void *customdata, + bool *stop, + bool *do_update, + float *progress) { BakeSimulationJob &job = *static_cast(customdata); G.is_rendering = true; @@ -266,8 +272,8 @@ static void bake_simulation_job_startjob(void *customdata, wmJobWorkerStatus *wo } } - worker_status->progress = 0.0f; - worker_status->do_update = true; + *progress = 0.0f; + *do_update = true; const int frames_to_bake = global_bake_end_frame - global_bake_start_frame + 1; @@ -280,7 +286,7 @@ static void bake_simulation_job_startjob(void *customdata, wmJobWorkerStatus *wo { const SubFrame frame{frame_f}; - if (G.is_break || worker_status->stop) { + if (G.is_break || (stop != nullptr && *stop)) { break; } @@ -332,8 +338,8 @@ static void bake_simulation_job_startjob(void *customdata, wmJobWorkerStatus *wo } } - worker_status->progress += progress_per_frame; - worker_status->do_update = true; + *progress += progress_per_frame; + *do_update = true; } for (ObjectBakeData &object_bake_data : job.objects) { @@ -357,8 +363,8 @@ static void bake_simulation_job_startjob(void *customdata, wmJobWorkerStatus *wo job.scene->r.cfra = old_frame; DEG_time_tag_update(job.bmain); - worker_status->progress = 1.0f; - worker_status->do_update = true; + *progress = 1.0f; + *do_update = true; } static void bake_simulation_job_endjob(void *customdata) diff --git a/source/blender/editors/object/object_data_transfer.cc b/source/blender/editors/object/object_data_transfer.cc index 6b0b28d72eb..034a52983cd 100644 --- a/source/blender/editors/object/object_data_transfer.cc +++ b/source/blender/editors/object/object_data_transfer.cc @@ -522,6 +522,11 @@ static int data_transfer_exec(bContext *C, wmOperator *op) false, op->reports)) { + + if (data_type == DT_TYPE_LNOR && use_create) { + ((Mesh *)ob_dst->data)->flag |= ME_AUTOSMOOTH; + } + DEG_id_tag_update(&ob_dst->id, ID_RECALC_GEOMETRY); changed = true; } diff --git a/source/blender/editors/object/object_edit.cc b/source/blender/editors/object/object_edit.cc index 6f4879a1772..1e50bbe0c56 100644 --- a/source/blender/editors/object/object_edit.cc +++ b/source/blender/editors/object/object_edit.cc @@ -1567,7 +1567,6 @@ void OBJECT_OT_paths_clear(wmOperatorType *ot) static int shade_smooth_exec(bContext *C, wmOperator *op) { const bool use_smooth = STREQ(op->idname, "OBJECT_OT_shade_smooth"); - const bool use_smooth_by_angle = STREQ(op->idname, "OBJECT_OT_shade_smooth_by_angle"); bool changed_multi = false; bool has_linked_data = false; @@ -1616,12 +1615,12 @@ static int shade_smooth_exec(bContext *C, wmOperator *op) bool changed = false; if (ob->type == OB_MESH) { - BKE_mesh_smooth_flag_set(static_cast(ob->data), use_smooth || use_smooth_by_angle); - if (use_smooth || use_smooth_by_angle) { - if (use_smooth_by_angle) { - const float angle = RNA_float_get(op->ptr, "angle"); - BKE_mesh_sharp_edges_set_from_angle(static_cast(ob->data), angle); - } + BKE_mesh_smooth_flag_set(static_cast(ob->data), use_smooth); + if (use_smooth) { + const bool use_auto_smooth = RNA_boolean_get(op->ptr, "use_auto_smooth"); + const float auto_smooth_angle = RNA_float_get(op->ptr, "auto_smooth_angle"); + BKE_mesh_auto_smooth_flag_set( + static_cast(ob->data), use_auto_smooth, auto_smooth_angle); } BKE_mesh_batch_cache_dirty_tag(static_cast(ob->data), BKE_MESH_BATCH_DIRTY_ALL); changed = true; @@ -1695,25 +1694,25 @@ void OBJECT_OT_shade_smooth(wmOperatorType *ot) /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; -} -void OBJECT_OT_shade_smooth_by_angle(wmOperatorType *ot) -{ - ot->name = "Shade Smooth by Angle"; - ot->description = - "Set the sharpness of mesh edges based on the angle between the neighboring faces"; - ot->idname = "OBJECT_OT_shade_smooth_by_angle"; + /* properties */ + PropertyRNA *prop; - ot->poll = shade_poll; - ot->exec = shade_smooth_exec; + prop = RNA_def_boolean( + ot->srna, + "use_auto_smooth", + false, + "Auto Smooth", + "Enable automatic smooth based on smooth/sharp faces/edges and angle between faces"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - PropertyRNA *prop = RNA_def_property(ot->srna, "angle", PROP_FLOAT, PROP_ANGLE); + prop = RNA_def_property(ot->srna, "auto_smooth_angle", PROP_FLOAT, PROP_ANGLE); RNA_def_property_range(prop, 0.0f, DEG2RADF(180.0f)); RNA_def_property_float_default(prop, DEG2RADF(30.0f)); - RNA_def_property_ui_text( - prop, "Angle", "Maximum angle between face normals that will be considered as smooth"); + RNA_def_property_ui_text(prop, + "Angle", + "Maximum angle between face normals that will be considered as smooth " + "(unused if custom split normals data are available)"); } /** \} */ diff --git a/source/blender/editors/object/object_gpencil_modifier.cc b/source/blender/editors/object/object_gpencil_modifier.cc index 244beea8983..8c10151da99 100644 --- a/source/blender/editors/object/object_gpencil_modifier.cc +++ b/source/blender/editors/object/object_gpencil_modifier.cc @@ -22,7 +22,7 @@ #include "BLI_listbase.h" #include "BLI_string.h" #include "BLI_string_utf8.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_utildefines.h" #include "BKE_context.h" diff --git a/source/blender/editors/object/object_intern.h b/source/blender/editors/object/object_intern.h index d63676d8f8c..5d5c0c056f7 100644 --- a/source/blender/editors/object/object_intern.h +++ b/source/blender/editors/object/object_intern.h @@ -83,7 +83,6 @@ 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_shade_smooth(struct wmOperatorType *ot); -void OBJECT_OT_shade_smooth_by_angle(struct wmOperatorType *ot); void OBJECT_OT_shade_flat(struct wmOperatorType *ot); void OBJECT_OT_paths_calculate(struct wmOperatorType *ot); void OBJECT_OT_paths_update(struct wmOperatorType *ot); diff --git a/source/blender/editors/object/object_modifier.cc b/source/blender/editors/object/object_modifier.cc index 9ceb0fee566..e7f17448f5e 100644 --- a/source/blender/editors/object/object_modifier.cc +++ b/source/blender/editors/object/object_modifier.cc @@ -3309,20 +3309,20 @@ static void oceanbake_update(void *customdata, float progress, int *cancel) *(oj->progress) = progress; } -static void oceanbake_startjob(void *customdata, wmJobWorkerStatus *worker_status) +static void oceanbake_startjob(void *customdata, bool *stop, bool *do_update, float *progress) { OceanBakeJob *oj = static_cast(customdata); - oj->stop = &worker_status->stop; - oj->do_update = &worker_status->do_update; - oj->progress = &worker_status->progress; + oj->stop = stop; + oj->do_update = do_update; + oj->progress = progress; G.is_break = false; /* XXX shared with render - replace with job 'stop' switch */ BKE_ocean_bake(oj->ocean, oj->och, oceanbake_update, (void *)oj); - worker_status->do_update = true; - worker_status->stop = false; + *do_update = true; + *stop = false; } static void oceanbake_endjob(void *customdata) diff --git a/source/blender/editors/object/object_ops.cc b/source/blender/editors/object/object_ops.cc index 7eb1585577c..cbb57578737 100644 --- a/source/blender/editors/object/object_ops.cc +++ b/source/blender/editors/object/object_ops.cc @@ -45,7 +45,6 @@ void ED_operatortypes_object() WM_operatortype_append(OBJECT_OT_editmode_toggle); WM_operatortype_append(OBJECT_OT_posemode_toggle); WM_operatortype_append(OBJECT_OT_shade_smooth); - WM_operatortype_append(OBJECT_OT_shade_smooth_by_angle); WM_operatortype_append(OBJECT_OT_shade_flat); WM_operatortype_append(OBJECT_OT_paths_calculate); WM_operatortype_append(OBJECT_OT_paths_update); diff --git a/source/blender/editors/object/object_remesh.cc b/source/blender/editors/object/object_remesh.cc index ed25b6e0945..47310aa7085 100644 --- a/source/blender/editors/object/object_remesh.cc +++ b/source/blender/editors/object/object_remesh.cc @@ -839,13 +839,13 @@ static Mesh *remesh_symmetry_mirror(Object *ob, Mesh *mesh, eSymmetryAxes symmet return mesh_mirror; } -static void quadriflow_start_job(void *customdata, wmJobWorkerStatus *worker_status) +static void quadriflow_start_job(void *customdata, bool *stop, bool *do_update, float *progress) { QuadriFlowJob *qj = static_cast(customdata); - qj->stop = &worker_status->stop; - qj->do_update = &worker_status->do_update; - qj->progress = &worker_status->progress; + qj->stop = stop; + qj->do_update = do_update; + qj->progress = progress; qj->success = 1; if (qj->is_nonblocking_job) { @@ -886,8 +886,8 @@ static void quadriflow_start_job(void *customdata, wmJobWorkerStatus *worker_sta BKE_id_free(nullptr, bisect_mesh); if (new_mesh == nullptr) { - worker_status->do_update = true; - worker_status->stop = false; + *do_update = true; + *stop = false; if (qj->success == 1) { /* This is not a user cancellation event. */ qj->success = 0; @@ -916,8 +916,8 @@ static void quadriflow_start_job(void *customdata, wmJobWorkerStatus *worker_sta BKE_mesh_batch_cache_dirty_tag(static_cast(ob->data), BKE_MESH_BATCH_DIRTY_ALL); - worker_status->do_update = true; - worker_status->stop = false; + *do_update = true; + *stop = false; } static void quadriflow_end_job(void *customdata) @@ -992,8 +992,9 @@ static int quadriflow_remesh_exec(bContext *C, wmOperator *op) if (op->flag == 0) { /* This is called directly from the exec operator, this operation is now blocking */ job->is_nonblocking_job = false; - wmJobWorkerStatus worker_status = {}; - quadriflow_start_job(job, &worker_status); + bool stop = false, do_update = true; + float progress; + quadriflow_start_job(job, &stop, &do_update, &progress); quadriflow_end_job(job); quadriflow_free_job(job); } diff --git a/source/blender/editors/object/object_select.cc b/source/blender/editors/object/object_select.cc index db9073652ee..c434d235ffb 100644 --- a/source/blender/editors/object/object_select.cc +++ b/source/blender/editors/object/object_select.cc @@ -27,7 +27,7 @@ #include "BLI_math_bits.h" #include "BLI_math_vector.h" #include "BLI_rand.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_utildefines.h" #include "BLT_translation.h" diff --git a/source/blender/editors/object/object_transform.cc b/source/blender/editors/object/object_transform.cc index e8ed19f39c3..e706cdef74a 100644 --- a/source/blender/editors/object/object_transform.cc +++ b/source/blender/editors/object/object_transform.cc @@ -63,8 +63,6 @@ #include "WM_api.hh" #include "WM_types.hh" -#include "ANIM_keyframing.hh" - #include "ED_armature.hh" #include "ED_gpencil_legacy.hh" #include "ED_keyframing.hh" @@ -349,7 +347,7 @@ static int object_clear_transform_generic_exec(bContext *C, /* run provided clearing function */ clear_func(ob, clear_delta); - blender::animrig::autokeyframe_object(C, scene, ob, ks); + ED_autokeyframe_object(C, scene, ob, ks); /* tag for updates */ DEG_id_tag_update(&ob->id, ID_RECALC_TRANSFORM); @@ -1261,7 +1259,7 @@ enum { ORIGIN_TO_CENTER_OF_MASS_VOLUME, }; -static float3 arithmetic_mean(const blender::Span values) +static float3 calculate_mean(const blender::Span values) { if (values.is_empty()) { return float3(0); @@ -1706,7 +1704,7 @@ static int object_origin_set_exec(bContext *C, wmOperator *op) cent = math::midpoint(bounds.min, bounds.max); } else if (around == V3D_AROUND_CENTER_MEDIAN) { - cent = arithmetic_mean(curves.positions()); + cent = calculate_mean(curves.positions()); } tot_change++; @@ -1737,7 +1735,7 @@ static int object_origin_set_exec(bContext *C, wmOperator *op) } } else if (around == V3D_AROUND_CENTER_MEDIAN) { - cent = arithmetic_mean(positions.span); + cent = calculate_mean(positions.span); } tot_change++; @@ -2084,7 +2082,8 @@ static void object_transform_axis_target_cancel(bContext *C, wmOperator *op) static int object_transform_axis_target_invoke(bContext *C, wmOperator *op, const wmEvent *event) { Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph); + ViewContext vc; + ED_view3d_viewcontext_init(C, &vc, depsgraph); if (vc.obact == nullptr || !object_is_target_compat(vc.obact)) { /* Falls back to texture space transform. */ diff --git a/source/blender/editors/object/object_vgroup.cc b/source/blender/editors/object/object_vgroup.cc index 977a00efd1f..3da580df243 100644 --- a/source/blender/editors/object/object_vgroup.cc +++ b/source/blender/editors/object/object_vgroup.cc @@ -22,6 +22,7 @@ #include "DNA_scene_types.h" #include "DNA_workspace_types.h" +#include "BLI_array.h" #include "BLI_array.hh" #include "BLI_bitmap.h" #include "BLI_blenlib.h" diff --git a/source/blender/editors/physics/dynamicpaint_ops.cc b/source/blender/editors/physics/dynamicpaint_ops.cc index f34f09804db..b411c2097eb 100644 --- a/source/blender/editors/physics/dynamicpaint_ops.cc +++ b/source/blender/editors/physics/dynamicpaint_ops.cc @@ -432,13 +432,13 @@ static void dynamicPaint_bakeImageSequence(DynamicPaintBakeJob *job) ED_update_for_newframe(job->bmain, job->depsgraph); } -static void dpaint_bake_startjob(void *customdata, wmJobWorkerStatus *worker_status) +static void dpaint_bake_startjob(void *customdata, bool *stop, bool *do_update, float *progress) { DynamicPaintBakeJob *job = static_cast(customdata); - job->stop = &worker_status->stop; - job->do_update = &worker_status->do_update; - job->progress = &worker_status->progress; + job->stop = stop; + job->do_update = do_update; + job->progress = progress; job->start = PIL_check_seconds_timer(); job->success = 1; @@ -452,8 +452,8 @@ static void dpaint_bake_startjob(void *customdata, wmJobWorkerStatus *worker_sta dynamicPaint_bakeImageSequence(job); - worker_status->do_update = true; - worker_status->stop = false; + *do_update = true; + *stop = false; } /* diff --git a/source/blender/editors/physics/particle_edit.cc b/source/blender/editors/physics/particle_edit.cc index 88b4df35ea1..bd3f5522313 100644 --- a/source/blender/editors/physics/particle_edit.cc +++ b/source/blender/editors/physics/particle_edit.cc @@ -512,7 +512,7 @@ static void PE_set_view3d_data(bContext *C, PEData *data) { PE_set_data(C, data); - data->vc = ED_view3d_viewcontext_init(C, data->depsgraph); + ED_view3d_viewcontext_init(C, &data->vc, data->depsgraph); if (!XRAY_ENABLED(data->vc.v3d)) { ED_view3d_depth_override(data->depsgraph, diff --git a/source/blender/editors/physics/physics_fluid.cc b/source/blender/editors/physics/physics_fluid.cc index 1c59482f4dc..6f11694c486 100644 --- a/source/blender/editors/physics/physics_fluid.cc +++ b/source/blender/editors/physics/physics_fluid.cc @@ -350,7 +350,7 @@ static void fluid_bake_endjob(void *customdata) } } -static void fluid_bake_startjob(void *customdata, wmJobWorkerStatus *worker_status) +static void fluid_bake_startjob(void *customdata, bool *stop, bool *do_update, float *progress) { FluidJob *job = static_cast(customdata); FluidDomainSettings *fds = job->fmd->domain; @@ -358,9 +358,9 @@ static void fluid_bake_startjob(void *customdata, wmJobWorkerStatus *worker_stat char temp_dir[FILE_MAX]; const char *relbase = BKE_modifier_path_relbase_from_global(job->ob); - job->stop = &worker_status->stop; - job->do_update = &worker_status->do_update; - job->progress = &worker_status->progress; + job->stop = stop; + job->do_update = do_update; + job->progress = progress; job->start = PIL_check_seconds_timer(); job->success = 1; @@ -425,8 +425,12 @@ static void fluid_bake_startjob(void *customdata, wmJobWorkerStatus *worker_stat fluid_bake_sequence(job); - worker_status->do_update = true; - worker_status->stop = false; + if (do_update) { + *do_update = true; + } + if (stop) { + *stop = false; + } } static void fluid_free_endjob(void *customdata) @@ -458,14 +462,14 @@ static void fluid_free_endjob(void *customdata) } } -static void fluid_free_startjob(void *customdata, wmJobWorkerStatus *worker_status) +static void fluid_free_startjob(void *customdata, bool *stop, bool *do_update, float *progress) { FluidJob *job = static_cast(customdata); FluidDomainSettings *fds = job->fmd->domain; - job->stop = &worker_status->stop; - job->do_update = &worker_status->do_update; - job->progress = &worker_status->progress; + job->stop = stop; + job->do_update = do_update; + job->progress = progress; job->start = PIL_check_seconds_timer(); job->success = 1; @@ -500,8 +504,8 @@ static void fluid_free_startjob(void *customdata, wmJobWorkerStatus *worker_stat UNUSED_VARS(cache_map); #endif - worker_status->do_update = true; - worker_status->stop = false; + *do_update = true; + *stop = false; /* Update scene so that viewport shows freed up scene */ ED_update_for_newframe(job->bmain, job->depsgraph); @@ -527,8 +531,7 @@ static int fluid_bake_exec(bContext *C, wmOperator *op) } WM_report_banners_cancel(job->bmain); - wmJobWorkerStatus worker_status = {}; - fluid_bake_startjob(job, &worker_status); + fluid_bake_startjob(job, nullptr, nullptr, nullptr); fluid_bake_endjob(job); fluid_bake_free(job); diff --git a/source/blender/editors/physics/physics_pointcache.cc b/source/blender/editors/physics/physics_pointcache.cc index 9418d2cba5d..e181c1a3d8e 100644 --- a/source/blender/editors/physics/physics_pointcache.cc +++ b/source/blender/editors/physics/physics_pointcache.cc @@ -127,13 +127,13 @@ static void ptcache_job_update(void *customdata, float progress, int *cancel) *(job->progress) = progress; } -static void ptcache_job_startjob(void *customdata, wmJobWorkerStatus *worker_status) +static void ptcache_job_startjob(void *customdata, bool *stop, bool *do_update, float *progress) { PointCacheJob *job = static_cast(customdata); - job->stop = &worker_status->stop; - job->do_update = &worker_status->do_update; - job->progress = &worker_status->progress; + job->stop = stop; + job->do_update = do_update; + job->progress = progress; G.is_break = false; @@ -144,8 +144,8 @@ static void ptcache_job_startjob(void *customdata, wmJobWorkerStatus *worker_sta BKE_ptcache_bake(job->baker); - worker_status->do_update = true; - worker_status->stop = false; + *do_update = true; + *stop = false; } static void ptcache_job_endjob(void *customdata) diff --git a/source/blender/editors/render/render_internal.cc b/source/blender/editors/render/render_internal.cc index f1935e7bfd8..1859b76aca0 100644 --- a/source/blender/editors/render/render_internal.cc +++ b/source/blender/editors/render/render_internal.cc @@ -15,7 +15,7 @@ #include "BLI_listbase.h" #include "BLI_rect.h" #include "BLI_string.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_threads.h" #include "BLI_timecode.h" #include "BLI_utildefines.h" @@ -44,7 +44,7 @@ #include "BKE_scene.h" #include "BKE_screen.hh" -#include "NOD_composite.hh" +#include "NOD_composite.h" #include "DEG_depsgraph.hh" @@ -701,13 +701,13 @@ static void current_scene_update(void *rjv, Scene *scene) rj->iuser.scene = scene; } -static void render_startjob(void *rjv, wmJobWorkerStatus *worker_status) +static void render_startjob(void *rjv, bool *stop, bool *do_update, float *progress) { RenderJob *rj = static_cast(rjv); - rj->stop = &worker_status->stop; - rj->do_update = &worker_status->do_update; - rj->progress = &worker_status->progress; + rj->stop = stop; + rj->do_update = do_update; + rj->progress = progress; RE_SetReports(rj->re, rj->reports); diff --git a/source/blender/editors/render/render_opengl.cc b/source/blender/editors/render/render_opengl.cc index 214871bcdff..b5614635a32 100644 --- a/source/blender/editors/render/render_opengl.cc +++ b/source/blender/editors/render/render_opengl.cc @@ -1065,7 +1065,6 @@ static void write_result(TaskPool *__restrict pool, WriteTaskData *task_data) } } if (reports.list.first != nullptr) { - /* TODO: Should rather use new #BKE_reports_move_to_reports ? */ BLI_spin_lock(&oglrender->reports_lock); for (Report *report = static_cast(reports.list.first); report != nullptr; report = report->next) @@ -1074,7 +1073,6 @@ static void write_result(TaskPool *__restrict pool, WriteTaskData *task_data) } BLI_spin_unlock(&oglrender->reports_lock); } - BKE_reports_free(&reports); if (!ok) { oglrender->pool_ok = false; } diff --git a/source/blender/editors/render/render_preview.cc b/source/blender/editors/render/render_preview.cc index 0c45d6be67b..f23e0abde6e 100644 --- a/source/blender/editors/render/render_preview.cc +++ b/source/blender/editors/render/render_preview.cc @@ -1478,15 +1478,18 @@ static void icon_preview_startjob(void *customdata, bool *stop, bool *do_update) /* use same function for icon & shader, so the job manager * does not run two of them at the same time. */ -static void common_preview_startjob(void *customdata, wmJobWorkerStatus *worker_status) +static void common_preview_startjob(void *customdata, + bool *stop, + bool *do_update, + float * /*progress*/) { ShaderPreview *sp = static_cast(customdata); if (ELEM(sp->pr_method, PR_ICON_RENDER, PR_ICON_DEFERRED)) { - icon_preview_startjob(customdata, &worker_status->stop, &worker_status->do_update); + icon_preview_startjob(customdata, stop, do_update); } else { - shader_preview_startjob(customdata, &worker_status->stop, &worker_status->do_update); + shader_preview_startjob(customdata, stop, do_update); } } @@ -1497,7 +1500,9 @@ static void common_preview_startjob(void *customdata, wmJobWorkerStatus *worker_ static void other_id_types_preview_render(IconPreview *ip, IconPreviewSize *cur_size, const ePreviewRenderMethod pr_method, - wmJobWorkerStatus *worker_status) + bool *stop, + bool *do_update, + float *progress) { ShaderPreview *sp = MEM_cnew("Icon ShaderPreview"); @@ -1533,7 +1538,7 @@ static void other_id_types_preview_render(IconPreview *ip, } } - common_preview_startjob(sp, worker_status); + common_preview_startjob(sp, stop, do_update, progress); shader_preview_free(sp); } @@ -1555,7 +1560,10 @@ static int icon_previewimg_size_index_get(const IconPreviewSize *icon_size, return -1; } -static void icon_preview_startjob_all_sizes(void *customdata, wmJobWorkerStatus *worker_status) +static void icon_preview_startjob_all_sizes(void *customdata, + bool *stop, + bool *do_update, + float *progress) { IconPreview *ip = (IconPreview *)customdata; @@ -1565,7 +1573,7 @@ static void icon_preview_startjob_all_sizes(void *customdata, wmJobWorkerStatus const ePreviewRenderMethod pr_method = (prv->tag & PRV_TAG_DEFFERED) ? PR_ICON_DEFERRED : PR_ICON_RENDER; - if (worker_status->stop) { + if (*stop) { break; } @@ -1625,7 +1633,7 @@ static void icon_preview_startjob_all_sizes(void *customdata, wmJobWorkerStatus break; } } - other_id_types_preview_render(ip, cur_size, pr_method, worker_status); + other_id_types_preview_render(ip, cur_size, pr_method, stop, do_update, progress); } } @@ -1725,7 +1733,7 @@ class PreviewLoadJob { void push_load_request(PreviewImage *preview, eIconSizes icon_size); private: - static void run_fn(void *customdata, wmJobWorkerStatus *worker_status); + static void run_fn(void *customdata, bool *stop, bool *do_update, float *progress); static void update_fn(void *customdata); static void end_fn(void *customdata); static void free_fn(void *customdata); @@ -1765,8 +1773,9 @@ void PreviewLoadJob::load_jobless(PreviewImage *preview, const eIconSizes icon_s job_data.push_load_request(preview, icon_size); - wmJobWorkerStatus worker_status = {}; - run_fn(&job_data, &worker_status); + bool stop = false, do_update = false; + float progress = 0; + run_fn(&job_data, &stop, &do_update, &progress); update_fn(&job_data); end_fn(&job_data); } @@ -1786,7 +1795,7 @@ void PreviewLoadJob::push_load_request(PreviewImage *preview, const eIconSizes i BLI_thread_queue_push(todo_queue_, &requested_previews_.back()); } -void PreviewLoadJob::run_fn(void *customdata, wmJobWorkerStatus *worker_status) +void PreviewLoadJob::run_fn(void *customdata, bool *stop, bool *do_update, float * /*progress*/) { PreviewLoadJob *job_data = static_cast(customdata); @@ -1795,7 +1804,7 @@ void PreviewLoadJob::run_fn(void *customdata, wmJobWorkerStatus *worker_status) while (RequestedPreview *request = static_cast( BLI_thread_queue_pop_timeout(job_data->todo_queue_, 100))) { - if (worker_status->stop) { + if (*stop) { break; } @@ -1825,7 +1834,7 @@ void PreviewLoadJob::run_fn(void *customdata, wmJobWorkerStatus *worker_status) IMB_freeImBuf(thumb); } - worker_status->do_update = true; + *do_update = true; } IMB_thumb_locks_release(); @@ -1929,6 +1938,8 @@ void ED_preview_icon_render( } IconPreview ip = {nullptr}; + bool stop = false, update = false; + float progress = 0.0f; ED_preview_ensure_dbase(true); @@ -1947,8 +1958,7 @@ void ED_preview_icon_render( icon_preview_add_size( &ip, prv_img->rect[icon_size], prv_img->w[icon_size], prv_img->h[icon_size]); - wmJobWorkerStatus worker_status = {}; - icon_preview_startjob_all_sizes(&ip, &worker_status); + icon_preview_startjob_all_sizes(&ip, &stop, &update, &progress); icon_preview_endjob(&ip); diff --git a/source/blender/editors/render/render_shading.cc b/source/blender/editors/render/render_shading.cc index c5f20a0f62c..7599af483f7 100644 --- a/source/blender/editors/render/render_shading.cc +++ b/source/blender/editors/render/render_shading.cc @@ -27,7 +27,7 @@ #include "BLI_math_vector.h" #include "BLI_path_util.h" #include "BLI_string.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_utildefines.h" #include "BLT_translation.h" @@ -59,7 +59,7 @@ #include "BKE_workspace.h" #include "BKE_world.h" -#include "NOD_composite.hh" +#include "NOD_composite.h" #include "DEG_depsgraph.hh" #include "DEG_depsgraph_build.hh" @@ -773,7 +773,7 @@ static int new_material_exec(bContext *C, wmOperator * /*op*/) } else { const char *name = DATA_("Material"); - if (!(ob != nullptr && ELEM(ob->type, OB_GPENCIL_LEGACY, OB_GREASE_PENCIL))) { + if (!(ob != nullptr && ob->type == OB_GPENCIL_LEGACY)) { ma = BKE_material_add(bmain, name); } else { @@ -1419,10 +1419,10 @@ static int light_cache_bake_exec(bContext *C, wmOperator *op) light_cache_bake_tag_cache(scene, op); - /* Not actually used. */ - wmJobWorkerStatus worker_status = {}; + bool stop = false, do_update; + float progress; /* Not actually used. */ /* Do the job. */ - EEVEE_lightbake_job(rj, &worker_status); + EEVEE_lightbake_job(rj, &stop, &do_update, &progress); /* Free baking data. Result is already stored in the scene data. */ EEVEE_lightbake_job_data_free(rj); @@ -1525,7 +1525,7 @@ static blender::Vector lightprobe_cache_irradiance_volume_subset_get(b auto is_irradiance_volume = [](Object *ob) -> bool { return ob->type == OB_LIGHTPROBE && - static_cast(ob->data)->type == LIGHTPROBE_TYPE_VOLUME; + static_cast(ob->data)->type == LIGHTPROBE_TYPE_GRID; }; blender::Vector probes; @@ -1657,8 +1657,7 @@ static int lightprobe_cache_bake_exec(bContext *C, wmOperator *op) /* TODO: abort if selected engine is not eevee. */ void *rj = EEVEE_NEXT_lightbake_job_data_alloc(bmain, view_layer, scene, probes, scene->r.cfra); /* Do the job. */ - wmJobWorkerStatus worker_status = {}; - EEVEE_NEXT_lightbake_job(rj, &worker_status); + EEVEE_NEXT_lightbake_job(rj, nullptr, nullptr, nullptr); /* Free baking data. Result is already stored in the scene data. */ EEVEE_NEXT_lightbake_job_data_free(rj); @@ -1668,17 +1667,18 @@ static int lightprobe_cache_bake_exec(bContext *C, wmOperator *op) void OBJECT_OT_lightprobe_cache_bake(wmOperatorType *ot) { static const EnumPropertyItem light_cache_subset_items[] = { - {LIGHTCACHE_SUBSET_ALL, "ALL", 0, "All Volumes", "Bake all light probe volumes"}, + {LIGHTCACHE_SUBSET_ALL, "ALL", 0, "All Light Probes", "Bake all light probes"}, + {LIGHTCACHE_SUBSET_DIRTY, + "DIRTY", + 0, + "Dirty Only", + "Only bake light probes that are marked as dirty"}, {LIGHTCACHE_SUBSET_SELECTED, "SELECTED", 0, "Selected Only", - "Only bake selected light probe volumes"}, - {LIGHTCACHE_SUBSET_ACTIVE, - "ACTIVE", - 0, - "Active Only", - "Only bake the active light probe volume"}, + "Only bake selected light probes"}, + {LIGHTCACHE_SUBSET_ACTIVE, "ACTIVE", 0, "Active Only", "Only bake the active light probe"}, {0, nullptr, 0, nullptr, nullptr}, }; diff --git a/source/blender/editors/render/render_update.cc b/source/blender/editors/render/render_update.cc index cd933587085..098c11b416c 100644 --- a/source/blender/editors/render/render_update.cc +++ b/source/blender/editors/render/render_update.cc @@ -35,7 +35,7 @@ #include "BKE_paint.hh" #include "BKE_scene.h" -#include "NOD_composite.hh" +#include "NOD_composite.h" #include "RE_engine.h" #include "RE_pipeline.h" diff --git a/source/blender/editors/screen/area.cc b/source/blender/editors/screen/area.cc index 164d4d87ba5..41146e00888 100644 --- a/source/blender/editors/screen/area.cc +++ b/source/blender/editors/screen/area.cc @@ -16,7 +16,7 @@ #include "BLI_blenlib.h" #include "BLI_linklist_stack.h" #include "BLI_rand.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_utildefines.h" #include "BKE_context.h" @@ -1708,10 +1708,8 @@ static void area_calc_totrct(ScrArea *area, const rcti *window_rect) area->winy = BLI_rcti_size_y(&area->totrct) + 1; } -/** - * Update the `ARegion::visible` flag. - */ -static void region_evaulate_visibility(ARegion *region) +/* used for area initialize below */ +static void region_subwindow(ARegion *region) { bool hidden = (region->flag & (RGN_FLAG_POLL_FAILED | RGN_FLAG_HIDDEN | RGN_FLAG_TOO_SMALL)) != 0; @@ -2010,7 +2008,7 @@ void ED_area_update_region_sizes(wmWindowManager *wm, wmWindow *win, ScrArea *ar area_azone_init(win, screen, area); LISTBASE_FOREACH (ARegion *, region, &area->regionbase) { - region_evaulate_visibility(region); + region_subwindow(region); /* region size may have changed, init does necessary adjustments */ if (region->type->init) { @@ -2114,7 +2112,7 @@ void ED_area_init(wmWindowManager *wm, wmWindow *win, ScrArea *area) /* region windows, default and own handlers */ LISTBASE_FOREACH (ARegion *, region, &area->regionbase) { - region_evaulate_visibility(region); + region_subwindow(region); if (region->visible) { /* default region handlers */ @@ -2182,8 +2180,7 @@ static void area_offscreen_exit(wmWindowManager *wm, wmWindow *win, ScrArea *are } WM_event_modal_handler_region_replace(win, region, nullptr); - WM_draw_region_free(region); - region->visible = false; + WM_draw_region_free(region, true); MEM_SAFE_FREE(region->headerstr); @@ -2227,7 +2224,7 @@ void ED_region_floating_init(ARegion *region) BLI_assert(region->alignment == RGN_ALIGN_FLOAT); /* refresh can be called before window opened */ - region_evaulate_visibility(region); + region_subwindow(region); region_update_rect(region); } diff --git a/source/blender/editors/screen/screen_edit.cc b/source/blender/editors/screen/screen_edit.cc index fab0ca57336..7afd54aa614 100644 --- a/source/blender/editors/screen/screen_edit.cc +++ b/source/blender/editors/screen/screen_edit.cc @@ -805,9 +805,7 @@ void ED_region_exit(bContext *C, ARegion *region) WM_event_remove_handlers(C, ®ion->handlers); WM_event_modal_handler_region_replace(win, region, nullptr); - WM_draw_region_free(region); - /* The region is not in a state that it can be visible in anymore. Reinitializing is needed. */ - region->visible = false; + WM_draw_region_free(region, true); MEM_SAFE_FREE(region->headerstr); diff --git a/source/blender/editors/screen/screen_ops.cc b/source/blender/editors/screen/screen_ops.cc index b0366231c35..d55695fa4e7 100644 --- a/source/blender/editors/screen/screen_ops.cc +++ b/source/blender/editors/screen/screen_ops.cc @@ -482,6 +482,15 @@ bool ED_operator_editmesh_region_view3d(bContext *C) return false; } +bool ED_operator_editmesh_auto_smooth(bContext *C) +{ + Object *obedit = CTX_data_edit_object(C); + if (obedit && obedit->type == OB_MESH && (((Mesh *)(obedit->data))->flag & ME_AUTOSMOOTH)) { + return nullptr != BKE_editmesh_from_object(obedit); + } + return false; +} + bool ED_operator_editarmature(bContext *C) { Object *obedit = CTX_data_edit_object(C); diff --git a/source/blender/editors/sculpt_paint/CMakeLists.txt b/source/blender/editors/sculpt_paint/CMakeLists.txt index 071d2625ed3..9dbf9d12cf7 100644 --- a/source/blender/editors/sculpt_paint/CMakeLists.txt +++ b/source/blender/editors/sculpt_paint/CMakeLists.txt @@ -97,7 +97,6 @@ set(SRC set(LIB bf_blenkernel - PRIVATE bf::animrig PRIVATE bf::blenlib PRIVATE bf::dna PRIVATE bf::intern::atomic diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_add.cc b/source/blender/editors/sculpt_paint/curves_sculpt_add.cc index fb092bf01ed..95c5eef50cc 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_add.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_add.cc @@ -128,7 +128,7 @@ struct AddOperationExecutor { transforms_ = CurvesSurfaceTransforms(*curves_ob_orig_, curves_id_orig_->surface); Object &surface_ob_orig = *curves_id_orig_->surface; - const Mesh &surface_orig = *static_cast(surface_ob_orig.data); + Mesh &surface_orig = *static_cast(surface_ob_orig.data); if (surface_orig.faces_num == 0) { report_empty_original_surface(stroke_extension.reports); return; @@ -207,7 +207,15 @@ struct AddOperationExecutor { } const Span surface_looptris_orig = surface_orig.looptris(); - const Span corner_normals_su = surface_orig.corner_normals(); + + /* Find normals. */ + if (!CustomData_has_layer(&surface_orig.loop_data, CD_NORMAL)) { + BKE_mesh_calc_normals_split(&surface_orig); + } + const Span corner_normals_su = { + reinterpret_cast(CustomData_get_layer(&surface_orig.loop_data, CD_NORMAL)), + surface_orig.totloop}; + const geometry::ReverseUVSampler reverse_uv_sampler{surface_uv_map, surface_looptris_orig}; geometry::AddCurvesOnMeshInputs add_inputs; diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_density.cc b/source/blender/editors/sculpt_paint/curves_sculpt_density.cc index 3d7f13d9ace..f96de0b55ce 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_density.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_density.cc @@ -76,7 +76,7 @@ struct DensityAddOperationExecutor { CurvesGeometry *curves_orig_ = nullptr; Object *surface_ob_orig_ = nullptr; - const Mesh *surface_orig_ = nullptr; + Mesh *surface_orig_ = nullptr; Object *surface_ob_eval_ = nullptr; Mesh *surface_eval_ = nullptr; @@ -115,7 +115,7 @@ struct DensityAddOperationExecutor { } surface_ob_orig_ = curves_id_orig_->surface; - surface_orig_ = static_cast(surface_ob_orig_->data); + surface_orig_ = static_cast(surface_ob_orig_->data); if (surface_orig_->faces_num == 0) { report_empty_original_surface(stroke_extension.reports); return; @@ -257,7 +257,14 @@ struct DensityAddOperationExecutor { } self_->new_deformed_root_positions_.extend(new_positions_cu); - const Span corner_normals_su = surface_orig_->corner_normals(); + /* Find normals. */ + if (!CustomData_has_layer(&surface_orig_->loop_data, CD_NORMAL)) { + BKE_mesh_calc_normals_split(surface_orig_); + } + const Span corner_normals_su = {reinterpret_cast(CustomData_get_layer( + &surface_orig_->loop_data, CD_NORMAL)), + surface_orig_->totloop}; + const Span surface_looptris_orig = surface_orig_->looptris(); const geometry::ReverseUVSampler reverse_uv_sampler{surface_uv_map, surface_looptris_orig}; diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_puff.cc b/source/blender/editors/sculpt_paint/curves_sculpt_puff.cc index 9f7aaff99de..91627a8cf84 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_puff.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_puff.cc @@ -73,8 +73,8 @@ struct PuffOperationExecutor { CurvesSurfaceTransforms transforms_; - const Object *surface_ob_ = nullptr; - const Mesh *surface_ = nullptr; + Object *surface_ob_ = nullptr; + Mesh *surface_ = nullptr; Span surface_positions_; Span surface_corner_verts_; Span surface_looptris_; @@ -113,14 +113,20 @@ struct PuffOperationExecutor { falloff_shape_ = static_cast(brush_->falloff_shape); surface_ob_ = curves_id_->surface; - surface_ = static_cast(surface_ob_->data); + surface_ = static_cast(surface_ob_->data); transforms_ = CurvesSurfaceTransforms(*object_, surface_ob_); + if (!CustomData_has_layer(&surface_->loop_data, CD_NORMAL)) { + BKE_mesh_calc_normals_split(surface_); + } + corner_normals_su_ = { + reinterpret_cast(CustomData_get_layer(&surface_->loop_data, CD_NORMAL)), + surface_->totloop}; + surface_positions_ = surface_->vert_positions(); surface_corner_verts_ = surface_->corner_verts(); surface_looptris_ = surface_->looptris(); - corner_normals_su_ = surface_->corner_normals(); BKE_bvhtree_from_mesh_get(&surface_bvh_, surface_, BVHTREE_FROM_LOOPTRI, 2); BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh_); }); diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_slide.cc b/source/blender/editors/sculpt_paint/curves_sculpt_slide.cc index 91c1d9c489e..31811424d8f 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_slide.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_slide.cc @@ -99,7 +99,7 @@ struct SlideOperationExecutor { CurvesGeometry *curves_orig_ = nullptr; Object *surface_ob_orig_ = nullptr; - const Mesh *surface_orig_ = nullptr; + Mesh *surface_orig_ = nullptr; Span surface_looptris_orig_; VArraySpan surface_uv_map_orig_; Span corner_normals_orig_su_; @@ -166,19 +166,25 @@ struct SlideOperationExecutor { transforms_ = CurvesSurfaceTransforms(*curves_ob_orig_, curves_id_orig_->surface); surface_ob_orig_ = curves_id_orig_->surface; - surface_orig_ = static_cast(surface_ob_orig_->data); + surface_orig_ = static_cast(surface_ob_orig_->data); if (surface_orig_->faces_num == 0) { report_empty_original_surface(stroke_extension.reports); return; } surface_looptris_orig_ = surface_orig_->looptris(); - corner_normals_orig_su_ = surface_orig_->corner_normals(); surface_uv_map_orig_ = *surface_orig_->attributes().lookup(uv_map_name, ATTR_DOMAIN_CORNER); if (surface_uv_map_orig_.is_empty()) { report_missing_uv_map_on_original_surface(stroke_extension.reports); return; } + if (!CustomData_has_layer(&surface_orig_->loop_data, CD_NORMAL)) { + BKE_mesh_calc_normals_split(surface_orig_); + } + corner_normals_orig_su_ = {reinterpret_cast( + CustomData_get_layer(&surface_orig_->loop_data, CD_NORMAL)), + surface_orig_->totloop}; + surface_ob_eval_ = DEG_get_evaluated_object(ctx_.depsgraph, surface_ob_orig_); if (surface_ob_eval_ == nullptr) { return; @@ -212,7 +218,7 @@ struct SlideOperationExecutor { self_->initial_positions_cu_ = curves_orig_->positions(); self_->initial_deformed_positions_cu_ = deformation.positions; - /* First find all curves to slide. When the mouse moves, only those curves will be moved. */ + /* First find all curves to slide. When the mouse moves, only those curves will be moved. */ this->find_curves_to_slide_with_symmetry(); return; } diff --git a/source/blender/editors/sculpt_paint/grease_pencil_draw_ops.cc b/source/blender/editors/sculpt_paint/grease_pencil_draw_ops.cc index 73bfc930c94..10f6cc7b52f 100644 --- a/source/blender/editors/sculpt_paint/grease_pencil_draw_ops.cc +++ b/source/blender/editors/sculpt_paint/grease_pencil_draw_ops.cc @@ -4,7 +4,6 @@ #include "BKE_context.h" #include "BKE_grease_pencil.hh" -#include "BKE_report.h" #include "DEG_depsgraph_query.hh" @@ -13,11 +12,10 @@ #include "ED_grease_pencil.hh" #include "ED_image.hh" +#include "ED_keyframing.hh" #include "ED_object.hh" #include "ED_screen.hh" -#include "ANIM_keyframing.hh" - #include "RNA_access.hh" #include "RNA_define.hh" #include "RNA_enum_types.hh" @@ -117,50 +115,16 @@ static void stroke_done(const bContext *C, PaintStroke *stroke) GreasePencilStrokeOperation *operation = static_cast( paint_stroke_mode_data(stroke)); operation->on_stroke_done(*C); - operation->~GreasePencilStrokeOperation(); } static int grease_pencil_stroke_invoke(bContext *C, wmOperator *op, const wmEvent *event) { - const Scene *scene = CTX_data_scene(C); - const Object *object = CTX_data_active_object(C); - if (!object || object->type != OB_GREASE_PENCIL) { - return OPERATOR_CANCELLED; - } - - GreasePencil &grease_pencil = *static_cast(object->data); - if (!grease_pencil.has_active_layer()) { - BKE_report(op->reports, RPT_ERROR, "No active Grease Pencil layer"); - return OPERATOR_CANCELLED; - } - const Paint *paint = BKE_paint_get_active_from_context(C); const Brush *brush = BKE_paint_brush_for_read(paint); if (brush == nullptr) { return OPERATOR_CANCELLED; } - const int current_frame = scene->r.cfra; - - if (!grease_pencil.get_active_layer()->frames().contains(current_frame)) { - if (!blender::animrig::is_autokey_on(scene)) { - BKE_report(op->reports, RPT_ERROR, "No Grease Pencil frame to draw on"); - return OPERATOR_CANCELLED; - } - const ToolSettings *ts = CTX_data_tool_settings(C); - bke::greasepencil::Layer &active_layer = *grease_pencil.get_active_layer_for_write(); - if ((ts->gpencil_flags & GP_TOOL_FLAG_RETAIN_LAST) != 0) { - /* For additive drawing, we duplicate the frame that's currently visible and insert it at the - * current frame. */ - grease_pencil.insert_duplicate_frame( - active_layer, active_layer.frame_key_at(current_frame), current_frame, false); - } - else { - /* Otherwise we just insert a blank keyframe. */ - grease_pencil.insert_blank_frame(active_layer, current_frame, 0, BEZT_KEYTYPE_KEYFRAME); - } - } - op->customdata = paint_stroke_new(C, op, stroke_get_location, diff --git a/source/blender/editors/sculpt_paint/grease_pencil_erase.cc b/source/blender/editors/sculpt_paint/grease_pencil_erase.cc index 3ba46f9526a..429137b272b 100644 --- a/source/blender/editors/sculpt_paint/grease_pencil_erase.cc +++ b/source/blender/editors/sculpt_paint/grease_pencil_erase.cc @@ -59,13 +59,7 @@ struct EraseOperationExecutor { int2 mouse_position_pixels{}; int64_t eraser_squared_radius_pixels{}; - bke::greasepencil::DrawingTransforms transforms_; - - EraseOperationExecutor(const bContext &C) - { - Object *object = CTX_data_active_object(&C); - transforms_ = bke::greasepencil::DrawingTransforms(*object); - } + EraseOperationExecutor(const bContext & /*C*/) {} /** * Computes the intersections between a 2D line segment and a circle with integer values. @@ -753,24 +747,22 @@ struct EraseOperationExecutor { GreasePencil &grease_pencil = *static_cast(obact->data); bool changed = false; - const auto execute_eraser_on_drawing = [&](const int layer_index, Drawing &drawing) { + const auto execute_eraser_on_drawing = [&](int drawing_index, Drawing &drawing) { const bke::CurvesGeometry &src = drawing.strokes(); /* Evaluated geometry. */ bke::crazyspace::GeometryDeformation deformation = bke::crazyspace::get_evaluated_grease_pencil_drawing_deformation( - ob_eval, *obact, layer_index); + ob_eval, *obact, drawing_index); /* Compute screen space positions. */ Array screen_space_positions(src.points_num()); threading::parallel_for(src.points_range(), 4096, [&](const IndexRange src_points) { for (const int src_point : src_points) { - ED_view3d_project_float_global( - region, - math::transform_point(transforms_.layer_space_to_world_space, - deformation.positions[src_point]), - screen_space_positions[src_point], - V3D_PROJ_TEST_NOP); + ED_view3d_project_float_global(region, + deformation.positions[src_point], + screen_space_positions[src_point], + V3D_PROJ_TEST_NOP); } }); diff --git a/source/blender/editors/sculpt_paint/grease_pencil_paint.cc b/source/blender/editors/sculpt_paint/grease_pencil_paint.cc index c661981be91..573e28184f9 100644 --- a/source/blender/editors/sculpt_paint/grease_pencil_paint.cc +++ b/source/blender/editors/sculpt_paint/grease_pencil_paint.cc @@ -2,21 +2,14 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ -#include "BKE_brush.hh" -#include "BKE_colortools.h" #include "BKE_context.h" #include "BKE_curves.hh" #include "BKE_grease_pencil.h" #include "BKE_grease_pencil.hh" -#include "BKE_material.h" #include "BKE_scene.h" -#include "BLI_length_parameterize.hh" -#include "BLI_math_geom.h" - #include "DEG_depsgraph_query.hh" -#include "ED_grease_pencil.hh" #include "ED_view3d.hh" #include "WM_api.hh" @@ -26,104 +19,14 @@ namespace blender::ed::sculpt_paint::greasepencil { -static constexpr float POINT_OVERRIDE_THRESHOLD_PX = 3.0f; -static constexpr float POINT_RESAMPLE_MIN_DISTANCE_PX = 10.0f; - -static float calc_brush_radius(ViewContext *vc, - const Brush *brush, - const Scene *scene, - const float3 location) -{ - if (!BKE_brush_use_locked_size(scene, brush)) { - return paint_calc_object_space_radius(vc, location, BKE_brush_size_get(scene, brush)); - } - return BKE_brush_unprojected_radius_get(scene, brush); -} - -template -static inline void linear_interpolation(const T &a, const T &b, MutableSpan dst) -{ - dst.first() = a; - const float step = 1.0f / dst.size(); - for (const int i : dst.index_range().drop_front(1)) { - dst[i] = bke::attribute_math::mix2(i * step, a, b); - } -} - -static float2 arithmetic_mean(Span values) -{ - return std::accumulate(values.begin(), values.end(), float2(0)) / values.size(); -} - -/** Sample a bezier curve at a fixed resolution and return the sampled points in an array. */ -static Array sample_curve_2d(Span positions, const int64_t resolution) -{ - BLI_assert(positions.size() % 3 == 0); - const int64_t num_handles = positions.size() / 3; - if (num_handles == 1) { - return Array(resolution, positions[1]); - } - const int64_t num_segments = num_handles - 1; - const int64_t num_points = num_segments * resolution; - - Array points(num_points); - const Span curve_segments = positions.drop_front(1).drop_back(1); - threading::parallel_for(IndexRange(num_segments), 32 * resolution, [&](const IndexRange range) { - for (const int64_t segment_i : range) { - IndexRange segment_range(segment_i * resolution, resolution); - bke::curves::bezier::evaluate_segment(curve_segments[segment_i * 3 + 0], - curve_segments[segment_i * 3 + 1], - curve_segments[segment_i * 3 + 2], - curve_segments[segment_i * 3 + 3], - points.as_mutable_span().slice(segment_range)); - } - }); - return points; -} - -/** Morph \a src onto \a target such that the points have the same spacing as in \a src and - * write the result to \a dst. */ -static void morph_points_to_curve(Span src, Span target, MutableSpan dst) -{ - BLI_assert(src.size() == dst.size()); - Array accumulated_lengths_src(src.size() - 1); - length_parameterize::accumulate_lengths(src, false, accumulated_lengths_src); - - Array accumulated_lengths_target(target.size() - 1); - length_parameterize::accumulate_lengths(target, false, accumulated_lengths_target); - - Array segment_indices(accumulated_lengths_src.size()); - Array segment_factors(accumulated_lengths_src.size()); - length_parameterize::sample_at_lengths( - accumulated_lengths_target, accumulated_lengths_src, segment_indices, segment_factors); - - length_parameterize::interpolate( - target, segment_indices, segment_factors, dst.drop_back(1)); - dst.last() = src.last(); -} - class PaintOperation : public GreasePencilStrokeOperation { - private: - /* Screen space coordinates from input samples. */ - Vector screen_space_coords_orig_; - /* Temporary vector of curve fitted screen space coordinates per input sample from the active - * smoothing window. */ - Vector> screen_space_curve_fitted_coords_; - /* Screen space coordinates after smoothing. */ - Vector screen_space_smoothed_coords_; - /* The start index of the smoothing window. */ - int active_smooth_start_index_ = 0; - - friend struct PaintOperationExecutor; public: + ~PaintOperation() override {} + void on_stroke_begin(const bContext &C, const InputSample &start_sample) override; void on_stroke_extended(const bContext &C, const InputSample &extension_sample) override; void on_stroke_done(const bContext &C) override; - - private: - void simplify_stroke(bke::greasepencil::Drawing &drawing, float epsilon_px); - void process_stroke_end(bke::greasepencil::Drawing &drawing); }; /** @@ -131,466 +34,126 @@ class PaintOperation : public GreasePencilStrokeOperation { * because it avoids passing a very large number of parameters between functions. */ struct PaintOperationExecutor { - Scene *scene_; - ARegion *region_; - GreasePencil *grease_pencil_; - Brush *brush_; + PaintOperationExecutor(const bContext & /*C*/) {} - BrushGpencilSettings *settings_; - float4 vertex_color_; - - bke::greasepencil::Drawing *drawing_; - - bke::greasepencil::DrawingTransforms transforms_; - - PaintOperationExecutor(const bContext &C) + void execute(PaintOperation & /*self*/, const bContext &C, const InputSample &extension_sample) { - scene_ = CTX_data_scene(&C); - region_ = CTX_wm_region(&C); - Object *object = CTX_data_active_object(&C); - grease_pencil_ = static_cast(object->data); + using namespace blender::bke; + Depsgraph *depsgraph = CTX_data_depsgraph_pointer(&C); + ARegion *region = CTX_wm_region(&C); + Object *obact = CTX_data_active_object(&C); + Object *ob_eval = DEG_get_evaluated_object(depsgraph, obact); - Paint *paint = &scene_->toolsettings->gp_paint->paint; - brush_ = BKE_paint_brush(paint); - settings_ = brush_->gpencil_settings; + /** + * Note: We write to the evaluated object here, so that the additional copy from orig -> eval + * is not needed for every update. After the stroke is done, the result is written to the + * original object. + */ + GreasePencil &grease_pencil = *static_cast(ob_eval->data); - const bool use_vertex_color = (scene_->toolsettings->gp_paint->mode == - GPPAINT_FLAG_USE_VERTEXCOLOR); - const bool use_vertex_color_stroke = use_vertex_color && ELEM(settings_->vertex_mode, - GPPAINT_MODE_STROKE, - GPPAINT_MODE_BOTH); - vertex_color_ = use_vertex_color_stroke ? float4(brush_->rgb[0], - brush_->rgb[1], - brush_->rgb[2], - settings_->vertex_factor) : - float4(0.0f); + float4 plane{0.0f, -1.0f, 0.0f, 0.0f}; + float3 proj_pos; + ED_view3d_win_to_3d_on_plane(region, plane, extension_sample.mouse_position, false, proj_pos); - // const bool use_vertex_color_fill = use_vertex_color && ELEM( - // brush->gpencil_settings->vertex_mode, GPPAINT_MODE_STROKE, GPPAINT_MODE_BOTH); + bke::greasepencil::StrokePoint new_point{ + proj_pos, extension_sample.pressure * 100.0f, 1.0f, float4(1.0f)}; - /* The object should have an active layer. */ - BLI_assert(grease_pencil_->has_active_layer()); - bke::greasepencil::Layer &active_layer = *grease_pencil_->get_active_layer_for_write(); - const int drawing_index = active_layer.drawing_index_at(scene_->r.cfra); + grease_pencil.runtime->stroke_cache.points.append(std::move(new_point)); - /* Drawing should exist. */ - BLI_assert(drawing_index >= 0); - drawing_ = - &reinterpret_cast(grease_pencil_->drawing(drawing_index))->wrap(); - - transforms_ = bke::greasepencil::DrawingTransforms(*object); - } - - float3 screen_space_to_drawing_plane(const float2 co) - { - /* TODO: Use correct plane/projection. */ - float4 plane; - plane_from_point_normal_v3( - plane, transforms_.layer_space_to_world_space.location(), float3{0, -1, 0}); - float3 proj_point; - ED_view3d_win_to_3d_on_plane(region_, plane, co, false, proj_point); - return math::transform_point(transforms_.world_space_to_layer_space, proj_point); - } - - float radius_from_input_sample(const bContext &C, const InputSample &sample) - { - ViewContext vc = ED_view3d_viewcontext_init(const_cast(&C), - CTX_data_depsgraph_pointer(&C)); - float radius = calc_brush_radius( - &vc, brush_, scene_, screen_space_to_drawing_plane(sample.mouse_position)); - if (BKE_brush_use_size_pressure(brush_)) { - radius *= BKE_curvemapping_evaluateF(settings_->curve_sensitivity, 0, sample.pressure); - } - return radius; - } - - float opacity_from_input_sample(const InputSample &sample) - { - float opacity = BKE_brush_alpha_get(scene_, brush_); - if (BKE_brush_use_alpha_pressure(brush_)) { - opacity *= BKE_curvemapping_evaluateF(settings_->curve_strength, 0, sample.pressure); - } - return opacity; - } - - void process_start_sample(PaintOperation &self, - const bContext &C, - const InputSample &start_sample, - const int material_index) - { - const float2 start_coords = start_sample.mouse_position; - const float start_radius = this->radius_from_input_sample(C, start_sample); - const float start_opacity = this->opacity_from_input_sample(start_sample); - const ColorGeometry4f start_vertex_color = ColorGeometry4f(vertex_color_); - - self.screen_space_coords_orig_.append(start_coords); - self.screen_space_curve_fitted_coords_.append(Vector({start_coords})); - self.screen_space_smoothed_coords_.append(start_coords); - - /* Resize the curves geometry so there is one more curve with a single point. */ - bke::CurvesGeometry &curves = drawing_->strokes_for_write(); - const int num_old_points = curves.points_num(); - curves.resize(curves.points_num() + 1, curves.curves_num() + 1); - curves.offsets_for_write().last(1) = num_old_points; - - curves.positions_for_write().last() = screen_space_to_drawing_plane(start_coords); - drawing_->radii_for_write().last() = start_radius; - drawing_->opacities_for_write().last() = start_opacity; - drawing_->vertex_colors_for_write().last() = start_vertex_color; - - bke::MutableAttributeAccessor attributes = curves.attributes_for_write(); - bke::SpanAttributeWriter materials = attributes.lookup_or_add_for_write_span( - "material_index", ATTR_DOMAIN_CURVE); - bke::SpanAttributeWriter cyclic = attributes.lookup_or_add_for_write_span( - "cyclic", ATTR_DOMAIN_CURVE); - cyclic.span.last() = false; - materials.span.last() = material_index; - - cyclic.finish(); - materials.finish(); - - curves.curve_types_for_write().last() = CURVE_TYPE_POLY; - curves.update_curve_types(); - - /* Initialize the rest of the attributes with default values. */ - Set attributes_to_skip{{"position", - "curve_type", - "material_index", - "cyclic", - "radius", - "opacity", - "vertex_color"}}; - attributes.for_all( - [&](const bke::AttributeIDRef &id, const bke::AttributeMetaData /*meta_data*/) { - if (attributes_to_skip.contains(id.name())) { - return true; - } - bke::GSpanAttributeWriter attribute = attributes.lookup_for_write_span(id); - const CPPType &type = attribute.span.type(); - GMutableSpan new_data = attribute.span.slice(attribute.domain == ATTR_DOMAIN_POINT ? - curves.points_range().take_back(1) : - curves.curves_range().take_back(1)); - type.fill_assign_n(type.default_value(), new_data.data(), new_data.size()); - attribute.finish(); - return true; - }); - - drawing_->tag_topology_changed(); - } - - void active_smoothing(PaintOperation &self, - const IndexRange smooth_window, - MutableSpan curve_positions) - { - const Span coords_to_smooth = self.screen_space_coords_orig_.as_span().slice( - smooth_window); - - /* Detect corners in the current slice of coordinates. */ - const float corner_min_radius_px = 5.0f; - const float corner_max_radius_px = 30.0f; - const int64_t corner_max_samples = 64; - const float corner_angle_threshold = 0.6f; - IndexMaskMemory memory; - const IndexMask corner_mask = ed::greasepencil::polyline_detect_corners( - coords_to_smooth.drop_front(1).drop_back(1), - corner_min_radius_px, - corner_max_radius_px, - corner_max_samples, - corner_angle_threshold, - memory); - - /* Pre-blur the coordinates for the curve fitting. This generally leads to a better fit. */ - Array coords_pre_blur(smooth_window.size()); - const int pre_blur_iterations = 3; - ed::greasepencil::gaussian_blur_1D(coords_to_smooth, - pre_blur_iterations, - 1.0f, - true, - true, - false, - coords_pre_blur.as_mutable_span()); - - /* Curve fitting. The output will be a set of handles (float2 triplets) in a flat array. */ - const float max_error_threshold_px = 5.0f; - Array curve_points = ed::greasepencil::polyline_fit_curve( - coords_pre_blur, max_error_threshold_px * settings_->active_smooth, corner_mask); - - /* Sampling the curve at a fixed resolution. */ - const int64_t sample_resolution = 32; - Array sampled_curve_points = sample_curve_2d(curve_points, sample_resolution); - - /* Morphing the coordinates onto the curve. Result is stored in a temporary array. */ - Array coords_smoothed(coords_to_smooth.size()); - morph_points_to_curve(coords_to_smooth, sampled_curve_points, coords_smoothed); - - MutableSpan window_coords = self.screen_space_smoothed_coords_.as_mutable_span().slice( - smooth_window); - MutableSpan positions_slice = curve_positions.slice(smooth_window); - const float converging_threshold_px = 0.1f; - bool stop_counting_converged = false; - int num_converged = 0; - for (const int64_t window_i : smooth_window.index_range()) { - /* Record the curve fitting of this point. */ - self.screen_space_curve_fitted_coords_[window_i].append(coords_smoothed[window_i]); - Span fit_coords = self.screen_space_curve_fitted_coords_[window_i]; - - /* We compare the previous arithmetic mean to the current. Going from the back to the front, - * if a point hasn't moved by a minimum threshold, it counts as converged. */ - float2 new_pos = arithmetic_mean(fit_coords); - if (!stop_counting_converged) { - float2 prev_pos = window_coords[window_i]; - if (math::distance(new_pos, prev_pos) < converging_threshold_px) { - num_converged++; - } - else { - stop_counting_converged = true; - } - } - - /* Update the positions in the current cache. */ - window_coords[window_i] = new_pos; - positions_slice[window_i] = screen_space_to_drawing_plane(new_pos); - } - - /* Remove all the converged points from the active window and shrink the window accordingly. */ - if (num_converged > 0) { - self.active_smooth_start_index_ += num_converged; - self.screen_space_curve_fitted_coords_.remove(0, num_converged); - } - } - - void process_extension_sample(PaintOperation &self, - const bContext &C, - const InputSample &extension_sample) - { - const float2 coords = extension_sample.mouse_position; - const float radius = this->radius_from_input_sample(C, extension_sample); - const float opacity = this->opacity_from_input_sample(extension_sample); - const ColorGeometry4f vertex_color = ColorGeometry4f(vertex_color_); - - bke::CurvesGeometry &curves = drawing_->strokes_for_write(); - bke::MutableAttributeAccessor attributes = curves.attributes_for_write(); - - const float2 prev_coords = self.screen_space_coords_orig_.last(); - const float prev_radius = drawing_->radii().last(); - const float prev_opacity = drawing_->opacities().last(); - const ColorGeometry4f prev_vertex_color = drawing_->vertex_colors().last(); - - /* Overwrite last point if it's very close. */ - if (math::distance(coords, prev_coords) < POINT_OVERRIDE_THRESHOLD_PX) { - curves.positions_for_write().last() = screen_space_to_drawing_plane(coords); - drawing_->radii_for_write().last() = math::max(radius, prev_radius); - drawing_->opacities_for_write().last() = math::max(opacity, prev_opacity); - return; - } - - /* If the next sample is far away, we subdivide the segment to add more points. */ - int new_points_num = 1; - const float distance_px = math::distance(coords, prev_coords); - if (distance_px > POINT_RESAMPLE_MIN_DISTANCE_PX) { - const int subdivisions = int(math::floor(distance_px / POINT_RESAMPLE_MIN_DISTANCE_PX)) - 1; - new_points_num += subdivisions; - } - - /* Resize the curves geometry. */ - curves.resize(curves.points_num() + new_points_num, curves.curves_num()); - curves.offsets_for_write().last() = curves.points_num(); - const IndexRange curve_points = curves.points_by_curve()[curves.curves_range().last()]; - - /* Subdivide stroke in new_points. */ - const IndexRange new_points = curve_points.take_back(new_points_num); - Array new_screen_space_coords(new_points_num); - MutableSpan positions = curves.positions_for_write(); - MutableSpan new_positions = positions.slice(new_points); - MutableSpan new_radii = drawing_->radii_for_write().slice(new_points); - MutableSpan new_opacities = drawing_->opacities_for_write().slice(new_points); - MutableSpan new_vertex_colors = drawing_->vertex_colors_for_write().slice( - new_points); - linear_interpolation(prev_coords, coords, new_screen_space_coords); - linear_interpolation(prev_radius, radius, new_radii); - linear_interpolation(prev_opacity, opacity, new_opacities); - linear_interpolation(prev_vertex_color, vertex_color, new_vertex_colors); - - /* Update screen space buffers with new points. */ - self.screen_space_coords_orig_.extend(new_screen_space_coords); - self.screen_space_smoothed_coords_.extend(new_screen_space_coords); - for (float2 new_position : new_screen_space_coords) { - self.screen_space_curve_fitted_coords_.append(Vector({new_position})); - } - - /* Only start smoothing if there are enough points. */ - const int64_t min_active_smoothing_points_num = 8; - const IndexRange smooth_window = self.screen_space_coords_orig_.index_range().drop_front( - self.active_smooth_start_index_); - if (smooth_window.size() < min_active_smoothing_points_num) { - for (const int64_t i : new_screen_space_coords.index_range()) { - new_positions[i] = screen_space_to_drawing_plane(new_screen_space_coords[i]); - } - } - else { - /* Active smoothing is done in a window at the end of the new stroke. */ - this->active_smoothing(self, smooth_window, positions.slice(curve_points)); - } - - /* Initialize the rest of the attributes with default values. */ - Set attributes_to_skip{{"position", "radius", "opacity", "vertex_color"}}; - attributes.for_all([&](const bke::AttributeIDRef &id, const bke::AttributeMetaData meta_data) { - if (attributes_to_skip.contains(id.name()) || meta_data.domain != ATTR_DOMAIN_POINT) { - return true; - } - bke::GSpanAttributeWriter attribute = attributes.lookup_for_write_span(id); - const CPPType &type = attribute.span.type(); - GMutableSpan new_data = attribute.span.slice(new_points); - type.fill_assign_n(type.default_value(), new_data.data(), new_data.size()); - attribute.finish(); - return true; - }); - } - - void execute(PaintOperation &self, const bContext &C, const InputSample &extension_sample) - { - this->process_extension_sample(self, C, extension_sample); - drawing_->tag_topology_changed(); + BKE_grease_pencil_batch_cache_dirty_tag(&grease_pencil, BKE_GREASEPENCIL_BATCH_DIRTY_ALL); } }; -void PaintOperation::on_stroke_begin(const bContext &C, const InputSample &start_sample) +void PaintOperation::on_stroke_begin(const bContext & /*C*/, const InputSample & /*start_sample*/) { - Scene *scene = CTX_data_scene(&C); - Object *object = CTX_data_active_object(&C); - GreasePencil *grease_pencil = static_cast(object->data); - - Paint *paint = &scene->toolsettings->gp_paint->paint; - Brush *brush = BKE_paint_brush(paint); - - BKE_curvemapping_init(brush->gpencil_settings->curve_sensitivity); - BKE_curvemapping_init(brush->gpencil_settings->curve_strength); - BKE_curvemapping_init(brush->gpencil_settings->curve_jitter); - BKE_curvemapping_init(brush->gpencil_settings->curve_rand_pressure); - BKE_curvemapping_init(brush->gpencil_settings->curve_rand_strength); - BKE_curvemapping_init(brush->gpencil_settings->curve_rand_uv); - BKE_curvemapping_init(brush->gpencil_settings->curve_rand_hue); - BKE_curvemapping_init(brush->gpencil_settings->curve_rand_saturation); - BKE_curvemapping_init(brush->gpencil_settings->curve_rand_value); - - Material *material = BKE_grease_pencil_object_material_ensure_from_active_input_brush( - CTX_data_main(&C), object, brush); - const int material_index = BKE_grease_pencil_object_material_index_get(object, material); - - PaintOperationExecutor executor{C}; - executor.process_start_sample(*this, C, start_sample, material_index); - - DEG_id_tag_update(&grease_pencil->id, ID_RECALC_GEOMETRY); - WM_event_add_notifier(&C, NC_GEOM | ND_DATA, grease_pencil); } void PaintOperation::on_stroke_extended(const bContext &C, const InputSample &extension_sample) { - Object *object = CTX_data_active_object(&C); - GreasePencil *grease_pencil = static_cast(object->data); - PaintOperationExecutor executor{C}; executor.execute(*this, C, extension_sample); - - DEG_id_tag_update(&grease_pencil->id, ID_RECALC_GEOMETRY); - WM_event_add_notifier(&C, NC_GEOM | ND_DATA, grease_pencil); -} - -static float dist_to_interpolated_2d( - float2 pos, float2 posA, float2 posB, float val, float valA, float valB) -{ - const float dist1 = math::distance_squared(posA, pos); - const float dist2 = math::distance_squared(posB, pos); - - if (dist1 + dist2 > 1e-5f) { - const float interpolated_val = math::interpolate(valB, valA, dist1 / (dist1 + dist2)); - return math::distance(interpolated_val, val); - } - return 0.0f; -} - -void PaintOperation::simplify_stroke(bke::greasepencil::Drawing &drawing, const float epsilon_px) -{ - const int stroke_index = drawing.strokes().curves_range().last(); - const IndexRange points = drawing.strokes().points_by_curve()[stroke_index]; - bke::CurvesGeometry &curves = drawing.strokes_for_write(); - const VArray radii = drawing.radii(); - - /* Distance function for `ramer_douglas_peucker_simplify`. */ - const Span positions_2d = this->screen_space_smoothed_coords_.as_span(); - const auto dist_function = [&](int64_t first_index, int64_t last_index, int64_t index) { - /* 2D coordinates are only stored for the current stroke, so offset the indices. */ - const float dist_position_px = dist_to_line_segment_v2( - positions_2d[index - points.first()], - positions_2d[first_index - points.first()], - positions_2d[last_index - points.first()]); - const float dist_radii_px = dist_to_interpolated_2d(positions_2d[index - points.first()], - positions_2d[first_index - points.first()], - positions_2d[last_index - points.first()], - radii[index], - radii[first_index], - radii[last_index]); - return math::max(dist_position_px, dist_radii_px); - }; - - Array points_to_delete(curves.points_num(), false); - int64_t total_points_to_delete = ed::greasepencil::ramer_douglas_peucker_simplify( - points, epsilon_px, dist_function, points_to_delete.as_mutable_span()); - - if (total_points_to_delete > 0) { - IndexMaskMemory memory; - curves.remove_points(IndexMask::from_bools(points_to_delete, memory)); - } -} - -void PaintOperation::process_stroke_end(bke::greasepencil::Drawing &drawing) -{ - const int stroke_index = drawing.strokes().curves_range().last(); - const IndexRange points = drawing.strokes().points_by_curve()[stroke_index]; - bke::CurvesGeometry &curves = drawing.strokes_for_write(); - const VArray radii = drawing.radii(); - - /* Remove points at the end that have a radius close to 0. */ - int64_t points_to_remove = 0; - for (int64_t index = points.last(); index >= points.first(); index--) { - if (radii[index] < 1e-5f) { - points_to_remove++; - } - else { - break; - } - } - if (points_to_remove > 0) { - curves.resize(curves.points_num() - points_to_remove, curves.curves_num()); - curves.offsets_for_write().last() = curves.points_num(); - } } void PaintOperation::on_stroke_done(const bContext &C) { using namespace blender::bke; + Depsgraph *depsgraph = CTX_data_depsgraph_pointer(&C); Scene *scene = CTX_data_scene(&C); - Object *object = CTX_data_active_object(&C); - GreasePencil &grease_pencil = *static_cast(object->data); + Object *obact = CTX_data_active_object(&C); + Object *ob_eval = DEG_get_evaluated_object(depsgraph, obact); - /* Grease Pencil should have an active layer. */ - BLI_assert(grease_pencil.has_active_layer()); - bke::greasepencil::Layer &active_layer = *grease_pencil.get_active_layer_for_write(); - const int drawing_index = active_layer.drawing_index_at(scene->r.cfra); + GreasePencil &grease_pencil_orig = *static_cast(obact->data); + GreasePencil &grease_pencil_eval = *static_cast(ob_eval->data); + BLI_assert(grease_pencil_orig.has_active_layer()); + const bke::greasepencil::Layer &active_layer_orig = *grease_pencil_orig.get_active_layer(); + int index_orig = active_layer_orig.drawing_index_at(scene->r.cfra); - /* Drawing should exist. */ - BLI_assert(drawing_index >= 0); - bke::greasepencil::Drawing &drawing = - reinterpret_cast(grease_pencil.drawing(drawing_index))->wrap(); + bke::greasepencil::Drawing &drawing_orig = + reinterpret_cast(grease_pencil_orig.drawing(index_orig))->wrap(); - const float simplifiy_threshold_px = 0.5f; - this->simplify_stroke(drawing, simplifiy_threshold_px); - this->process_stroke_end(drawing); - drawing.tag_topology_changed(); + const Span stroke_points = + grease_pencil_eval.runtime->stroke_buffer(); + CurvesGeometry &curves = drawing_orig.strokes_for_write(); - DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY); - WM_main_add_notifier(NC_GEOM | ND_DATA, &grease_pencil.id); + int num_old_curves = curves.curves_num(); + int num_old_points = curves.points_num(); + curves.resize(num_old_points + stroke_points.size(), num_old_curves + 1); + + curves.offsets_for_write()[num_old_curves] = num_old_points; + curves.offsets_for_write()[num_old_curves + 1] = num_old_points + stroke_points.size(); + + const OffsetIndices points_by_curve = curves.points_by_curve(); + const IndexRange new_points_range = points_by_curve[curves.curves_num() - 1]; + const IndexRange new_curves_range = IndexRange(num_old_curves, 1); + + /* Set position, radius and opacity attribute. */ + bke::MutableAttributeAccessor attributes = curves.attributes_for_write(); + MutableSpan positions = curves.positions_for_write(); + MutableSpan radii = drawing_orig.radii_for_write(); + MutableSpan opacities = drawing_orig.opacities_for_write(); + for (const int i : IndexRange(stroke_points.size())) { + const bke::greasepencil::StrokePoint &point = stroke_points[i]; + const int point_i = new_points_range[i]; + positions[point_i] = point.position; + radii[point_i] = point.radius; + opacities[point_i] = point.opacity; + } + + /* Set material index attribute. */ + int material_index = 0; + SpanAttributeWriter materials = attributes.lookup_or_add_for_write_span( + "material_index", ATTR_DOMAIN_CURVE); + + materials.span.slice(new_curves_range).fill(material_index); + + /* Set curve_type attribute. */ + curves.fill_curve_types(new_curves_range, CURVE_TYPE_POLY); + + /* Explicitly set all other attributes besides those processed above to default values. */ + Set attributes_to_skip{ + {"position", "radius", "opacity", "material_index", "curve_type"}}; + attributes.for_all( + [&](const bke::AttributeIDRef &id, const bke::AttributeMetaData /*meta_data*/) { + if (attributes_to_skip.contains(id.name())) { + return true; + } + bke::GSpanAttributeWriter attribute = attributes.lookup_for_write_span(id); + const CPPType &type = attribute.span.type(); + GMutableSpan new_data = attribute.span.slice( + attribute.domain == ATTR_DOMAIN_POINT ? new_points_range : new_curves_range); + type.fill_assign_n(type.default_value(), new_data.data(), new_data.size()); + attribute.finish(); + return true; + }); + + grease_pencil_eval.runtime->stroke_cache.clear(); + drawing_orig.tag_positions_changed(); + + materials.finish(); + + DEG_id_tag_update(&grease_pencil_orig.id, ID_RECALC_GEOMETRY); + WM_main_add_notifier(NC_GEOM | ND_DATA, &grease_pencil_orig.id); } std::unique_ptr new_paint_operation() diff --git a/source/blender/editors/sculpt_paint/paint_cursor.cc b/source/blender/editors/sculpt_paint/paint_cursor.cc index ee588755dbc..0cadb3c7142 100644 --- a/source/blender/editors/sculpt_paint/paint_cursor.cc +++ b/source/blender/editors/sculpt_paint/paint_cursor.cc @@ -16,7 +16,6 @@ #include "DNA_brush_types.h" #include "DNA_color_types.h" #include "DNA_customdata_types.h" -#include "DNA_material_types.h" #include "DNA_mesh_types.h" #include "DNA_object_types.h" #include "DNA_scene_types.h" @@ -29,7 +28,6 @@ #include "BKE_colortools.h" #include "BKE_context.h" #include "BKE_curve.h" -#include "BKE_grease_pencil.hh" #include "BKE_image.h" #include "BKE_node_runtime.hh" #include "BKE_object.hh" @@ -43,7 +41,6 @@ #include "IMB_colormanagement.h" #include "IMB_imbuf_types.h" -#include "ED_grease_pencil.hh" #include "ED_image.hh" #include "ED_view3d.hh" @@ -1065,14 +1062,14 @@ static void cursor_draw_tiling_preview(const uint gpuattr, Object *ob, const float radius) { - const BoundBox bb = *BKE_object_boundbox_get(ob); + const BoundBox *bb = BKE_object_boundbox_get(ob); float orgLoc[3], location[3]; int tile_pass = 0; int start[3]; int end[3]; int cur[3]; - const float *bbMin = bb.vec[0]; - const float *bbMax = bb.vec[6]; + const float *bbMin = bb->vec[0]; + const float *bbMax = bb->vec[6]; const float *step = sd->paint.tile_offset; copy_v3_v3(orgLoc, true_location); @@ -1206,24 +1203,10 @@ static void SCULPT_layer_brush_height_preview_draw(const uint gpuattr, static bool paint_use_2d_cursor(ePaintMode mode) { - switch (mode) { - case PAINT_MODE_SCULPT: - case PAINT_MODE_VERTEX: - case PAINT_MODE_WEIGHT: - return false; - case PAINT_MODE_TEXTURE_3D: - case PAINT_MODE_TEXTURE_2D: - case PAINT_MODE_SCULPT_UV: - case PAINT_MODE_VERTEX_GPENCIL: - case PAINT_MODE_SCULPT_GPENCIL: - case PAINT_MODE_WEIGHT_GPENCIL: - case PAINT_MODE_SCULPT_CURVES: - case PAINT_MODE_GPENCIL: - return true; - case PAINT_MODE_INVALID: - BLI_assert_unreachable(); + if (mode >= PAINT_MODE_TEXTURE_3D) { + return true; } - return true; + return false; } enum PaintCursorDrawingType { @@ -1307,7 +1290,7 @@ static bool paint_cursor_context_init(bContext *C, } pcontext->mode = BKE_paintmode_get_active_from_context(C); - pcontext->vc = ED_view3d_viewcontext_init(C, pcontext->depsgraph); + ED_view3d_viewcontext_init(C, &pcontext->vc, pcontext->depsgraph); if (pcontext->brush->flag & BRUSH_CURVE) { pcontext->cursor_type = PAINT_CURSOR_CURVE; @@ -1439,15 +1422,10 @@ static void paint_update_mouse_cursor(PaintCursorContext *pcontext) * with the UI (dragging a number button for e.g.), see: #102792. */ return; } - if (pcontext->mode == PAINT_MODE_GPENCIL) { - WM_cursor_set(pcontext->win, WM_CURSOR_DOT); - } - else { - WM_cursor_set(pcontext->win, WM_CURSOR_PAINT); - } + WM_cursor_set(pcontext->win, WM_CURSOR_PAINT); } -static void paint_draw_2D_view_brush_cursor_default(PaintCursorContext *pcontext) +static void paint_draw_2D_view_brush_cursor(PaintCursorContext *pcontext) { immUniformColor3fvAlpha(pcontext->outline_col, pcontext->outline_alpha); @@ -1470,147 +1448,6 @@ static void paint_draw_2D_view_brush_cursor_default(PaintCursorContext *pcontext 40); } -static void grease_pencil_eraser_draw(PaintCursorContext *pcontext) -{ - float radius = static_cast(BKE_brush_size_get(pcontext->scene, pcontext->brush)); - - /* Red-ish color with alpha. */ - immUniformColor4ub(255, 100, 100, 20); - imm_draw_circle_fill_2d(pcontext->pos, pcontext->x, pcontext->y, radius, 40); - - immUnbindProgram(); - - immBindBuiltinProgram(GPU_SHADER_3D_LINE_DASHED_UNIFORM_COLOR); - - float viewport_size[4]; - GPU_viewport_size_get_f(viewport_size); - immUniform2f("viewport_size", viewport_size[2], viewport_size[3]); - - immUniformColor4f(1.0f, 0.39f, 0.39f, 0.78f); - immUniform1i("colors_len", 0); /* "simple" mode */ - immUniform1f("dash_width", 12.0f); - immUniform1f("udash_factor", 0.5f); - - /* XXX Dashed shader gives bad results with sets of small segments - * currently, temp hack around the issue. :( */ - const int nsegments = max_ii(8, radius / 2); - imm_draw_circle_wire_2d(pcontext->pos, pcontext->x, pcontext->y, radius, nsegments); -} - -static void grease_pencil_brush_cursor_draw(PaintCursorContext *pcontext) -{ - using namespace blender; - if ((pcontext->region) && (pcontext->region->regiontype != RGN_TYPE_WINDOW)) { - return; - } - if (pcontext->region && !BLI_rcti_isect_pt(&pcontext->region->winrct, pcontext->x, pcontext->y)) - { - return; - } - - Object *object = CTX_data_active_object(pcontext->C); - if (object->type != OB_GREASE_PENCIL) { - return; - } - - /* default radius and color */ - float color[3] = {1.0f, 1.0f, 1.0f}; - float darkcolor[3]; - float radius = 2.0f; - - const int x = pcontext->x; - const int y = pcontext->y; - - /* for paint use paint brush size and color */ - if (pcontext->mode == PAINT_MODE_GPENCIL) { - Paint *paint = pcontext->paint; - Brush *brush = pcontext->brush; - if ((brush == nullptr) || (brush->gpencil_settings == nullptr)) { - return; - } - - if ((paint->flags & PAINT_SHOW_BRUSH) == 0) { - return; - } - - /* Eraser has a special shape and use a different shader program. */ - if (brush->gpencil_tool == GPAINT_TOOL_ERASE) { - grease_pencil_eraser_draw(pcontext); - return; - } - - /* Note: For now, there is only as screen space sized cursor. */ - radius = BKE_brush_size_get(pcontext->scene, brush); - - /* Get current drawing material. */ - Material *ma = BKE_grease_pencil_object_material_from_brush_get(object, brush); - if (ma) { - MaterialGPencilStyle *gp_style = ma->gp_style; - - /* Follow user settings for the size of the draw cursor: - * - Fixed size, or - * - Brush size (i.e. stroke thickness) - */ - if ((gp_style) && ((brush->gpencil_settings->flag & GP_BRUSH_STABILIZE_MOUSE) == 0) && - ((brush->gpencil_settings->flag & GP_BRUSH_STABILIZE_MOUSE_TEMP) == 0) && - (brush->gpencil_tool == GPAINT_TOOL_DRAW)) - { - - const bool use_vertex_color = (pcontext->scene->toolsettings->gp_paint->mode == - GPPAINT_FLAG_USE_VERTEXCOLOR); - const bool use_vertex_color_stroke = use_vertex_color && - ELEM(brush->gpencil_settings->vertex_mode, - GPPAINT_MODE_STROKE, - GPPAINT_MODE_BOTH); - - copy_v3_v3(color, use_vertex_color_stroke ? brush->rgb : gp_style->stroke_rgba); - } - } - } - - GPU_line_width(1.0f); - /* Inner Ring: Color from UI panel */ - immUniformColor4f(color[0], color[1], color[2], 0.8f); - imm_draw_circle_wire_2d(pcontext->pos, x, y, radius, 32); - - /* Outer Ring: Dark color for contrast on light backgrounds (e.g. gray on white) */ - mul_v3_v3fl(darkcolor, color, 0.40f); - immUniformColor4f(darkcolor[0], darkcolor[1], darkcolor[2], 0.8f); - imm_draw_circle_wire_2d(pcontext->pos, x, y, radius + 1, 32); - - /* Draw line for lazy mouse */ - /* TODO: No stabilize mode yet. */ - // if ((last_mouse_position) && - // (pcontext->xbrush->gpencil_settings->flag & GP_BRUSH_STABILIZE_MOUSE_TEMP)) - // { - // GPU_line_smooth(true); - // GPU_blend(GPU_BLEND_ALPHA); - - // copy_v3_v3(color, pcontext->brush->add_col); - // immUniformColor4f(color[0], color[1], color[2], 0.8f); - - // immBegin(GPU_PRIM_LINES, 2); - // immVertex2f(pos, x, y); - // immVertex2f(pos, - // last_mouse_position[0] + pcontext->region->winrct.xmin, - // last_mouse_position[1] + pcontext->region->winrct.ymin); - // immEnd(); - // } -} - -static void paint_draw_2D_view_brush_cursor(PaintCursorContext *pcontext) -{ - switch (pcontext->mode) { - case PAINT_MODE_GPENCIL: { - grease_pencil_brush_cursor_draw(pcontext); - break; - } - default: { - paint_draw_2D_view_brush_cursor_default(pcontext); - } - } -} - static void paint_draw_legacy_3D_view_brush_cursor(PaintCursorContext *pcontext) { GPU_line_width(1.0f); @@ -2105,8 +1942,6 @@ static void paint_draw_cursor(bContext *C, int x, int y, void * /*unused*/) paint_draw_curve_cursor(pcontext.brush, &pcontext.vc); break; case PAINT_CURSOR_2D: - paint_update_mouse_cursor(&pcontext); - paint_cursor_update_rake_rotation(&pcontext); paint_cursor_check_and_draw_alpha_overlays(&pcontext); paint_cursor_update_anchored_location(&pcontext); diff --git a/source/blender/editors/sculpt_paint/paint_curve.cc b/source/blender/editors/sculpt_paint/paint_curve.cc index 17fcfdd9872..f3509836b5b 100644 --- a/source/blender/editors/sculpt_paint/paint_curve.cc +++ b/source/blender/editors/sculpt_paint/paint_curve.cc @@ -686,9 +686,6 @@ static int paintcurve_draw_exec(bContext *C, wmOperator * /*op*/) case PAINT_MODE_SCULPT_CURVES: name = "SCULPT_CURVES_OT_brush_stroke"; break; - case PAINT_MODE_GPENCIL: - name = "GREASE_PENCIL_OT_brush_stroke"; - break; default: return OPERATOR_PASS_THROUGH; } diff --git a/source/blender/editors/sculpt_paint/paint_hide.cc b/source/blender/editors/sculpt_paint/paint_hide.cc index f9705fc35fe..1c46d7e577c 100644 --- a/source/blender/editors/sculpt_paint/paint_hide.cc +++ b/source/blender/editors/sculpt_paint/paint_hide.cc @@ -76,9 +76,11 @@ static void partialvis_update_mesh(Object *ob, Mesh *me = static_cast(ob->data); const float(*positions)[3] = BKE_pbvh_get_vert_positions(pbvh); const float *paint_mask; + int totvert, i; bool any_changed = false, any_visible = false; - const blender::Span verts = BKE_pbvh_node_get_vert_indices(node); + BKE_pbvh_node_num_verts(pbvh, node, nullptr, &totvert); + const int *vert_indices = BKE_pbvh_node_get_vert_indices(node); paint_mask = static_cast(CustomData_get_layer(&me->vert_data, CD_PAINT_MASK)); bool *hide_vert = static_cast(CustomData_get_layer_named_for_write( @@ -90,16 +92,16 @@ static void partialvis_update_mesh(Object *ob, SCULPT_undo_push_node(ob, node, SCULPT_UNDO_HIDDEN); - for (const int vert : verts) { - float vmask = paint_mask ? paint_mask[vert] : 0; + for (i = 0; i < totvert; i++) { + float vmask = paint_mask ? paint_mask[vert_indices[i]] : 0; /* Hide vertex if in the hide volume. */ - if (is_effected(area, planes, positions[vert], vmask)) { - hide_vert[vert] = (action == PARTIALVIS_HIDE); + if (is_effected(area, planes, positions[vert_indices[i]], vmask)) { + hide_vert[vert_indices[i]] = (action == PARTIALVIS_HIDE); any_changed = true; } - if (!hide_vert[vert]) { + if (!hide_vert[vert_indices[i]]) { any_visible = true; } } @@ -198,14 +200,17 @@ static void partialvis_update_grids(Depsgraph *depsgraph, } static void partialvis_update_bmesh_verts(BMesh *bm, - const blender::Set &verts, + GSet *verts, PartialVisAction action, PartialVisArea area, float planes[4][4], bool *any_changed, bool *any_visible) { - for (BMVert *v : verts) { + GSetIterator gs_iter; + + GSET_ITER (gs_iter, verts) { + BMVert *v = static_cast(BLI_gsetIterator_getKey(&gs_iter)); float *vmask = static_cast( CustomData_bmesh_get(&bm->vdata, v->head.data, CD_PAINT_MASK)); @@ -226,9 +231,13 @@ static void partialvis_update_bmesh_verts(BMesh *bm, } } -static void partialvis_update_bmesh_faces(const blender::Set &faces) +static void partialvis_update_bmesh_faces(GSet *faces) { - for (BMFace *f : faces) { + GSetIterator gs_iter; + + GSET_ITER (gs_iter, faces) { + BMFace *f = static_cast(BLI_gsetIterator_getKey(&gs_iter)); + if (paint_is_bmesh_face_hidden(f)) { BM_elem_flag_enable(f, BM_ELEM_HIDDEN); } @@ -246,25 +255,22 @@ static void partialvis_update_bmesh(Object *ob, float planes[4][4]) { BMesh *bm; + GSet *unique, *other, *faces; bool any_changed = false, any_visible = false; bm = BKE_pbvh_get_bmesh(pbvh); + unique = BKE_pbvh_bmesh_node_unique_verts(node); + other = BKE_pbvh_bmesh_node_other_verts(node); + faces = BKE_pbvh_bmesh_node_faces(node); SCULPT_undo_push_node(ob, node, SCULPT_UNDO_HIDDEN); - partialvis_update_bmesh_verts(bm, - BKE_pbvh_bmesh_node_unique_verts(node), - action, - area, - planes, - &any_changed, - &any_visible); + partialvis_update_bmesh_verts(bm, unique, action, area, planes, &any_changed, &any_visible); - partialvis_update_bmesh_verts( - bm, BKE_pbvh_bmesh_node_other_verts(node), action, area, planes, &any_changed, &any_visible); + partialvis_update_bmesh_verts(bm, other, action, area, planes, &any_changed, &any_visible); /* Finally loop over node faces and tag the ones that are fully hidden. */ - partialvis_update_bmesh_faces(BKE_pbvh_bmesh_node_faces(node)); + partialvis_update_bmesh_faces(faces); if (any_changed) { BKE_pbvh_node_mark_rebuild_draw(node); @@ -285,10 +291,11 @@ static void clip_planes_from_rect(bContext *C, float clip_planes[4][4], const rcti *rect) { + ViewContext vc; BoundBox bb; view3d_operator_needs_opengl(C); - ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph); + ED_view3d_viewcontext_init(C, &vc, depsgraph); ED_view3d_clipping_calc(&bb, clip_planes, vc.region, vc.obact, rect); } diff --git a/source/blender/editors/sculpt_paint/paint_image.cc b/source/blender/editors/sculpt_paint/paint_image.cc index d97d8280979..aa37f6a4bd6 100644 --- a/source/blender/editors/sculpt_paint/paint_image.cc +++ b/source/blender/editors/sculpt_paint/paint_image.cc @@ -811,14 +811,14 @@ static blender::float3 paint_init_pivot_mesh(Object *ob) static void paint_init_pivot_curves(Object *ob, float location[3]) { - const BoundBox bbox = *BKE_object_boundbox_get(ob); - interp_v3_v3v3(location, bbox.vec[0], bbox.vec[6], 0.5f); + const BoundBox *bbox = BKE_object_boundbox_get(ob); + interp_v3_v3v3(location, bbox->vec[0], bbox->vec[6], 0.5f); } static void paint_init_pivot_grease_pencil(Object *ob, float location[3]) { - const BoundBox bbox = *BKE_object_boundbox_get(ob); - interp_v3_v3v3(location, bbox.vec[0], bbox.vec[6], 0.5f); + const BoundBox *bbox = BKE_object_boundbox_get(ob); + interp_v3_v3v3(location, bbox->vec[0], bbox->vec[6], 0.5f); } void paint_init_pivot(Object *ob, Scene *scene) diff --git a/source/blender/editors/sculpt_paint/paint_image_ops_paint.cc b/source/blender/editors/sculpt_paint/paint_image_ops_paint.cc index afa3857447c..4e84a75515b 100644 --- a/source/blender/editors/sculpt_paint/paint_image_ops_paint.cc +++ b/source/blender/editors/sculpt_paint/paint_image_ops_paint.cc @@ -283,7 +283,7 @@ static PaintOperation *texture_paint_init(bContext *C, wmOperator *op, const flo PaintOperation *pop = MEM_new("PaintOperation"); /* caller frees */ Brush *brush = BKE_paint_brush(&settings->imapaint.paint); int mode = RNA_enum_get(op->ptr, "mode"); - pop->vc = ED_view3d_viewcontext_init(C, depsgraph); + ED_view3d_viewcontext_init(C, &pop->vc, depsgraph); copy_v2_v2(pop->prevmouse, mouse); copy_v2_v2(pop->startmouse, mouse); diff --git a/source/blender/editors/sculpt_paint/paint_image_proj.cc b/source/blender/editors/sculpt_paint/paint_image_proj.cc index 47141fdc7f5..b42be1b0695 100644 --- a/source/blender/editors/sculpt_paint/paint_image_proj.cc +++ b/source/blender/editors/sculpt_paint/paint_image_proj.cc @@ -829,10 +829,10 @@ static bool project_paint_PickColor( /** * Check if 'pt' is in front of the 3 verts on the Z axis (used for screen-space occlusion test) * \return - * - `0`: no occlusion. - * - `-1`: no occlusion but 2D intersection is true. - * - `1`: occluded. - * - `2`: occluded with `w[3]` weights set (need to know in some cases). + * - `0`: no occlusion + * - `-1`: no occlusion but 2D intersection is true + * - `1`: occluded + * - `2`: occluded with `w[3]` weights set (need to know in some cases) */ static int project_paint_occlude_ptv(const float pt[3], const float v1[4], diff --git a/source/blender/editors/sculpt_paint/paint_mask.cc b/source/blender/editors/sculpt_paint/paint_mask.cc index 6041d6bd575..cec9d7fc1ae 100644 --- a/source/blender/editors/sculpt_paint/paint_mask.cc +++ b/source/blender/editors/sculpt_paint/paint_mask.cc @@ -119,7 +119,7 @@ static int mask_flood_fill_exec(bContext *C, wmOperator *op) bool redraw = false; PBVHVertexIter vi; BKE_pbvh_vertex_iter_begin (pbvh, node, vi, PBVH_ITER_UNIQUE) { - float prevmask = vi.mask; + float prevmask = *vi.mask; const float new_mask = mask_flood_fill_get_new_value_for_elem(prevmask, mode, value); if (prevmask != new_mask) { SCULPT_mask_vert_set(BKE_pbvh_type(ob->sculpt->pbvh), mask_write, new_mask, vi); @@ -291,7 +291,7 @@ static void sculpt_gesture_context_init_common(bContext *C, SculptGestureContext *sgcontext) { Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - sgcontext->vc = ED_view3d_viewcontext_init(C, depsgraph); + ED_view3d_viewcontext_init(C, &sgcontext->vc, depsgraph); Object *ob = sgcontext->vc.obact; /* Operator properties. */ @@ -794,7 +794,7 @@ static void mask_gesture_apply_task(SculptGestureContext *sgcontext, BKE_pbvh_vertex_iter_begin (sgcontext->ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { if (sculpt_gesture_is_vertex_effected(sgcontext, vd.vertex)) { - float prevmask = vd.mask; + float prevmask = vd.mask ? *vd.mask : 0.0f; if (!any_masked) { any_masked = true; @@ -1503,7 +1503,7 @@ static void project_line_gesture_apply_task(SculptGestureContext *sgcontext, PBV float disp[3]; sub_v3_v3v3(disp, projected_pos, vd.co); - const float mask = vd.mask; + const float mask = vd.mask ? *vd.mask : 0.0f; mul_v3_fl(disp, 1.0f - mask); if (is_zero_v3(disp)) { continue; diff --git a/source/blender/editors/sculpt_paint/paint_ops.cc b/source/blender/editors/sculpt_paint/paint_ops.cc index b0953690efb..b170214bd18 100644 --- a/source/blender/editors/sculpt_paint/paint_ops.cc +++ b/source/blender/editors/sculpt_paint/paint_ops.cc @@ -804,9 +804,10 @@ static Brush *brush_tool_cycle(Main *bmain, Paint *paint, Brush *brush_orig, con } } else { - /* If user wants to switch to brush with the same tool as + /* If user wants to switch to brush with the same tool as * currently active brush do a cycling via all possible - * brushes with requested tool. */ + * brushes with requested tool. + */ first_brush = brush_orig->id.next ? static_cast(brush_orig->id.next) : static_cast(bmain->brushes.first); } diff --git a/source/blender/editors/sculpt_paint/paint_stroke.cc b/source/blender/editors/sculpt_paint/paint_stroke.cc index be75f13c87a..b6cfaff3569 100644 --- a/source/blender/editors/sculpt_paint/paint_stroke.cc +++ b/source/blender/editors/sculpt_paint/paint_stroke.cc @@ -916,7 +916,7 @@ PaintStroke *paint_stroke_new(bContext *C, RegionView3D *rv3d = CTX_wm_region_view3d(C); float zoomx, zoomy; - stroke->vc = ED_view3d_viewcontext_init(C, depsgraph); + ED_view3d_viewcontext_init(C, &stroke->vc, depsgraph); stroke->get_location = get_location; stroke->test_start = test_start; diff --git a/source/blender/editors/sculpt_paint/paint_utils.cc b/source/blender/editors/sculpt_paint/paint_utils.cc index 7c120172cf0..6da6ca1648c 100644 --- a/source/blender/editors/sculpt_paint/paint_utils.cc +++ b/source/blender/editors/sculpt_paint/paint_utils.cc @@ -62,7 +62,7 @@ #include "BLI_sys_types.h" #include "ED_mesh.hh" /* for face mask functions */ -#include "DRW_select_buffer.hh" +#include "DRW_select_buffer.h" #include "WM_api.hh" #include "WM_types.hh" @@ -414,12 +414,13 @@ void paint_sample_color( const int *material_indices = (const int *)CustomData_get_layer_named( &me_eval->face_data, CD_PROP_INT32, "material_index"); + ViewContext vc; const int mval[2] = {x, y}; uint faceindex; uint faces_num = me->faces_num; if (CustomData_has_layer(&me_eval->loop_data, CD_PROP_FLOAT2)) { - ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph); + ED_view3d_viewcontext_init(C, &vc, depsgraph); view3d_operator_needs_opengl(C); diff --git a/source/blender/editors/sculpt_paint/paint_vertex.cc b/source/blender/editors/sculpt_paint/paint_vertex.cc index 7216928bf86..9a0fae2c1db 100644 --- a/source/blender/editors/sculpt_paint/paint_vertex.cc +++ b/source/blender/editors/sculpt_paint/paint_vertex.cc @@ -959,7 +959,7 @@ static VPaintData *vpaint_init_vpaint(bContext *C, vpd->type = type; vpd->domain = domain; - vpd->vc = ED_view3d_viewcontext_init(C, depsgraph); + ED_view3d_viewcontext_init(C, &vpd->vc, depsgraph); vwpaint::view_angle_limits_init(&vpd->normal_angle_precalc, vp->paint.brush->falloff_angle, (vp->paint.brush->flag & BRUSH_FRONTFACE_FALLOFF) != 0); diff --git a/source/blender/editors/sculpt_paint/paint_vertex_weight_ops.cc b/source/blender/editors/sculpt_paint/paint_vertex_weight_ops.cc index 57d79220516..5eff4e5ef25 100644 --- a/source/blender/editors/sculpt_paint/paint_vertex_weight_ops.cc +++ b/source/blender/editors/sculpt_paint/paint_vertex_weight_ops.cc @@ -170,10 +170,11 @@ void PAINT_OT_weight_from_bones(wmOperatorType *ot) static int weight_sample_invoke(bContext *C, wmOperator *op, const wmEvent *event) { Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + ViewContext vc; Mesh *me; bool changed = false; - ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph); + ED_view3d_viewcontext_init(C, &vc, depsgraph); me = BKE_mesh_from_object(vc.obact); const MDeformVert *dvert = BKE_mesh_deform_verts(me); @@ -316,7 +317,8 @@ static bool weight_paint_sample_mark_groups(const MDeformVert *dvert, static int weight_sample_group_invoke(bContext *C, wmOperator *op, const wmEvent *event) { Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); - ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph); + ViewContext vc; + ED_view3d_viewcontext_init(C, &vc, depsgraph); BLI_assert(vc.v3d && vc.rv3d); /* Ensured by poll. */ Mesh *me = BKE_mesh_from_object(vc.obact); diff --git a/source/blender/editors/sculpt_paint/paint_vertex_weight_utils.cc b/source/blender/editors/sculpt_paint/paint_vertex_weight_utils.cc index 9b5d3265c03..dfe157c9641 100644 --- a/source/blender/editors/sculpt_paint/paint_vertex_weight_utils.cc +++ b/source/blender/editors/sculpt_paint/paint_vertex_weight_utils.cc @@ -9,7 +9,7 @@ */ #include "BLI_listbase.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "DNA_armature_types.h" #include "DNA_mesh_types.h" diff --git a/source/blender/editors/sculpt_paint/paint_weight.cc b/source/blender/editors/sculpt_paint/paint_weight.cc index 9c8cc8e3a97..bc38e0e113a 100644 --- a/source/blender/editors/sculpt_paint/paint_weight.cc +++ b/source/blender/editors/sculpt_paint/paint_weight.cc @@ -932,7 +932,7 @@ static bool wpaint_stroke_test_start(bContext *C, wmOperator *op, const float mo /* make mode data storage */ wpd = (WPaintData *)MEM_callocN(sizeof(WPaintData), "WPaintData"); paint_stroke_set_mode_data(stroke, wpd); - wpd->vc = ED_view3d_viewcontext_init(C, depsgraph); + ED_view3d_viewcontext_init(C, &wpd->vc, depsgraph); vwpaint::view_angle_limits_init(&wpd->normal_angle_precalc, vp->paint.brush->falloff_angle, (vp->paint.brush->flag & BRUSH_FRONTFACE_FALLOFF) != 0); diff --git a/source/blender/editors/sculpt_paint/sculpt.cc b/source/blender/editors/sculpt_paint/sculpt.cc index 0c2d7dcb40e..adf24f407a3 100644 --- a/source/blender/editors/sculpt_paint/sculpt.cc +++ b/source/blender/editors/sculpt_paint/sculpt.cc @@ -1375,10 +1375,10 @@ void SCULPT_orig_vert_data_unode_init(SculptOrigVertData *data, Object *ob, Scul data->bm_log = ss->bm_log; } else { - data->coords = reinterpret_cast(data->unode->co.data()); - data->normals = reinterpret_cast(data->unode->no.data()); - data->vmasks = data->unode->mask.data(); - data->colors = reinterpret_cast(data->unode->col.data()); + data->coords = data->unode->co; + data->normals = data->unode->no; + data->vmasks = data->unode->mask; + data->colors = data->unode->col; } } @@ -1428,7 +1428,7 @@ void SCULPT_orig_face_data_unode_init(SculptOrigFaceData *data, Object *ob, Scul data->bm_log = ss->bm_log; } else { - data->face_sets = unode->face_sets.data(); + data->face_sets = unode->face_sets; } } @@ -1505,10 +1505,11 @@ static void paint_mesh_restore_node(Object *ob, case SCULPT_UNDO_MASK: { switch (BKE_pbvh_type(ss->pbvh)) { case PBVH_FACES: { - const Span verts = BKE_pbvh_node_get_unique_vert_indices(node); - for (const int i : verts.index_range()) { - mask_write.layer[verts[i]] = unode->mask[i]; + PBVHVertexIter vd; + BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { + mask_write.layer[vd.index] = unode->mask[vd.i]; } + BKE_pbvh_vertex_iter_end; break; } case PBVH_BMESH: { @@ -2043,7 +2044,7 @@ static void calc_area_normal_and_center_task(Object *ob, if (ss->cache && !ss->cache->accum) { unode = SCULPT_undo_push_node(ob, node, SCULPT_UNDO_COORDS); - use_original = (!unode->co.is_empty() || unode->bm_entry); + use_original = (unode->co || unode->bm_entry); } SculptBrushTest normal_test; @@ -3315,8 +3316,16 @@ static void do_gravity_task(SculptSession *ss, if (!sculpt_brush_test_sq_fn(&test, vd.co)) { continue; } - const float fade = SCULPT_brush_strength_factor( - ss, brush, vd.co, sqrtf(test.dist), vd.no, vd.fno, vd.mask, vd.vertex, thread_id, nullptr); + const float fade = SCULPT_brush_strength_factor(ss, + brush, + vd.co, + sqrtf(test.dist), + vd.no, + vd.fno, + vd.mask ? *vd.mask : 0.0f, + vd.vertex, + thread_id, + nullptr); mul_v3_v3fl(proxy[vd.i], offset, fade); @@ -3819,8 +3828,7 @@ static void sculpt_combine_proxies_node(Object &object, float(*orco)[3] = nullptr; if (use_orco && !ss->bm) { - orco = reinterpret_cast( - (SCULPT_undo_push_node(&object, &node, SCULPT_UNDO_COORDS)->co.data())); + orco = SCULPT_undo_push_node(&object, &node, SCULPT_UNDO_COORDS)->co; } int proxy_count; @@ -4044,9 +4052,9 @@ static void do_tiled(Sculpt *sd, SculptSession *ss = ob->sculpt; StrokeCache *cache = ss->cache; const float radius = cache->radius; - const BoundBox bb = *BKE_object_boundbox_get(ob); - const float *bbMin = bb.vec[0]; - const float *bbMax = bb.vec[6]; + const BoundBox *bb = BKE_object_boundbox_get(ob); + const float *bbMin = bb->vec[0]; + const float *bbMax = bb->vec[6]; const float *step = sd->paint.tile_offset; /* These are integer locations, for real location: multiply with step and add orgLoc. @@ -4978,7 +4986,7 @@ static void sculpt_raycast_cb(PBVHNode *node, void *data_v, float *tmin) else { /* Intersect with coordinates from before we started stroke. */ SculptUndoNode *unode = SCULPT_undo_get_node(node, SCULPT_UNDO_COORDS); - origco = (unode) ? reinterpret_cast(unode->co.data()) : nullptr; + origco = (unode) ? unode->co : nullptr; use_origco = origco ? true : false; } } @@ -5016,7 +5024,7 @@ static void sculpt_find_nearest_to_ray_cb(PBVHNode *node, void *data_v, float *t else { /* Intersect with coordinates from before we started stroke. */ SculptUndoNode *unode = SCULPT_undo_get_node(node, SCULPT_UNDO_COORDS); - origco = (unode) ? reinterpret_cast(unode->co.data()) : nullptr; + origco = (unode) ? unode->co : nullptr; use_origco = origco ? true : false; } } @@ -5087,13 +5095,14 @@ bool SCULPT_cursor_geometry_info_update(bContext *C, Sculpt *sd = scene->toolsettings->sculpt; Object *ob; SculptSession *ss; + ViewContext vc; const Brush *brush = BKE_paint_brush(BKE_paint_get_active_from_context(C)); float ray_start[3], ray_end[3], ray_normal[3], depth, face_normal[3], sampled_normal[3], mat[3][3]; float viewDir[3] = {0.0f, 0.0f, 1.0f}; bool original = false; - ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph); + ED_view3d_viewcontext_init(C, &vc, depsgraph); ob = vc.obact; ss = ob->sculpt; @@ -5226,8 +5235,9 @@ bool SCULPT_stroke_get_location_ex(bContext *C, StrokeCache *cache; float ray_start[3], ray_end[3], ray_normal[3], depth, face_normal[3]; bool original; + ViewContext vc; - ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph); + ED_view3d_viewcontext_init(C, &vc, depsgraph); ob = vc.obact; diff --git a/source/blender/editors/sculpt_paint/sculpt_boundary.cc b/source/blender/editors/sculpt_paint/sculpt_boundary.cc index 746c2c23d36..783c94b80ee 100644 --- a/source/blender/editors/sculpt_paint/sculpt_boundary.cc +++ b/source/blender/editors/sculpt_paint/sculpt_boundary.cc @@ -684,7 +684,7 @@ static void do_boundary_brush_bend_task(Object *ob, const Brush *brush, PBVHNode continue; } - const float mask = 1.0f - vd.mask; + const float mask = vd.mask ? 1.0f - *vd.mask : 1.0f; const float automask = SCULPT_automasking_factor_get( ss->cache->automasking, ss, vd.vertex, &automask_data); float t_orig_co[3]; @@ -732,7 +732,7 @@ static void do_boundary_brush_slide_task(Object *ob, const Brush *brush, PBVHNod continue; } - const float mask = 1.0f - vd.mask; + const float mask = vd.mask ? 1.0f - *vd.mask : 1.0f; const float automask = SCULPT_automasking_factor_get( ss->cache->automasking, ss, vd.vertex, &automask_data); float *target_co = SCULPT_brush_deform_target_vertex_co_get(ss, brush->deform_target, &vd); @@ -778,7 +778,7 @@ static void do_boundary_brush_inflate_task(Object *ob, const Brush *brush, PBVHN continue; } - const float mask = 1.0f - vd.mask; + const float mask = vd.mask ? 1.0f - *vd.mask : 1.0f; const float automask = SCULPT_automasking_factor_get( ss->cache->automasking, ss, vd.vertex, &automask_data); float *target_co = SCULPT_brush_deform_target_vertex_co_get(ss, brush->deform_target, &vd); @@ -822,7 +822,7 @@ static void do_boundary_brush_grab_task(Object *ob, const Brush *brush, PBVHNode continue; } - const float mask = 1.0f - vd.mask; + const float mask = vd.mask ? 1.0f - *vd.mask : 1.0f; const float automask = SCULPT_automasking_factor_get( ss->cache->automasking, ss, vd.vertex, &automask_data); float *target_co = SCULPT_brush_deform_target_vertex_co_get(ss, brush->deform_target, &vd); @@ -873,7 +873,7 @@ static void do_boundary_brush_twist_task(Object *ob, const Brush *brush, PBVHNod continue; } - const float mask = 1.0f - vd.mask; + const float mask = vd.mask ? 1.0f - *vd.mask : 1.0f; const float automask = SCULPT_automasking_factor_get( ss->cache->automasking, ss, vd.vertex, &automask_data); float t_orig_co[3]; @@ -933,7 +933,7 @@ static void do_boundary_brush_smooth_task(Object *ob, const Brush *brush, PBVHNo } float disp[3]; float avg[3]; - const float mask = 1.0f - vd.mask; + const float mask = vd.mask ? 1.0f - *vd.mask : 1.0f; mul_v3_v3fl(avg, coord_accum, 1.0f / total_neighbors); sub_v3_v3v3(disp, avg, vd.co); float *target_co = SCULPT_brush_deform_target_vertex_co_get(ss, brush->deform_target, &vd); diff --git a/source/blender/editors/sculpt_paint/sculpt_brush_types.cc b/source/blender/editors/sculpt_paint/sculpt_brush_types.cc index 9edba724cdc..53b4478917a 100644 --- a/source/blender/editors/sculpt_paint/sculpt_brush_types.cc +++ b/source/blender/editors/sculpt_paint/sculpt_brush_types.cc @@ -278,7 +278,7 @@ static void do_draw_brush_task(Object *ob, const Brush *brush, const float *offs sqrtf(test.dist), vd.no, vd.fno, - vd.mask, + vd.mask ? *vd.mask : 0.0f, vd.vertex, thread_id, &automask_data, @@ -292,7 +292,7 @@ static void do_draw_brush_task(Object *ob, const Brush *brush, const float *offs sqrtf(test.dist), vd.no, vd.fno, - vd.mask, + vd.mask ? *vd.mask : 0.0f, vd.vertex, thread_id, &automask_data); @@ -385,7 +385,7 @@ static void do_fill_brush_task( sqrtf(test.dist), vd.no, vd.fno, - vd.mask, + vd.mask ? *vd.mask : 0.0f, vd.vertex, thread_id, &automask_data); @@ -478,7 +478,7 @@ static void do_scrape_brush_task( sqrtf(test.dist), vd.no, vd.fno, - vd.mask, + vd.mask ? *vd.mask : 0.0f, vd.vertex, thread_id, &automask_data); @@ -594,7 +594,7 @@ static void do_clay_thumb_brush_task(Object *ob, sqrtf(test.dist), vd.no, vd.fno, - vd.mask, + vd.mask ? *vd.mask : 0.0f, vd.vertex, thread_id, &automask_data); @@ -742,7 +742,7 @@ static void do_flatten_brush_task( sqrtf(test.dist), vd.no, vd.fno, - vd.mask, + vd.mask ? *vd.mask : 0.0f, vd.vertex, thread_id, &automask_data); @@ -882,7 +882,7 @@ static void do_clay_brush_task( sqrtf(test.dist), vd.no, vd.fno, - vd.mask, + vd.mask ? *vd.mask : 0.0f, vd.vertex, thread_id, &automask_data); @@ -1001,7 +1001,7 @@ static void do_clay_strips_brush_task(Object *ob, ss->cache->radius * test.dist, vd.no, vd.fno, - vd.mask, + vd.mask ? *vd.mask : 0.0f, vd.vertex, thread_id, &automask_data); @@ -1147,7 +1147,7 @@ static void do_snake_hook_brush_task(Object *ob, sqrtf(test.dist), vd.no, vd.fno, - vd.mask, + vd.mask ? *vd.mask : 0.0f, vd.vertex, thread_id, &automask_data); @@ -1194,7 +1194,9 @@ static void do_snake_hook_brush_task(Object *ob, float disp[3]; BKE_kelvinlet_grab_triscale(disp, ¶ms, vd.co, ss->cache->location, proxy[vd.i]); mul_v3_fl(disp, bstrength * 20.0f); - mul_v3_fl(disp, 1.0f - vd.mask); + if (vd.mask) { + mul_v3_fl(disp, 1.0f - *vd.mask); + } mul_v3_fl( disp, SCULPT_automasking_factor_get(ss->cache->automasking, ss, vd.vertex, &automask_data)); @@ -1275,7 +1277,7 @@ static void do_thumb_brush_task(Object *ob, const Brush *brush, const float *con sqrtf(test.dist), orig_data.no, nullptr, - vd.mask, + vd.mask ? *vd.mask : 0.0f, vd.vertex, thread_id, &automask_data); @@ -1346,7 +1348,7 @@ static void do_rotate_brush_task(Object *ob, const Brush *brush, const float ang sqrtf(test.dist), orig_data.no, nullptr, - vd.mask, + vd.mask ? *vd.mask : 0.0f, vd.vertex, thread_id, &automask_data); @@ -1414,7 +1416,7 @@ static void do_layer_brush_task(Object *ob, Sculpt *sd, const Brush *brush, PBVH sqrtf(test.dist), vd.no, vd.fno, - vd.mask, + vd.mask ? *vd.mask : 0.0f, vd.vertex, thread_id, &automask_data); @@ -1440,8 +1442,13 @@ static void do_layer_brush_task(Object *ob, Sculpt *sd, const Brush *brush, PBVH else { (*disp_factor) += fade * bstrength * (1.05f - fabsf(*disp_factor)); } - const float clamp_mask = 1.0f - vd.mask; - *disp_factor = clamp_f(*disp_factor, -clamp_mask, clamp_mask); + if (vd.mask) { + const float clamp_mask = 1.0f - *vd.mask; + *disp_factor = clamp_f(*disp_factor, -clamp_mask, clamp_mask); + } + else { + *disp_factor = clamp_f(*disp_factor, -1.0f, 1.0f); + } float final_co[3]; float normal[3]; @@ -1520,7 +1527,7 @@ static void do_inflate_brush_task(Object *ob, const Brush *brush, PBVHNode *node sqrtf(test.dist), vd.no, vd.fno, - vd.mask, + vd.mask ? *vd.mask : 0.0f, vd.vertex, thread_id, &automask_data); @@ -1585,7 +1592,7 @@ static void do_nudge_brush_task(Object *ob, const Brush *brush, const float *con sqrtf(test.dist), vd.no, vd.fno, - vd.mask, + vd.mask ? *vd.mask : 0.0f, vd.vertex, thread_id, &automask_data); @@ -1663,7 +1670,7 @@ static void do_crease_brush_task(Object *ob, sqrtf(test.dist), vd.no, vd.fno, - vd.mask, + vd.mask ? *vd.mask : 0.0f, vd.vertex, thread_id, &automask_data); @@ -1775,7 +1782,7 @@ static void do_pinch_brush_task(Object *ob, sqrtf(test.dist), vd.no, vd.fno, - vd.mask, + vd.mask ? *vd.mask : 0.0f, vd.vertex, thread_id, &automask_data); @@ -1890,7 +1897,7 @@ static void do_grab_brush_task(Object *ob, sqrtf(test.dist), orig_data.no, nullptr, - vd.mask, + vd.mask ? *vd.mask : 0.0f, vd.vertex, thread_id, &automask_data); @@ -2005,7 +2012,9 @@ static void do_elastic_deform_brush_task(Object *ob, break; } - mul_v3_fl(final_disp, 1.0f - vd.mask); + if (vd.mask) { + mul_v3_fl(final_disp, 1.0f - *vd.mask); + } mul_v3_fl( final_disp, @@ -2083,7 +2092,7 @@ static void do_draw_sharp_brush_task(Object *ob, sqrtf(test.dist), orig_data.no, nullptr, - vd.mask, + vd.mask ? *vd.mask : 0.0f, vd.vertex, thread_id, &automask_data); @@ -2162,7 +2171,7 @@ static void do_topology_slide_task(Object *ob, const Brush *brush, PBVHNode *nod sqrtf(test.dist), orig_data.no, nullptr, - vd.mask, + vd.mask ? *vd.mask : 0.0f, vd.vertex, thread_id, &automask_data); @@ -2322,7 +2331,7 @@ static void do_topology_relax_task(Object *ob, const Brush *brush, PBVHNode *nod sqrtf(test.dist), orig_data.no, nullptr, - vd.mask, + vd.mask ? *vd.mask : 0.0f, vd.vertex, thread_id, &automask_data); @@ -2402,7 +2411,7 @@ static void do_displacement_eraser_brush_task(Object *ob, const Brush *brush, PB sqrtf(test.dist), vd.no, vd.fno, - vd.mask, + vd.mask ? *vd.mask : 0.0f, vd.vertex, thread_id, &automask_data); @@ -2465,7 +2474,7 @@ static void do_displacement_smear_brush_task(Object *ob, const Brush *brush, PBV sqrtf(test.dist), vd.no, vd.fno, - vd.mask, + vd.mask ? *vd.mask : 0.0f, vd.vertex, thread_id, &automask_data); @@ -2625,7 +2634,7 @@ static void do_topology_rake_bmesh_task( sqrtf(test.dist), vd.no, vd.fno, - vd.mask, + *vd.mask, vd.vertex, thread_id, &automask_data) * @@ -2711,9 +2720,9 @@ static void do_mask_brush_draw_task(Object *ob, thread_id, &automask_data); - float mask = vd.mask; + float mask = *vd.mask; if (bstrength > 0.0f) { - mask += fade * bstrength * (1.0f - vd.mask); + mask += fade * bstrength * (1.0f - *vd.mask); } else { mask += fade * bstrength * mask; diff --git a/source/blender/editors/sculpt_paint/sculpt_cloth.cc b/source/blender/editors/sculpt_paint/sculpt_cloth.cc index 9235ac0615e..da2bfff8add 100644 --- a/source/blender/editors/sculpt_paint/sculpt_cloth.cc +++ b/source/blender/editors/sculpt_paint/sculpt_cloth.cc @@ -513,7 +513,7 @@ static void do_cloth_brush_apply_forces_task(Object *ob, dist, vd.no, vd.fno, - vd.mask, + vd.mask ? *vd.mask : 0.0f, vd.vertex, thread_id, &automask_data); @@ -761,7 +761,7 @@ static void do_cloth_brush_solve_simulation_task(Object *ob, sub_v3_v3v3(pos_diff, cloth_sim->pos[i], cloth_sim->prev_pos[i]); mul_v3_fl(pos_diff, (1.0f - cloth_sim->damping) * sim_factor); - const float mask_v = (1.0f - (vd.mask)) * + const float mask_v = (1.0f - (vd.mask ? *vd.mask : 0.0f)) * SCULPT_automasking_factor_get(automasking, ss, vd.vertex, &automask_data); madd_v3_v3fl(cloth_sim->pos[i], pos_diff, mask_v); @@ -1384,7 +1384,7 @@ static void cloth_filter_apply_forces_task(Object *ob, BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { SCULPT_automasking_node_update(ss, &automask_data, &vd); - float fade = vd.mask; + float fade = vd.mask ? *vd.mask : 0.0f; fade *= SCULPT_automasking_factor_get( ss->filter_cache->automasking, ss, vd.vertex, &automask_data); fade = 1.0f - fade; diff --git a/source/blender/editors/sculpt_paint/sculpt_detail.cc b/source/blender/editors/sculpt_paint/sculpt_detail.cc index aad1c608bb0..c858589a33f 100644 --- a/source/blender/editors/sculpt_paint/sculpt_detail.cc +++ b/source/blender/editors/sculpt_paint/sculpt_detail.cc @@ -39,15 +39,9 @@ #include "RNA_access.hh" #include "RNA_define.hh" -#include "PIL_time.h" - -#include "CLG_log.h" - #include #include -static CLG_LogRef LOG = {"sculpt.detail"}; - /* -------------------------------------------------------------------- */ /** \name Internal Utilities * \{ */ @@ -115,8 +109,6 @@ static int sculpt_detail_flood_fill_exec(bContext *C, wmOperator *op) SCULPT_undo_push_begin(ob, op); SCULPT_undo_push_node(ob, nullptr, SCULPT_UNDO_COORDS); - const double start_time = PIL_check_seconds_timer(); - while (BKE_pbvh_bmesh_update_topology( ss->pbvh, PBVH_Collapse | PBVH_Subdivide, center, nullptr, size, false, false)) { @@ -125,8 +117,6 @@ static int sculpt_detail_flood_fill_exec(bContext *C, wmOperator *op) } } - CLOG_INFO(&LOG, 2, "Detail flood fill took %f seconds.", PIL_check_seconds_timer() - start_time); - SCULPT_undo_push_end(ob); /* Force rebuild of PBVH for better BB placement. */ @@ -256,7 +246,8 @@ static int sample_detail(bContext *C, const int event_xy[2], int mode) CTX_wm_region_set(C, region); Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph); + ViewContext vc; + ED_view3d_viewcontext_init(C, &vc, depsgraph); Object *ob = vc.obact; if (ob == nullptr) { diff --git a/source/blender/editors/sculpt_paint/sculpt_dyntopo.cc b/source/blender/editors/sculpt_paint/sculpt_dyntopo.cc index 53aef690c6b..4b0ba0df32b 100644 --- a/source/blender/editors/sculpt_paint/sculpt_dyntopo.cc +++ b/source/blender/editors/sculpt_paint/sculpt_dyntopo.cc @@ -163,6 +163,10 @@ static void SCULPT_dynamic_topology_disable_ex( else { BKE_sculptsession_bm_to_me(ob, true); + /* Reset Face Sets as they are no longer valid. */ + CustomData_free_layer_named(&me->face_data, ".sculpt_face_set", me->faces_num); + me->face_sets_color_default = 1; + /* Sync the visibility to vertices manually as the `pmap` is still not initialized. */ bool *hide_vert = (bool *)CustomData_get_layer_named_for_write( &me->vert_data, CD_PROP_BOOL, ".hide_vert", me->totvert); diff --git a/source/blender/editors/sculpt_paint/sculpt_expand.cc b/source/blender/editors/sculpt_paint/sculpt_expand.cc index 19e5e4e5483..3e816cfb09c 100644 --- a/source/blender/editors/sculpt_paint/sculpt_expand.cc +++ b/source/blender/editors/sculpt_paint/sculpt_expand.cc @@ -1298,7 +1298,7 @@ static void sculpt_expand_mask_update_task(SculptSession *ss, PBVHVertexIter vd; BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_ALL) { - const float initial_mask = vd.mask; + const float initial_mask = *vd.mask; const bool enabled = sculpt_expand_state_get(ss, expand_cache, vd.vertex); if (expand_cache->check_islands && @@ -1387,7 +1387,7 @@ static void sculpt_expand_colors_update_task(SculptSession *ss, PBVHNode *node) fade = 0.0f; } - fade *= 1.0f - vd.mask; + fade *= 1.0f - *vd.mask; fade = clamp_f(fade, 0.0f, 1.0f); float final_color[4]; diff --git a/source/blender/editors/sculpt_paint/sculpt_face_set.cc b/source/blender/editors/sculpt_paint/sculpt_face_set.cc index 444aeb67013..2af35ab5783 100644 --- a/source/blender/editors/sculpt_paint/sculpt_face_set.cc +++ b/source/blender/editors/sculpt_paint/sculpt_face_set.cc @@ -156,7 +156,7 @@ static void do_draw_face_sets_brush_task(Object *ob, const Brush *brush, PBVHNod sqrtf(test.dist), vd.no, vd.fno, - vd.mask, + vd.mask ? *vd.mask : 0.0f, vd.vertex, thread_id, &automask_data); @@ -177,7 +177,7 @@ static void do_draw_face_sets_brush_task(Object *ob, const Brush *brush, PBVHNod sqrtf(test.dist), vd.no, vd.fno, - vd.mask, + vd.mask ? *vd.mask : 0.0f, vd.vertex, thread_id, &automask_data); @@ -235,7 +235,7 @@ static void do_relax_face_sets_brush_task(Object *ob, sqrtf(test.dist), vd.no, vd.fno, - vd.mask, + vd.mask ? *vd.mask : 0.0f, vd.vertex, thread_id, &automask_data); diff --git a/source/blender/editors/sculpt_paint/sculpt_filter_color.cc b/source/blender/editors/sculpt_paint/sculpt_filter_color.cc index 2830e91f90f..e67837b531d 100644 --- a/source/blender/editors/sculpt_paint/sculpt_filter_color.cc +++ b/source/blender/editors/sculpt_paint/sculpt_filter_color.cc @@ -94,7 +94,7 @@ static void color_filter_task(Object *ob, float orig_color[3], final_color[4], hsv_color[3]; int hue; float brightness, contrast, gain, delta, offset; - float fade = vd.mask; + float fade = vd.mask ? *vd.mask : 0.0f; fade = 1.0f - fade; fade *= filter_strength; fade *= SCULPT_automasking_factor_get( diff --git a/source/blender/editors/sculpt_paint/sculpt_filter_mask.cc b/source/blender/editors/sculpt_paint/sculpt_filter_mask.cc index 6cc641431ce..633a78b40fa 100644 --- a/source/blender/editors/sculpt_paint/sculpt_filter_mask.cc +++ b/source/blender/editors/sculpt_paint/sculpt_filter_mask.cc @@ -83,7 +83,7 @@ static void mask_filter_task(SculptSession *ss, BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { float delta, gain, offset, max, min; - float mask = vd.mask; + float mask = *vd.mask; SculptVertexNeighborIter ni; switch (mode) { case MASK_FILTER_SMOOTH: @@ -144,7 +144,7 @@ static void mask_filter_task(SculptSession *ss, break; } mask = clamp_f(mask, 0.0f, 1.0f); - if (mask != vd.mask) { + if (mask != *vd.mask) { SCULPT_mask_vert_set(BKE_pbvh_type(ss->pbvh), mask_write, mask, vd); update = true; } diff --git a/source/blender/editors/sculpt_paint/sculpt_filter_mesh.cc b/source/blender/editors/sculpt_paint/sculpt_filter_mesh.cc index 2e1eb277c12..45681b33965 100644 --- a/source/blender/editors/sculpt_paint/sculpt_filter_mesh.cc +++ b/source/blender/editors/sculpt_paint/sculpt_filter_mesh.cc @@ -148,7 +148,8 @@ void SCULPT_filter_cache_init(bContext *C, invert_m4_m4(ss->filter_cache->obmat_inv, ob->object_to_world); Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph); + ViewContext vc; + ED_view3d_viewcontext_init(C, &vc, depsgraph); ss->filter_cache->vc = vc; if (vc.rv3d) { @@ -371,7 +372,7 @@ static void mesh_filter_task(Object *ob, SCULPT_automasking_node_update(ss, &automask_data, &vd); float orig_co[3], val[3], avg[3], disp[3], disp2[3], transform[3][3], final_pos[3]; - float fade = vd.mask; + float fade = vd.mask ? *vd.mask : 0.0f; fade = 1.0f - fade; fade *= filter_strength; fade *= SCULPT_automasking_factor_get( @@ -669,7 +670,7 @@ static void mesh_filter_surface_smooth_displace_task(Object *ob, BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { SCULPT_automasking_node_update(ss, &automask_data, &vd); - float fade = vd.mask; + float fade = vd.mask ? *vd.mask : 0.0f; fade = 1.0f - fade; fade *= filter_strength; fade *= SCULPT_automasking_factor_get( diff --git a/source/blender/editors/sculpt_paint/sculpt_intern.hh b/source/blender/editors/sculpt_paint/sculpt_intern.hh index cdd1ccb6618..cfcecf03246 100644 --- a/source/blender/editors/sculpt_paint/sculpt_intern.hh +++ b/source/blender/editors/sculpt_paint/sculpt_intern.hh @@ -20,15 +20,12 @@ #include "BKE_paint.hh" #include "BKE_pbvh_api.hh" -#include "BLI_array.hh" -#include "BLI_bit_vector.hh" #include "BLI_bitmap.h" #include "BLI_compiler_attrs.h" #include "BLI_compiler_compat.h" #include "BLI_generic_array.hh" #include "BLI_gsqueue.h" #include "BLI_implicit_sharing.hh" -#include "BLI_math_vector_types.hh" #include "BLI_set.hh" #include "BLI_span.hh" #include "BLI_threads.h" @@ -180,30 +177,30 @@ struct SculptUndoNode { char idname[MAX_ID_NAME]; /* Name instead of pointer. */ void *node; /* only during push, not valid afterwards! */ - blender::Array co; - blender::Array orig_co; - blender::Array no; - blender::Array col; - blender::Array mask; + float (*co)[3]; + float (*orig_co)[3]; + float (*no)[3]; + float (*col)[4]; + float *mask; int totvert; - blender::Array loop_col; - blender::Array orig_loop_col; + float (*loop_col)[4]; + float (*orig_loop_col)[4]; int totloop; /* non-multires */ - int maxvert; /* to verify if totvert it still the same */ - blender::Array index; /* Unique vertex indices, to restore into right location */ + int maxvert; /* to verify if totvert it still the same */ + int *index; /* Unique vertex indices, to restore into right location */ int maxloop; - blender::Array loop_index; + int *loop_index; - blender::BitVector<> vert_hidden; + BLI_bitmap *vert_hidden; /* multires */ - int maxgrid; /* same for grid */ - int gridsize; /* same for grid */ - int totgrid; /* to restore into right location */ - blender::Array grids; /* to restore into right location */ + int maxgrid; /* same for grid */ + int gridsize; /* same for grid */ + int totgrid; /* to restore into right location */ + int *grids; /* to restore into right location */ BLI_bitmap **grid_hidden; /* bmesh */ @@ -231,9 +228,9 @@ struct SculptUndoNode { float pivot_rot[4]; /* Sculpt Face Sets */ - blender::Array face_sets; + int *face_sets; - blender::Array faces; + PBVHFaceRef *faces; int faces_num; size_t undo_size; diff --git a/source/blender/editors/sculpt_paint/sculpt_multiplane_scrape.cc b/source/blender/editors/sculpt_paint/sculpt_multiplane_scrape.cc index eb5fa596da0..42136bae2e2 100644 --- a/source/blender/editors/sculpt_paint/sculpt_multiplane_scrape.cc +++ b/source/blender/editors/sculpt_paint/sculpt_multiplane_scrape.cc @@ -77,7 +77,7 @@ static void calc_multiplane_scrape_surface_task(Object *ob, sqrtf(test.dist), vd.no, vd.fno, - vd.mask, + vd.mask ? *vd.mask : 0.0f, vd.vertex, thread_id, &automask_data); @@ -172,7 +172,7 @@ static void do_multiplane_scrape_brush_task(Object *ob, len_v3(local_co), vd.no, vd.fno, - vd.mask, + vd.mask ? *vd.mask : 0.0f, vd.vertex, thread_id, &automask_data); diff --git a/source/blender/editors/sculpt_paint/sculpt_ops.cc b/source/blender/editors/sculpt_paint/sculpt_ops.cc index 7adc5e110dd..981b16a22f3 100644 --- a/source/blender/editors/sculpt_paint/sculpt_ops.cc +++ b/source/blender/editors/sculpt_paint/sculpt_ops.cc @@ -744,7 +744,7 @@ static void do_mask_by_color_contiguous_update_node(Object *ob, PBVHVertexIter vd; BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { - const float current_mask = vd.mask; + const float current_mask = *vd.mask; const float new_mask = mask_by_color_floodfill[vd.index]; const float mask = sculpt_mask_by_color_final_mask_get( current_mask, new_mask, invert, preserve_mask); @@ -857,7 +857,7 @@ static void do_mask_by_color_task(Object *ob, float col[4]; SCULPT_vertex_color_get(ss, vd.vertex, col); - const float current_mask = vd.mask; + const float current_mask = *vd.mask; const float new_mask = sculpt_mask_by_color_delta_get(active_color, col, threshold, invert); const float mask = sculpt_mask_by_color_final_mask_get( current_mask, new_mask, invert, preserve_mask); @@ -1027,22 +1027,22 @@ static void sculpt_bake_cavity_exec_task(Object *ob, mask = automask; break; case AUTOMASK_BAKE_MULTIPLY: - mask = vd.mask * automask; + mask = *vd.mask * automask; break; break; case AUTOMASK_BAKE_DIVIDE: - mask = automask > 0.00001f ? vd.mask / automask : 0.0f; + mask = automask > 0.00001f ? *vd.mask / automask : 0.0f; break; break; case AUTOMASK_BAKE_ADD: - mask = vd.mask + automask; + mask = *vd.mask + automask; break; case AUTOMASK_BAKE_SUBTRACT: - mask = vd.mask - automask; + mask = *vd.mask - automask; break; } - mask = vd.mask + (mask - vd.mask) * factor; + mask = *vd.mask + (mask - *vd.mask) * factor; CLAMP(mask, 0.0f, 1.0f); SCULPT_mask_vert_set(BKE_pbvh_type(ss->pbvh), mask_write, mask, vd); diff --git a/source/blender/editors/sculpt_paint/sculpt_paint_color.cc b/source/blender/editors/sculpt_paint/sculpt_paint_color.cc index 417900ea41e..32bdfccec32 100644 --- a/source/blender/editors/sculpt_paint/sculpt_paint_color.cc +++ b/source/blender/editors/sculpt_paint/sculpt_paint_color.cc @@ -63,7 +63,7 @@ static void do_color_smooth_task(Object *ob, const Brush *brush, PBVHNode *node) sqrtf(test.dist), vd.no, vd.fno, - vd.mask, + vd.mask ? *vd.mask : 0.0f, vd.vertex, thread_id, &automask_data); @@ -157,7 +157,7 @@ static void do_paint_brush_task(Object *ob, distance_to_stroke_location, vd.no, vd.fno, - vd.mask, + vd.mask ? *vd.mask : 0.0f, vd.vertex, thread_id, &automask_data); @@ -367,7 +367,7 @@ static void do_smear_brush_task(Object *ob, const Brush *brush, PBVHNode *node) sqrtf(test.dist), vd.no, vd.fno, - vd.mask, + vd.mask ? *vd.mask : 0.0f, vd.vertex, thread_id, &automask_data); diff --git a/source/blender/editors/sculpt_paint/sculpt_pose.cc b/source/blender/editors/sculpt_paint/sculpt_pose.cc index f5266fc5037..82288272ff7 100644 --- a/source/blender/editors/sculpt_paint/sculpt_pose.cc +++ b/source/blender/editors/sculpt_paint/sculpt_pose.cc @@ -176,7 +176,7 @@ static void do_pose_brush_task(Object *ob, const Brush *brush, PBVHNode *node) mul_v3_fl(disp, segments[ik].weights[vd.index]); /* Apply the vertex mask to the displacement. */ - const float mask = 1.0f - vd.mask; + const float mask = vd.mask ? 1.0f - *vd.mask : 1.0f; const float automask = SCULPT_automasking_factor_get( ss->cache->automasking, ss, vd.vertex, &automask_data); mul_v3_fl(disp, mask * automask); diff --git a/source/blender/editors/sculpt_paint/sculpt_smooth.cc b/source/blender/editors/sculpt_paint/sculpt_smooth.cc index b9cc1f194a0..190327154a5 100644 --- a/source/blender/editors/sculpt_paint/sculpt_smooth.cc +++ b/source/blender/editors/sculpt_paint/sculpt_smooth.cc @@ -210,7 +210,7 @@ static void do_enhance_details_brush_task(Object *ob, sqrtf(test.dist), vd.no, vd.fno, - vd.mask, + vd.mask ? *vd.mask : 0.0f, vd.vertex, thread_id, &automask_data); @@ -285,20 +285,21 @@ static void do_smooth_brush_task(Object *ob, SCULPT_automasking_node_update(ss, &automask_data, &vd); - const float fade = bstrength * SCULPT_brush_strength_factor(ss, - brush, - vd.co, - sqrtf(test.dist), - vd.no, - vd.fno, - smooth_mask ? 0.0f : vd.mask, - vd.vertex, - thread_id, - &automask_data); + const float fade = bstrength * SCULPT_brush_strength_factor( + ss, + brush, + vd.co, + sqrtf(test.dist), + vd.no, + vd.fno, + smooth_mask ? 0.0f : (vd.mask ? *vd.mask : 0.0f), + vd.vertex, + thread_id, + &automask_data); if (smooth_mask) { - float val = SCULPT_neighbor_mask_average(ss, vd.vertex) - vd.mask; + float val = SCULPT_neighbor_mask_average(ss, vd.vertex) - *vd.mask; val *= fade * bstrength; - float new_mask = vd.mask + val; + float new_mask = *vd.mask + val; CLAMP(new_mask, 0.0f, 1.0f); SCULPT_mask_vert_set(BKE_pbvh_type(ss->pbvh), mask_write, new_mask, vd); @@ -454,7 +455,7 @@ static void do_surface_smooth_brush_laplacian_task(Object *ob, const Brush *brus sqrtf(test.dist), vd.no, vd.fno, - vd.mask, + vd.mask ? *vd.mask : 0.0f, vd.vertex, thread_id, &automask_data); @@ -498,7 +499,7 @@ static void do_surface_smooth_brush_displace_task(Object *ob, const Brush *brush sqrtf(test.dist), vd.no, vd.fno, - vd.mask, + vd.mask ? *vd.mask : 0.0f, vd.vertex, thread_id, &automask_data); diff --git a/source/blender/editors/sculpt_paint/sculpt_transform.cc b/source/blender/editors/sculpt_paint/sculpt_transform.cc index f3d74ee01bc..a49fd1fd634 100644 --- a/source/blender/editors/sculpt_paint/sculpt_transform.cc +++ b/source/blender/editors/sculpt_paint/sculpt_transform.cc @@ -151,7 +151,7 @@ static void sculpt_transform_task(Object *ob, const float transform_mats[8][4][4 SCULPT_orig_vert_data_update(&orig_data, &vd); float *start_co; float transformed_co[3], orig_co[3], disp[3]; - float fade = vd.mask; + float fade = vd.mask ? *vd.mask : 0.0f; copy_v3_v3(orig_co, orig_data.co); char symm_area = SCULPT_get_vertex_symm_area(orig_co); @@ -227,7 +227,7 @@ static void sculpt_elastic_transform_task(Object *ob, BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { SCULPT_orig_vert_data_update(&orig_data, &vd); float transformed_co[3], orig_co[3], disp[3]; - const float fade = vd.mask; + const float fade = vd.mask ? *vd.mask : 0.0f; copy_v3_v3(orig_co, orig_data.co); copy_v3_v3(transformed_co, vd.co); @@ -315,7 +315,9 @@ void ED_sculpt_update_modal_transform(bContext *C, Object *ob) transform_radius = BKE_brush_unprojected_radius_get(scene, brush); } else { - ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph); + ViewContext vc; + + ED_view3d_viewcontext_init(C, &vc, depsgraph); transform_radius = paint_calc_object_space_radius( &vc, ss->init_pivot_pos, BKE_brush_size_get(scene, brush)); @@ -426,7 +428,7 @@ static int sculpt_set_pivot_position_exec(bContext *C, wmOperator *op) for (PBVHNode *node : nodes) { PBVHVertexIter vd; BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { - const float mask = vd.mask; + const float mask = (vd.mask) ? *vd.mask : 0.0f; if (mask < 1.0f) { if (SCULPT_check_vertex_pivot_symmetry(vd.co, ss->pivot_pos, symm)) { add_v3_v3(avg, vd.co); @@ -444,7 +446,7 @@ static int sculpt_set_pivot_position_exec(bContext *C, wmOperator *op) for (PBVHNode *node : nodes) { PBVHVertexIter vd; BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { - const float mask = vd.mask; + const float mask = (vd.mask) ? *vd.mask : 0.0f; if (mask < (0.5f + threshold) && mask > (0.5f - threshold)) { if (SCULPT_check_vertex_pivot_symmetry(vd.co, ss->pivot_pos, symm)) { add_v3_v3(avg, vd.co); diff --git a/source/blender/editors/sculpt_paint/sculpt_undo.cc b/source/blender/editors/sculpt_paint/sculpt_undo.cc index d628560c6ec..db320ee3a2d 100644 --- a/source/blender/editors/sculpt_paint/sculpt_undo.cc +++ b/source/blender/editors/sculpt_paint/sculpt_undo.cc @@ -319,26 +319,28 @@ static void update_cb_partial(PBVHNode *node, void *userdata) if (BKE_pbvh_node_has_vert_with_normal_update_tag(data->pbvh, node)) { BKE_pbvh_node_mark_update(node); } - const blender::Span verts = BKE_pbvh_node_get_vert_indices(node); + int verts_num; + BKE_pbvh_node_num_verts(data->pbvh, node, nullptr, &verts_num); + const int *vert_indices = BKE_pbvh_node_get_vert_indices(node); if (data->modified_mask_verts != nullptr) { - for (const int vert : verts) { - if (data->modified_mask_verts[vert]) { + for (int i = 0; i < verts_num; i++) { + if (data->modified_mask_verts[vert_indices[i]]) { BKE_pbvh_node_mark_update_mask(node); break; } } } if (data->modified_color_verts != nullptr) { - for (const int vert : verts) { - if (data->modified_color_verts[vert]) { + for (int i = 0; i < verts_num; i++) { + if (data->modified_color_verts[vert_indices[i]]) { BKE_pbvh_node_mark_update_color(node); break; } } } if (data->modified_hidden_verts != nullptr) { - for (const int vert : verts) { - if (data->modified_hidden_verts[vert]) { + for (int i = 0; i < verts_num; i++) { + if (data->modified_hidden_verts[vert_indices[i]]) { if (data->rebuild) { BKE_pbvh_node_mark_update_visibility(node); } @@ -388,6 +390,7 @@ static bool sculpt_undo_restore_coords(bContext *C, Depsgraph *depsgraph, Sculpt Object *ob = BKE_view_layer_active_object_get(view_layer); SculptSession *ss = ob->sculpt; SubdivCCG *subdiv_ccg = ss->subdiv_ccg; + int *index; if (unode->maxvert) { /* Regular mesh restore. */ @@ -411,14 +414,14 @@ static bool sculpt_undo_restore_coords(bContext *C, Depsgraph *depsgraph, Sculpt } /* No need for float comparison here (memory is exactly equal or not). */ - const Span index = unode->index; + index = unode->index; blender::MutableSpan positions = ss->vert_positions; if (ss->shapekey_active) { float(*vertCos)[3]; vertCos = BKE_keyblock_convert_to_vertcos(ob, ss->shapekey_active); - if (!unode->orig_co.is_empty()) { + if (unode->orig_co) { if (ss->deform_modifiers_active) { for (int i = 0; i < unode->totvert; i++) { sculpt_undo_restore_deformed(ss, unode, i, index[i], vertCos[index[i]]); @@ -446,7 +449,7 @@ static bool sculpt_undo_restore_coords(bContext *C, Depsgraph *depsgraph, Sculpt MEM_freeN(vertCos); } else { - if (!unode->orig_co.is_empty()) { + if (unode->orig_co) { if (ss->deform_modifiers_active) { for (int i = 0; i < unode->totvert; i++) { sculpt_undo_restore_deformed(ss, unode, i, index[i], positions[index[i]]); @@ -472,20 +475,19 @@ static bool sculpt_undo_restore_coords(bContext *C, Depsgraph *depsgraph, Sculpt /* Multires restore. */ CCGElem **grids, *grid; CCGKey key; + float(*co)[3]; int gridsize; grids = subdiv_ccg->grids; gridsize = subdiv_ccg->grid_size; BKE_subdiv_ccg_key_top_level(&key, subdiv_ccg); - blender::MutableSpan co = unode->co; - int index = 0; + co = unode->co; for (int j = 0; j < unode->totgrid; j++) { grid = grids[unode->grids[j]]; - for (int i = 0; i < gridsize * gridsize; i++) { - swap_v3_v3(CCG_elem_offset_co(&key, grid, i), co[index]); - index++; + for (int i = 0; i < gridsize * gridsize; i++, co++) { + swap_v3_v3(CCG_elem_offset_co(&key, grid, i), co[0]); } } } @@ -507,8 +509,8 @@ static bool sculpt_undo_restore_hidden(bContext *C, SculptUndoNode *unode, bool if (unode->maxvert) { for (int i = 0; i < unode->totvert; i++) { const int vert_index = unode->index[i]; - if (unode->vert_hidden[i].test() != hide_vert[vert_index]) { - unode->vert_hidden[i].set(!unode->vert_hidden[i].test()); + if ((BLI_BITMAP_TEST(unode->vert_hidden, i) != 0) != hide_vert[vert_index]) { + BLI_BITMAP_FLIP(unode->vert_hidden, i); hide_vert[vert_index] = !hide_vert[vert_index]; modified_vertices[vert_index] = true; } @@ -537,15 +539,15 @@ static bool sculpt_undo_restore_color(bContext *C, SculptUndoNode *unode, bool * /* NOTE: even with loop colors we still store derived * vertex colors for original data lookup. */ - if (!unode->col.is_empty() && unode->loop_col.is_empty()) { - BKE_pbvh_swap_colors(ss->pbvh, unode->index, unode->col); + if (unode->col && !unode->loop_col) { + BKE_pbvh_swap_colors(ss->pbvh, unode->index, unode->totvert, unode->col); modified = true; } Mesh *me = BKE_object_get_original_mesh(ob); - if (!unode->loop_col.is_empty() && unode->maxloop == me->totloop) { - BKE_pbvh_swap_colors(ss->pbvh, unode->loop_index, unode->loop_col); + if (unode->loop_col && unode->maxloop == me->totloop) { + BKE_pbvh_swap_colors(ss->pbvh, unode->loop_index, unode->totloop, unode->loop_col); modified = true; } @@ -568,6 +570,7 @@ static bool sculpt_undo_restore_mask(bContext *C, SculptUndoNode *unode, bool *m Mesh *mesh = BKE_object_get_original_mesh(ob); SculptSession *ss = ob->sculpt; SubdivCCG *subdiv_ccg = ss->subdiv_ccg; + int *index; if (unode->maxvert) { /* Regular mesh restore. */ @@ -575,7 +578,7 @@ static bool sculpt_undo_restore_mask(bContext *C, SculptUndoNode *unode, bool *m CustomData_get_layer_for_write(&mesh->vert_data, CD_PAINT_MASK, mesh->totvert)); ss->vmask = vmask; - const Span index = unode->index; + index = unode->index; for (int i = 0; i < unode->totvert; i++) { if (vmask[index[i]] != unode->mask[i]) { @@ -588,20 +591,19 @@ static bool sculpt_undo_restore_mask(bContext *C, SculptUndoNode *unode, bool *m /* Multires restore. */ CCGElem **grids, *grid; CCGKey key; + float *mask; int gridsize; grids = subdiv_ccg->grids; gridsize = subdiv_ccg->grid_size; BKE_subdiv_ccg_key_top_level(&key, subdiv_ccg); - blender::MutableSpan mask = unode->mask; - int index = 0; + mask = unode->mask; for (int j = 0; j < unode->totgrid; j++) { grid = grids[unode->grids[j]]; - for (int i = 0; i < gridsize * gridsize; i++) { - SWAP(float, *CCG_elem_offset_mask(&key, grid, i), mask[index]); - index++; + for (int i = 0; i < gridsize * gridsize; i++, mask++) { + SWAP(float, *CCG_elem_offset_mask(&key, grid, i), *mask); } } } @@ -1094,7 +1096,36 @@ static void sculpt_undo_free_list(ListBase *lb) SculptUndoNode *unode = static_cast(lb->first); while (unode != nullptr) { SculptUndoNode *unode_next = unode->next; - + if (unode->co) { + MEM_freeN(unode->co); + } + if (unode->col) { + MEM_freeN(unode->col); + } + if (unode->loop_col) { + MEM_freeN(unode->loop_col); + } + if (unode->no) { + MEM_freeN(unode->no); + } + if (unode->index) { + MEM_freeN(unode->index); + } + if (unode->faces) { + MEM_freeN(unode->faces); + } + if (unode->loop_index) { + MEM_freeN(unode->loop_index); + } + if (unode->grids) { + MEM_freeN(unode->grids); + } + if (unode->orig_co) { + MEM_freeN(unode->orig_co); + } + if (unode->vert_hidden) { + MEM_freeN(unode->vert_hidden); + } if (unode->grid_hidden) { for (int i = 0; i < unode->totgrid; i++) { if (unode->grid_hidden[i]) { @@ -1103,6 +1134,9 @@ static void sculpt_undo_free_list(ListBase *lb) } MEM_freeN(unode->grid_hidden); } + if (unode->mask) { + MEM_freeN(unode->mask); + } if (unode->bm_entry) { BM_log_entry_drop(unode->bm_entry); @@ -1112,7 +1146,11 @@ static void sculpt_undo_free_list(ListBase *lb) sculpt_undo_geometry_free_data(&unode->geometry_modified); sculpt_undo_geometry_free_data(&unode->geometry_bmesh_enter); - MEM_delete(unode); + if (unode->face_sets) { + MEM_freeN(unode->face_sets); + } + + MEM_freeN(unode); unode = unode_next; } @@ -1200,7 +1238,7 @@ static size_t sculpt_undo_alloc_and_store_hidden(PBVH *pbvh, SculptUndoNode *uno static SculptUndoNode *sculpt_undo_alloc_node_type(Object *object, SculptUndoType type) { const size_t alloc_size = sizeof(SculptUndoNode); - SculptUndoNode *unode = MEM_new(__func__); + SculptUndoNode *unode = static_cast(MEM_callocN(alloc_size, "SculptUndoNode")); STRNCPY(unode->idname, object->id.name); unode->type = type; @@ -1237,7 +1275,8 @@ static void sculpt_undo_store_faces(SculptSession *ss, SculptUndoNode *unode) } BKE_pbvh_face_iter_end(fd); - unode->faces.reinitialize(unode->faces_num); + unode->faces = static_cast( + MEM_malloc_arrayN(sizeof(*unode->faces), unode->faces_num, __func__)); BKE_pbvh_face_iter_begin (ss->pbvh, static_cast(unode->node), fd) { unode->faces[fd.i] = fd.face; } @@ -1273,26 +1312,30 @@ static SculptUndoNode *sculpt_undo_alloc_node(Object *ob, PBVHNode *node, Sculpt BKE_pbvh_node_num_loops(ss->pbvh, node, &totloop); - unode->loop_index.reinitialize(totloop); + unode->loop_index = static_cast(MEM_calloc_arrayN(totloop, sizeof(int), __func__)); unode->maxloop = 0; unode->totloop = totloop; - usculpt->undo_size += unode->loop_index.as_span().size_in_bytes(); + size_t alloc_size = sizeof(int) * size_t(totloop); + usculpt->undo_size += alloc_size; } if (need_faces) { sculpt_undo_store_faces(ss, unode); - usculpt->undo_size += unode->faces.as_span().size_in_bytes(); + const size_t alloc_size = sizeof(*unode->faces) * size_t(unode->faces_num); + usculpt->undo_size += alloc_size; } switch (type) { case SCULPT_UNDO_COORDS: { - unode->co.reinitialize(allvert); - usculpt->undo_size += unode->co.as_span().size_in_bytes(); + size_t alloc_size = sizeof(*unode->co) * size_t(allvert); + unode->co = static_cast(MEM_callocN(alloc_size, "SculptUndoNode.co")); + usculpt->undo_size += alloc_size; /* Needed for original data lookup. */ - unode->no.reinitialize(allvert); - usculpt->undo_size += unode->no.as_span().size_in_bytes(); + alloc_size = sizeof(*unode->no) * size_t(allvert); + unode->no = static_cast(MEM_callocN(alloc_size, "SculptUndoNode.no")); + usculpt->undo_size += alloc_size; break; } case SCULPT_UNDO_HIDDEN: { @@ -1300,27 +1343,32 @@ static SculptUndoNode *sculpt_undo_alloc_node(Object *ob, PBVHNode *node, Sculpt usculpt->undo_size += sculpt_undo_alloc_and_store_hidden(ss->pbvh, unode); } else { - unode->vert_hidden.resize(allvert); + unode->vert_hidden = BLI_BITMAP_NEW(allvert, "SculptUndoNode.vert_hidden"); usculpt->undo_size += BLI_BITMAP_SIZE(allvert); } break; } case SCULPT_UNDO_MASK: { - unode->mask.reinitialize(allvert); - usculpt->undo_size += unode->mask.as_span().size_in_bytes(); + const size_t alloc_size = sizeof(*unode->mask) * size_t(allvert); + unode->mask = static_cast(MEM_callocN(alloc_size, "SculptUndoNode.mask")); + usculpt->undo_size += alloc_size; break; } case SCULPT_UNDO_COLOR: { /* Allocate vertex colors, even for loop colors we still * need this for original data lookup. */ - unode->col.reinitialize(allvert); - usculpt->undo_size += unode->col.as_span().size_in_bytes(); + const size_t alloc_size = sizeof(*unode->col) * size_t(allvert); + unode->col = static_cast(MEM_callocN(alloc_size, "SculptUndoNode.col")); + usculpt->undo_size += alloc_size; /* Allocate loop colors separately too. */ if (ss->vcol_domain == ATTR_DOMAIN_CORNER) { - unode->loop_col.reinitialize(unode->totloop); - unode->undo_size += unode->loop_col.as_span().size_in_bytes(); + size_t alloc_size_loop = sizeof(float) * 4 * size_t(unode->totloop); + + unode->loop_col = static_cast( + MEM_calloc_arrayN(unode->totloop, sizeof(float) * 4, "SculptUndoNode.loop_col")); + usculpt->undo_size += alloc_size_loop; } break; } @@ -1332,8 +1380,8 @@ static SculptUndoNode *sculpt_undo_alloc_node(Object *ob, PBVHNode *node, Sculpt case SCULPT_UNDO_GEOMETRY: break; case SCULPT_UNDO_FACE_SETS: { - unode->face_sets.reinitialize(unode->faces_num); - usculpt->undo_size += unode->face_sets.as_span().size_in_bytes(); + const size_t alloc_size = sizeof(*unode->face_sets) * size_t(unode->faces_num); + usculpt->undo_size += alloc_size; break; } } @@ -1344,19 +1392,23 @@ static SculptUndoNode *sculpt_undo_alloc_node(Object *ob, PBVHNode *node, Sculpt unode->totgrid = totgrid; unode->gridsize = gridsize; - unode->grids.reinitialize(totgrid); - usculpt->undo_size += unode->grids.as_span().size_in_bytes(); + const size_t alloc_size = sizeof(*unode->grids) * size_t(totgrid); + unode->grids = static_cast(MEM_callocN(alloc_size, "SculptUndoNode.grids")); + usculpt->undo_size += alloc_size; } else { /* Regular mesh. */ unode->maxvert = ss->totvert; - unode->index.reinitialize(allvert); - usculpt->undo_size += unode->index.as_span().size_in_bytes(); + + const size_t alloc_size = sizeof(*unode->index) * size_t(allvert); + unode->index = static_cast(MEM_callocN(alloc_size, "SculptUndoNode.index")); + usculpt->undo_size += alloc_size; } if (ss->deform_modifiers_active) { - unode->orig_co.reinitialize(allvert); - usculpt->undo_size += unode->orig_co.as_span().size_in_bytes(); + const size_t alloc_size = sizeof(*unode->orig_co) * size_t(allvert); + unode->orig_co = static_cast(MEM_callocN(alloc_size, "undoSculpt orig_cos")); + usculpt->undo_size += alloc_size; } return unode; @@ -1393,13 +1445,17 @@ static void sculpt_undo_store_hidden(Object *ob, SculptUndoNode *unode) return; } - if (!unode->grids.is_empty()) { + if (unode->grids) { /* Already stored during allocation. */ } else { - const blender::Span verts = BKE_pbvh_node_get_vert_indices(node); - for (const int i : verts.index_range()) - unode->vert_hidden[i].set(hide_vert[verts[i]]); + int allvert; + + BKE_pbvh_node_num_verts(pbvh, node, nullptr, &allvert); + const int *vert_indices = BKE_pbvh_node_get_vert_indices(node); + for (int i = 0; i < allvert; i++) { + BLI_BITMAP_SET(unode->vert_hidden, i, hide_vert[vert_indices[i]]); + } } } @@ -1409,7 +1465,7 @@ static void sculpt_undo_store_mask(Object *ob, SculptUndoNode *unode) PBVHVertexIter vd; BKE_pbvh_vertex_iter_begin (ss->pbvh, static_cast(unode->node), vd, PBVH_ITER_ALL) { - unode->mask[vd.i] = vd.mask; + unode->mask[vd.i] = *vd.mask; } BKE_pbvh_vertex_iter_end; } @@ -1420,12 +1476,15 @@ static void sculpt_undo_store_color(Object *ob, SculptUndoNode *unode) BLI_assert(BKE_pbvh_type(ss->pbvh) == PBVH_FACES); + int allvert; + BKE_pbvh_node_num_verts(ss->pbvh, static_cast(unode->node), nullptr, &allvert); + /* NOTE: even with loop colors we still store (derived) * vertex colors for original data lookup. */ - BKE_pbvh_store_colors_vertex(ss->pbvh, unode->index, unode->col); + BKE_pbvh_store_colors_vertex(ss->pbvh, unode->index, allvert, unode->col); - if (!unode->loop_col.is_empty() && unode->totloop) { - BKE_pbvh_store_colors(ss->pbvh, unode->loop_index, unode->loop_col); + if (unode->loop_col && unode->totloop) { + BKE_pbvh_store_colors(ss->pbvh, unode->loop_index, unode->totloop, unode->loop_col); } } @@ -1454,7 +1513,8 @@ static SculptUndoNode *sculpt_undo_geometry_push(Object *object, SculptUndoType static void sculpt_undo_store_face_sets(SculptSession *ss, SculptUndoNode *unode) { - unode->face_sets.reinitialize(unode->faces_num); + unode->face_sets = static_cast( + MEM_malloc_arrayN(sizeof(*unode->face_sets), unode->faces_num, __func__)); PBVHFaceIter fd; BKE_pbvh_face_iter_begin (ss->pbvh, static_cast(unode->node), fd) { @@ -1472,7 +1532,7 @@ static SculptUndoNode *sculpt_undo_bmesh_push(Object *ob, PBVHNode *node, Sculpt SculptUndoNode *unode = static_cast(usculpt->nodes.first); if (unode == nullptr) { - unode = MEM_new(__func__); + unode = MEM_cnew(__func__); STRNCPY(unode->idname, ob->id.name); unode->type = type; @@ -1514,12 +1574,15 @@ static SculptUndoNode *sculpt_undo_bmesh_push(Object *ob, PBVHNode *node, Sculpt break; case SCULPT_UNDO_HIDDEN: { + GSetIterator gs_iter; + GSet *faces = BKE_pbvh_bmesh_node_faces(node); BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_ALL) { BM_log_vert_before_modified(ss->bm_log, vd.bm_vert, vd.cd_vert_mask_offset); } BKE_pbvh_vertex_iter_end; - for (BMFace *f : BKE_pbvh_bmesh_node_faces(node)) { + GSET_ITER (gs_iter, faces) { + BMFace *f = static_cast(BLI_gsetIterator_getKey(&gs_iter)); BM_log_face_modified(ss->bm_log, f); } break; @@ -1572,24 +1635,27 @@ SculptUndoNode *SCULPT_undo_push_node(Object *ob, PBVHNode *node, SculptUndoType * fully initialized. */ - if (!unode->grids.is_empty()) { + if (unode->grids) { int totgrid; const int *grids; BKE_pbvh_node_get_grids(ss->pbvh, node, &grids, &totgrid, nullptr, nullptr, nullptr); - unode->grids.as_mutable_span().copy_from({grids, totgrid}); + memcpy(unode->grids, grids, sizeof(int) * totgrid); } else { - unode->index.as_mutable_span().copy_from(BKE_pbvh_node_get_vert_indices(node)); + const int *loop_indices; + int allvert, allloop; - if (!unode->loop_index.is_empty()) { - const int *loop_indices; - int allloop; + BKE_pbvh_node_num_verts(ss->pbvh, static_cast(unode->node), nullptr, &allvert); + const int *vert_indices = BKE_pbvh_node_get_vert_indices(node); + memcpy(unode->index, vert_indices, sizeof(int) * allvert); + + if (unode->loop_index) { BKE_pbvh_node_num_loops(ss->pbvh, static_cast(unode->node), &allloop); BKE_pbvh_node_get_loops( ss->pbvh, static_cast(unode->node), &loop_indices, nullptr); if (allloop) { - unode->loop_index.as_mutable_span().copy_from({loop_indices, allloop}); + memcpy(unode->loop_index, loop_indices, sizeof(int) * allloop); unode->maxloop = BKE_object_get_original_mesh(ob)->totloop; } @@ -1716,8 +1782,11 @@ void SCULPT_undo_push_end_ex(Object *ob, const bool use_nested_undo) /* We don't need normals in the undo stack. */ LISTBASE_FOREACH (SculptUndoNode *, unode, &usculpt->nodes) { - usculpt->undo_size -= unode->no.as_span().size_in_bytes(); - unode->no = {}; + if (unode->no) { + usculpt->undo_size -= MEM_allocN_len(unode->no); + MEM_freeN(unode->no); + unode->no = nullptr; + } } /* We could remove this and enforce all callers run in an operator using 'OPTYPE_UNDO'. */ diff --git a/source/blender/editors/space_action/CMakeLists.txt b/source/blender/editors/space_action/CMakeLists.txt index 81c80e60e17..34baabeb6bd 100644 --- a/source/blender/editors/space_action/CMakeLists.txt +++ b/source/blender/editors/space_action/CMakeLists.txt @@ -37,7 +37,6 @@ set(LIB PRIVATE bf::blenlib PRIVATE bf::dna PRIVATE bf::intern::guardedalloc - PRIVATE bf::animrig ) diff --git a/source/blender/editors/space_action/action_edit.cc b/source/blender/editors/space_action/action_edit.cc index 92a6fe790e4..538b511c36f 100644 --- a/source/blender/editors/space_action/action_edit.cc +++ b/source/blender/editors/space_action/action_edit.cc @@ -43,7 +43,6 @@ #include "UI_view2d.hh" -#include "ANIM_keyframing.hh" #include "ED_anim_api.hh" #include "ED_gpencil_legacy.hh" #include "ED_grease_pencil.hh" @@ -820,7 +819,8 @@ static void insert_grease_pencil_key(bAnimContext *ac, static void insert_fcurve_key(bAnimContext *ac, bAnimListElem *ale, const AnimationEvalContext anim_eval_context, - eInsertKeyFlags flag) + eInsertKeyFlags flag, + ListBase *nla_cache) { FCurve *fcu = (FCurve *)ale->key_data; @@ -838,16 +838,17 @@ static void insert_fcurve_key(bAnimContext *ac, * (TODO: add the full-blown PointerRNA relative parsing case here...) */ if (ale->id && !ale->owner) { - blender::animrig::insert_keyframe(ac->bmain, - reports, - ale->id, - nullptr, - ((fcu->grp) ? (fcu->grp->name) : (nullptr)), - fcu->rna_path, - fcu->array_index, - &anim_eval_context, - eBezTriple_KeyframeType(ts->keyframe_type), - flag); + insert_keyframe(ac->bmain, + reports, + ale->id, + nullptr, + ((fcu->grp) ? (fcu->grp->name) : (nullptr)), + fcu->rna_path, + fcu->array_index, + &anim_eval_context, + eBezTriple_KeyframeType(ts->keyframe_type), + nla_cache, + flag); } else { AnimData *adt = ANIM_nla_mapping_get(ac, ale); @@ -870,6 +871,7 @@ static void insert_fcurve_key(bAnimContext *ac, static void insert_action_keys(bAnimContext *ac, short mode) { ListBase anim_data = {nullptr, nullptr}; + ListBase nla_cache = {nullptr, nullptr}; eAnimFilter_Flags filter; Scene *scene = ac->scene; @@ -917,7 +919,7 @@ static void insert_action_keys(bAnimContext *ac, short mode) break; case ANIMTYPE_FCURVE: - insert_fcurve_key(ac, ale, anim_eval_context, flag); + insert_fcurve_key(ac, ale, anim_eval_context, flag, &nla_cache); break; default: @@ -925,6 +927,8 @@ static void insert_action_keys(bAnimContext *ac, short mode) } } + BKE_animsys_free_nla_keyframing_context_cache(&nla_cache); + ANIM_animdata_update(ac, &anim_data); ANIM_animdata_freelist(&anim_data); } diff --git a/source/blender/editors/space_clip/clip_buttons.cc b/source/blender/editors/space_clip/clip_buttons.cc index ccb0d34dc41..23c63b93b49 100644 --- a/source/blender/editors/space_clip/clip_buttons.cc +++ b/source/blender/editors/space_clip/clip_buttons.cc @@ -791,7 +791,7 @@ void uiTemplateMovieclipInformation(uiLayout *layout, * as metadata panel. So if the cache is skipped here it is not really a memory saver, but * skipping the cache could lead to a performance impact depending on the order in which panels * and the main area is drawn. Basically, if it is this template drawn first and then the main - * area it will lead to frame read and processing happening twice. */ + * area it will lead to frame read and processing happening twice. */ ImBuf *ibuf = BKE_movieclip_get_ibuf_flag(clip, user, clip->flag, 0); int width, height; diff --git a/source/blender/editors/space_clip/clip_editor.cc b/source/blender/editors/space_clip/clip_editor.cc index 7c3e69a2d04..93d298c3199 100644 --- a/source/blender/editors/space_clip/clip_editor.cc +++ b/source/blender/editors/space_clip/clip_editor.cc @@ -280,8 +280,8 @@ ImBuf *ED_space_clip_get_stable_buffer(const SpaceClip *sc, bool ED_space_clip_get_position(const SpaceClip *sc, const ARegion *region, - const int mval[2], - float r_fpos[2]) + int mval[2], + float fpos[2]) { ImBuf *ibuf = ED_space_clip_get_buffer(sc); if (!ibuf) { @@ -289,7 +289,7 @@ bool ED_space_clip_get_position(const SpaceClip *sc, } /* map the mouse coords to the backdrop image space */ - ED_clip_mouse_pos(sc, region, mval, r_fpos); + ED_clip_mouse_pos(sc, region, mval, fpos); IMB_freeImBuf(ibuf); return true; @@ -545,12 +545,9 @@ void ED_clip_point_stable_pos__reverse(const SpaceClip *sc, r_co[1] = (pos[1] * height * zoomy) + float(sy); } -void ED_clip_mouse_pos(const SpaceClip *sc, - const ARegion *region, - const int mval[2], - float r_co[2]) +void ED_clip_mouse_pos(const SpaceClip *sc, const ARegion *region, const int mval[2], float co[2]) { - ED_clip_point_stable_pos(sc, region, mval[0], mval[1], &r_co[0], &r_co[1]); + ED_clip_point_stable_pos(sc, region, mval[0], mval[1], &co[0], &co[1]); } bool ED_space_clip_check_show_trackedit(const SpaceClip *sc) @@ -1001,7 +998,7 @@ static void do_prefetch_movie(MovieClip *clip, } } -static void prefetch_startjob(void *pjv, wmJobWorkerStatus *worker_status) +static void prefetch_startjob(void *pjv, bool *stop, bool *do_update, float *progress) { PrefetchJob *pj = static_cast(pjv); @@ -1013,9 +1010,9 @@ static void prefetch_startjob(void *pjv, wmJobWorkerStatus *worker_status) pj->end_frame, pj->render_size, pj->render_flag, - &worker_status->stop, - &worker_status->do_update, - &worker_status->progress); + stop, + do_update, + progress); } else if (pj->clip->source == MCLIP_SRC_MOVIE) { /* read movie in a single thread */ @@ -1026,9 +1023,9 @@ static void prefetch_startjob(void *pjv, wmJobWorkerStatus *worker_status) pj->end_frame, pj->render_size, pj->render_flag, - &worker_status->stop, - &worker_status->do_update, - &worker_status->progress); + stop, + do_update, + progress); } else { BLI_assert_msg(0, "Unknown movie clip source when prefetching frames"); diff --git a/source/blender/editors/space_clip/clip_ops.cc b/source/blender/editors/space_clip/clip_ops.cc index a7229051692..f7c4d420695 100644 --- a/source/blender/editors/space_clip/clip_ops.cc +++ b/source/blender/editors/space_clip/clip_ops.cc @@ -1451,7 +1451,7 @@ static void do_sequence_proxy(void *pjv, MEM_freeN(handles); } -static void proxy_startjob(void *pjv, wmJobWorkerStatus *worker_status) +static void proxy_startjob(void *pjv, bool *stop, bool *do_update, float *progress) { ProxyJob *pj = static_cast(pjv); MovieClip *clip = pj->clip; @@ -1471,9 +1471,9 @@ static void proxy_startjob(void *pjv, wmJobWorkerStatus *worker_status) build_count, build_undistort_sizes, build_undistort_count, - &worker_status->stop, - &worker_status->do_update, - &worker_status->progress); + stop, + do_update, + progress); } else { do_sequence_proxy(pjv, @@ -1481,9 +1481,9 @@ static void proxy_startjob(void *pjv, wmJobWorkerStatus *worker_status) build_count, build_undistort_sizes, build_undistort_count, - &worker_status->stop, - &worker_status->do_update, - &worker_status->progress); + stop, + do_update, + progress); } } diff --git a/source/blender/editors/space_clip/tracking_ops.cc b/source/blender/editors/space_clip/tracking_ops.cc index b377a2c7ea6..0f2162fdd15 100644 --- a/source/blender/editors/space_clip/tracking_ops.cc +++ b/source/blender/editors/space_clip/tracking_ops.cc @@ -1297,7 +1297,7 @@ static int average_tracks_exec(bContext *C, wmOperator *op) } /* Create new empty track, which will be the averaged result. - * Makes it simple to average all selection to it. */ + * Makes it simple to average all selection to it. */ MovieTrackingTrack *result_track = BKE_tracking_track_add_empty(tracking, &tracking_object->tracks); diff --git a/source/blender/editors/space_clip/tracking_ops_solve.cc b/source/blender/editors/space_clip/tracking_ops_solve.cc index b5a745f9423..73f61cc0abf 100644 --- a/source/blender/editors/space_clip/tracking_ops_solve.cc +++ b/source/blender/editors/space_clip/tracking_ops_solve.cc @@ -92,15 +92,11 @@ static void solve_camera_updatejob(void *scv) STRNCPY(tracking->stats->message, scj->stats_message); } -static void solve_camera_startjob(void *scv, wmJobWorkerStatus *worker_status) +static void solve_camera_startjob(void *scv, bool *stop, bool *do_update, float *progress) { SolveCameraJob *scj = (SolveCameraJob *)scv; - BKE_tracking_reconstruction_solve(scj->context, - &worker_status->stop, - &worker_status->do_update, - &worker_status->progress, - scj->stats_message, - sizeof(scj->stats_message)); + BKE_tracking_reconstruction_solve( + scj->context, stop, do_update, progress, scj->stats_message, sizeof(scj->stats_message)); } static void solve_camera_freejob(void *scv) @@ -188,8 +184,7 @@ static int solve_camera_exec(bContext *C, wmOperator *op) solve_camera_freejob(scj); return OPERATOR_CANCELLED; } - wmJobWorkerStatus worker_status = {}; - solve_camera_startjob(scj, &worker_status); + solve_camera_startjob(scj, nullptr, nullptr, nullptr); solve_camera_freejob(scj); return OPERATOR_FINISHED; } diff --git a/source/blender/editors/space_clip/tracking_ops_track.cc b/source/blender/editors/space_clip/tracking_ops_track.cc index 75f57f57de7..9f97b85e56c 100644 --- a/source/blender/editors/space_clip/tracking_ops_track.cc +++ b/source/blender/editors/space_clip/tracking_ops_track.cc @@ -203,7 +203,13 @@ static bool track_markers_initjob(bContext *C, TrackMarkersJob *tmj, bool backwa return true; } -static void track_markers_startjob(void *tmv, wmJobWorkerStatus *worker_status) +static void track_markers_startjob( + void *tmv, + /* Cannot be const, this function implements wm_jobs_start_callback. + * NOLINTNEXTLINE: readability-non-const-parameter. */ + bool *stop, + bool *do_update, + float *progress) { TrackMarkersJob *tmj = (TrackMarkersJob *)tmv; int framenr = tmj->sfra; @@ -234,8 +240,8 @@ static void track_markers_startjob(void *tmv, wmJobWorkerStatus *worker_status) break; } - worker_status->do_update = true; - worker_status->progress = float(framenr - tmj->sfra) / (tmj->efra - tmj->sfra); + *do_update = true; + *progress = float(framenr - tmj->sfra) / (tmj->efra - tmj->sfra); if (tmj->backwards) { framenr--; @@ -246,7 +252,7 @@ static void track_markers_startjob(void *tmv, wmJobWorkerStatus *worker_status) tmj->lastfra = framenr; - if (worker_status->stop || track_markers_testbreak()) { + if (*stop || track_markers_testbreak()) { break; } } @@ -350,8 +356,9 @@ static int track_markers(bContext *C, wmOperator *op, bool use_job) return OPERATOR_RUNNING_MODAL; } - wmJobWorkerStatus worker_status = {}; - track_markers_startjob(tmj, &worker_status); + bool stop = false, do_update = false; + float progress = 0.0f; + track_markers_startjob(tmj, &stop, &do_update, &progress); track_markers_endjob(tmj); track_markers_freejob(tmj); return OPERATOR_FINISHED; diff --git a/source/blender/editors/space_file/asset_catalog_tree_view.cc b/source/blender/editors/space_file/asset_catalog_tree_view.cc index 4eaefb59013..4ecb2bfc88b 100644 --- a/source/blender/editors/space_file/asset_catalog_tree_view.cc +++ b/source/blender/editors/space_file/asset_catalog_tree_view.cc @@ -111,7 +111,7 @@ class AssetCatalogDropTarget : public ui::TreeViewItemDropTarget { AssetCatalogTreeItem &catalog_item_; public: - AssetCatalogDropTarget(AssetCatalogTreeViewItem &item, AssetCatalogTreeItem &catalog_item); + AssetCatalogDropTarget(AssetCatalogTreeView &tree_view, AssetCatalogTreeItem &catalog_item); bool can_drop(const wmDrag &drag, const char **r_disabled_hint) const override; std::string drop_tooltip(const ui::DragInfo &drag_info) const override; @@ -149,7 +149,7 @@ class AssetCatalogTreeViewAllItem : public ui::BasicTreeViewItem { void build_row(uiLayout &row) override; struct DropTarget : public ui::TreeViewItemDropTarget { - DropTarget(AssetCatalogTreeViewAllItem &item); + DropTarget(AssetCatalogTreeView &tree_view); bool can_drop(const wmDrag &drag, const char **r_disabled_hint) const override; std::string drop_tooltip(const ui::DragInfo &drag_info) const override; @@ -163,7 +163,7 @@ class AssetCatalogTreeViewUnassignedItem : public ui::BasicTreeViewItem { using BasicTreeViewItem::BasicTreeViewItem; struct DropTarget : public ui::TreeViewItemDropTarget { - DropTarget(AssetCatalogTreeViewUnassignedItem &item); + DropTarget(AssetCatalogTreeView &tree_view); bool can_drop(const wmDrag &drag, const char **r_disabled_hint) const override; std::string drop_tooltip(const ui::DragInfo &drag_info) const override; @@ -343,7 +343,8 @@ bool AssetCatalogTreeViewItem::rename(const bContext &C, StringRefNull new_name) std::unique_ptr AssetCatalogTreeViewItem::create_drop_target() { - return std::make_unique(*this, catalog_item_); + return std::make_unique( + static_cast(get_tree_view()), catalog_item_); } std::unique_ptr AssetCatalogTreeViewItem:: @@ -355,9 +356,9 @@ std::unique_ptr AssetCatalogTreeViewItem:: /* ---------------------------------------------------------------------- */ -AssetCatalogDropTarget::AssetCatalogDropTarget(AssetCatalogTreeViewItem &item, +AssetCatalogDropTarget::AssetCatalogDropTarget(AssetCatalogTreeView &tree_view, AssetCatalogTreeItem &catalog_item) - : ui::TreeViewItemDropTarget(item), catalog_item_(catalog_item) + : ui::TreeViewItemDropTarget(tree_view), catalog_item_(catalog_item) { } @@ -584,11 +585,12 @@ void AssetCatalogTreeViewAllItem::build_row(uiLayout &row) std::unique_ptr AssetCatalogTreeViewAllItem::create_drop_target() { - return std::make_unique(*this); + return std::make_unique( + static_cast(get_tree_view())); } -AssetCatalogTreeViewAllItem::DropTarget::DropTarget(AssetCatalogTreeViewAllItem &item) - : ui::TreeViewItemDropTarget(item) +AssetCatalogTreeViewAllItem::DropTarget::DropTarget(AssetCatalogTreeView &tree_view) + : ui::TreeViewItemDropTarget(tree_view) { } @@ -639,12 +641,12 @@ bool AssetCatalogTreeViewAllItem::DropTarget::on_drop(bContext * /*C*/, std::unique_ptr AssetCatalogTreeViewUnassignedItem:: create_drop_target() { - return std::make_unique(*this); + return std::make_unique( + static_cast(get_tree_view())); } -AssetCatalogTreeViewUnassignedItem::DropTarget::DropTarget( - AssetCatalogTreeViewUnassignedItem &item) - : ui::TreeViewItemDropTarget(item) +AssetCatalogTreeViewUnassignedItem::DropTarget::DropTarget(AssetCatalogTreeView &tree_view) + : ui::TreeViewItemDropTarget(tree_view) { } diff --git a/source/blender/editors/space_file/filelist.cc b/source/blender/editors/space_file/filelist.cc index 3d2d471dd5a..fc730bbee99 100644 --- a/source/blender/editors/space_file/filelist.cc +++ b/source/blender/editors/space_file/filelist.cc @@ -38,7 +38,7 @@ #include "BLI_linklist.h" #include "BLI_math_vector.h" #include "BLI_stack.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_task.h" #include "BLI_threads.h" #include "BLI_utildefines.h" @@ -4049,7 +4049,7 @@ static bool filelist_readjob_is_partial_read(const FileListReadJob *read_job) * some current entries are kept and we just call the readjob to update the main files (see * #FileListReadJob.only_main_data). */ -static void filelist_readjob_startjob(void *flrjv, wmJobWorkerStatus *worker_status) +static void filelist_readjob_startjob(void *flrjv, bool *stop, bool *do_update, float *progress) { FileListReadJob *flrj = static_cast(flrjv); @@ -4082,8 +4082,7 @@ static void filelist_readjob_startjob(void *flrjv, wmJobWorkerStatus *worker_sta BLI_mutex_unlock(&flrj->lock); - flrj->tmp_filelist->read_job_fn( - flrj, &worker_status->stop, &worker_status->do_update, &worker_status->progress); + flrj->tmp_filelist->read_job_fn(flrj, stop, do_update, progress); } /** @@ -4198,9 +4197,12 @@ void filelist_readjob_start(FileList *filelist, const int space_notifier, const const bool no_threads = (filelist->tags & FILELIST_TAGS_NO_THREADS) || flrj->only_main_data; if (no_threads) { + bool dummy_stop = false; + bool dummy_do_update = false; + float dummy_progress = 0.0f; + /* Single threaded execution. Just directly call the callbacks. */ - wmJobWorkerStatus worker_status = {}; - filelist_readjob_startjob(flrj, &worker_status); + filelist_readjob_startjob(flrj, &dummy_stop, &dummy_do_update, &dummy_progress); filelist_readjob_endjob(flrj); filelist_readjob_free(flrj); diff --git a/source/blender/editors/space_file/fsmenu.cc b/source/blender/editors/space_file/fsmenu.cc index a28f0f36295..122fe8cb14e 100644 --- a/source/blender/editors/space_file/fsmenu.cc +++ b/source/blender/editors/space_file/fsmenu.cc @@ -15,7 +15,7 @@ #include "BLI_blenlib.h" #include "BLI_ghash.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_utildefines.h" #include "BLT_translation.h" @@ -593,7 +593,13 @@ int fsmenu_get_active_indices(FSMenu *fsmenu, enum FSMenuCategory category, cons * before being defined as unreachable by the OS, we need to validate the bookmarks in an * asynchronous job. */ -static void fsmenu_bookmark_validate_job_startjob(void *fsmenuv, wmJobWorkerStatus *worker_status) +static void fsmenu_bookmark_validate_job_startjob( + void *fsmenuv, + /* Cannot be const, this function implements wm_jobs_start_callback. + * NOLINTNEXTLINE: readability-non-const-parameter. */ + bool *stop, + bool *do_update, + float * /*progress*/) { FSMenu *fsmenu = static_cast(fsmenuv); @@ -603,13 +609,13 @@ static void fsmenu_bookmark_validate_job_startjob(void *fsmenuv, wmJobWorkerStat for (size_t i = ARRAY_SIZE(categories); i--;) { FSMenuEntry *fsm_iter = ED_fsmenu_get_category(fsmenu, FSMenuCategory(categories[i])); for (; fsm_iter; fsm_iter = fsm_iter->next) { - if (worker_status->stop) { + if (*stop) { return; } /* Note that we do not really need atomics primitives or thread locks here, since this only * sets one short, which is assumed to be 'atomic'-enough for us here. */ fsmenu_entry_refresh_valid(fsm_iter); - worker_status->do_update = true; + *do_update = true; } } } diff --git a/source/blender/editors/space_file/space_file.cc b/source/blender/editors/space_file/space_file.cc index 848050dd121..bbb9a7c9d30 100644 --- a/source/blender/editors/space_file/space_file.cc +++ b/source/blender/editors/space_file/space_file.cc @@ -129,7 +129,7 @@ static void file_free(SpaceLink *sl) MEM_SAFE_FREE(sfile->params); MEM_SAFE_FREE(sfile->asset_params); if (sfile->runtime != nullptr) { - BKE_reports_free(&sfile->runtime->is_blendfile_readable_reports); + BKE_reports_clear(&sfile->runtime->is_blendfile_readable_reports); } MEM_SAFE_FREE(sfile->runtime); diff --git a/source/blender/editors/space_graph/CMakeLists.txt b/source/blender/editors/space_graph/CMakeLists.txt index 97fc9a66d28..ce43f7ed055 100644 --- a/source/blender/editors/space_graph/CMakeLists.txt +++ b/source/blender/editors/space_graph/CMakeLists.txt @@ -38,7 +38,6 @@ set(LIB PRIVATE bf::blenlib PRIVATE bf::dna PRIVATE bf::intern::guardedalloc - PRIVATE bf::animrig ) if(WITH_AUDASPACE) diff --git a/source/blender/editors/space_graph/graph_edit.cc b/source/blender/editors/space_graph/graph_edit.cc index a901686b1f2..fe0b4eef053 100644 --- a/source/blender/editors/space_graph/graph_edit.cc +++ b/source/blender/editors/space_graph/graph_edit.cc @@ -47,7 +47,6 @@ #include "UI_interface.hh" #include "UI_view2d.hh" -#include "ANIM_keyframing.hh" #include "ED_anim_api.hh" #include "ED_keyframes_edit.hh" #include "ED_keyframing.hh" @@ -106,6 +105,7 @@ static const EnumPropertyItem prop_graphkeys_insertkey_types[] = { static void insert_graph_keys(bAnimContext *ac, eGraphKeys_InsertKey_Types mode) { ListBase anim_data = {nullptr, nullptr}; + ListBase nla_cache = {nullptr, nullptr}; int filter; size_t num_items; @@ -204,16 +204,17 @@ static void insert_graph_keys(bAnimContext *ac, eGraphKeys_InsertKey_Types mode) * up adding the keyframes on a new F-Curve in the action data instead. */ if (ale->id && !ale->owner && !fcu->driver) { - blender::animrig::insert_keyframe(ac->bmain, - reports, - ale->id, - nullptr, - ((fcu->grp) ? (fcu->grp->name) : (nullptr)), - fcu->rna_path, - fcu->array_index, - &anim_eval_context, - eBezTriple_KeyframeType(ts->keyframe_type), - flag); + insert_keyframe(ac->bmain, + reports, + ale->id, + nullptr, + ((fcu->grp) ? (fcu->grp->name) : (nullptr)), + fcu->rna_path, + fcu->array_index, + &anim_eval_context, + eBezTriple_KeyframeType(ts->keyframe_type), + &nla_cache, + flag); } else { AnimData *adt = ANIM_nla_mapping_get(ac, ale); @@ -236,6 +237,8 @@ static void insert_graph_keys(bAnimContext *ac, eGraphKeys_InsertKey_Types mode) } } + BKE_animsys_free_nla_keyframing_context_cache(&nla_cache); + ANIM_animdata_update(ac, &anim_data); ANIM_animdata_freelist(&anim_data); } diff --git a/source/blender/editors/space_image/image_buttons.cc b/source/blender/editors/space_image/image_buttons.cc index c1bf5be8e92..86cd3873f65 100644 --- a/source/blender/editors/space_image/image_buttons.cc +++ b/source/blender/editors/space_image/image_buttons.cc @@ -801,7 +801,7 @@ void uiTemplateImage(uiLayout *layout, const float dpi_fac = UI_SCALE_FAC; const int menus_width = 230 * dpi_fac; - /* Use #BKE_image_acquire_renderresult so we get the correct slot in the menu. */ + /* use BKE_image_acquire_renderresult so we get the correct slot in the menu */ rr = BKE_image_acquire_renderresult(scene, ima); uiblock_layer_pass_buttons(layout, ima, rr, iuser, menus_width, &ima->render_slot); BKE_image_release_renderresult(scene, ima); diff --git a/source/blender/editors/space_image/image_ops.cc b/source/blender/editors/space_image/image_ops.cc index dc5d9f5aa04..d8d9686e840 100644 --- a/source/blender/editors/space_image/image_ops.cc +++ b/source/blender/editors/space_image/image_ops.cc @@ -1854,7 +1854,7 @@ static bool save_image_op( WM_cursor_wait(false); /* Remember file path for next save. */ - STRNCPY(G.filepath_last_image, opts->filepath); + STRNCPY(G.ima, opts->filepath); WM_main_add_notifier(NC_IMAGE | NA_EDITED, ima); @@ -2336,7 +2336,7 @@ bool ED_image_should_save_modified(const Main *bmain) uint modified_images_count = ED_image_save_all_modified_info(bmain, &reports); bool should_save = modified_images_count || !BLI_listbase_is_empty(&reports.list); - BKE_reports_free(&reports); + BKE_reports_clear(&reports); return should_save; } @@ -3334,10 +3334,7 @@ void IMAGE_OT_unpack(wmOperatorType *ot) /** \name Sample Image Operator * \{ */ -bool ED_space_image_get_position(SpaceImage *sima, - ARegion *region, - const int mval[2], - float r_fpos[2]) +bool ED_space_image_get_position(SpaceImage *sima, ARegion *region, int mval[2], float fpos[2]) { void *lock; ImBuf *ibuf = ED_space_image_acquire_buffer(sima, &lock, 0); @@ -3347,7 +3344,7 @@ bool ED_space_image_get_position(SpaceImage *sima, return false; } - UI_view2d_region_to_view(®ion->v2d, mval[0], mval[1], &r_fpos[0], &r_fpos[1]); + UI_view2d_region_to_view(®ion->v2d, mval[0], mval[1], &fpos[0], &fpos[1]); ED_space_image_release_buffer(sima, ibuf, lock); return true; diff --git a/source/blender/editors/space_node/drawnode.cc b/source/blender/editors/space_node/drawnode.cc index bd2b53b63e9..b58190ec80c 100644 --- a/source/blender/editors/space_node/drawnode.cc +++ b/source/blender/editors/space_node/drawnode.cc @@ -24,6 +24,7 @@ #include "BKE_main.h" #include "BKE_node.hh" #include "BKE_node_runtime.hh" +#include "BKE_node_tree_anonymous_attributes.hh" #include "BKE_node_tree_update.h" #include "BKE_scene.h" #include "BKE_tracking.h" @@ -61,11 +62,10 @@ #include "IMB_colormanagement.h" #include "IMB_imbuf_types.h" -#include "NOD_composite.hh" +#include "NOD_composite.h" #include "NOD_geometry.hh" #include "NOD_node_declaration.hh" #include "NOD_shader.h" -#include "NOD_socket.hh" #include "NOD_texture.h" #include "node_intern.hh" /* own include */ @@ -1508,35 +1508,13 @@ static void std_node_socket_interface_draw(ID *id, } col = uiLayoutColumn(layout, false); + uiItemR(col, &ptr, "hide_value", DEFAULT_FLAGS, nullptr, ICON_NONE); const bNodeTree *node_tree = reinterpret_cast(id); if (interface_socket->flag & NODE_INTERFACE_SOCKET_INPUT && node_tree->type == NTREE_GEOMETRY) { - if (ELEM(type, SOCK_INT, SOCK_VECTOR)) { - uiItemR(col, &ptr, "default_input", DEFAULT_FLAGS, nullptr, ICON_NONE); - } - } - - { - uiLayout *sub = uiLayoutColumn(col, false); - uiLayoutSetActive(sub, interface_socket->default_input == NODE_INPUT_DEFAULT_VALUE); - uiItemR(sub, &ptr, "hide_value", DEFAULT_FLAGS, nullptr, ICON_NONE); - } - - if (interface_socket->flag & NODE_INTERFACE_SOCKET_INPUT && node_tree->type == NTREE_GEOMETRY) { - if (U.experimental.use_grease_pencil_version3) { - if (type == SOCK_BOOLEAN) { - uiItemR(col, &ptr, "layer_selection_field", DEFAULT_FLAGS, nullptr, ICON_NONE); - } - } - uiLayout *sub = uiLayoutColumn(col, false); - uiLayoutSetActive(sub, !is_layer_selection_field(*interface_socket)); - uiItemR(sub, &ptr, "hide_in_modifier", DEFAULT_FLAGS, nullptr, ICON_NONE); - if (nodes::socket_type_supports_fields(type)) { - uiLayout *sub_sub = uiLayoutColumn(col, false); - uiLayoutSetActive(sub_sub, - (interface_socket->default_input == NODE_INPUT_DEFAULT_VALUE) && - !is_layer_selection_field(*interface_socket)); - uiItemR(sub_sub, &ptr, "force_non_field", DEFAULT_FLAGS, nullptr, ICON_NONE); + uiItemR(col, &ptr, "hide_in_modifier", DEFAULT_FLAGS, nullptr, ICON_NONE); + if (bke::anonymous_attribute_inferencing::is_possible_field_socket(type)) { + uiItemR(col, &ptr, "force_non_field", DEFAULT_FLAGS, nullptr, ICON_NONE); } } } diff --git a/source/blender/editors/space_node/node_draw.cc b/source/blender/editors/space_node/node_draw.cc index 44325527059..cfd7f8e2a1a 100644 --- a/source/blender/editors/space_node/node_draw.cc +++ b/source/blender/editors/space_node/node_draw.cc @@ -287,39 +287,28 @@ static bool compare_node_depth(const bNode *a, const bNode *b) return false; } -void tree_draw_order_update(bNodeTree &ntree) +void node_sort(bNodeTree &ntree) { Array sort_nodes = ntree.all_nodes(); std::stable_sort(sort_nodes.begin(), sort_nodes.end(), compare_node_depth); + + /* If nothing was changed, exit early. Otherwise the node tree's runtime + * node vector needs to be rebuilt, since it cannot be reordered in place. */ + if (sort_nodes == ntree.all_nodes()) { + return; + } + + BKE_ntree_update_tag_node_reordered(&ntree); + + ntree.runtime->nodes_by_id.clear(); + BLI_listbase_clear(&ntree.nodes); for (const int i : sort_nodes.index_range()) { - sort_nodes[i]->ui_order = i; + BLI_addtail(&ntree.nodes, sort_nodes[i]); + ntree.runtime->nodes_by_id.add_new(sort_nodes[i]); + sort_nodes[i]->runtime->index_in_tree = i; } } -Array tree_draw_order_calc_nodes(bNodeTree &ntree) -{ - Array nodes = ntree.all_nodes(); - if (nodes.is_empty()) { - return {}; - } - std::sort(nodes.begin(), nodes.end(), [](const bNode *a, const bNode *b) { - return a->ui_order < b->ui_order; - }); - return nodes; -} - -Array tree_draw_order_calc_nodes_reversed(bNodeTree &ntree) -{ - Array nodes = ntree.all_nodes(); - if (nodes.is_empty()) { - return {}; - } - std::sort(nodes.begin(), nodes.end(), [](const bNode *a, const bNode *b) { - return a->ui_order > b->ui_order; - }); - return nodes; -} - static Array node_uiblocks_init(const bContext &C, const Span nodes) { Array blocks(nodes.size()); @@ -1186,7 +1175,7 @@ static void node_socket_outline_color_get(const bool selected, } else if (socket_type == SOCK_CUSTOM) { /* Until there is a better place for per socket color, - * the outline color for virtual sockets is set here. */ + * the outline color for virtual sockets is set here. */ copy_v4_v4(r_outline_color, virtual_node_socket_outline_color); } else { @@ -1402,14 +1391,7 @@ static void create_inspection_string_for_geometry_info(const geo_log::GeometryIn break; } case bke::GeometryComponent::Type::GreasePencil: { - const geo_log::GeometryInfoLog::GreasePencilInfo &grease_pencil_info = - *value_log.grease_pencil_info; - char line[256]; - SNPRINTF(line, - TIP_("\u2022 Grease Pencil: %s layers"), - to_string(grease_pencil_info.layers_num).c_str()); - ss << line; - break; + /* TODO. Do nothing for now. */ break; } } @@ -2672,7 +2654,7 @@ static void node_draw_extra_info_panel_back(const bNode &node, const rctf &extra { const rctf &node_rect = node.runtime->totr; rctf panel_back_rect = extra_info_rect; - /* Extend the panel behind hidden nodes to accommodate the large rounded corners. */ + /* Extend the panel behind hidden nodes to accomodate the large rounded corners. */ if (node.flag & NODE_HIDDEN) { panel_back_rect.ymin = BLI_rctf_cent_y(&node_rect); } @@ -3334,9 +3316,13 @@ int node_get_resize_cursor(NodeResizeDirection directions) static const bNode *find_node_under_cursor(SpaceNode &snode, const float2 &cursor) { - for (const bNode *node : tree_draw_order_calc_nodes_reversed(*snode.edittree)) { - if (BLI_rctf_isect_pt(&node->runtime->totr, cursor[0], cursor[1])) { - return node; + const Span nodes = snode.edittree->all_nodes(); + if (nodes.is_empty()) { + return nullptr; + } + for (int i = nodes.index_range().last(); i >= 0; i--) { + if (BLI_rctf_isect_pt(&nodes[i]->runtime->totr, cursor[0], cursor[1])) { + return nodes[i]; } } return nullptr; @@ -4055,7 +4041,7 @@ static void draw_nodetree(const bContext &C, SpaceNode *snode = CTX_wm_space_node(&C); ntree.ensure_topology_cache(); - Array nodes = tree_draw_order_calc_nodes(ntree); + const Span nodes = ntree.all_nodes(); Array blocks = node_uiblocks_init(C, nodes); diff --git a/source/blender/editors/space_node/node_edit.cc b/source/blender/editors/space_node/node_edit.cc index 1c818c1b7bc..fc746cce3f8 100644 --- a/source/blender/editors/space_node/node_edit.cc +++ b/source/blender/editors/space_node/node_edit.cc @@ -65,7 +65,7 @@ #include "IMB_imbuf_types.h" -#include "NOD_composite.hh" +#include "NOD_composite.h" #include "NOD_geometry.hh" #include "NOD_shader.h" #include "NOD_socket.hh" @@ -267,7 +267,12 @@ static void compo_progressjob(void *cjv, float progress) } /* Only this runs inside thread. */ -static void compo_startjob(void *cjv, wmJobWorkerStatus *worker_status) +static void compo_startjob(void *cjv, + /* Cannot be const, this function implements wm_jobs_start_callback. + * NOLINTNEXTLINE: readability-non-const-parameter. */ + bool *stop, + bool *do_update, + float *progress) { CompoJob *cj = (CompoJob *)cjv; bNodeTree *ntree = cj->localtree; @@ -277,9 +282,9 @@ static void compo_startjob(void *cjv, wmJobWorkerStatus *worker_status) return; } - cj->stop = &worker_status->stop; - cj->do_update = &worker_status->do_update; - cj->progress = &worker_status->progress; + cj->stop = stop; + cj->do_update = do_update; + cj->progress = progress; ntree->runtime->test_break = compo_breakjob; ntree->runtime->tbh = cj; @@ -846,9 +851,9 @@ namespace blender::ed::space_node { static bool socket_is_occluded(const float2 &location, const bNode &node_the_socket_belongs_to, - const Span sorted_nodes) + const SpaceNode &snode) { - for (bNode *node : sorted_nodes) { + LISTBASE_FOREACH_BACKWARD (bNode *, node, &snode.edittree->nodes) { if (node == &node_the_socket_belongs_to) { /* Nodes after this one are underneath and can't occlude the socket. */ return false; @@ -1168,14 +1173,13 @@ bNodeSocket *node_find_indicated_socket(SpaceNode &snode, bNodeTree &node_tree = *snode.edittree; node_tree.ensure_topology_cache(); - - const Array sorted_nodes = tree_draw_order_calc_nodes_reversed(*snode.edittree); - if (sorted_nodes.is_empty()) { + const Span nodes = node_tree.all_nodes(); + if (nodes.is_empty()) { return nullptr; } - for (const int i : sorted_nodes.index_range()) { - bNode &node = *sorted_nodes[i]; + for (int i = nodes.index_range().last(); i >= 0; i--) { + bNode &node = *nodes[i]; BLI_rctf_init_pt_radius(&rect, cursor, size_sock_padded); if (!(node.flag & NODE_HIDDEN)) { @@ -1196,13 +1200,13 @@ bNodeSocket *node_find_indicated_socket(SpaceNode &snode, const float2 location = sock->runtime->location; if (sock->flag & SOCK_MULTI_INPUT && !(node.flag & NODE_HIDDEN)) { if (cursor_isect_multi_input_socket(cursor, *sock)) { - if (!socket_is_occluded(location, node, sorted_nodes)) { + if (!socket_is_occluded(location, node, snode)) { return sock; } } } else if (BLI_rctf_isect_pt(&rect, location.x, location.y)) { - if (!socket_is_occluded(location, node, sorted_nodes)) { + if (!socket_is_occluded(location, node, snode)) { return sock; } } @@ -1214,7 +1218,7 @@ bNodeSocket *node_find_indicated_socket(SpaceNode &snode, if (node.is_socket_icon_drawn(*sock)) { const float2 location = sock->runtime->location; if (BLI_rctf_isect_pt(&rect, location.x, location.y)) { - if (!socket_is_occluded(location, node, sorted_nodes)) { + if (!socket_is_occluded(location, node, snode)) { return sock; } } diff --git a/source/blender/editors/space_node/node_group.cc b/source/blender/editors/space_node/node_group.cc index d6250f237a7..0095e4eea2a 100644 --- a/source/blender/editors/space_node/node_group.cc +++ b/source/blender/editors/space_node/node_group.cc @@ -54,7 +54,7 @@ #include "UI_resources.hh" #include "NOD_common.h" -#include "NOD_composite.hh" +#include "NOD_composite.h" #include "NOD_geometry.hh" #include "NOD_shader.h" #include "NOD_socket.hh" diff --git a/source/blender/editors/space_node/node_intern.hh b/source/blender/editors/space_node/node_intern.hh index f80512bc732..cd420627a01 100644 --- a/source/blender/editors/space_node/node_intern.hh +++ b/source/blender/editors/space_node/node_intern.hh @@ -96,7 +96,7 @@ struct SpaceNode_Runtime { bool recalc_auto_compositing; /** - * Indicates that the compositing int the space tree needs to be re-evaluated using + * Indicates that the compositing int the space tree needs to be re-evaluated using * regular compositing pipeline. */ bool recalc_regular_compositing; @@ -174,14 +174,10 @@ void node_draw_space(const bContext &C, ARegion ®ion); void node_socket_add_tooltip(const bNodeTree &ntree, const bNodeSocket &sock, uiLayout &layout); /** - * Update node draw order nodes based on selection: unselected nodes first, then selected, + * Sort nodes by selection: unselected nodes first, then selected, * then the active node at the very end. Relative order is kept intact. */ -void tree_draw_order_update(bNodeTree &ntree); -/** Return the nodes in draw order, with the top nodes at the end. */ -Array tree_draw_order_calc_nodes(bNodeTree &ntree); -/** Return the nodes in reverse draw order, with the top nodes at the start. */ -Array tree_draw_order_calc_nodes_reversed(bNodeTree &ntree); +void node_sort(bNodeTree &ntree); void node_set_cursor(wmWindow &win, SpaceNode &snode, const float2 &cursor); /* DPI scaled coords */ diff --git a/source/blender/editors/space_node/node_relationships.cc b/source/blender/editors/space_node/node_relationships.cc index 51752ab4c7f..27bb1604807 100644 --- a/source/blender/editors/space_node/node_relationships.cc +++ b/source/blender/editors/space_node/node_relationships.cc @@ -435,6 +435,27 @@ static bool socket_can_be_viewed(const bNode &node, const bNodeSocket &socket) SOCK_RGBA); } +static eCustomDataType socket_type_to_custom_data_type(const eNodeSocketDatatype socket_type) +{ + switch (socket_type) { + case SOCK_FLOAT: + return CD_PROP_FLOAT; + case SOCK_INT: + return CD_PROP_INT32; + case SOCK_VECTOR: + return CD_PROP_FLOAT3; + case SOCK_BOOLEAN: + return CD_PROP_BOOL; + case SOCK_RGBA: + return CD_PROP_COLOR; + case SOCK_ROTATION: + return CD_PROP_QUATERNION; + default: + /* Fallback. */ + return CD_AUTO_FROM_NAME; + } +} + /** * Find the socket to link to in a viewer node. */ @@ -453,8 +474,8 @@ static bNodeSocket *node_link_viewer_get_socket(bNodeTree &ntree, return viewer_socket; } NodeGeometryViewer *storage = (NodeGeometryViewer *)viewer_node.storage; - const eCustomDataType data_type = *bke::socket_type_to_custom_data_type( - eNodeSocketDatatype(src_socket.type)); + const eCustomDataType data_type = socket_type_to_custom_data_type( + (eNodeSocketDatatype)src_socket.type); BLI_assert(data_type != CD_AUTO_FROM_NAME); storage->data_type = data_type; viewer_node.typeinfo->updatefunc(&ntree, &viewer_node); @@ -1761,7 +1782,7 @@ static int node_parent_set_exec(bContext *C, wmOperator * /*op*/) } } - tree_draw_order_update(ntree); + node_sort(ntree); WM_event_add_notifier(C, NC_NODE | ND_DISPLAY, nullptr); return OPERATOR_FINISHED; @@ -1848,7 +1869,7 @@ static int node_join_exec(bContext *C, wmOperator * /*op*/) } } - tree_draw_order_update(ntree); + node_sort(ntree); ED_node_tree_propagate_change(C, &bmain, snode.edittree); WM_event_add_notifier(C, NC_NODE | ND_DISPLAY, nullptr); @@ -1876,13 +1897,15 @@ void NODE_OT_join(wmOperatorType *ot) /** \name Attach Operator * \{ */ -static bNode *node_find_frame_to_attach(ARegion ®ion, bNodeTree &ntree, const int2 mouse_xy) +static bNode *node_find_frame_to_attach(ARegion ®ion, + const bNodeTree &ntree, + const int2 mouse_xy) { /* convert mouse coordinates to v2d space */ float2 cursor; UI_view2d_region_to_view(®ion.v2d, mouse_xy.x, mouse_xy.y, &cursor.x, &cursor.y); - for (bNode *frame : tree_draw_order_calc_nodes_reversed(ntree)) { + LISTBASE_FOREACH_BACKWARD (bNode *, frame, &ntree.nodes) { /* skip selected, those are the nodes we want to attach */ if ((frame->type != NODE_FRAME) || (frame->flag & NODE_SELECT)) { continue; @@ -1906,7 +1929,7 @@ static int node_attach_invoke(bContext *C, wmOperator * /*op*/, const wmEvent *e return OPERATOR_FINISHED; } - for (bNode *node : tree_draw_order_calc_nodes_reversed(*snode.edittree)) { + LISTBASE_FOREACH_BACKWARD (bNode *, node, &ntree.nodes) { if (!(node->flag & NODE_SELECT)) { continue; } @@ -1931,7 +1954,7 @@ static int node_attach_invoke(bContext *C, wmOperator * /*op*/, const wmEvent *e nodeAttachNode(&ntree, node, frame); } - tree_draw_order_update(ntree); + node_sort(ntree); WM_event_add_notifier(C, NC_NODE | ND_DISPLAY, nullptr); return OPERATOR_FINISHED; @@ -2006,7 +2029,7 @@ static int node_detach_exec(bContext *C, wmOperator * /*op*/) } } - tree_draw_order_update(ntree); + node_sort(ntree); WM_event_add_notifier(C, NC_NODE | ND_DISPLAY, nullptr); return OPERATOR_FINISHED; @@ -2438,7 +2461,7 @@ static void node_link_insert_offset_ntree(NodeInsertOfsData *iofsd, rctf totr_frame; /* check nodes front to back */ - for (bNode *frame : tree_draw_order_calc_nodes_reversed(*ntree)) { + LISTBASE_FOREACH_BACKWARD (bNode *, frame, &ntree->nodes) { /* skip selected, those are the nodes we want to attach */ if ((frame->type != NODE_FRAME) || (frame->flag & NODE_SELECT)) { continue; diff --git a/source/blender/editors/space_node/node_select.cc b/source/blender/editors/space_node/node_select.cc index 9dc531cda76..96091a9aba5 100644 --- a/source/blender/editors/space_node/node_select.cc +++ b/source/blender/editors/space_node/node_select.cc @@ -8,7 +8,6 @@ #include #include -#include #include "DNA_node_types.h" #include "DNA_windowmanager_types.h" @@ -127,7 +126,8 @@ static bool node_frame_select_isect_mouse(const SpaceNode &snode, static bNode *node_under_mouse_select(const SpaceNode &snode, const float2 mouse) { - for (bNode *node : tree_draw_order_calc_nodes_reversed(*snode.edittree)) { + const bNodeTree &ntree = *snode.edittree; + LISTBASE_FOREACH_BACKWARD (bNode *, node, &ntree.nodes) { switch (node->type) { case NODE_FRAME: { if (node_frame_select_isect_mouse(snode, *node, mouse)) { @@ -148,7 +148,8 @@ static bNode *node_under_mouse_select(const SpaceNode &snode, const float2 mouse static bool node_under_mouse_tweak(const SpaceNode &snode, const float2 &mouse) { - for (bNode *node : tree_draw_order_calc_nodes_reversed(*snode.edittree)) { + const bNodeTree &ntree = *snode.edittree; + LISTBASE_FOREACH_BACKWARD (const bNode *, node, &ntree.nodes) { switch (node->type) { case NODE_REROUTE: { const float2 location = node_to_view(*node, {node->locx, node->locy}); @@ -460,7 +461,7 @@ static int node_select_grouped_exec(bContext *C, wmOperator *op) } if (changed) { - tree_draw_order_update(node_tree); + node_sort(node_tree); WM_event_add_notifier(C, NC_NODE | NA_SELECTED, nullptr); return OPERATOR_FINISHED; } @@ -528,7 +529,7 @@ void node_select_single(bContext &C, bNode &node) ED_node_set_active(bmain, &snode, &node_tree, &node, &active_texture_changed); ED_node_set_active_viewer_key(&snode); - tree_draw_order_update(node_tree); + node_sort(node_tree); if (active_texture_changed && has_workbench_in_texture_color(wm, scene, ob)) { DEG_id_tag_update(&node_tree.id, ID_RECALC_COPY_ON_WRITE); } @@ -686,7 +687,7 @@ static bool node_mouse_select(bContext *C, viewer_path::activate_geometry_node(bmain, snode, *node); } ED_node_set_active_viewer_key(&snode); - tree_draw_order_update(node_tree); + node_sort(node_tree); if ((active_texture_changed && has_workbench_in_texture_color(wm, scene, ob)) || viewer_node_changed) { @@ -817,7 +818,7 @@ static int node_box_select_exec(bContext *C, wmOperator *op) } } - tree_draw_order_update(node_tree); + node_sort(node_tree); WM_event_add_notifier(C, NC_NODE | NA_SELECTED, nullptr); @@ -1127,7 +1128,7 @@ static int node_select_all_exec(bContext *C, wmOperator *op) break; } - tree_draw_order_update(node_tree); + node_sort(node_tree); WM_event_add_notifier(C, NC_NODE | NA_SELECTED, nullptr); return OPERATOR_FINISHED; @@ -1179,7 +1180,7 @@ static int node_select_linked_to_exec(bContext *C, wmOperator * /*op*/) } } - tree_draw_order_update(node_tree); + node_sort(node_tree); WM_event_add_notifier(C, NC_NODE | NA_SELECTED, nullptr); return OPERATOR_FINISHED; @@ -1229,7 +1230,7 @@ static int node_select_linked_from_exec(bContext *C, wmOperator * /*op*/) } } - tree_draw_order_update(node_tree); + node_sort(node_tree); WM_event_add_notifier(C, NC_NODE | NA_SELECTED, nullptr); return OPERATOR_FINISHED; diff --git a/source/blender/editors/space_node/node_shader_preview.cc b/source/blender/editors/space_node/node_shader_preview.cc index 5de59f3d948..506919316ee 100644 --- a/source/blender/editors/space_node/node_shader_preview.cc +++ b/source/blender/editors/space_node/node_shader_preview.cc @@ -689,13 +689,16 @@ static void update_needed_flag(NestedTreePreviews &tree_previews, } } -static void shader_preview_startjob(void *customdata, wmJobWorkerStatus *worker_status) +static void shader_preview_startjob(void *customdata, + bool *stop, + bool *do_update, + float * /*progress*/) { ShaderNodesPreviewJob *job_data = static_cast(customdata); - job_data->stop = &worker_status->stop; - job_data->do_update = &worker_status->do_update; - worker_status->do_update = true; + job_data->stop = stop; + job_data->do_update = do_update; + *do_update = true; bool size_changed = job_data->tree_previews->preview_size != U.node_preview_res; if (size_changed) { job_data->tree_previews->preview_size = U.node_preview_res; @@ -783,7 +786,9 @@ static void ensure_nodetree_previews(const bContext &C, return; } if (tree_previews.rendering) { - WM_jobs_stop(CTX_wm_manager(&C), CTX_wm_space_node(&C), shader_preview_startjob); + WM_jobs_stop(CTX_wm_manager(&C), + CTX_wm_space_node(&C), + reinterpret_cast(shader_preview_startjob)); return; } tree_previews.rendering = true; @@ -835,7 +840,7 @@ static void ensure_nodetree_previews(const bContext &C, void stop_preview_job(wmWindowManager &wm) { - WM_jobs_stop(&wm, nullptr, shader_preview_startjob); + WM_jobs_stop(&wm, nullptr, reinterpret_cast(shader_preview_startjob)); } void free_previews(wmWindowManager &wm, SpaceNode &snode) diff --git a/source/blender/editors/space_node/node_templates.cc b/source/blender/editors/space_node/node_templates.cc index 54f05fbdf14..127da55fcf6 100644 --- a/source/blender/editors/space_node/node_templates.cc +++ b/source/blender/editors/space_node/node_templates.cc @@ -15,6 +15,7 @@ #include "DNA_node_types.h" #include "DNA_screen_types.h" +#include "BLI_array.h" #include "BLI_listbase.h" #include "BLI_string.h" #include "BLI_string_utf8.h" @@ -364,7 +365,7 @@ static Vector ui_node_link_items(NodeLinkArg *arg, using namespace blender::nodes; r_node_decl.emplace(NodeDeclaration()); - blender::nodes::build_node_declaration(*arg->node_type, *r_node_decl, nullptr, nullptr); + blender::nodes::build_node_declaration(*arg->node_type, *r_node_decl); Span socket_decls = (in_out == SOCK_IN) ? r_node_decl->inputs : r_node_decl->outputs; int index = 0; diff --git a/source/blender/editors/space_node/space_node.cc b/source/blender/editors/space_node/space_node.cc index e82c92d2227..323eb5dc141 100644 --- a/source/blender/editors/space_node/space_node.cc +++ b/source/blender/editors/space_node/space_node.cc @@ -452,8 +452,15 @@ static SpaceLink *node_create(const ScrArea * /*area*/, const Scene * /*scene*/) static void node_free(SpaceLink *sl) { SpaceNode *snode = (SpaceNode *)sl; - BLI_freelistN(&snode->treepath); - MEM_delete(snode->runtime); + + LISTBASE_FOREACH_MUTABLE (bNodeTreePath *, path, &snode->treepath) { + MEM_freeN(path); + } + + if (snode->runtime) { + snode->runtime->linkdrag.reset(); + MEM_delete(snode->runtime); + } } /* spacetype; init callback */ @@ -1064,7 +1071,7 @@ static int /*eContextResult*/ node_context(const bContext *C, } if (CTX_data_equals(member, "selected_nodes")) { if (snode->edittree) { - for (bNode *node : snode->edittree->all_nodes()) { + LISTBASE_FOREACH_BACKWARD (bNode *, node, &snode->edittree->nodes) { if (node->flag & NODE_SELECT) { CTX_data_list_add(result, &snode->edittree->id, &RNA_Node, node); } diff --git a/source/blender/editors/space_outliner/outliner_draw.cc b/source/blender/editors/space_outliner/outliner_draw.cc index 458b0d3d831..8aa84a9732f 100644 --- a/source/blender/editors/space_outliner/outliner_draw.cc +++ b/source/blender/editors/space_outliner/outliner_draw.cc @@ -21,7 +21,7 @@ #include "BLI_blenlib.h" #include "BLI_mempool.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_utildefines.h" #include "BLT_translation.h" @@ -2527,14 +2527,14 @@ static BIFIconID tree_element_get_icon_from_id(const ID *id) case ID_LP: { const LightProbe *lp = (LightProbe *)id; switch (lp->type) { - case LIGHTPROBE_TYPE_SPHERE: - return ICON_LIGHTPROBE_SPHERE; - case LIGHTPROBE_TYPE_PLANE: - return ICON_LIGHTPROBE_PLANE; - case LIGHTPROBE_TYPE_VOLUME: - return ICON_LIGHTPROBE_VOLUME; + case LIGHTPROBE_TYPE_CUBE: + return ICON_LIGHTPROBE_CUBEMAP; + case LIGHTPROBE_TYPE_PLANAR: + return ICON_LIGHTPROBE_PLANAR; + case LIGHTPROBE_TYPE_GRID: + return ICON_LIGHTPROBE_GRID; default: - return ICON_LIGHTPROBE_SPHERE; + return ICON_LIGHTPROBE_CUBEMAP; } } case ID_BR: diff --git a/source/blender/editors/space_sequencer/sequencer_drag_drop.cc b/source/blender/editors/space_sequencer/sequencer_drag_drop.cc index 9a866c8579a..b38a6541283 100644 --- a/source/blender/editors/space_sequencer/sequencer_drag_drop.cc +++ b/source/blender/editors/space_sequencer/sequencer_drag_drop.cc @@ -12,7 +12,7 @@ #include "DNA_sound_types.h" #include "BLI_blenlib.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BKE_context.h" #include "BKE_global.h" @@ -518,7 +518,10 @@ struct DropJobData { float scene_fps; }; -static void prefetch_data_fn(void *custom_data, wmJobWorkerStatus * /*worker_status*/) +static void prefetch_data_fn(void *custom_data, + bool * /*stop*/, + bool * /*do_update*/, + float * /*progress*/) { DropJobData *job_data = (DropJobData *)custom_data; @@ -613,21 +616,21 @@ static void audio_prefetch(bContext *C, wmDrag *drag) } } -static void movie_drop_on_enter(wmDropBox *drop, wmDrag * /*drag*/) +static void movie_drop_draw_activate(wmDropBox *drop, wmDrag * /*drag*/) { if (generic_drop_draw_handling(drop)) { return; } } -static void sound_drop_on_enter(wmDropBox *drop, wmDrag * /*drag*/) +static void sound_drop_draw_activate(wmDropBox *drop, wmDrag * /*drag*/) { if (generic_drop_draw_handling(drop)) { return; } } -static void image_drop_on_enter(wmDropBox *drop, wmDrag * /*drag*/) +static void image_drop_draw_activate(wmDropBox *drop, wmDrag * /*drag*/) { if (generic_drop_draw_handling(drop)) { return; @@ -638,7 +641,7 @@ static void image_drop_on_enter(wmDropBox *drop, wmDrag * /*drag*/) coords->channel_len = 1; } -static void sequencer_drop_on_exit(wmDropBox *drop, wmDrag * /*drag*/) +static void sequencer_drop_draw_deactivate(wmDropBox *drop, wmDrag * /*drag*/) { SeqDropCoords *coords = static_cast(drop->draw_data); if (coords) { @@ -666,8 +669,8 @@ static void sequencer_dropboxes_add_to_lb(ListBase *lb) lb, "SEQUENCER_OT_image_strip_add", image_drop_poll, sequencer_drop_copy, nullptr, nullptr); drop->draw_droptip = nop_draw_droptip_fn; drop->draw_in_view = draw_seq_in_view; - drop->on_enter = image_drop_on_enter; - drop->on_exit = sequencer_drop_on_exit; + drop->draw_activate = image_drop_draw_activate; + drop->draw_deactivate = sequencer_drop_draw_deactivate; drop->on_drag_start = audio_prefetch; @@ -675,8 +678,8 @@ static void sequencer_dropboxes_add_to_lb(ListBase *lb) lb, "SEQUENCER_OT_movie_strip_add", movie_drop_poll, sequencer_drop_copy, nullptr, nullptr); drop->draw_droptip = nop_draw_droptip_fn; drop->draw_in_view = draw_seq_in_view; - drop->on_enter = movie_drop_on_enter; - drop->on_exit = sequencer_drop_on_exit; + drop->draw_activate = movie_drop_draw_activate; + drop->draw_deactivate = sequencer_drop_draw_deactivate; drop->on_drag_start = video_prefetch; @@ -684,8 +687,8 @@ static void sequencer_dropboxes_add_to_lb(ListBase *lb) lb, "SEQUENCER_OT_sound_strip_add", sound_drop_poll, sequencer_drop_copy, nullptr, nullptr); drop->draw_droptip = nop_draw_droptip_fn; drop->draw_in_view = draw_seq_in_view; - drop->on_enter = sound_drop_on_enter; - drop->on_exit = sequencer_drop_on_exit; + drop->draw_activate = sound_drop_draw_activate; + drop->draw_deactivate = sequencer_drop_draw_deactivate; } static bool image_drop_preview_poll(bContext * /*C*/, wmDrag *drag, const wmEvent * /*event*/) diff --git a/source/blender/editors/space_sequencer/sequencer_preview.cc b/source/blender/editors/space_sequencer/sequencer_preview.cc index 468b5c16069..def9f516020 100644 --- a/source/blender/editors/space_sequencer/sequencer_preview.cc +++ b/source/blender/editors/space_sequencer/sequencer_preview.cc @@ -114,7 +114,7 @@ static void push_preview_job_audio_task(TaskPool *__restrict task_pool, } /* Only this runs inside thread. */ -static void preview_startjob(void *data, wmJobWorkerStatus *worker_status) +static void preview_startjob(void *data, bool *stop, bool *do_update, float *progress) { TaskPool *task_pool = BLI_task_pool_create(nullptr, TASK_PRIORITY_LOW); PreviewJob *pj = static_cast(data); @@ -128,9 +128,9 @@ static void preview_startjob(void *data, wmJobWorkerStatus *worker_status) float current_progress = (pj->total > 0) ? float(pj->processed) / float(pj->total) : 1.0f; - if (current_progress != worker_status->progress) { - worker_status->progress = current_progress; - worker_status->do_update = true; + if (current_progress != *progress) { + *progress = current_progress; + *do_update = true; } BLI_condition_wait(&pj->preview_suspend_cond, pj->mutex); @@ -142,7 +142,7 @@ static void preview_startjob(void *data, wmJobWorkerStatus *worker_status) break; } - if (worker_status->stop || G.is_break) { + if (*stop || G.is_break) { BLI_task_pool_cancel(task_pool); LISTBASE_FOREACH (PreviewJobAudio *, previewjb, &pj->previews) { @@ -159,7 +159,7 @@ static void preview_startjob(void *data, wmJobWorkerStatus *worker_status) } LISTBASE_FOREACH_MUTABLE (PreviewJobAudio *, previewjb, &pj->previews) { - push_preview_job_audio_task(task_pool, pj, previewjb, &worker_status->stop); + push_preview_job_audio_task(task_pool, pj, previewjb, stop); BLI_remlink(&pj->previews, previewjb); } diff --git a/source/blender/editors/space_sequencer/sequencer_proxy.cc b/source/blender/editors/space_sequencer/sequencer_proxy.cc index 5f92e370d0f..13253e38f26 100644 --- a/source/blender/editors/space_sequencer/sequencer_proxy.cc +++ b/source/blender/editors/space_sequencer/sequencer_proxy.cc @@ -116,13 +116,14 @@ static int sequencer_rebuild_proxy_exec(bContext *C, wmOperator * /*o*/) LISTBASE_FOREACH (Sequence *, seq, SEQ_active_seqbase_get(ed)) { if (seq->flag & SELECT) { ListBase queue = {nullptr, nullptr}; + bool stop = false, do_update; + float progress; SEQ_proxy_rebuild_context(bmain, depsgraph, scene, seq, file_list, &queue, false); - wmJobWorkerStatus worker_status = {}; LISTBASE_FOREACH (LinkData *, link, &queue) { SeqIndexBuildContext *context = static_cast(link->data); - SEQ_proxy_rebuild(context, &worker_status); + SEQ_proxy_rebuild(context, &stop, &do_update, &progress); SEQ_proxy_rebuild_finish(context, false); } SEQ_relations_free_imbuf(scene, &ed->seqbase, false); diff --git a/source/blender/editors/space_sequencer/sequencer_thumbnails.cc b/source/blender/editors/space_sequencer/sequencer_thumbnails.cc index 3bb1455cae3..61192e8ac15 100644 --- a/source/blender/editors/space_sequencer/sequencer_thumbnails.cc +++ b/source/blender/editors/space_sequencer/sequencer_thumbnails.cc @@ -133,7 +133,7 @@ static void seq_get_thumb_image_dimensions(Sequence *seq, } } -static void thumbnail_start_job(void *data, wmJobWorkerStatus *worker_status) +static void thumbnail_start_job(void *data, bool *stop, bool * /*do_update*/, float * /*progress*/) { ThumbnailDrawJob *tj = static_cast(data); const Scene *scene = tj->scene; @@ -143,7 +143,7 @@ static void thumbnail_start_job(void *data, wmJobWorkerStatus *worker_status) /* First pass: render visible images. */ BLI_ghashIterator_init(&gh_iter, tj->sequences_ghash); - while (!BLI_ghashIterator_done(&gh_iter) & !worker_status->stop) { + while (!BLI_ghashIterator_done(&gh_iter) & !*stop) { Sequence *seq_orig = static_cast(BLI_ghashIterator_getKey(&gh_iter)); ThumbDataItem *val = static_cast( BLI_ghash_lookup(tj->sequences_ghash, seq_orig)); @@ -152,7 +152,7 @@ static void thumbnail_start_job(void *data, wmJobWorkerStatus *worker_status) seq_get_thumb_image_dimensions( val->seq_dupli, tj->pixelx, tj->pixely, &frame_step, tj->thumb_height, nullptr, nullptr); SEQ_render_thumbnails( - &tj->context, val->seq_dupli, seq_orig, frame_step, tj->view_area, &worker_status->stop); + &tj->context, val->seq_dupli, seq_orig, frame_step, tj->view_area, stop); SEQ_relations_sequence_free_anim(val->seq_dupli); } BLI_ghashIterator_step(&gh_iter); @@ -160,7 +160,7 @@ static void thumbnail_start_job(void *data, wmJobWorkerStatus *worker_status) /* Second pass: render "guaranteed" set of images. */ BLI_ghashIterator_init(&gh_iter, tj->sequences_ghash); - while (!BLI_ghashIterator_done(&gh_iter) & !worker_status->stop) { + while (!BLI_ghashIterator_done(&gh_iter) & !*stop) { Sequence *seq_orig = static_cast(BLI_ghashIterator_getKey(&gh_iter)); ThumbDataItem *val = static_cast( BLI_ghash_lookup(tj->sequences_ghash, seq_orig)); @@ -168,8 +168,7 @@ static void thumbnail_start_job(void *data, wmJobWorkerStatus *worker_status) if (check_seq_need_thumbnails(scene, seq_orig, tj->view_area)) { seq_get_thumb_image_dimensions( val->seq_dupli, tj->pixelx, tj->pixely, &frame_step, tj->thumb_height, nullptr, nullptr); - SEQ_render_thumbnails_base_set( - &tj->context, val->seq_dupli, seq_orig, tj->view_area, &worker_status->stop); + SEQ_render_thumbnails_base_set(&tj->context, val->seq_dupli, seq_orig, tj->view_area, stop); SEQ_relations_sequence_free_anim(val->seq_dupli); } BLI_ghashIterator_step(&gh_iter); @@ -305,7 +304,7 @@ static void sequencer_thumbnail_start_job_if_necessary( if (v2d->cur.xmax != sseq->runtime.last_thumbnail_area.xmax || v2d->cur.ymax != sseq->runtime.last_thumbnail_area.ymax) { - WM_jobs_stop(CTX_wm_manager(C), nullptr, thumbnail_start_job); + WM_jobs_stop(CTX_wm_manager(C), nullptr, reinterpret_cast(thumbnail_start_job)); } sequencer_thumbnail_init_job(C, v2d, ed, thumb_height); diff --git a/source/blender/editors/space_sequencer/sequencer_timeline_draw.cc b/source/blender/editors/space_sequencer/sequencer_timeline_draw.cc index 0919a613910..dd65f3b1508 100644 --- a/source/blender/editors/space_sequencer/sequencer_timeline_draw.cc +++ b/source/blender/editors/space_sequencer/sequencer_timeline_draw.cc @@ -10,7 +10,7 @@ #include #include "BLI_blenlib.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_threads.h" #include "BLI_utildefines.h" diff --git a/source/blender/editors/space_spreadsheet/space_spreadsheet.cc b/source/blender/editors/space_spreadsheet/space_spreadsheet.cc index 83dbd8f9727..a688f7dd387 100644 --- a/source/blender/editors/space_spreadsheet/space_spreadsheet.cc +++ b/source/blender/editors/space_spreadsheet/space_spreadsheet.cc @@ -311,8 +311,7 @@ Object *spreadsheet_get_object_eval(const SpaceSpreadsheet *sspreadsheet, OB_VOLUME, OB_CURVES_LEGACY, OB_FONT, - OB_CURVES, - OB_GREASE_PENCIL)) + OB_CURVES)) { return nullptr; } @@ -513,10 +512,6 @@ static void spreadsheet_main_region_listener(const wmRegionListenerParams *param ED_region_tag_redraw(region); break; } - case NC_GPENCIL: { - ED_region_tag_redraw(region); - break; - } case NC_VIEWER_PATH: { if (sspreadsheet->object_eval_state == SPREADSHEET_OBJECT_EVAL_STATE_VIEWER_NODE) { ED_region_tag_redraw(region); @@ -570,10 +565,6 @@ static void spreadsheet_header_region_listener(const wmRegionListenerParams *par ED_region_tag_redraw(region); break; } - case NC_GPENCIL: { - ED_region_tag_redraw(region); - break; - } case NC_VIEWER_PATH: { if (sspreadsheet->object_eval_state == SPREADSHEET_OBJECT_EVAL_STATE_VIEWER_NODE) { ED_region_tag_redraw(region); diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc index 67b094ebbc8..406cb8f6841 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc +++ b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc @@ -11,9 +11,7 @@ #include "BKE_curves.hh" #include "BKE_editmesh.h" #include "BKE_geometry_fields.hh" -#include "BKE_geometry_set.hh" #include "BKE_global.h" -#include "BKE_grease_pencil.hh" #include "BKE_instances.hh" #include "BKE_lib_id.h" #include "BKE_mesh.hh" @@ -175,11 +173,12 @@ static std::unique_ptr build_mesh_debug_columns(const Mesh &mesh, void GeometryDataSource::foreach_default_column_ids( FunctionRef fn) const { - std::optional attributes = this->get_component_attributes(); - if (!attributes.has_value()) { + if (!component_->attributes().has_value()) { return; } - if (attributes->domain_size(domain_) == 0) { + const bke::AttributeAccessor attributes = *component_->attributes(); + + if (attributes.domain_size(domain_) == 0) { return; } @@ -187,13 +186,9 @@ void GeometryDataSource::foreach_default_column_ids( fn({(char *)"Name"}, false); } - if (component_->type() == bke::GeometryComponent::Type::GreasePencil) { - fn({(char *)"Name"}, false); - } - extra_columns_.foreach_default_column_ids(fn); - attributes->for_all( + attributes.for_all( [&](const bke::AttributeIDRef &attribute_id, const bke::AttributeMetaData &meta_data) { if (meta_data.domain != domain_) { return true; @@ -226,11 +221,11 @@ void GeometryDataSource::foreach_default_column_ids( std::unique_ptr GeometryDataSource::get_column_values( const SpreadsheetColumnID &column_id) const { - std::optional attributes = this->get_component_attributes(); - if (!attributes.has_value()) { + if (!component_->attributes().has_value()) { return {}; } - const int domain_num = attributes->domain_size(domain_); + const bke::AttributeAccessor attributes = *component_->attributes(); + const int domain_num = attributes.domain_size(domain_); if (domain_num == 0) { return {}; } @@ -271,19 +266,6 @@ std::unique_ptr GeometryDataSource::get_column_values( } } } - else if (component_->type() == bke::GeometryComponent::Type::GreasePencil) { - if (const GreasePencil *grease_pencil = - static_cast(*component_).get()) - { - if (domain_ == ATTR_DOMAIN_LAYER && STREQ(column_id.name, "Name")) { - const Span layers = grease_pencil->layers(); - return std::make_unique( - column_id.name, VArray::ForFunc(domain_num, [layers](int64_t index) { - return std::string(layers[index]->name()); - })); - } - } - } else if (G.debug_value == 4001 && component_->type() == bke::GeometryComponent::Type::Mesh) { const bke::MeshComponent &component = static_cast(*component_); if (const Mesh *mesh = component.get()) { @@ -295,7 +277,7 @@ std::unique_ptr GeometryDataSource::get_column_values( } } - bke::GAttributeReader attribute = attributes->lookup(column_id.name); + bke::GAttributeReader attribute = attributes.lookup(column_id.name); if (!attribute) { return {}; } @@ -314,11 +296,11 @@ std::unique_ptr GeometryDataSource::get_column_values( int GeometryDataSource::tot_rows() const { - std::optional attributes = this->get_component_attributes(); - if (!attributes.has_value()) { + if (!component_->attributes().has_value()) { return {}; } - return attributes->domain_size(domain_); + const bke::AttributeAccessor attributes = *component_->attributes(); + return attributes.domain_size(domain_); } bool GeometryDataSource::has_selection_filter() const @@ -439,28 +421,6 @@ IndexMask GeometryDataSource::apply_selection_filter(IndexMaskMemory &memory) co } } -std::optional GeometryDataSource::get_component_attributes() const -{ - if (component_->type() != bke::GeometryComponent::Type::GreasePencil) { - return component_->attributes(); - } - const GreasePencil *grease_pencil = geometry_set_.get_grease_pencil(); - if (!grease_pencil) { - return {}; - } - if (domain_ == ATTR_DOMAIN_LAYER) { - return grease_pencil->attributes(); - } - if (layer_index_ >= 0 && layer_index_ < grease_pencil->layers().size()) { - if (const bke::greasepencil::Drawing *drawing = - bke::greasepencil::get_eval_grease_pencil_layer_drawing(*grease_pencil, layer_index_)) - { - return drawing->strokes().attributes(); - } - } - return {}; -} - void VolumeDataSource::foreach_default_column_ids( FunctionRef fn) const { @@ -564,11 +524,6 @@ bke::GeometrySet spreadsheet_get_display_geometry_set(const SpaceSpreadsheet *ss geometry_set.replace_curves(&const_cast(curves_id), bke::GeometryOwnershipType::ReadOnly); } - else if (object_orig->type == OB_GREASE_PENCIL) { - const GreasePencil &grease_pencil = *static_cast(object_orig->data); - geometry_set.replace_grease_pencil(&const_cast(grease_pencil), - bke::GeometryOwnershipType::ReadOnly); - } } else { if (BLI_listbase_is_single(&sspreadsheet->viewer_path.path)) { @@ -600,7 +555,6 @@ std::unique_ptr data_source_from_geometry(const bContext *C, Object SpaceSpreadsheet *sspreadsheet = CTX_wm_space_spreadsheet(C); const eAttrDomain domain = (eAttrDomain)sspreadsheet->attribute_domain; const auto component_type = bke::GeometryComponent::Type(sspreadsheet->geometry_component_type); - const int active_layer_index = sspreadsheet->active_layer_index; bke::GeometrySet geometry_set = spreadsheet_get_display_geometry_set(sspreadsheet, object_eval); if (!geometry_set.has(component_type)) { return {}; @@ -610,7 +564,7 @@ std::unique_ptr data_source_from_geometry(const bContext *C, Object return std::make_unique(std::move(geometry_set)); } return std::make_unique( - object_eval, std::move(geometry_set), component_type, domain, active_layer_index); + object_eval, std::move(geometry_set), component_type, domain); } } // namespace blender::ed::spreadsheet diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.hh b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.hh index dfc7f5429eb..53f18a679e0 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.hh +++ b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.hh @@ -43,8 +43,6 @@ class GeometryDataSource : public DataSource { const bke::GeometrySet geometry_set_; const bke::GeometryComponent *component_; eAttrDomain domain_; - /* Layer index for grease pencil component. */ - int layer_index_; ExtraColumns extra_columns_; /* Some data is computed on the fly only when it is requested. Computing it does not change the @@ -58,13 +56,11 @@ class GeometryDataSource : public DataSource { bke::GeometrySet geometry_set, const bke::GeometryComponent::Type component_type, const eAttrDomain domain, - const int layer_index = -1, ExtraColumns extra_columns = {}) : object_eval_(object_eval), geometry_set_(std::move(geometry_set)), component_(geometry_set_.get_component(component_type)), domain_(domain), - layer_index_(layer_index), extra_columns_(std::move(extra_columns)) { } @@ -84,9 +80,6 @@ class GeometryDataSource : public DataSource { const SpreadsheetColumnID &column_id) const override; int tot_rows() const override; - - private: - std::optional get_component_attributes() const; }; class VolumeDataSource : public DataSource { diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_dataset_draw.cc b/source/blender/editors/space_spreadsheet/spreadsheet_dataset_draw.cc index 95925d93add..e9a4c36a63b 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_dataset_draw.cc +++ b/source/blender/editors/space_spreadsheet/spreadsheet_dataset_draw.cc @@ -8,8 +8,6 @@ #include "DNA_windowmanager_types.h" #include "BKE_context.h" -#include "BKE_curves.hh" -#include "BKE_grease_pencil.hh" #include "BKE_volume.h" #include "RNA_access.hh" @@ -32,7 +30,6 @@ class GeometryDataSetTreeView; class GeometryDataSetTreeViewItem : public ui::AbstractTreeViewItem { bke::GeometryComponent::Type component_type_; - std::optional layer_index_; std::optional domain_; BIFIconID icon_; @@ -41,15 +38,6 @@ class GeometryDataSetTreeViewItem : public ui::AbstractTreeViewItem { StringRef label, BIFIconID icon); GeometryDataSetTreeViewItem(bke::GeometryComponent::Type component_type, - int layer_index, - StringRef label, - BIFIconID icon); - GeometryDataSetTreeViewItem(bke::GeometryComponent::Type component_type, - eAttrDomain domain, - StringRef label, - BIFIconID icon); - GeometryDataSetTreeViewItem(bke::GeometryComponent::Type component_type, - int layer_index, eAttrDomain domain, StringRef label, BIFIconID icon); @@ -82,47 +70,6 @@ class GeometryDataSetTreeView : public ui::AbstractTreeView { { } - void build_grease_pencil() - { - if (!U.experimental.use_grease_pencil_version3) { - return; - } - - GeometryDataSetTreeViewItem &grease_pencil = this->add_tree_item( - bke::GeometryComponent::Type::GreasePencil, - IFACE_("Grease Pencil"), - ICON_OUTLINER_DATA_GREASEPENCIL); - GeometryDataSetTreeViewItem &grease_pencil_layers = - grease_pencil.add_tree_item( - bke::GeometryComponent::Type::GreasePencil, - ATTR_DOMAIN_LAYER, - IFACE_("Layer"), - ICON_OUTLINER_DATA_GP_LAYER); - - if (!geometry_set_.has_grease_pencil()) { - return; - } - - const Span layers = - geometry_set_.get_grease_pencil()->layers(); - for (const int layer_i : layers.index_range()) { - const bke::greasepencil::Layer *layer = layers[layer_i]; - GeometryDataSetTreeViewItem &curve = - grease_pencil_layers.add_tree_item( - bke::GeometryComponent::Type::GreasePencil, layer_i, layer->name(), ICON_CURVE_DATA); - curve.add_tree_item(bke::GeometryComponent::Type::GreasePencil, - layer_i, - ATTR_DOMAIN_POINT, - IFACE_("Control Point"), - ICON_CURVE_BEZCIRCLE); - curve.add_tree_item(bke::GeometryComponent::Type::GreasePencil, - layer_i, - ATTR_DOMAIN_CURVE, - IFACE_("Spline"), - ICON_CURVE_PATH); - } - } - void build_tree() override { GeometryDataSetTreeViewItem &mesh = this->add_tree_item( @@ -147,8 +94,6 @@ class GeometryDataSetTreeView : public ui::AbstractTreeView { curve.add_tree_item( bke::GeometryComponent::Type::Curve, ATTR_DOMAIN_CURVE, IFACE_("Spline"), ICON_CURVE_PATH); - this->build_grease_pencil(); - GeometryDataSetTreeViewItem &pointcloud = this->add_tree_item( bke::GeometryComponent::Type::PointCloud, IFACE_("Point Cloud"), ICON_POINTCLOUD_DATA); pointcloud.add_tree_item(bke::GeometryComponent::Type::PointCloud, @@ -173,12 +118,6 @@ GeometryDataSetTreeViewItem::GeometryDataSetTreeViewItem( label_ = label; this->set_collapsed(false); } -GeometryDataSetTreeViewItem::GeometryDataSetTreeViewItem( - bke::GeometryComponent::Type component_type, int layer_index, StringRef label, BIFIconID icon) - : component_type_(component_type), layer_index_(layer_index), icon_(icon) -{ - label_ = label; -} GeometryDataSetTreeViewItem::GeometryDataSetTreeViewItem( bke::GeometryComponent::Type component_type, eAttrDomain domain, @@ -188,16 +127,6 @@ GeometryDataSetTreeViewItem::GeometryDataSetTreeViewItem( { label_ = label; } -GeometryDataSetTreeViewItem::GeometryDataSetTreeViewItem( - bke::GeometryComponent::Type component_type, - int layer_index, - eAttrDomain domain, - StringRef label, - BIFIconID icon) - : component_type_(component_type), layer_index_(layer_index), domain_(domain), icon_(icon) -{ - label_ = label; -} void GeometryDataSetTreeViewItem::on_activate(bContext &C) { @@ -207,9 +136,6 @@ void GeometryDataSetTreeViewItem::on_activate(bContext &C) if (domain_) { tree_view.sspreadsheet_.attribute_domain = *domain_; } - if (layer_index_) { - tree_view.sspreadsheet_.active_layer_index = *layer_index_; - } PointerRNA ptr = RNA_pointer_create(&tree_view.screen_.id, &RNA_SpaceSpreadsheet, &sspreadsheet); RNA_property_update(&C, &ptr, RNA_struct_find_property(&ptr, "attribute_domain")); RNA_property_update(&C, &ptr, RNA_struct_find_property(&ptr, "geometry_component_type")); @@ -241,19 +167,13 @@ std::optional GeometryDataSetTreeViewItem::should_be_active() const return false; } - if (!layer_index_) { - return sspreadsheet.geometry_component_type == uint8_t(component_type_) && - sspreadsheet.attribute_domain == *domain_; - } - return sspreadsheet.geometry_component_type == uint8_t(component_type_) && - sspreadsheet.attribute_domain == *domain_ && - sspreadsheet.active_layer_index == *layer_index_; + sspreadsheet.attribute_domain == *domain_; } bool GeometryDataSetTreeViewItem::supports_collapsing() const { - return true; + return false; } GeometryDataSetTreeView &GeometryDataSetTreeViewItem::get_tree() const @@ -278,15 +198,6 @@ std::optional GeometryDataSetTreeViewItem::count() const return std::nullopt; } - if (component_type_ == bke::GeometryComponent::Type::GreasePencil && layer_index_) { - if (const bke::greasepencil::Drawing *drawing = - bke::greasepencil::get_eval_grease_pencil_layer_drawing(*geometry.get_grease_pencil(), - *layer_index_)) - { - return drawing->strokes().attributes().domain_size(*domain_); - } - } - if (const bke::GeometryComponent *component = geometry.get_component(component_type_)) { return component->attribute_domain_size(*domain_); } diff --git a/source/blender/editors/space_text/text_format.cc b/source/blender/editors/space_text/text_format.cc index a3e3bafcd0b..d214520dc29 100644 --- a/source/blender/editors/space_text/text_format.cc +++ b/source/blender/editors/space_text/text_format.cc @@ -11,7 +11,7 @@ #include "MEM_guardedalloc.h" #include "BLI_blenlib.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "DNA_space_types.h" #include "DNA_text_types.h" diff --git a/source/blender/editors/space_view3d/CMakeLists.txt b/source/blender/editors/space_view3d/CMakeLists.txt index 5e159c37c46..3a84d6e6b71 100644 --- a/source/blender/editors/space_view3d/CMakeLists.txt +++ b/source/blender/editors/space_view3d/CMakeLists.txt @@ -32,7 +32,6 @@ set(SRC space_view3d.cc view3d_buttons.cc view3d_camera_control.cc - view3d_context.cc view3d_cursor_snap.cc view3d_draw.cc view3d_edit.cc diff --git a/source/blender/editors/space_view3d/space_view3d.cc b/source/blender/editors/space_view3d/space_view3d.cc index 36793c8c2eb..de314ef1c78 100644 --- a/source/blender/editors/space_view3d/space_view3d.cc +++ b/source/blender/editors/space_view3d/space_view3d.cc @@ -95,6 +95,54 @@ /* ******************** manage regions ********************* */ +RegionView3D *ED_view3d_context_rv3d(bContext *C) +{ + RegionView3D *rv3d = CTX_wm_region_view3d(C); + + if (rv3d == nullptr) { + ScrArea *area = CTX_wm_area(C); + if (area && area->spacetype == SPACE_VIEW3D) { + ARegion *region = BKE_area_find_region_active_win(area); + if (region) { + rv3d = static_cast(region->regiondata); + } + } + } + return rv3d; +} + +bool ED_view3d_context_user_region(bContext *C, View3D **r_v3d, ARegion **r_region) +{ + ScrArea *area = CTX_wm_area(C); + + *r_v3d = nullptr; + *r_region = nullptr; + + if (area && area->spacetype == SPACE_VIEW3D) { + ARegion *region = CTX_wm_region(C); + View3D *v3d = (View3D *)area->spacedata.first; + + if (region) { + RegionView3D *rv3d; + if ((region->regiontype == RGN_TYPE_WINDOW) && + (rv3d = static_cast(region->regiondata)) && + (rv3d->viewlock & RV3D_LOCK_ROTATION) == 0) + { + *r_v3d = v3d; + *r_region = region; + return true; + } + + if (ED_view3d_area_user_region(area, v3d, r_region)) { + *r_v3d = v3d; + return true; + } + } + } + + return false; +} + bool ED_view3d_area_user_region(const ScrArea *area, const View3D *v3d, ARegion **r_region) { RegionView3D *rv3d = nullptr; @@ -186,7 +234,7 @@ void ED_view3d_stop_render_preview(wmWindowManager *wm, ARegion *region) } /* A bit overkill but this make sure the viewport is reset completely. (fclem) */ - WM_draw_region_free(region); + WM_draw_region_free(region, false); } void ED_view3d_shade_update(Main *bmain, View3D *v3d, ScrArea *area) @@ -491,7 +539,7 @@ static bool view3d_drop_id_in_main_region_poll(bContext *C, return WM_drag_is_ID_type(drag, id_type); } -static void view3d_ob_drop_on_enter(wmDropBox *drop, wmDrag *drag) +static void view3d_ob_drop_draw_activate(wmDropBox *drop, wmDrag *drag) { V3DSnapCursorState *state = static_cast(drop->draw_data); if (state) { @@ -528,7 +576,7 @@ static void view3d_ob_drop_on_enter(wmDropBox *drop, wmDrag *drag) } } -static void view3d_ob_drop_on_exit(wmDropBox *drop, wmDrag * /*drag*/) +static void view3d_ob_drop_draw_deactivate(wmDropBox *drop, wmDrag * /*drag*/) { V3DSnapCursorState *state = static_cast(drop->draw_data); if (state) { @@ -749,9 +797,10 @@ static void view3d_ob_drop_matrix_from_snap(V3DSnapCursorState *snap_state, mat4_to_size(scale, ob->object_to_world); rescale_m4(obmat_final, scale); - if (const std::optional bb = BKE_object_boundbox_get(ob)) { + const BoundBox *bb = BKE_object_boundbox_get(ob); + if (bb) { float offset[3]; - BKE_boundbox_calc_center_aabb(&bb.value(), offset); + BKE_boundbox_calc_center_aabb(bb, offset); offset[2] = bb->vec[0][2]; mul_mat3_m4_v3(obmat_final, offset); sub_v3_v3(obmat_final[3], offset); @@ -934,8 +983,8 @@ static void view3d_dropboxes() nullptr); drop->draw_droptip = WM_drag_draw_item_name_fn; - drop->on_enter = view3d_ob_drop_on_enter; - drop->on_exit = view3d_ob_drop_on_exit; + drop->draw_activate = view3d_ob_drop_draw_activate; + drop->draw_deactivate = view3d_ob_drop_draw_deactivate; drop = WM_dropbox_add(lb, "OBJECT_OT_transform_to_mouse", @@ -945,8 +994,8 @@ static void view3d_dropboxes() nullptr); drop->draw_droptip = WM_drag_draw_item_name_fn; - drop->on_enter = view3d_ob_drop_on_enter; - drop->on_exit = view3d_ob_drop_on_exit; + drop->draw_activate = view3d_ob_drop_draw_activate; + drop->draw_deactivate = view3d_ob_drop_draw_deactivate; WM_dropbox_add(lb, "OBJECT_OT_collection_external_asset_drop", @@ -1993,6 +2042,65 @@ static void space_view3d_refresh(const bContext *C, ScrArea *area) MEM_SAFE_FREE(v3d->runtime.local_stats); } +const char *view3d_context_dir[] = { + "active_object", + "selected_ids", + nullptr, +}; + +static int view3d_context(const bContext *C, const char *member, bContextDataResult *result) +{ + /* fallback to the scene layer, + * allows duplicate and other object operators to run outside the 3d view */ + + if (CTX_data_dir(member)) { + CTX_data_dir_set(result, view3d_context_dir); + return CTX_RESULT_OK; + } + if (CTX_data_equals(member, "active_object")) { + /* In most cases the active object is the `view_layer->basact->object`. + * For the 3D view however it can be nullptr when hidden. + * + * This is ignored in the case the object is in any mode (besides object-mode), + * since the object's mode impacts the current tool, cursor, gizmos etc. + * If we didn't have this exception, changing visibility would need to perform + * many of the same updates as changing the objects mode. + * + * Further, there are multiple ways to hide objects - by collection, by object type, etc. + * it's simplest if all these methods behave consistently - respecting the object-mode + * without showing the object. + * + * See #85532 for alternatives that were considered. */ + const Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + BKE_view_layer_synced_ensure(scene, view_layer); + Base *base = BKE_view_layer_active_base_get(view_layer); + if (base) { + Object *ob = base->object; + /* if hidden but in edit mode, we still display, can happen with animation */ + if ((base->flag & BASE_ENABLED_AND_MAYBE_VISIBLE_IN_VIEWPORT) != 0 || + (ob->mode != OB_MODE_OBJECT)) { + CTX_data_id_pointer_set(result, &ob->id); + } + } + + return CTX_RESULT_OK; + } + if (CTX_data_equals(member, "selected_ids")) { + ListBase selected_objects; + CTX_data_selected_objects(C, &selected_objects); + LISTBASE_FOREACH (CollectionPointerLink *, object_ptr_link, &selected_objects) { + ID *selected_id = object_ptr_link->ptr.owner_id; + CTX_data_id_list_add(result, selected_id); + } + BLI_freelistN(&selected_objects); + CTX_data_type_set(result, CTX_DATA_TYPE_COLLECTION); + return CTX_RESULT_OK; + } + + return CTX_RESULT_MEMBER_NOT_FOUND; +} + static void view3d_id_remap_v3d_ob_centers(View3D *v3d, const IDRemapper *mappings) { if (BKE_id_remapper_apply(mappings, (ID **)&v3d->ob_center, ID_REMAP_APPLY_DEFAULT) == diff --git a/source/blender/editors/space_view3d/view3d_context.cc b/source/blender/editors/space_view3d/view3d_context.cc deleted file mode 100644 index 8a6baac2b55..00000000000 --- a/source/blender/editors/space_view3d/view3d_context.cc +++ /dev/null @@ -1,142 +0,0 @@ -/* SPDX-FileCopyrightText: 2008 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -/** \file - * \ingroup spview3d - */ - -#include "DNA_object_types.h" -#include "DNA_screen_types.h" -#include "DNA_space_types.h" - -#include "BKE_context.h" -#include "BKE_layer.h" -#include "BKE_screen.hh" - -#include "BLI_listbase.h" - -#include "ED_view3d.hh" - -#include "RNA_types.hh" - -#include "view3d_intern.h" - -/* -------------------------------------------------------------------- */ -/** \name View3D Context Callback - * \{ */ - -const char *view3d_context_dir[] = { - "active_object", - "selected_ids", - nullptr, -}; - -int view3d_context(const bContext *C, const char *member, bContextDataResult *result) -{ - /* fallback to the scene layer, - * allows duplicate and other object operators to run outside the 3d view */ - - if (CTX_data_dir(member)) { - CTX_data_dir_set(result, view3d_context_dir); - return CTX_RESULT_OK; - } - if (CTX_data_equals(member, "active_object")) { - /* In most cases the active object is the `view_layer->basact->object`. - * For the 3D view however it can be nullptr when hidden. - * - * This is ignored in the case the object is in any mode (besides object-mode), - * since the object's mode impacts the current tool, cursor, gizmos etc. - * If we didn't have this exception, changing visibility would need to perform - * many of the same updates as changing the objects mode. - * - * Further, there are multiple ways to hide objects - by collection, by object type, etc. - * it's simplest if all these methods behave consistently - respecting the object-mode - * without showing the object. - * - * See #85532 for alternatives that were considered. */ - const Scene *scene = CTX_data_scene(C); - ViewLayer *view_layer = CTX_data_view_layer(C); - BKE_view_layer_synced_ensure(scene, view_layer); - Base *base = BKE_view_layer_active_base_get(view_layer); - if (base) { - Object *ob = base->object; - /* if hidden but in edit mode, we still display, can happen with animation */ - if ((base->flag & BASE_ENABLED_AND_MAYBE_VISIBLE_IN_VIEWPORT) != 0 || - (ob->mode != OB_MODE_OBJECT)) { - CTX_data_id_pointer_set(result, &ob->id); - } - } - - return CTX_RESULT_OK; - } - if (CTX_data_equals(member, "selected_ids")) { - ListBase selected_objects; - CTX_data_selected_objects(C, &selected_objects); - LISTBASE_FOREACH (CollectionPointerLink *, object_ptr_link, &selected_objects) { - ID *selected_id = object_ptr_link->ptr.owner_id; - CTX_data_id_list_add(result, selected_id); - } - BLI_freelistN(&selected_objects); - CTX_data_type_set(result, CTX_DATA_TYPE_COLLECTION); - return CTX_RESULT_OK; - } - - return CTX_RESULT_MEMBER_NOT_FOUND; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name View3D Context Queries - * \{ */ - -RegionView3D *ED_view3d_context_rv3d(bContext *C) -{ - RegionView3D *rv3d = CTX_wm_region_view3d(C); - - if (rv3d == nullptr) { - ScrArea *area = CTX_wm_area(C); - if (area && area->spacetype == SPACE_VIEW3D) { - ARegion *region = BKE_area_find_region_active_win(area); - if (region) { - rv3d = static_cast(region->regiondata); - } - } - } - return rv3d; -} - -bool ED_view3d_context_user_region(bContext *C, View3D **r_v3d, ARegion **r_region) -{ - ScrArea *area = CTX_wm_area(C); - - *r_v3d = nullptr; - *r_region = nullptr; - - if (area && area->spacetype == SPACE_VIEW3D) { - ARegion *region = CTX_wm_region(C); - View3D *v3d = (View3D *)area->spacedata.first; - - if (region) { - RegionView3D *rv3d; - if ((region->regiontype == RGN_TYPE_WINDOW) && - (rv3d = static_cast(region->regiondata)) && - (rv3d->viewlock & RV3D_LOCK_ROTATION) == 0) - { - *r_v3d = v3d; - *r_region = region; - return true; - } - - if (ED_view3d_area_user_region(area, v3d, r_region)) { - *r_v3d = v3d; - return true; - } - } - } - - return false; -} - -/** \} */ diff --git a/source/blender/editors/space_view3d/view3d_draw.cc b/source/blender/editors/space_view3d/view3d_draw.cc index a379a113ab7..a3e7ea882b3 100644 --- a/source/blender/editors/space_view3d/view3d_draw.cc +++ b/source/blender/editors/space_view3d/view3d_draw.cc @@ -15,7 +15,7 @@ #include "BLI_math_vector.hh" #include "BLI_rect.h" #include "BLI_string.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_threads.h" #include "BKE_armature.h" @@ -48,7 +48,7 @@ #include "DNA_windowmanager_types.h" #include "DRW_engine.h" -#include "DRW_select_buffer.hh" +#include "DRW_select_buffer.h" #include "ED_gpencil_legacy.hh" #include "ED_info.hh" @@ -2234,7 +2234,7 @@ static void validate_object_select_id(Depsgraph *depsgraph, if (obact_eval && ((obact_eval->base_flag & BASE_ENABLED_AND_MAYBE_VISIBLE_IN_VIEWPORT) != 0)) { BKE_view_layer_synced_ensure(scene, view_layer); Base *base = BKE_view_layer_base_find(view_layer, obact); - DRW_select_buffer_context_create(depsgraph, &base, 1, -1); + DRW_select_buffer_context_create(&base, 1, -1); } v3d->runtime.flag |= V3D_RUNTIME_DEPTHBUF_OVERRIDDEN; diff --git a/source/blender/editors/space_view3d/view3d_gizmo_preselect_type.cc b/source/blender/editors/space_view3d/view3d_gizmo_preselect_type.cc index ba3544b9a35..4a344d1319d 100644 --- a/source/blender/editors/space_view3d/view3d_gizmo_preselect_type.cc +++ b/source/blender/editors/space_view3d/view3d_gizmo_preselect_type.cc @@ -141,7 +141,8 @@ static int gizmo_preselect_elem_test_select(bContext *C, wmGizmo *gz, const int } } - ViewContext vc = em_setup_viewcontext(C); + ViewContext vc; + em_setup_viewcontext(C, &vc); copy_v2_v2_int(vc.mval, mval); { @@ -370,7 +371,8 @@ static int gizmo_preselect_edgering_test_select(bContext *C, wmGizmo *gz, const } } - ViewContext vc = em_setup_viewcontext(C); + ViewContext vc; + em_setup_viewcontext(C, &vc); copy_v2_v2_int(vc.mval, mval); uint base_index; diff --git a/source/blender/editors/space_view3d/view3d_intern.h b/source/blender/editors/space_view3d/view3d_intern.h index 695025cf726..99cd295456b 100644 --- a/source/blender/editors/space_view3d/view3d_intern.h +++ b/source/blender/editors/space_view3d/view3d_intern.h @@ -25,7 +25,6 @@ struct Scene; struct ViewContext; struct ViewLayer; struct bContext; -struct bContextDataResult; struct wmGizmoGroupType; struct wmGizmoType; struct wmKeyConfig; @@ -35,10 +34,6 @@ struct wmOperatorType; void VIEW3D_OT_toggle_matcap_flip(struct wmOperatorType *ot); -/* `view3d_context.cc` */ - -int view3d_context(const bContext *C, const char *member, bContextDataResult *result); - /* `view3d_ops.cc` */ void view3d_operatortypes(void); diff --git a/source/blender/editors/space_view3d/view3d_navigate.cc b/source/blender/editors/space_view3d/view3d_navigate.cc index 6198fd0cfe5..f5c3a6a7092 100644 --- a/source/blender/editors/space_view3d/view3d_navigate.cc +++ b/source/blender/editors/space_view3d/view3d_navigate.cc @@ -158,9 +158,10 @@ void ViewOpsData::state_restore() /* ROTATE and ZOOM. */ { - /* For Rotate this only changes when orbiting from a camera view. - * In this case the `dist` is calculated based on the camera relative to the `ofs`. */ - + /** + * For Rotate this only changes when orbiting from a camera view. + * In this case the `dist` is calculated based on the camera relative to the `ofs`. + */ /* Note this does not remove auto-keys on locked cameras. */ this->rv3d->dist = this->init.dist; } diff --git a/source/blender/editors/space_view3d/view3d_project.cc b/source/blender/editors/space_view3d/view3d_project.cc index 1d898e70113..20168a57dc5 100644 --- a/source/blender/editors/space_view3d/view3d_project.cc +++ b/source/blender/editors/space_view3d/view3d_project.cc @@ -94,10 +94,9 @@ eV3DProjStatus ED_view3d_project_base(const ARegion *region, Base *base, float r return ret; } -/** - * `perspmat` is typically either: - * - 'rv3d->perspmat', is_local == false. - * - 'rv3d->persmatob', is_local == true. +/* perspmat is typically... + * - 'rv3d->perspmat', is_local == false + * - 'rv3d->persmatob', is_local == true */ static eV3DProjStatus ed_view3d_project__internal(const ARegion *region, const float perspmat[4][4], diff --git a/source/blender/editors/space_view3d/view3d_select.cc b/source/blender/editors/space_view3d/view3d_select.cc index a9898bf0a54..cc057c6d4d8 100644 --- a/source/blender/editors/space_view3d/view3d_select.cc +++ b/source/blender/editors/space_view3d/view3d_select.cc @@ -96,7 +96,7 @@ #include "DEG_depsgraph_query.hh" #include "DRW_engine.h" -#include "DRW_select_buffer.hh" +#include "DRW_select_buffer.h" #include "ANIM_bone_collections.h" @@ -113,22 +113,22 @@ float ED_view3d_select_dist_px() return 75.0f * U.pixelsize; } -ViewContext ED_view3d_viewcontext_init(bContext *C, Depsgraph *depsgraph) +void ED_view3d_viewcontext_init(bContext *C, ViewContext *vc, Depsgraph *depsgraph) { /* TODO: should return whether there is valid context to continue. */ - ViewContext vc = {}; - vc.C = C; - vc.region = CTX_wm_region(C); - vc.bmain = CTX_data_main(C); - vc.depsgraph = depsgraph; - vc.scene = CTX_data_scene(C); - vc.view_layer = CTX_data_view_layer(C); - vc.v3d = CTX_wm_view3d(C); - vc.win = CTX_wm_window(C); - vc.rv3d = CTX_wm_region_view3d(C); - vc.obact = CTX_data_active_object(C); - vc.obedit = CTX_data_edit_object(C); - return vc; + + memset(vc, 0, sizeof(ViewContext)); + vc->C = C; + vc->region = CTX_wm_region(C); + vc->bmain = CTX_data_main(C); + vc->depsgraph = depsgraph; + vc->scene = CTX_data_scene(C); + vc->view_layer = CTX_data_view_layer(C); + vc->v3d = CTX_wm_view3d(C); + vc->win = CTX_wm_window(C); + vc->rv3d = CTX_wm_region_view3d(C); + vc->obact = CTX_data_active_object(C); + vc->obedit = CTX_data_edit_object(C); } void ED_view3d_viewcontext_init_object(ViewContext *vc, Object *obact) @@ -205,7 +205,7 @@ static void editselect_buf_cache_init(ViewContext *vc, short select_mode) Base **bases = BKE_view_layer_array_from_bases_in_edit_mode( vc->scene, vc->view_layer, vc->v3d, &bases_len); - DRW_select_buffer_context_create(vc->depsgraph, bases, bases_len, select_mode); + DRW_select_buffer_context_create(bases, bases_len, select_mode); MEM_freeN(bases); } else { @@ -213,7 +213,7 @@ static void editselect_buf_cache_init(ViewContext *vc, short select_mode) if (vc->obact) { BKE_view_layer_synced_ensure(vc->scene, vc->view_layer); Base *base = BKE_view_layer_base_find(vc->view_layer, vc->obact); - DRW_select_buffer_context_create(vc->depsgraph, &base, 1, select_mode); + DRW_select_buffer_context_create(&base, 1, select_mode); } } } @@ -1188,15 +1188,14 @@ static bool do_lasso_select_grease_pencil(ViewContext *vc, GreasePencil &grease_pencil = *static_cast(vc->obedit->data); /* Get selection domain from tool settings. */ - const eAttrDomain selection_domain = ED_grease_pencil_selection_domain_get( - vc->scene->toolsettings); + const eAttrDomain selection_domain = ED_grease_pencil_selection_domain_get(vc->C); bool changed = false; grease_pencil.foreach_editable_drawing( - vc->scene->r.cfra, [&](const int layer_index, blender::bke::greasepencil::Drawing &drawing) { + vc->scene->r.cfra, [&](int drawing_index, blender::bke::greasepencil::Drawing &drawing) { bke::crazyspace::GeometryDeformation deformation = bke::crazyspace::get_evaluated_grease_pencil_drawing_deformation( - ob_eval, *vc->obedit, layer_index); + ob_eval, *vc->obedit, drawing_index); changed = ed::curves::select_lasso( *vc, @@ -1461,6 +1460,7 @@ static bool view3d_lasso_select(bContext *C, * with short array we convert */ static int view3d_lasso_select_exec(bContext *C, wmOperator *op) { + ViewContext vc; int mcoords_len; const int(*mcoords)[2] = WM_gesture_lasso_path_to_array(C, op, &mcoords_len); @@ -1470,7 +1470,7 @@ static int view3d_lasso_select_exec(bContext *C, wmOperator *op) BKE_object_update_select_id(CTX_data_main(C)); /* setup view context for argument to callbacks */ - ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph); + ED_view3d_viewcontext_init(C, &vc, depsgraph); eSelectOp sel_op = static_cast(RNA_enum_get(op->ptr, "mode")); bool changed_multi = view3d_lasso_select(C, &vc, mcoords, mcoords_len, sel_op); @@ -2410,6 +2410,7 @@ static Base *ed_view3d_give_base_under_cursor_ex(bContext *C, int *r_material_slot) { Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + ViewContext vc; Base *basact = nullptr; GPUSelectResult buffer[MAXPICKELEMS]; @@ -2417,7 +2418,7 @@ static Base *ed_view3d_give_base_under_cursor_ex(bContext *C, view3d_operator_needs_opengl(C); BKE_object_update_select_id(CTX_data_main(C)); - ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph); + ED_view3d_viewcontext_init(C, &vc, depsgraph); const bool do_nearest = !XRAY_ACTIVE(vc.v3d); const bool do_material_slot_selection = r_material_slot != nullptr; @@ -2586,8 +2587,9 @@ static bool ed_object_select_pick(bContext *C, const bool object_only) { Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + ViewContext vc; /* Setup view context for argument to callbacks. */ - ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph); + ED_view3d_viewcontext_init(C, &vc, depsgraph); Scene *scene = vc.scene; View3D *v3d = vc.v3d; @@ -3060,8 +3062,9 @@ static bool ed_curves_select_pick(bContext &C, const int mval[2], const SelectPi { using namespace blender; Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(&C); + ViewContext vc; /* Setup view context for argument to callbacks. */ - ViewContext vc = ED_view3d_viewcontext_init(&C, depsgraph); + ED_view3d_viewcontext_init(&C, &vc, depsgraph); uint bases_len; Base **bases_ptr = BKE_view_layer_array_from_bases_in_edit_mode_unique_data( @@ -3107,19 +3110,18 @@ static bool ed_curves_select_pick(bContext &C, const int mval[2], const SelectPi for (Base *base : bases.slice(range)) { Curves &curves_id = *static_cast(base->object->data); bke::CurvesGeometry &curves = curves_id.geometry.wrap(); - if (!ed::curves::has_anything_selected(curves)) { - continue; - } - bke::GSpanAttributeWriter selection = ed::curves::ensure_selection_attribute( - curves, selection_domain, CD_PROP_BOOL); - ed::curves::fill_selection_false(selection.span); - selection.finish(); + if (ed::curves::has_anything_selected(curves)) { + bke::GSpanAttributeWriter selection = ed::curves::ensure_selection_attribute( + curves, selection_domain, CD_PROP_BOOL); + ed::curves::fill_selection_false(selection.span); + selection.finish(); - deselected = true; - /* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a - * generic attribute for now. */ - DEG_id_tag_update(&curves_id.id, ID_RECALC_GEOMETRY); - WM_event_add_notifier(&C, NC_GEOM | ND_DATA, &curves_id); + deselected = true; + /* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a + * generic attribute for now. */ + DEG_id_tag_update(&curves_id.id, ID_RECALC_GEOMETRY); + WM_event_add_notifier(&C, NC_GEOM | ND_DATA, &curves_id); + } } }); } @@ -3161,23 +3163,23 @@ static bool ed_grease_pencil_select_pick(bContext *C, { using namespace blender; Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + ViewContext vc; /* Setup view context for argument to callbacks. */ - ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph); + ED_view3d_viewcontext_init(C, &vc, depsgraph); /* Collect editable drawings. */ const Object *ob_eval = DEG_get_evaluated_object(vc.depsgraph, const_cast(vc.obedit)); GreasePencil &grease_pencil = *static_cast(vc.obedit->data); Vector drawings; - Vector layer_indices; + Vector drawing_indices; grease_pencil.foreach_editable_drawing( - vc.scene->r.cfra, [&](const int layer_index, blender::bke::greasepencil::Drawing &drawing) { + vc.scene->r.cfra, [&](int drawing_index, blender::bke::greasepencil::Drawing &drawing) { drawings.append(&drawing); - layer_indices.append(layer_index); + drawing_indices.append(drawing_index); }); /* Get selection domain from tool settings. */ - const eAttrDomain selection_domain = ED_grease_pencil_selection_domain_get( - vc.scene->toolsettings); + const eAttrDomain selection_domain = ED_grease_pencil_selection_domain_get(C); const ClosestGreasePencilDrawing closest = threading::parallel_reduce( drawings.index_range(), @@ -3189,7 +3191,7 @@ static bool ed_grease_pencil_select_pick(bContext *C, /* Get deformation by modifiers. */ bke::crazyspace::GeometryDeformation deformation = bke::crazyspace::get_evaluated_grease_pencil_drawing_deformation( - ob_eval, *vc.obedit, layer_indices[i]); + ob_eval, *vc.obedit, drawing_indices[i]); std::optional new_closest_elem = ed::curves::closest_elem_find_screen_space(vc, *vc.obedit, @@ -3214,15 +3216,14 @@ static bool ed_grease_pencil_select_pick(bContext *C, threading::parallel_for(drawings.index_range(), 1L, [&](const IndexRange range) { for (const int i : range) { bke::CurvesGeometry &curves = drawings[i]->geometry.wrap(); - if (!ed::curves::has_anything_selected(curves)) { - continue; - } - bke::GSpanAttributeWriter selection = ed::curves::ensure_selection_attribute( - curves, selection_domain, CD_PROP_BOOL); - ed::curves::fill_selection_false(selection.span); - selection.finish(); + if (ed::curves::has_anything_selected(curves)) { + bke::GSpanAttributeWriter selection = ed::curves::ensure_selection_attribute( + curves, selection_domain, CD_PROP_BOOL); + ed::curves::fill_selection_false(selection.span); + selection.finish(); - deselected = true; + deselected = true; + } } }); } @@ -3269,7 +3270,8 @@ static int view3d_select_exec(bContext *C, wmOperator *op) } Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph); + ViewContext vc; + ED_view3d_viewcontext_init(C, &vc, depsgraph); SelectPick_Params params{}; ED_select_pick_params_from_operator(op->ptr, ¶ms); @@ -4188,14 +4190,14 @@ static bool do_grease_pencil_box_select(ViewContext *vc, const rcti *rect, const GreasePencil &grease_pencil = *static_cast(vc->obedit->data); /* Get selection domain from tool settings. */ - const eAttrDomain selection_domain = ED_grease_pencil_selection_domain_get(scene->toolsettings); + const eAttrDomain selection_domain = ED_grease_pencil_selection_domain_get(vc->C); bool changed = false; grease_pencil.foreach_editable_drawing( - scene->r.cfra, [&](const int layer_index, blender::bke::greasepencil::Drawing &drawing) { + scene->r.cfra, [&](int drawing_index, blender::bke::greasepencil::Drawing &drawing) { bke::crazyspace::GeometryDeformation deformation = bke::crazyspace::get_evaluated_grease_pencil_drawing_deformation( - ob_eval, *vc->obedit, layer_index); + ob_eval, *vc->obedit, drawing_index); changed |= ed::curves::select_box(*vc, drawing.strokes_for_write(), deformation.positions, @@ -4216,6 +4218,7 @@ static int view3d_box_select_exec(bContext *C, wmOperator *op) { using namespace blender; Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + ViewContext vc; rcti rect; bool changed_multi = false; @@ -4226,7 +4229,7 @@ static int view3d_box_select_exec(bContext *C, wmOperator *op) BKE_object_update_select_id(CTX_data_main(C)); /* setup view context for argument to callbacks */ - ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph); + ED_view3d_viewcontext_init(C, &vc, depsgraph); eSelectOp sel_op = static_cast(RNA_enum_get(op->ptr, "mode")); WM_operator_properties_border_to_rcti(op, &rect); @@ -5035,15 +5038,14 @@ static bool grease_pencil_circle_select(ViewContext *vc, GreasePencil &grease_pencil = *static_cast(vc->obedit->data); /* Get selection domain from tool settings. */ - const eAttrDomain selection_domain = ED_grease_pencil_selection_domain_get( - vc->scene->toolsettings); + const eAttrDomain selection_domain = ED_grease_pencil_selection_domain_get(vc->C); bool changed = false; grease_pencil.foreach_editable_drawing( - vc->scene->r.cfra, [&](const int layer_index, blender::bke::greasepencil::Drawing &drawing) { + vc->scene->r.cfra, [&](int drawing_index, blender::bke::greasepencil::Drawing &drawing) { bke::crazyspace::GeometryDeformation deformation = bke::crazyspace::get_evaluated_grease_pencil_drawing_deformation( - ob_eval, *vc->obedit, layer_index); + ob_eval, *vc->obedit, drawing_index); changed = ed::curves::select_circle(*vc, drawing.strokes_for_write(), @@ -5181,7 +5183,8 @@ static void view3d_circle_select_recalc(void *user_data) if (obedit_active) { switch (obedit_active->type) { case OB_MESH: { - ViewContext vc = em_setup_viewcontext(C); + ViewContext vc; + em_setup_viewcontext(C, &vc); FOREACH_OBJECT_IN_MODE_BEGIN ( vc.scene, vc.view_layer, vc.v3d, vc.obact->type, vc.obact->mode, ob_iter) { @@ -5220,6 +5223,7 @@ static void view3d_circle_select_cancel(bContext *C, wmOperator *op) static int view3d_circle_select_exec(bContext *C, wmOperator *op) { Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + ViewContext vc; const int radius = RNA_int_get(op->ptr, "radius"); const int mval[2] = {RNA_int_get(op->ptr, "x"), RNA_int_get(op->ptr, "y")}; @@ -5231,7 +5235,7 @@ static int view3d_circle_select_exec(bContext *C, wmOperator *op) const eSelectOp sel_op = ED_select_op_modal( static_cast(RNA_enum_get(op->ptr, "mode")), WM_gesture_is_modal_first(gesture)); - ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph); + ED_view3d_viewcontext_init(C, &vc, depsgraph); Object *obact = vc.obact; Object *obedit = vc.obedit; diff --git a/source/blender/editors/space_view3d/view3d_snap.cc b/source/blender/editors/space_view3d/view3d_snap.cc index cf01edc6118..8b75c0d53e6 100644 --- a/source/blender/editors/space_view3d/view3d_snap.cc +++ b/source/blender/editors/space_view3d/view3d_snap.cc @@ -11,10 +11,10 @@ #include "DNA_armature_types.h" #include "DNA_object_types.h" +#include "BLI_array.h" #include "BLI_math_matrix.h" #include "BLI_math_vector.h" #include "BLI_utildefines.h" -#include "BLI_vector.hh" #include "BKE_action.h" #include "BKE_armature.h" @@ -43,12 +43,9 @@ #include "ED_transverts.hh" #include "ANIM_bone_collections.h" -#include "ANIM_keyframing.hh" #include "view3d_intern.h" -using blender::Vector; - static bool snap_curs_to_sel_ex(bContext *C, const int pivot_point, float r_cursor[3]); static bool snap_calc_active_center(bContext *C, const bool select_only, float r_center[3]); @@ -158,7 +155,7 @@ static int snap_sel_to_grid_exec(bContext *C, wmOperator * /*op*/) } /* auto-keyframing */ - blender::animrig::autokeyframe_pchan(C, scene, ob, pchan, ks); + ED_autokeyframe_pchan(C, scene, ob, pchan, ks); } /* if the bone has a parent and is connected to the parent, * don't do anything - will break chain unless we do auto-ik. @@ -186,32 +183,40 @@ static int snap_sel_to_grid_exec(bContext *C, wmOperator * /*op*/) XFormObjectData_Container *xds = nullptr; /* Build object array. */ - Vector objects_eval; + Object **objects_eval = nullptr; + uint objects_eval_len; { + BLI_array_declare(objects_eval); FOREACH_SELECTED_EDITABLE_OBJECT_BEGIN (view_layer_eval, v3d, ob_eval) { - objects_eval.append(ob_eval); + BLI_array_append(objects_eval, ob_eval); } FOREACH_SELECTED_EDITABLE_OBJECT_END; + objects_eval_len = BLI_array_len(objects_eval); } if (use_transform_skip_children) { ViewLayer *view_layer = CTX_data_view_layer(C); - Vector objects(objects_eval.size()); - for (Object *ob_eval : objects_eval) { - objects.append_unchecked(DEG_get_original_object(ob_eval)); + Object **objects = static_cast( + MEM_malloc_arrayN(objects_eval_len, sizeof(*objects), __func__)); + + for (int ob_index = 0; ob_index < objects_eval_len; ob_index++) { + Object *ob_eval = objects_eval[ob_index]; + objects[ob_index] = DEG_get_original_object(ob_eval); } BKE_scene_graph_evaluated_ensure(depsgraph, bmain); xcs = ED_object_xform_skip_child_container_create(); ED_object_xform_skip_child_container_item_ensure_from_array( - xcs, scene, view_layer, objects.data(), objects.size()); + xcs, scene, view_layer, objects, objects_eval_len); + MEM_freeN(objects); } if (use_transform_data_origin) { BKE_scene_graph_evaluated_ensure(depsgraph, bmain); xds = ED_object_data_xform_container_create(); } - for (Object *ob_eval : objects_eval) { + for (int ob_index = 0; ob_index < objects_eval_len; ob_index++) { + Object *ob_eval = objects_eval[ob_index]; Object *ob = DEG_get_original_object(ob_eval); vec[0] = -ob_eval->object_to_world[3][0] + gridf * floorf(0.5f + ob_eval->object_to_world[3][0] / gridf); @@ -238,7 +243,7 @@ static int snap_sel_to_grid_exec(bContext *C, wmOperator * /*op*/) } /* auto-keyframing */ - blender::animrig::autokeyframe_object(C, scene, ob, ks); + ED_autokeyframe_object(C, scene, ob, ks); if (use_transform_data_origin) { ED_object_data_xform_container_item_ensure(xds, ob); @@ -247,6 +252,10 @@ static int snap_sel_to_grid_exec(bContext *C, wmOperator * /*op*/) DEG_id_tag_update(&ob->id, ID_RECALC_TRANSFORM); } + if (objects_eval) { + MEM_freeN(objects_eval); + } + if (use_transform_skip_children) { ED_object_xform_skip_child_container_update_all(xcs, bmain, depsgraph); ED_object_xform_skip_child_container_destroy(xcs); @@ -431,7 +440,7 @@ static bool snap_selected_to_location(bContext *C, } /* auto-keyframing */ - blender::animrig::autokeyframe_pchan(C, scene, ob, pchan, ks); + ED_autokeyframe_pchan(C, scene, ob, pchan, ks); } else { copy_v3_v3(pchan->loc, cursor_pose); @@ -463,13 +472,16 @@ static bool snap_selected_to_location(bContext *C, /* Build object array, tag objects we're transforming. */ ViewLayer *view_layer = CTX_data_view_layer(C); - Vector objects; + Object **objects = nullptr; + uint objects_len; { + BLI_array_declare(objects); FOREACH_SELECTED_EDITABLE_OBJECT_BEGIN (view_layer, v3d, ob) { - objects.append(ob); + BLI_array_append(objects, ob); ob->flag |= OB_DONE; } FOREACH_SELECTED_EDITABLE_OBJECT_END; + objects_len = BLI_array_len(objects); } const bool use_transform_skip_children = use_toolsettings && @@ -485,7 +497,7 @@ static bool snap_selected_to_location(bContext *C, BKE_scene_graph_evaluated_ensure(depsgraph, bmain); xcs = ED_object_xform_skip_child_container_create(); ED_object_xform_skip_child_container_item_ensure_from_array( - xcs, scene, view_layer, objects.data(), objects.size()); + xcs, scene, view_layer, objects, objects_len); } if (use_transform_data_origin) { BKE_scene_graph_evaluated_ensure(depsgraph, bmain); @@ -493,12 +505,14 @@ static bool snap_selected_to_location(bContext *C, /* Initialize the transform data in a separate loop because the depsgraph * may be evaluated while setting the locations. */ - for (Object *ob : objects) { + for (int ob_index = 0; ob_index < objects_len; ob_index++) { + Object *ob = objects[ob_index]; ED_object_data_xform_container_item_ensure(xds, ob); } } - for (Object *ob : objects) { + for (int ob_index = 0; ob_index < objects_len; ob_index++) { + Object *ob = objects[ob_index]; if (ob->parent && BKE_object_flag_test_recursive(ob->parent, OB_DONE)) { continue; } @@ -538,7 +552,7 @@ static bool snap_selected_to_location(bContext *C, } /* auto-keyframing */ - blender::animrig::autokeyframe_object(C, scene, ob, ks); + ED_autokeyframe_object(C, scene, ob, ks); } else { add_v3_v3(ob->loc, cursor_parent); @@ -547,6 +561,10 @@ static bool snap_selected_to_location(bContext *C, DEG_id_tag_update(&ob->id, ID_RECALC_TRANSFORM); } + if (objects) { + MEM_freeN(objects); + } + if (use_transform_skip_children) { ED_object_xform_skip_child_container_update_all(xcs, bmain, depsgraph); ED_object_xform_skip_child_container_destroy(xcs); diff --git a/source/blender/editors/space_view3d/view3d_utils.cc b/source/blender/editors/space_view3d/view3d_utils.cc index 5414950fca6..ea09eff0325 100644 --- a/source/blender/editors/space_view3d/view3d_utils.cc +++ b/source/blender/editors/space_view3d/view3d_utils.cc @@ -51,8 +51,6 @@ #include "ED_undo.hh" #include "ED_view3d.hh" -#include "ANIM_keyframing.hh" - #include "UI_resources.hh" #include "view3d_intern.h" /* own include */ @@ -640,11 +638,12 @@ bool ED_view3d_camera_lock_sync(const Depsgraph *depsgraph, View3D *v3d, RegionV bool ED_view3d_camera_autokey( const Scene *scene, ID *id_key, bContext *C, const bool do_rotate, const bool do_translate) { - if (blender::animrig::autokeyframe_cfra_can_key(scene, id_key)) { + if (autokeyframe_cfra_can_key(scene, id_key)) { const float cfra = float(scene->r.cfra); - blender::Vector sources; + ListBase dsources = {nullptr, nullptr}; + /* add data-source override for the camera object */ - ANIM_relative_keyingset_add_source(sources, id_key); + ANIM_relative_keyingset_add_source(&dsources, id_key, nullptr, nullptr); /* insert keyframes * 1) on the first frame @@ -653,13 +652,16 @@ bool ED_view3d_camera_autokey( */ if (do_rotate) { KeyingSet *ks = ANIM_get_keyingset_for_autokeying(scene, ANIM_KS_ROTATION_ID); - ANIM_apply_keyingset(C, &sources, ks, MODIFYKEY_MODE_INSERT, cfra); + ANIM_apply_keyingset(C, &dsources, nullptr, ks, MODIFYKEY_MODE_INSERT, cfra); } if (do_translate) { KeyingSet *ks = ANIM_get_keyingset_for_autokeying(scene, ANIM_KS_LOCATION_ID); - ANIM_apply_keyingset(C, &sources, ks, MODIFYKEY_MODE_INSERT, cfra); + ANIM_apply_keyingset(C, &dsources, nullptr, ks, MODIFYKEY_MODE_INSERT, cfra); } + /* free temp data */ + BLI_freelistN(&dsources); + return true; } return false; diff --git a/source/blender/editors/transform/CMakeLists.txt b/source/blender/editors/transform/CMakeLists.txt index 1498ecd147b..814407a735b 100644 --- a/source/blender/editors/transform/CMakeLists.txt +++ b/source/blender/editors/transform/CMakeLists.txt @@ -32,7 +32,6 @@ set(SRC transform_convert_curves.cc transform_convert_gpencil_legacy.cc transform_convert_graph.cc - transform_convert_grease_pencil.cc transform_convert_lattice.cc transform_convert_mask.cc transform_convert_mball.cc diff --git a/source/blender/editors/transform/transform.cc b/source/blender/editors/transform/transform.cc index d8256eb6332..6edbc4ddf88 100644 --- a/source/blender/editors/transform/transform.cc +++ b/source/blender/editors/transform/transform.cc @@ -35,8 +35,6 @@ #include "ED_screen.hh" #include "ED_space_api.hh" -#include "ANIM_keyframing.hh" - #include "SEQ_transform.h" #include "WM_api.hh" @@ -426,16 +424,11 @@ void removeAspectRatio(TransInfo *t, float vec[2]) static void viewRedrawForce(const bContext *C, TransInfo *t) { if (t->options & CTX_GPENCIL_STROKES) { - if (t->obedit_type == OB_GREASE_PENCIL) { - WM_event_add_notifier(C, NC_GEOM | ND_DATA, nullptr); - } - else if (t->obedit_type == OB_GPENCIL_LEGACY) { - bGPdata *gpd = ED_gpencil_data_get_active(C); - if (gpd) { - DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY); - } - WM_event_add_notifier(C, NC_GPENCIL | NA_EDITED, nullptr); + bGPdata *gpd = ED_gpencil_data_get_active(C); + if (gpd) { + DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY); } + WM_event_add_notifier(C, NC_GPENCIL | NA_EDITED, nullptr); } else if (t->spacetype == SPACE_VIEW3D) { if (t->options & CTX_PAINT_CURVE) { @@ -453,7 +446,7 @@ static void viewRedrawForce(const bContext *C, TransInfo *t) /* For real-time animation record - send notifiers recognized by animation editors */ /* XXX: is this notifier a lame duck? */ - if ((t->animtimer) && blender::animrig::is_autokey_on(t->scene)) { + if ((t->animtimer) && IS_AUTOKEY_ON(t->scene)) { WM_event_add_notifier(C, NC_OBJECT | ND_KEYS, nullptr); } } @@ -529,7 +522,7 @@ static void viewRedrawPost(bContext *C, TransInfo *t) if (t->spacetype == SPACE_VIEW3D) { /* if autokeying is enabled, send notifiers that keyframes were added */ - if (blender::animrig::is_autokey_on(t->scene)) { + if (IS_AUTOKEY_ON(t->scene)) { WM_main_add_notifier(NC_ANIMATION | ND_KEYFRAME | NA_EDITED, nullptr); } @@ -1587,7 +1580,7 @@ static void drawTransformPixel(const bContext * /*C*/, ARegion *region, void *ar if ((U.autokey_flag & AUTOKEY_FLAG_NOWARNING) == 0) { if (region == t->region) { if (t->options & (CTX_OBJECT | CTX_POSE_BONE)) { - if (ob && blender::animrig::autokeyframe_cfra_can_key(scene, &ob->id)) { + if (ob && autokeyframe_cfra_can_key(scene, &ob->id)) { drawAutoKeyWarning(t, region); } } @@ -1963,7 +1956,7 @@ bool initTransform(bContext *C, TransInfo *t, wmOperator *op, const wmEvent *eve * * By definition transform-data has selected items in beginning, * so only the first item in each container needs to be checked - * when looking for the presence of selected data. */ + * when looking for the presence of selected data. */ if (t->flag & T_PROP_EDIT) { bool has_selected_any = false; FOREACH_TRANS_DATA_CONTAINER (t, tc) { @@ -2099,30 +2092,32 @@ bool initTransform(bContext *C, TransInfo *t, wmOperator *op, const wmEvent *eve if ((t->flag & T_EDIT) && t->obedit_type == OB_MESH) { FOREACH_TRANS_DATA_CONTAINER (t, tc) { - BMEditMesh *em = nullptr; /* BKE_editmesh_from_object(t->obedit); */ - bool do_skip = false; + if (((Mesh *)(tc->obedit->data))->flag & ME_AUTOSMOOTH) { + BMEditMesh *em = nullptr; /* BKE_editmesh_from_object(t->obedit); */ + bool do_skip = false; - /* Currently only used for two of three most frequent transform ops, - * can include more ops. - * Note that scaling cannot be included here, - * non-uniform scaling will affect normals. */ - if (ELEM(t->mode, TFM_TRANSLATION, TFM_ROTATION)) { - if (em->bm->totvertsel == em->bm->totvert) { - /* No need to invalidate if whole mesh is selected. */ - do_skip = true; + /* Currently only used for two of three most frequent transform ops, + * can include more ops. + * Note that scaling cannot be included here, + * non-uniform scaling will affect normals. */ + if (ELEM(t->mode, TFM_TRANSLATION, TFM_ROTATION)) { + if (em->bm->totvertsel == em->bm->totvert) { + /* No need to invalidate if whole mesh is selected. */ + do_skip = true; + } } - } - if (t->flag & T_MODAL) { - RNA_property_boolean_set(op->ptr, prop, false); - } - else if (!do_skip) { - const bool preserve_clnor = RNA_property_boolean_get(op->ptr, prop); - if (preserve_clnor) { - BKE_editmesh_lnorspace_update(em); - t->flag |= T_CLNOR_REBUILD; + if (t->flag & T_MODAL) { + RNA_property_boolean_set(op->ptr, prop, false); + } + else if (!do_skip) { + const bool preserve_clnor = RNA_property_boolean_get(op->ptr, prop); + if (preserve_clnor) { + BKE_editmesh_lnorspace_update(em, static_cast(tc->obedit->data)); + t->flag |= T_CLNOR_REBUILD; + } + BM_lnorspace_invalidate(em->bm, true); } - BM_lnorspace_invalidate(em->bm, true); } } } diff --git a/source/blender/editors/transform/transform.hh b/source/blender/editors/transform/transform.hh index 86123446352..734c6200490 100644 --- a/source/blender/editors/transform/transform.hh +++ b/source/blender/editors/transform/transform.hh @@ -676,7 +676,7 @@ struct TransInfo { * \{ */ /** - * \note Caller needs to free `t` on a 0 return. + * \note caller needs to free `t` on a 0 return * \warning \a event might be NULL (when tweaking from redo panel) * \see #saveTransform which writes these values back. */ diff --git a/source/blender/editors/transform/transform_convert.cc b/source/blender/editors/transform/transform_convert.cc index 0818adadc9d..0150e06a513 100644 --- a/source/blender/editors/transform/transform_convert.cc +++ b/source/blender/editors/transform/transform_convert.cc @@ -17,7 +17,6 @@ #include "BLI_listbase.h" #include "BLI_math_matrix.h" #include "BLI_math_vector.h" -#include "BLI_math_vector.hh" #include "BKE_action.h" #include "BKE_anim_data.h" @@ -39,8 +38,6 @@ #include "ED_screen_types.hh" #include "ED_sequencer.hh" -#include "ANIM_keyframing.hh" - #include "UI_view2d.hh" #include "WM_types.hh" @@ -53,8 +50,6 @@ /* Own include. */ #include "transform_convert.hh" -using namespace blender; - bool transform_mode_use_local_origins(const TransInfo *t) { return ELEM(t->mode, TFM_ROTATION, TFM_RESIZE, TFM_TRACKBALL); @@ -176,39 +171,6 @@ static void sort_trans_data_selected_first(TransInfo *t) } } -static float3 prop_dist_loc_get(const TransDataContainer *tc, - const TransData *td, - const bool use_island, - const float proj_vec[3]) -{ - float3 r_vec; - - if (use_island) { - if (tc->use_local_mat) { - mul_v3_m4v3(r_vec, tc->mat, td->iloc); - } - else { - mul_v3_m3v3(r_vec, td->mtx, td->iloc); - } - } - else { - if (tc->use_local_mat) { - mul_v3_m4v3(r_vec, tc->mat, td->center); - } - else { - mul_v3_m3v3(r_vec, td->mtx, td->center); - } - } - - if (proj_vec) { - float vec_p[3]; - project_v3_v3v3(vec_p, r_vec, proj_vec); - sub_v3_v3(r_vec, vec_p); - } - - return r_vec; -} - /** * Distance calculated from not-selected vertex to nearest selected vertex. */ @@ -259,9 +221,31 @@ static void set_prop_dist(TransInfo *t, const bool with_dist) for (a = 0; a < tc->data_len; a++, td++) { if (td->flag & TD_SELECTED) { /* Initialize, it was malloced. */ + float vec[3]; td->rdist = 0.0f; - const float3 vec = prop_dist_loc_get(tc, td, use_island, proj_vec); + if (use_island) { + if (tc->use_local_mat) { + mul_v3_m4v3(vec, tc->mat, td->iloc); + } + else { + mul_v3_m3v3(vec, td->mtx, td->iloc); + } + } + else { + if (tc->use_local_mat) { + mul_v3_m4v3(vec, tc->mat, td->center); + } + else { + mul_v3_m3v3(vec, td->mtx, td->center); + } + } + + if (proj_vec) { + float vec_p[3]; + project_v3_v3v3(vec_p, vec, proj_vec); + sub_v3_v3(vec, vec_p); + } BLI_kdtree_3d_insert(td_tree, td_table_index, vec); td_table[td_table_index++] = td; @@ -281,7 +265,30 @@ static void set_prop_dist(TransInfo *t, const bool with_dist) TransData *td = tc->data; for (a = 0; a < tc->data_len; a++, td++) { if ((td->flag & TD_SELECTED) == 0) { - const float3 vec = prop_dist_loc_get(tc, td, use_island, proj_vec); + float vec[3]; + + if (use_island) { + if (tc->use_local_mat) { + mul_v3_m4v3(vec, tc->mat, td->iloc); + } + else { + mul_v3_m3v3(vec, td->mtx, td->iloc); + } + } + else { + if (tc->use_local_mat) { + mul_v3_m4v3(vec, tc->mat, td->center); + } + else { + mul_v3_m3v3(vec, td->mtx, td->center); + } + } + + if (proj_vec) { + float vec_p[3]; + project_v3_v3v3(vec_p, vec, proj_vec); + sub_v3_v3(vec, vec_p); + } KDTreeNearest_3d nearest; const int td_index = BLI_kdtree_3d_find_nearest(td_tree, vec, &nearest); @@ -718,25 +725,23 @@ static int countAndCleanTransDataContainer(TransInfo *t) static void init_proportional_edit(TransInfo *t) { /* NOTE: Proportional editing is not usable in pose mode yet #32444. */ - /* NOTE: This `ELEM` uses more than 16 elements and so has been split. */ - if (!(ELEM(t->data_type, - &TransConvertType_Action, - &TransConvertType_Curve, - &TransConvertType_Curves, - &TransConvertType_Graph, - &TransConvertType_GPencil, - &TransConvertType_GreasePencil, - &TransConvertType_Lattice, - &TransConvertType_Mask, - &TransConvertType_MBall, - &TransConvertType_Mesh, - &TransConvertType_MeshEdge, - &TransConvertType_MeshSkin, - &TransConvertType_MeshUV, - &TransConvertType_MeshVertCData, - &TransConvertType_Node, - &TransConvertType_Object) || - ELEM(t->data_type, &TransConvertType_Particle))) + if (!ELEM(t->data_type, + &TransConvertType_Action, + &TransConvertType_Curve, + &TransConvertType_Curves, + &TransConvertType_Graph, + &TransConvertType_GPencil, + &TransConvertType_Lattice, + &TransConvertType_Mask, + &TransConvertType_MBall, + &TransConvertType_Mesh, + &TransConvertType_MeshEdge, + &TransConvertType_MeshSkin, + &TransConvertType_MeshUV, + &TransConvertType_MeshVertCData, + &TransConvertType_Node, + &TransConvertType_Object, + &TransConvertType_Particle)) { /* Disable proportional editing */ t->options |= CTX_NO_PET; @@ -799,7 +804,6 @@ static void init_TransDataContainers(TransInfo *t, &TransConvertType_Curve, &TransConvertType_Curves, &TransConvertType_GPencil, - &TransConvertType_GreasePencil, &TransConvertType_Lattice, &TransConvertType_MBall, &TransConvertType_Mesh, @@ -816,7 +820,6 @@ static void init_TransDataContainers(TransInfo *t, const short object_type = obact ? obact->type : -1; if ((object_mode & OB_MODE_EDIT) || (t->data_type == &TransConvertType_GPencil) || - (t->data_type == &TransConvertType_GreasePencil) || ((object_mode & OB_MODE_POSE) && (object_type == OB_ARMATURE))) { if (t->data_container) { @@ -864,9 +867,6 @@ static void init_TransDataContainers(TransInfo *t, else if (t->data_type == &TransConvertType_GPencil) { tc->use_local_mat = true; } - else if (t->data_type == &TransConvertType_GreasePencil) { - tc->use_local_mat = true; - } if (tc->use_local_mat) { BLI_assert((t->flag & T_2D_EDIT) == 0); @@ -916,14 +916,8 @@ static TransConvertTypeInfo *convert_type_get(const TransInfo *t, Object **r_obj if (t->options & CTX_EDGE_DATA) { return &TransConvertType_MeshEdge; } - if ((t->options & CTX_GPENCIL_STROKES) && (t->spacetype == SPACE_VIEW3D)) { - if (t->obedit_type == OB_GREASE_PENCIL) { - return &TransConvertType_GreasePencil; - } - else if (t->obedit_type == OB_GPENCIL_LEGACY) { - return &TransConvertType_GPencil; - } - return nullptr; + if (t->options & CTX_GPENCIL_STROKES) { + return &TransConvertType_GPencil; } if (t->spacetype == SPACE_IMAGE) { if (t->options & CTX_MASK) { @@ -1201,7 +1195,7 @@ void animrecord_check_state(TransInfo *t, ID *id) * - we're not only keying for available channels * - the option to add new actions for each round is not enabled */ - if (blender::animrig::is_autokey_flag(scene, AUTOKEY_FLAG_INSERTAVAIL) == 0 && + if (IS_AUTOKEY_FLAG(scene, INSERTAVAIL) == 0 && (scene->toolsettings->autokey_flag & ANIMRECORD_FLAG_WITHNLA)) { /* if playback has just looped around, diff --git a/source/blender/editors/transform/transform_convert.hh b/source/blender/editors/transform/transform_convert.hh index c735ec53841..b9da5d40c53 100644 --- a/source/blender/editors/transform/transform_convert.hh +++ b/source/blender/editors/transform/transform_convert.hh @@ -11,10 +11,6 @@ #include "RE_engine.h" -#include "BKE_curves.hh" - -#include "BLI_index_mask.hh" - struct BMEditMesh; struct BMesh; struct BezTriple; @@ -110,19 +106,6 @@ void transform_convert_clip_mirror_modifier_apply(TransDataContainer *tc); */ void animrecord_check_state(TransInfo *t, ID *id); -/* `transform_convert_curves.cc` */ - -/** - * Used for both curves and grease pencil objects. - */ -void curve_populate_trans_data_structs(TransDataContainer &tc, - blender::bke::CurvesGeometry &curves, - std::optional> value_attribute, - const blender::IndexMask &selected_indices, - bool use_proportional_edit, - bool use_connected_only, - int trans_data_offset); - /* `transform_convert_action.cc` */ extern TransConvertTypeInfo TransConvertType_Action; @@ -148,7 +131,7 @@ extern TransConvertTypeInfo TransConvertType_Cursor3D; extern TransConvertTypeInfo TransConvertType_Curve; -/* `transform_convert_curves.cc` */ +/* transform_convert_curves.cc */ extern TransConvertTypeInfo TransConvertType_Curves; @@ -160,10 +143,6 @@ extern TransConvertTypeInfo TransConvertType_Graph; extern TransConvertTypeInfo TransConvertType_GPencil; -/* `transform_convert_greasepencil.cc` */ - -extern TransConvertTypeInfo TransConvertType_GreasePencil; - /* `transform_convert_lattice.cc` */ extern TransConvertTypeInfo TransConvertType_Lattice; diff --git a/source/blender/editors/transform/transform_convert_armature.cc b/source/blender/editors/transform/transform_convert_armature.cc index c4a628d0883..1a1d1d2870f 100644 --- a/source/blender/editors/transform/transform_convert_armature.cc +++ b/source/blender/editors/transform/transform_convert_armature.cc @@ -39,7 +39,6 @@ #include "RNA_prototypes.h" #include "ANIM_bone_collections.h" -#include "ANIM_keyframing.hh" #include "transform.hh" #include "transform_orientations.hh" @@ -64,7 +63,7 @@ struct BoneInitData { * and we will insert a keyframe at the end of transform. */ static bool motionpath_need_update_pose(Scene *scene, Object *ob) { - if (blender::animrig::autokeyframe_cfra_can_key(scene, &ob->id)) { + if (autokeyframe_cfra_can_key(scene, &ob->id)) { return (ob->pose->avs.path_bakeflag & MOTIONPATH_BAKE_HAS_PATHS) != 0; } @@ -90,13 +89,14 @@ static void autokeyframe_pose( bAction *act = (adt) ? adt->action : nullptr; bPose *pose = ob->pose; - if (!blender::animrig::autokeyframe_cfra_can_key(scene, id)) { + if (!autokeyframe_cfra_can_key(scene, id)) { return; } ReportList *reports = CTX_wm_reports(C); ToolSettings *ts = scene->toolsettings; KeyingSet *active_ks = ANIM_scene_get_active_keyingset(scene); + ListBase nla_cache = {nullptr, nullptr}; Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); const AnimationEvalContext anim_eval_context = BKE_animsys_eval_context_construct( depsgraph, BKE_scene_frame_get(scene)); @@ -120,18 +120,19 @@ static void autokeyframe_pose( continue; } - blender::Vector sources; + ListBase dsources = {nullptr, nullptr}; + /* Add data-source override for the camera object. */ - ANIM_relative_keyingset_add_source(sources, id, &RNA_PoseBone, pchan); + ANIM_relative_keyingset_add_source(&dsources, id, &RNA_PoseBone, pchan); /* only insert into active keyingset? */ - if (blender::animrig::is_autokey_flag(scene, AUTOKEY_FLAG_ONLYKEYINGSET) && (active_ks)) { + if (IS_AUTOKEY_FLAG(scene, ONLYKEYINGSET) && (active_ks)) { /* Run the active Keying Set on the current data-source. */ ANIM_apply_keyingset( - C, &sources, active_ks, MODIFYKEY_MODE_INSERT, anim_eval_context.eval_time); + C, &dsources, nullptr, active_ks, MODIFYKEY_MODE_INSERT, anim_eval_context.eval_time); } /* only insert into available channels? */ - else if (blender::animrig::is_autokey_flag(scene, AUTOKEY_FLAG_INSERTAVAIL)) { + else if (IS_AUTOKEY_FLAG(scene, INSERTAVAIL)) { if (act) { LISTBASE_FOREACH (FCurve *, fcu, &act->curves) { /* only insert keyframes for this F-Curve if it affects the current bone */ @@ -144,22 +145,23 @@ static void autokeyframe_pose( * NOTE: this will do constraints too, but those are ok to do here too? */ if (STREQ(pchan_name, pchan->name)) { - blender::animrig::insert_keyframe(bmain, - reports, - id, - act, - ((fcu->grp) ? (fcu->grp->name) : (nullptr)), - fcu->rna_path, - fcu->array_index, - &anim_eval_context, - eBezTriple_KeyframeType(ts->keyframe_type), - flag); + insert_keyframe(bmain, + reports, + id, + act, + ((fcu->grp) ? (fcu->grp->name) : (nullptr)), + fcu->rna_path, + fcu->array_index, + &anim_eval_context, + eBezTriple_KeyframeType(ts->keyframe_type), + &nla_cache, + flag); } } } } /* only insert keyframe if needed? */ - else if (blender::animrig::is_autokey_flag(scene, AUTOKEY_FLAG_INSERTNEEDED)) { + else if (IS_AUTOKEY_FLAG(scene, INSERTNEEDED)) { bool do_loc = false, do_rot = false, do_scale = false; /* Filter the conditions when this happens @@ -194,24 +196,33 @@ static void autokeyframe_pose( } if (do_loc) { - KeyingSet *ks = ANIM_builtin_keyingset_get_named(ANIM_KS_LOCATION_ID); - ANIM_apply_keyingset(C, &sources, ks, MODIFYKEY_MODE_INSERT, anim_eval_context.eval_time); + KeyingSet *ks = ANIM_builtin_keyingset_get_named(nullptr, ANIM_KS_LOCATION_ID); + ANIM_apply_keyingset( + C, &dsources, nullptr, ks, MODIFYKEY_MODE_INSERT, anim_eval_context.eval_time); } if (do_rot) { - KeyingSet *ks = ANIM_builtin_keyingset_get_named(ANIM_KS_ROTATION_ID); - ANIM_apply_keyingset(C, &sources, ks, MODIFYKEY_MODE_INSERT, anim_eval_context.eval_time); + KeyingSet *ks = ANIM_builtin_keyingset_get_named(nullptr, ANIM_KS_ROTATION_ID); + ANIM_apply_keyingset( + C, &dsources, nullptr, ks, MODIFYKEY_MODE_INSERT, anim_eval_context.eval_time); } if (do_scale) { - KeyingSet *ks = ANIM_builtin_keyingset_get_named(ANIM_KS_SCALING_ID); - ANIM_apply_keyingset(C, &sources, ks, MODIFYKEY_MODE_INSERT, anim_eval_context.eval_time); + KeyingSet *ks = ANIM_builtin_keyingset_get_named(nullptr, ANIM_KS_SCALING_ID); + ANIM_apply_keyingset( + C, &dsources, nullptr, ks, MODIFYKEY_MODE_INSERT, anim_eval_context.eval_time); } } /* insert keyframe in all (transform) channels */ else { - KeyingSet *ks = ANIM_builtin_keyingset_get_named(ANIM_KS_LOC_ROT_SCALE_ID); - ANIM_apply_keyingset(C, &sources, ks, MODIFYKEY_MODE_INSERT, anim_eval_context.eval_time); + KeyingSet *ks = ANIM_builtin_keyingset_get_named(nullptr, ANIM_KS_LOC_ROT_SCALE_ID); + ANIM_apply_keyingset( + C, &dsources, nullptr, ks, MODIFYKEY_MODE_INSERT, anim_eval_context.eval_time); } + + /* free temp info */ + BLI_freelistN(&dsources); } + + BKE_animsys_free_nla_keyframing_context_cache(&nla_cache); } static bConstraint *add_temporary_ik_constraint(bPoseChannel *pchan, @@ -1464,7 +1475,7 @@ static void recalcData_pose(TransInfo *t) /* TODO: autokeyframe calls need some setting to specify to add samples * (FPoints) instead of keyframes? */ - if ((t->animtimer) && (t->context) && blender::animrig::is_autokey_on(t->scene)) { + if ((t->animtimer) && (t->context) && IS_AUTOKEY_ON(t->scene)) { /* XXX: this currently doesn't work, since flags aren't set yet! */ int targetless_ik = (t->flag & T_AUTOIK); diff --git a/source/blender/editors/transform/transform_convert_curves.cc b/source/blender/editors/transform/transform_convert_curves.cc index 54c117733cf..a40dd42041c 100644 --- a/source/blender/editors/transform/transform_convert_curves.cc +++ b/source/blender/editors/transform/transform_convert_curves.cc @@ -6,8 +6,6 @@ * \ingroup edtransform */ -#include - #include "BLI_array.hh" #include "BLI_inplace_priority_queue.hh" #include "BLI_math_matrix.h" @@ -97,14 +95,82 @@ static void createTransCurvesVerts(bContext * /*C*/, TransInfo *t) Curves *curves_id = static_cast(tc.obedit->data); bke::CurvesGeometry &curves = curves_id->geometry.wrap(); - curve_populate_trans_data_structs( - tc, - curves, - {} /* Currently no transform for attributes other than position. */, - selection_per_object[i], - use_proportional_edit, - use_connected_only, - 0 /* No data offset for curves. */); + float mtx[3][3], smtx[3][3]; + copy_m3_m4(mtx, tc.obedit->object_to_world); + pseudoinverse_m3_m3(smtx, mtx, PSEUDOINVERSE_EPSILON); + + MutableSpan positions = curves.positions_for_write(); + if (use_proportional_edit) { + const OffsetIndices points_by_curve = curves.points_by_curve(); + const VArray selection = *curves.attributes().lookup_or_default( + ".selection", ATTR_DOMAIN_POINT, true); + threading::parallel_for(curves.curves_range(), 512, [&](const IndexRange range) { + Vector closest_distances; + for (const int curve_i : range) { + const IndexRange points = points_by_curve[curve_i]; + const bool has_any_selected = ed::curves::has_anything_selected(selection, points); + if (!has_any_selected && use_connected_only) { + for (const int point_i : points) { + TransData &td = tc.data[point_i]; + td.flag |= TD_SKIP; + } + continue; + } + + closest_distances.reinitialize(points.size()); + closest_distances.fill(std::numeric_limits::max()); + + for (const int i : IndexRange(points.size())) { + const int point_i = points[i]; + TransData &td = tc.data[point_i]; + float3 *elem = &positions[point_i]; + + copy_v3_v3(td.iloc, *elem); + copy_v3_v3(td.center, td.iloc); + td.loc = *elem; + + td.flag = 0; + if (selection[point_i]) { + closest_distances[i] = 0.0f; + td.flag = TD_SELECTED; + } + + td.ext = nullptr; + + copy_m3_m3(td.smtx, smtx); + copy_m3_m3(td.mtx, mtx); + } + + if (use_connected_only) { + calculate_curve_point_distances_for_proportional_editing( + positions.slice(points), closest_distances.as_mutable_span()); + for (const int i : IndexRange(points.size())) { + TransData &td = tc.data[points[i]]; + td.dist = closest_distances[i]; + } + } + } + }); + } + else { + const IndexMask selected_indices = selection_per_object[i]; + threading::parallel_for(selected_indices.index_range(), 1024, [&](const IndexRange range) { + for (const int selection_i : range) { + TransData *td = &tc.data[selection_i]; + float3 *elem = &positions[selected_indices[selection_i]]; + + copy_v3_v3(td->iloc, *elem); + copy_v3_v3(td->center, td->iloc); + td->loc = *elem; + + td->flag = TD_SELECTED; + td->ext = nullptr; + + copy_m3_m3(td->smtx, smtx); + copy_m3_m3(td->mtx, mtx); + } + }); + } } } @@ -123,106 +189,6 @@ static void recalcData_curves(TransInfo *t) } // namespace blender::ed::transform::curves -void curve_populate_trans_data_structs(TransDataContainer &tc, - blender::bke::CurvesGeometry &curves, - std::optional> value_attribute, - const blender::IndexMask &selected_indices, - bool use_proportional_edit, - bool use_connected_only, - int trans_data_offset) -{ - using namespace blender; - - float mtx[3][3], smtx[3][3]; - copy_m3_m4(mtx, tc.obedit->object_to_world); - pseudoinverse_m3_m3(smtx, mtx, PSEUDOINVERSE_EPSILON); - - MutableSpan positions = curves.positions_for_write(); - if (use_proportional_edit) { - const OffsetIndices points_by_curve = curves.points_by_curve(); - const VArray selection = *curves.attributes().lookup_or_default( - ".selection", ATTR_DOMAIN_POINT, true); - threading::parallel_for(curves.curves_range(), 512, [&](const IndexRange range) { - Vector closest_distances; - for (const int curve_i : range) { - const IndexRange points = points_by_curve[curve_i]; - const bool has_any_selected = ed::curves::has_anything_selected(selection, points); - if (!has_any_selected && use_connected_only) { - for (const int point_i : points) { - TransData &td = tc.data[point_i + trans_data_offset]; - td.flag |= TD_SKIP; - } - continue; - } - - closest_distances.reinitialize(points.size()); - closest_distances.fill(std::numeric_limits::max()); - - for (const int i : IndexRange(points.size())) { - const int point_i = points[i]; - TransData &td = tc.data[point_i + trans_data_offset]; - float3 *elem = &positions[point_i]; - - copy_v3_v3(td.iloc, *elem); - copy_v3_v3(td.center, td.iloc); - td.loc = *elem; - - td.flag = 0; - if (selection[point_i]) { - closest_distances[i] = 0.0f; - td.flag = TD_SELECTED; - } - - if (value_attribute) { - float *value = &((*value_attribute)[point_i]); - td.val = value; - td.ival = *value; - } - - td.ext = nullptr; - - copy_m3_m3(td.smtx, smtx); - copy_m3_m3(td.mtx, mtx); - } - - if (use_connected_only) { - blender::ed::transform::curves::calculate_curve_point_distances_for_proportional_editing( - positions.slice(points), closest_distances.as_mutable_span()); - for (const int i : IndexRange(points.size())) { - TransData &td = tc.data[points[i] + trans_data_offset]; - td.dist = closest_distances[i]; - } - } - } - }); - } - else { - threading::parallel_for(selected_indices.index_range(), 1024, [&](const IndexRange range) { - for (const int selection_i : range) { - TransData *td = &tc.data[selection_i + trans_data_offset]; - const int point_i = selected_indices[selection_i]; - float3 *elem = &positions[point_i]; - - copy_v3_v3(td->iloc, *elem); - copy_v3_v3(td->center, td->iloc); - td->loc = *elem; - - if (value_attribute) { - float *value = &((*value_attribute)[point_i]); - td->val = value; - td->ival = *value; - } - - td->flag = TD_SELECTED; - td->ext = nullptr; - - copy_m3_m3(td->smtx, smtx); - copy_m3_m3(td->mtx, mtx); - } - }); - } -} - /** \} */ TransConvertTypeInfo TransConvertType_Curves = { diff --git a/source/blender/editors/transform/transform_convert_gpencil_legacy.cc b/source/blender/editors/transform/transform_convert_gpencil_legacy.cc index dce5c6ea149..255fe809dc8 100644 --- a/source/blender/editors/transform/transform_convert_gpencil_legacy.cc +++ b/source/blender/editors/transform/transform_convert_gpencil_legacy.cc @@ -24,8 +24,7 @@ #include "BKE_layer.h" #include "ED_gpencil_legacy.hh" - -#include "ANIM_keyframing.hh" +#include "ED_keyframing.hh" #include "transform.hh" #include "transform_convert.hh" @@ -222,7 +221,7 @@ static void createTransGPencil_curves(bContext *C, } if ((gpf->framenum != cfra) && (!is_multiedit)) { - if (blender::animrig::is_autokey_on(scene)) { + if (IS_AUTOKEY_ON(scene)) { gpf = BKE_gpencil_frame_addcopy(gpl, cfra); } /* In some weird situations (frame-lock enabled) return nullptr. */ @@ -522,7 +521,7 @@ static void createTransGPencil_strokes(bContext *C, * spent too much time editing the wrong frame... */ if ((gpf->framenum != cfra) && (!is_multiedit)) { - if (blender::animrig::is_autokey_on(scene)) { + if (IS_AUTOKEY_ON(scene)) { gpf = BKE_gpencil_frame_addcopy(gpl, cfra); } /* In some weird situations (frame-lock enabled) return nullptr. */ diff --git a/source/blender/editors/transform/transform_convert_grease_pencil.cc b/source/blender/editors/transform/transform_convert_grease_pencil.cc deleted file mode 100644 index e547e5b7eff..00000000000 --- a/source/blender/editors/transform/transform_convert_grease_pencil.cc +++ /dev/null @@ -1,156 +0,0 @@ -/* SPDX-FileCopyrightText: 2023 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -/** \file - * \ingroup edtransform - */ - -#include "BLI_math_matrix.h" - -#include "BKE_context.h" - -#include "ED_curves.hh" -#include "ED_grease_pencil.hh" - -#include "transform.hh" -#include "transform_convert.hh" - -/* -------------------------------------------------------------------- */ -/** \name Grease Pencil Transform Creation - * \{ */ - -namespace blender::ed::transform::greasepencil { - -static void createTransGreasePencilVerts(bContext *C, TransInfo *t) -{ - Scene *scene = CTX_data_scene(C); - MutableSpan trans_data_contrainers(t->data_container, t->data_container_len); - const bool use_proportional_edit = (t->flag & T_PROP_EDIT_ALL) != 0; - const bool use_connected_only = (t->flag & T_PROP_CONNECTED) != 0; - - int number_of_layers_total = 0; - /* Count the number layers in all objects. */ - for (const int i : trans_data_contrainers.index_range()) { - TransDataContainer &tc = trans_data_contrainers[i]; - GreasePencil &grease_pencil = *static_cast(tc.obedit->data); - - grease_pencil.foreach_editable_drawing( - scene->r.cfra, [&](int /*layer_index*/, bke::greasepencil::Drawing & /*drawing*/) { - number_of_layers_total++; - }); - } - - int layer_offset = 0; - IndexMaskMemory memory; - Array selection_per_layer_per_object(number_of_layers_total); - - /* Count selected elements per layer per object and create TransData structs. */ - for (const int i : trans_data_contrainers.index_range()) { - TransDataContainer &tc = trans_data_contrainers[i]; - GreasePencil &grease_pencil = *static_cast(tc.obedit->data); - - grease_pencil.foreach_editable_drawing( - scene->r.cfra, [&](int /*layer_index*/, bke::greasepencil::Drawing &drawing) { - const bke::CurvesGeometry &curves = drawing.strokes(); - - if (use_proportional_edit) { - tc.data_len += curves.point_num; - } - else { - selection_per_layer_per_object[layer_offset] = ed::curves::retrieve_selected_points( - curves, memory); - tc.data_len += selection_per_layer_per_object[layer_offset].size(); - } - - layer_offset++; - }); - - if (tc.data_len > 0) { - tc.data = MEM_cnew_array(tc.data_len, __func__); - } - } - - /* Reuse the variable `layer_offset` */ - layer_offset = 0; - - /* Populate TransData structs. */ - for (const int i : trans_data_contrainers.index_range()) { - TransDataContainer &tc = trans_data_contrainers[i]; - if (tc.data_len == 0) { - continue; - } - GreasePencil &grease_pencil = *static_cast(tc.obedit->data); - - float mtx[3][3], smtx[3][3]; - copy_m3_m4(mtx, tc.obedit->object_to_world); - pseudoinverse_m3_m3(smtx, mtx, PSEUDOINVERSE_EPSILON); - - int layer_points_offset = 0; - - grease_pencil.foreach_editable_drawing( - scene->r.cfra, [&](int /*layer_index*/, bke::greasepencil::Drawing &drawing) { - bke::CurvesGeometry &curves = drawing.strokes_for_write(); - const IndexMask selected_indices = selection_per_layer_per_object[layer_offset]; - - std::optional> value_attribute; - if (t->mode == TFM_CURVE_SHRINKFATTEN) { - MutableSpan radii = drawing.radii_for_write(); - value_attribute = radii; - } - else if (t->mode == TFM_GPENCIL_OPACITY) { - MutableSpan opacities = drawing.opacities_for_write(); - value_attribute = opacities; - } - - curve_populate_trans_data_structs(tc, - curves, - value_attribute, - selected_indices, - use_proportional_edit, - use_connected_only, - layer_points_offset); - - if (use_proportional_edit) { - layer_points_offset += curves.points_num(); - } - else { - layer_points_offset += selected_indices.size(); - } - layer_offset++; - }); - } -} - -static void recalcData_grease_pencil(TransInfo *t) -{ - bContext *C = t->context; - Scene *scene = CTX_data_scene(C); - - const Span trans_data_contrainers(t->data_container, t->data_container_len); - for (const TransDataContainer &tc : trans_data_contrainers) { - GreasePencil &grease_pencil = *static_cast(tc.obedit->data); - - grease_pencil.foreach_editable_drawing( - scene->r.cfra, [&](int /*layer_index*/, bke::greasepencil::Drawing &drawing) { - bke::CurvesGeometry &curves = drawing.strokes_for_write(); - - curves.calculate_bezier_auto_handles(); - curves.tag_positions_changed(); - drawing.tag_positions_changed(); - }); - - DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY); - } -} - -} // namespace blender::ed::transform::greasepencil - -/** \} */ - -TransConvertTypeInfo TransConvertType_GreasePencil = { - /*flags*/ (T_EDIT | T_POINTS), - /*create_trans_data*/ blender::ed::transform::greasepencil::createTransGreasePencilVerts, - /*recalc_data*/ blender::ed::transform::greasepencil::recalcData_grease_pencil, - /*special_aftertrans_update*/ nullptr, -}; diff --git a/source/blender/editors/transform/transform_convert_mask.cc b/source/blender/editors/transform/transform_convert_mask.cc index 838bcfb1886..f1501edef7c 100644 --- a/source/blender/editors/transform/transform_convert_mask.cc +++ b/source/blender/editors/transform/transform_convert_mask.cc @@ -19,10 +19,9 @@ #include "ED_clip.hh" #include "ED_image.hh" +#include "ED_keyframing.hh" #include "ED_mask.hh" -#include "ANIM_keyframing.hh" - #include "WM_api.hh" #include "WM_types.hh" @@ -454,7 +453,7 @@ static void special_aftertrans_update__mask(bContext *C, TransInfo *t) } /* TODO: don't key all masks. */ - if (blender::animrig::is_autokey_on(t->scene)) { + if (IS_AUTOKEY_ON(t->scene)) { Scene *scene = t->scene; if (ED_mask_layer_shape_auto_key_select(mask, scene->r.cfra)) { diff --git a/source/blender/editors/transform/transform_convert_object.cc b/source/blender/editors/transform/transform_convert_object.cc index eb5b03ccdc5..07800c7308f 100644 --- a/source/blender/editors/transform/transform_convert_object.cc +++ b/source/blender/editors/transform/transform_convert_object.cc @@ -24,7 +24,6 @@ #include "BKE_rigidbody.h" #include "BKE_scene.h" -#include "ANIM_keyframing.hh" #include "ED_keyframing.hh" #include "ED_object.hh" @@ -733,6 +732,135 @@ static void createTransObject(bContext *C, TransInfo *t) /** \name Transform (Auto-Keyframing) * \{ */ +/** + * Auto-keyframing feature - for objects + * + * \param tmode: A transform mode. + * + * \note Context may not always be available, + * so must check before using it as it's a luxury for a few cases. + */ +static void autokeyframe_object( + bContext *C, Scene *scene, ViewLayer *view_layer, Object *ob, int tmode) +{ + Main *bmain = CTX_data_main(C); + ID *id = &ob->id; + + /* TODO: this should probably be done per channel instead. */ + if (autokeyframe_cfra_can_key(scene, id)) { + ReportList *reports = CTX_wm_reports(C); + ToolSettings *ts = scene->toolsettings; + KeyingSet *active_ks = ANIM_scene_get_active_keyingset(scene); + ListBase dsources = {nullptr, nullptr}; + Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); + const AnimationEvalContext anim_eval_context = BKE_animsys_eval_context_construct( + depsgraph, BKE_scene_frame_get(scene)); + eInsertKeyFlags flag = eInsertKeyFlags(0); + + /* Get flags used for inserting keyframes. */ + flag = ANIM_get_keyframing_flags(scene, true); + + /* Add data-source override for the object. */ + ANIM_relative_keyingset_add_source(&dsources, id, nullptr, nullptr); + + if (IS_AUTOKEY_FLAG(scene, ONLYKEYINGSET) && (active_ks)) { + /* Only insert into active keyingset + * NOTE: we assume here that the active Keying Set + * does not need to have its iterator overridden. + */ + ANIM_apply_keyingset( + C, &dsources, nullptr, active_ks, MODIFYKEY_MODE_INSERT, anim_eval_context.eval_time); + } + else if (IS_AUTOKEY_FLAG(scene, INSERTAVAIL)) { + AnimData *adt = ob->adt; + + /* only key on available channels */ + if (adt && adt->action) { + ListBase nla_cache = {nullptr, nullptr}; + LISTBASE_FOREACH (FCurve *, fcu, &adt->action->curves) { + insert_keyframe(bmain, + reports, + id, + adt->action, + (fcu->grp ? fcu->grp->name : nullptr), + fcu->rna_path, + fcu->array_index, + &anim_eval_context, + eBezTriple_KeyframeType(ts->keyframe_type), + &nla_cache, + flag); + } + + BKE_animsys_free_nla_keyframing_context_cache(&nla_cache); + } + } + else if (IS_AUTOKEY_FLAG(scene, INSERTNEEDED)) { + bool do_loc = false, do_rot = false, do_scale = false; + + /* filter the conditions when this happens (assume that curarea->spacetype==SPACE_VIE3D) */ + if (tmode == TFM_TRANSLATION) { + do_loc = true; + } + else if (ELEM(tmode, TFM_ROTATION, TFM_TRACKBALL)) { + if (scene->toolsettings->transform_pivot_point == V3D_AROUND_ACTIVE) { + BKE_view_layer_synced_ensure(scene, view_layer); + if (ob != BKE_view_layer_active_object_get(view_layer)) { + do_loc = true; + } + } + else if (scene->toolsettings->transform_pivot_point == V3D_AROUND_CURSOR) { + do_loc = true; + } + + if ((scene->toolsettings->transform_flag & SCE_XFORM_AXIS_ALIGN) == 0) { + do_rot = true; + } + } + else if (tmode == TFM_RESIZE) { + if (scene->toolsettings->transform_pivot_point == V3D_AROUND_ACTIVE) { + BKE_view_layer_synced_ensure(scene, view_layer); + if (ob != BKE_view_layer_active_object_get(view_layer)) { + do_loc = true; + } + } + else if (scene->toolsettings->transform_pivot_point == V3D_AROUND_CURSOR) { + do_loc = true; + } + + if ((scene->toolsettings->transform_flag & SCE_XFORM_AXIS_ALIGN) == 0) { + do_scale = true; + } + } + + /* insert keyframes for the affected sets of channels using the builtin KeyingSets found */ + if (do_loc) { + KeyingSet *ks = ANIM_builtin_keyingset_get_named(nullptr, ANIM_KS_LOCATION_ID); + ANIM_apply_keyingset( + C, &dsources, nullptr, ks, MODIFYKEY_MODE_INSERT, anim_eval_context.eval_time); + } + if (do_rot) { + KeyingSet *ks = ANIM_builtin_keyingset_get_named(nullptr, ANIM_KS_ROTATION_ID); + ANIM_apply_keyingset( + C, &dsources, nullptr, ks, MODIFYKEY_MODE_INSERT, anim_eval_context.eval_time); + } + if (do_scale) { + KeyingSet *ks = ANIM_builtin_keyingset_get_named(nullptr, ANIM_KS_SCALING_ID); + ANIM_apply_keyingset( + C, &dsources, nullptr, ks, MODIFYKEY_MODE_INSERT, anim_eval_context.eval_time); + } + } + /* insert keyframe in all (transform) channels */ + else { + KeyingSet *ks = ANIM_builtin_keyingset_get_named(nullptr, ANIM_KS_LOC_ROT_SCALE_ID); + ANIM_apply_keyingset( + C, &dsources, nullptr, ks, MODIFYKEY_MODE_INSERT, anim_eval_context.eval_time); + } + + /* free temp info */ + BLI_freelistN(&dsources); + } +} + /* Return if we need to update motion paths, only if they already exist, * and we will insert a keyframe at the end of transform. */ static bool motionpath_need_update_object(Scene *scene, Object *ob) @@ -742,7 +870,7 @@ static bool motionpath_need_update_object(Scene *scene, Object *ob) * this should be a better fix for #24451 and #37755 */ - if (blender::animrig::autokeyframe_cfra_can_key(scene, &ob->id)) { + if (autokeyframe_cfra_can_key(scene, &ob->id)) { return (ob->avs.path_bakeflag & MOTIONPATH_BAKE_HAS_PATHS) != 0; } @@ -778,9 +906,9 @@ static void recalcData_objects(TransInfo *t) */ /* TODO: autokeyframe calls need some setting to specify to add samples * (FPoints) instead of keyframes? */ - if ((t->animtimer) && blender::animrig::is_autokey_on(t->scene)) { + if ((t->animtimer) && IS_AUTOKEY_ON(t->scene)) { animrecord_check_state(t, &ob->id); - blender::animrig::autokeyframe_object(t->context, t->scene, t->view_layer, ob, t->mode); + autokeyframe_object(t->context, t->scene, t->view_layer, ob, t->mode); } motionpath_update |= motionpath_need_update_object(t->scene, ob); @@ -855,7 +983,7 @@ static void special_aftertrans_update__object(bContext *C, TransInfo *t) /* Set auto-key if necessary. */ if (!canceled) { - blender::animrig::autokeyframe_object(C, t->scene, t->view_layer, ob, t->mode); + autokeyframe_object(C, t->scene, t->view_layer, ob, t->mode); } motionpath_update |= motionpath_need_update_object(t->scene, ob); diff --git a/source/blender/editors/transform/transform_convert_sequencer_image.cc b/source/blender/editors/transform/transform_convert_sequencer_image.cc index 79a0739b015..58f34dcc509 100644 --- a/source/blender/editors/transform/transform_convert_sequencer_image.cc +++ b/source/blender/editors/transform/transform_convert_sequencer_image.cc @@ -28,8 +28,6 @@ #include "ED_keyframing.hh" -#include "ANIM_keyframing.hh" - #include "UI_view2d.hh" #include "RNA_access.hh" @@ -179,24 +177,19 @@ static bool autokeyframe_sequencer_image(bContext *C, bool changed = false; if (do_rot) { prop = RNA_struct_find_property(&ptr, "rotation"); - changed |= blender::animrig::autokeyframe_property( - C, scene, &ptr, prop, -1, scene->r.cfra, false); + changed |= ED_autokeyframe_property(C, scene, &ptr, prop, -1, scene->r.cfra, false); } if (do_loc) { prop = RNA_struct_find_property(&ptr, "offset_x"); - changed |= blender::animrig::autokeyframe_property( - C, scene, &ptr, prop, -1, scene->r.cfra, false); + changed |= ED_autokeyframe_property(C, scene, &ptr, prop, -1, scene->r.cfra, false); prop = RNA_struct_find_property(&ptr, "offset_y"); - changed |= blender::animrig::autokeyframe_property( - C, scene, &ptr, prop, -1, scene->r.cfra, false); + changed |= ED_autokeyframe_property(C, scene, &ptr, prop, -1, scene->r.cfra, false); } if (do_scale) { prop = RNA_struct_find_property(&ptr, "scale_x"); - changed |= blender::animrig::autokeyframe_property( - C, scene, &ptr, prop, -1, scene->r.cfra, false); + changed |= ED_autokeyframe_property(C, scene, &ptr, prop, -1, scene->r.cfra, false); prop = RNA_struct_find_property(&ptr, "scale_y"); - changed |= blender::animrig::autokeyframe_property( - C, scene, &ptr, prop, -1, scene->r.cfra, false); + changed |= ED_autokeyframe_property(C, scene, &ptr, prop, -1, scene->r.cfra, false); } return changed; @@ -254,7 +247,7 @@ static void recalcData_sequencer_image(TransInfo *t) transform->rotation = tdseq->orig_rotation - t->values_final[0]; } - if ((t->animtimer) && blender::animrig::is_autokey_on(t->scene)) { + if ((t->animtimer) && IS_AUTOKEY_ON(t->scene)) { animrecord_check_state(t, &t->scene->id); autokeyframe_sequencer_image(t->context, t->scene, transform, t->mode); } @@ -282,7 +275,7 @@ static void special_aftertrans_update__sequencer_image(bContext * /*C*/, TransIn continue; } - if (blender::animrig::is_autokey_on(t->scene)) { + if (IS_AUTOKEY_ON(t->scene)) { autokeyframe_sequencer_image(t->context, t->scene, transform, t->mode); } } diff --git a/source/blender/editors/transform/transform_generics.cc b/source/blender/editors/transform/transform_generics.cc index 989a636b900..f39f3b376fc 100644 --- a/source/blender/editors/transform/transform_generics.cc +++ b/source/blender/editors/transform/transform_generics.cc @@ -230,11 +230,6 @@ void initTransInfo(bContext *C, TransInfo *t, wmOperator *op, const wmEvent *eve t->options |= CTX_GPENCIL_STROKES; } - /* Grease Pencil editing context */ - if (t->obedit_type == OB_GREASE_PENCIL && object_mode == OB_MODE_EDIT) { - t->options |= CTX_GPENCIL_STROKES; - } - /* Assign the space type, some exceptions for running in different mode */ if (area == nullptr) { /* background mode */ diff --git a/source/blender/editors/transform/transform_gizmo_3d.cc b/source/blender/editors/transform/transform_gizmo_3d.cc index 399c13de23e..b2abfb91e7d 100644 --- a/source/blender/editors/transform/transform_gizmo_3d.cc +++ b/source/blender/editors/transform/transform_gizmo_3d.cc @@ -10,7 +10,6 @@ * Used for 3D View */ -#include "BLI_array_utils.h" #include "BLI_function_ref.hh" #include "BLI_math_geom.h" #include "BLI_math_matrix.h" @@ -27,12 +26,12 @@ #include "BKE_editmesh.h" #include "BKE_global.h" #include "BKE_gpencil_legacy.h" -#include "BKE_grease_pencil.hh" #include "BKE_layer.h" #include "BKE_object.hh" #include "BKE_paint.hh" #include "BKE_pointcache.h" #include "BKE_scene.h" +#include "BLI_array_utils.h" #include "WM_api.hh" #include "WM_message.hh" @@ -42,7 +41,6 @@ #include "ED_gizmo_library.hh" #include "ED_gizmo_utils.hh" #include "ED_gpencil_legacy.hh" -#include "ED_grease_pencil.hh" #include "ED_object.hh" #include "ED_particle.hh" #include "ED_screen.hh" @@ -809,35 +807,6 @@ static int gizmo_3d_foreach_selected(const bContext *C, } FOREACH_EDIT_OBJECT_END(); } - else if (obedit->type == OB_GREASE_PENCIL) { - FOREACH_EDIT_OBJECT_BEGIN (ob_iter, use_mat_local) { - GreasePencil &grease_pencil = *static_cast(ob_iter->data); - - float4x4 mat_local; - if (use_mat_local) { - mat_local = float4x4(obedit->world_to_object) * float4x4(ob_iter->object_to_world); - } - - grease_pencil.foreach_editable_drawing( - scene->r.cfra, [&](int layer_index, blender::bke::greasepencil::Drawing &drawing) { - const bke::CurvesGeometry &curves = drawing.strokes(); - - const bke::crazyspace::GeometryDeformation deformation = - bke::crazyspace::get_evaluated_grease_pencil_drawing_deformation( - *depsgraph, *ob, layer_index); - - IndexMaskMemory memory; - const IndexMask selected_points = ed::curves::retrieve_selected_points(curves, - memory); - const Span positions = deformation.positions; - totsel += selected_points.size(); - selected_points.foreach_index([&](const int point_i) { - run_coord_with_matrix(positions[point_i], use_mat_local, mat_local.ptr()); - }); - }); - } - FOREACH_EDIT_OBJECT_END(); - } #undef FOREACH_EDIT_OBJECT_BEGIN #undef FOREACH_EDIT_OBJECT_END @@ -929,12 +898,12 @@ static int gizmo_3d_foreach_selected(const bContext *C, } /* Get the boundbox out of the evaluated object. */ - std::optional bb; + const BoundBox *bb = nullptr; if (use_only_center == false) { bb = BKE_object_boundbox_get(base->object); } - if (use_only_center || !bb) { + if (use_only_center || (bb == nullptr)) { user_fn(base->object->object_to_world[3]); } else { diff --git a/source/blender/editors/transform/transform_mode.cc b/source/blender/editors/transform/transform_mode.cc index d89b9b1b6c1..d9094a91744 100644 --- a/source/blender/editors/transform/transform_mode.cc +++ b/source/blender/editors/transform/transform_mode.cc @@ -566,17 +566,11 @@ void ElementRotation_ex(const TransInfo *t, /* Apply gpencil falloff. */ if (t->options & CTX_GPENCIL_STROKES) { - if (t->obedit_type == OB_GPENCIL_LEGACY) { - - bGPDstroke *gps = (bGPDstroke *)td->extra; - if (gps->runtime.multi_frame_falloff != 1.0f) { - float ident_mat[3][3]; - unit_m3(ident_mat); - interp_m3_m3m3(smat, ident_mat, smat, gps->runtime.multi_frame_falloff); - } - } - else if (t->obedit_type == OB_GREASE_PENCIL) { - /* pass */ + bGPDstroke *gps = (bGPDstroke *)td->extra; + if (gps->runtime.multi_frame_falloff != 1.0f) { + float ident_mat[3][3]; + unit_m3(ident_mat); + interp_m3_m3m3(smat, ident_mat, smat, gps->runtime.multi_frame_falloff); } } @@ -1036,26 +1030,21 @@ void ElementResize(const TransInfo *t, * Operating on copies as a temporary solution. */ if (t->options & CTX_GPENCIL_STROKES) { - if (t->obedit_type == OB_GPENCIL_LEGACY) { - bGPDstroke *gps = (bGPDstroke *)td->extra; - mul_v3_fl(vec, td->factor * gps->runtime.multi_frame_falloff); + bGPDstroke *gps = (bGPDstroke *)td->extra; + mul_v3_fl(vec, td->factor * gps->runtime.multi_frame_falloff); - /* Scale stroke thickness. */ - if (td->val) { - NumInput num_evil = t->num; - float values_final_evil[4]; - copy_v4_v4(values_final_evil, t->values_final); - transform_snap_increment(t, values_final_evil); - applyNumInput(&num_evil, values_final_evil); + /* Scale stroke thickness. */ + if (td->val) { + NumInput num_evil = t->num; + float values_final_evil[4]; + copy_v4_v4(values_final_evil, t->values_final); + transform_snap_increment(t, values_final_evil); + applyNumInput(&num_evil, values_final_evil); - float ratio = values_final_evil[0]; - float transformed_value = td->ival * fabs(ratio); - *td->val = max_ff(interpf(transformed_value, td->ival, gps->runtime.multi_frame_falloff), - 0.001f); - } - } - else if (t->obedit_type == OB_GREASE_PENCIL) { - mul_v3_fl(vec, td->factor); + float ratio = values_final_evil[0]; + float transformed_value = td->ival * fabs(ratio); + *td->val = max_ff(interpf(transformed_value, td->ival, gps->runtime.multi_frame_falloff), + 0.001f); } } else { diff --git a/source/blender/editors/transform/transform_mode_edge_rotate_normal.cc b/source/blender/editors/transform/transform_mode_edge_rotate_normal.cc index f1484ed41c4..ce341d57d70 100644 --- a/source/blender/editors/transform/transform_mode_edge_rotate_normal.cc +++ b/source/blender/editors/transform/transform_mode_edge_rotate_normal.cc @@ -131,7 +131,8 @@ static void initNormalRotation(TransInfo *t, wmOperator * /*op*/) BMEditMesh *em = BKE_editmesh_from_object(tc->obedit); BMesh *bm = em->bm; - BKE_editmesh_lnorspace_update(em); + BKE_editmesh_ensure_autosmooth(em, static_cast(tc->obedit->data)); + BKE_editmesh_lnorspace_update(em, static_cast(tc->obedit->data)); storeCustomLNorValue(tc, bm); } diff --git a/source/blender/editors/transform/transform_mode_edge_slide.cc b/source/blender/editors/transform/transform_mode_edge_slide.cc index a0800c5ba3a..237599bb809 100644 --- a/source/blender/editors/transform/transform_mode_edge_slide.cc +++ b/source/blender/editors/transform/transform_mode_edge_slide.cc @@ -241,7 +241,7 @@ static BMLoop *get_next_loop( BM_loop_calc_face_direction(l_tmp, tdir); cross_v3_v3v3(vec_accum, l_tmp->f->no, tdir); #if 0 - /* Rough guess, we can do better! */ + /* rough guess, we can do better! */ normalize_v3_length(vec_accum, (BM_edge_calc_length(e_prev) + BM_edge_calc_length(e_next)) / 2.0f); #else diff --git a/source/blender/editors/transform/transform_mode_gpopacity.cc b/source/blender/editors/transform/transform_mode_gpopacity.cc index e92a3831877..0a645af79ac 100644 --- a/source/blender/editors/transform/transform_mode_gpopacity.cc +++ b/source/blender/editors/transform/transform_mode_gpopacity.cc @@ -60,16 +60,10 @@ static void applyGPOpacity(TransInfo *t) bool recalc = false; FOREACH_TRANS_DATA_CONTAINER (t, tc) { TransData *td = tc->data; - - if (t->obedit_type == OB_GPENCIL_LEGACY) { - bGPdata *gpd = static_cast(td->ob->data); - const bool is_curve_edit = bool(GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd)); - /* Only recalculate data when in curve edit mode. */ - if (is_curve_edit) { - recalc = true; - } - } - else if (t->obedit_type == OB_GREASE_PENCIL) { + bGPdata *gpd = static_cast(td->ob->data); + const bool is_curve_edit = bool(GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd)); + /* Only recalculate data when in curve edit mode. */ + if (is_curve_edit) { recalc = true; } diff --git a/source/blender/editors/transform/transform_orientations.cc b/source/blender/editors/transform/transform_orientations.cc index 94d0c538e65..d6b6dbab41a 100644 --- a/source/blender/editors/transform/transform_orientations.cc +++ b/source/blender/editors/transform/transform_orientations.cc @@ -28,7 +28,7 @@ #include "BLI_math_rotation.h" #include "BLI_math_vector.h" #include "BLI_string.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_utildefines.h" #include "BKE_action.h" diff --git a/source/blender/editors/transform/transform_snap.cc b/source/blender/editors/transform/transform_snap.cc index 1aef7c14dd0..136bf630cbf 100644 --- a/source/blender/editors/transform/transform_snap.cc +++ b/source/blender/editors/transform/transform_snap.cc @@ -1333,7 +1333,7 @@ static void snap_source_closest_fn(TransInfo *t) FOREACH_TRANS_DATA_CONTAINER (t, tc) { TransData *td; for (td = tc->data, i = 0; i < tc->data_len && td->flag & TD_SELECTED; i++, td++) { - std::optional bb; + const BoundBox *bb = nullptr; if ((t->options & CTX_OBMODE_XFORM_OBDATA) == 0) { bb = BKE_object_boundbox_get(td->ob); diff --git a/source/blender/editors/util/ed_viewer_path.cc b/source/blender/editors/util/ed_viewer_path.cc index a0edcd5d984..f7b47a68781 100644 --- a/source/blender/editors/util/ed_viewer_path.cc +++ b/source/blender/editors/util/ed_viewer_path.cc @@ -5,7 +5,6 @@ #include "ED_viewer_path.hh" #include "ED_screen.hh" -#include "BKE_compute_contexts.hh" #include "BKE_context.h" #include "BKE_main.h" #include "BKE_node_runtime.hh" @@ -455,37 +454,4 @@ bNode *find_geometry_nodes_viewer(const ViewerPath &viewer_path, SpaceNode &snod return nullptr; } -[[nodiscard]] bool add_compute_context_for_viewer_path_elem( - const ViewerPathElem &elem_generic, ComputeContextBuilder &compute_context_builder) -{ - switch (ViewerPathElemType(elem_generic.type)) { - case VIEWER_PATH_ELEM_TYPE_VIEWER_NODE: - case VIEWER_PATH_ELEM_TYPE_ID: { - return false; - } - case VIEWER_PATH_ELEM_TYPE_MODIFIER: { - const auto &elem = reinterpret_cast(elem_generic); - compute_context_builder.push(elem.modifier_name); - return true; - } - case VIEWER_PATH_ELEM_TYPE_GROUP_NODE: { - const auto &elem = reinterpret_cast(elem_generic); - compute_context_builder.push(elem.node_id); - return true; - } - case VIEWER_PATH_ELEM_TYPE_SIMULATION_ZONE: { - const auto &elem = reinterpret_cast(elem_generic); - compute_context_builder.push(elem.sim_output_node_id); - return true; - } - case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE: { - const auto &elem = reinterpret_cast(elem_generic); - compute_context_builder.push(elem.repeat_output_node_id, - elem.iteration); - return true; - } - } - return false; -} - } // namespace blender::ed::viewer_path diff --git a/source/blender/editors/uvedit/uvedit_unwrap_ops.cc b/source/blender/editors/uvedit/uvedit_unwrap_ops.cc index 71d6e2d5b02..640c8aada98 100644 --- a/source/blender/editors/uvedit/uvedit_unwrap_ops.cc +++ b/source/blender/editors/uvedit/uvedit_unwrap_ops.cc @@ -607,7 +607,7 @@ static ParamHandle *construct_param_handle_subsurfed(const Scene *scene, { /* pointers to modifier data for unwrap control */ SubsurfModifierData *smd_real; - /* Modifier initialization data, will control what type of subdivision will happen. */ + /* Modifier initialization data, will control what type of subdivision will happen. */ SubsurfModifierData smd = {{nullptr}}; /* Holds a map to edit-faces for every subdivision-surface face. @@ -1403,15 +1403,17 @@ struct UVPackIslandsData { blender::geometry::UVPackIsland_Params pack_island_params; }; -static void pack_islands_startjob(void *pidv, wmJobWorkerStatus *worker_status) +static void pack_islands_startjob(void *pidv, bool *stop, bool *do_update, float *progress) { - worker_status->progress = 0.02f; + if (progress != nullptr) { + *progress = 0.02f; + } UVPackIslandsData *pid = static_cast(pidv); - pid->pack_island_params.stop = &worker_status->stop; - pid->pack_island_params.do_update = &worker_status->do_update; - pid->pack_island_params.progress = &worker_status->progress; + pid->pack_island_params.stop = stop; + pid->pack_island_params.do_update = do_update; + pid->pack_island_params.progress = progress; uvedit_pack_islands_multi(pid->scene, pid->objects, @@ -1422,8 +1424,12 @@ static void pack_islands_startjob(void *pidv, wmJobWorkerStatus *worker_status) !pid->use_job, &pid->pack_island_params); - worker_status->progress = 0.99f; - worker_status->do_update = true; + if (progress != nullptr) { + *progress = 0.99f; + } + if (do_update != nullptr) { + *do_update = true; + } } static void pack_islands_endjob(void *pidv) @@ -1548,8 +1554,7 @@ static int pack_islands_exec(bContext *C, wmOperator *op) return OPERATOR_FINISHED; } - wmJobWorkerStatus worker_status = {}; - pack_islands_startjob(pid, &worker_status); + pack_islands_startjob(pid, nullptr, nullptr, nullptr); pack_islands_endjob(pid); pack_islands_freejob(pid); diff --git a/source/blender/freestyle/intern/blender_interface/BlenderFileLoader.cpp b/source/blender/freestyle/intern/blender_interface/BlenderFileLoader.cpp index 2d0544e3bd2..d38f424ce14 100644 --- a/source/blender/freestyle/intern/blender_interface/BlenderFileLoader.cpp +++ b/source/blender/freestyle/intern/blender_interface/BlenderFileLoader.cpp @@ -422,7 +422,14 @@ void BlenderFileLoader::insertShapeNode(Object *ob, Mesh *me, int id) MLoopTri *mlooptri = (MLoopTri *)MEM_malloc_arrayN(tottri, sizeof(*mlooptri), __func__); blender::bke::mesh::looptris_calc(vert_positions, mesh_polys, corner_verts, {mlooptri, tottri}); const blender::Span looptri_faces = me->looptri_faces(); - const blender::Span lnors = me->corner_normals(); + + // Compute loop normals + BKE_mesh_calc_normals_split(me); + const float(*lnors)[3] = nullptr; + + if (CustomData_has_layer(&me->loop_data, CD_NORMAL)) { + lnors = (const float(*)[3])CustomData_get_layer(&me->loop_data, CD_NORMAL); + } // Get other mesh data const FreestyleEdge *fed = (const FreestyleEdge *)CustomData_get_layer(&me->edge_data, @@ -539,7 +546,7 @@ void BlenderFileLoader::insertShapeNode(Object *ob, Mesh *me, int id) v2[2] += _z_offset; v3[2] += _z_offset; - if (_smooth && (!sharp_faces[poly_i])) { + if (_smooth && (!sharp_faces[poly_i]) && lnors) { copy_v3_v3(n1, lnors[lt->tri[0]]); copy_v3_v3(n2, lnors[lt->tri[1]]); copy_v3_v3(n3, lnors[lt->tri[2]]); diff --git a/source/blender/freestyle/intern/geometry/Grid.h b/source/blender/freestyle/intern/geometry/Grid.h index 7655690bde4..85fa0bebcc1 100644 --- a/source/blender/freestyle/intern/geometry/Grid.h +++ b/source/blender/freestyle/intern/geometry/Grid.h @@ -357,7 +357,7 @@ class Grid { } while ((!visitor.stop()) && (nextRayCell(_current_cell, _current_cell))); } - /** Returns the cell next to the cell passed as argument. */ + /** returns the cell next to the cell passed as argument. */ bool nextRayCell(Vec3u ¤t_cell, Vec3u &next_cell); uint _timestamp; diff --git a/source/blender/freestyle/intern/stroke/Curve.cpp b/source/blender/freestyle/intern/stroke/Curve.cpp index bd7a0073229..2f0b614adad 100644 --- a/source/blender/freestyle/intern/stroke/Curve.cpp +++ b/source/blender/freestyle/intern/stroke/Curve.cpp @@ -553,7 +553,7 @@ Vec2d CurvePoint::directionFredo() const /* */ /**********************************/ -/* for functions */ +/* for functions */ Curve::~Curve() { diff --git a/source/blender/freestyle/intern/system/PointerSequence.h b/source/blender/freestyle/intern/system/PointerSequence.h index 3dd224fb02c..c3b17eed93a 100644 --- a/source/blender/freestyle/intern/system/PointerSequence.h +++ b/source/blender/freestyle/intern/system/PointerSequence.h @@ -30,20 +30,16 @@ * template parameter. If anyone knows a way around this limitation, then the second parameter can * be eliminated. * - * Example: - * \code{.cc} - * PointerSequence, Widget*> v; - * v.push_back(new Widget); - * cout << v[0] << endl; // operator[] is provided by std::vector, not by PointerSequence - * v.destroy(); // Deletes all pointers in sequence and sets them to nullptr. - * \endcode + * Example: + * PointerSequence, Widget*> v; + * v.push_back(new Widget); + * cout << v[0] << endl; // operator[] is provided by std::vector, not by PointerSequence + * v.destroy(); // Deletes all pointers in sequence and sets them to nullptr. * - * The idiom for removing a pointer from a sequence is: - * \code{.cc} - * Widget* w = v[3]; - * v.erase(v.begin() + 3); // or v[3] = 0; - * \endcode - * The user is now responsible for disposing of `w` properly. + * The idiom for removing a pointer from a sequence is: + * Widget* w = v[3]; + * v.erase(v.begin() + 3); // or v[3] = 0; + * The user is now responsible for disposing of w properly. */ #include diff --git a/source/blender/freestyle/intern/view_map/ViewMap.h b/source/blender/freestyle/intern/view_map/ViewMap.h index d6c108007a7..8deca28dd5d 100644 --- a/source/blender/freestyle/intern/view_map/ViewMap.h +++ b/source/blender/freestyle/intern/view_map/ViewMap.h @@ -692,19 +692,19 @@ class NonTVertex : public ViewVertex { return _SVertex->getPoint3D(); } - /** Returns the projected 3D. Y coordinate of the vertex. */ + /** Returns the projected 3D x coordinate of the vertex. */ virtual real getProjectedX() const { return _SVertex->point2D().x(); } - /** Returns the projected 3D. Y coordinate of the vertex. */ + /** Returns the projected 3D y coordinate of the vertex. */ virtual real getProjectedY() const { return _SVertex->point2D().y(); } - /** Returns the projected 3D. Z coordinate of the vertex. */ + /** Returns the projected 3D z coordinate of the vertex. */ virtual real getProjectedZ() const { return _SVertex->point2D().z(); diff --git a/source/blender/functions/FN_lazy_function_execute.hh b/source/blender/functions/FN_lazy_function_execute.hh index 267a5da5825..f75f61b8afd 100644 --- a/source/blender/functions/FN_lazy_function_execute.hh +++ b/source/blender/functions/FN_lazy_function_execute.hh @@ -46,34 +46,6 @@ class BasicParams : public Params { bool try_enable_multi_threading_impl() override; }; -/** - * Wraps an existing #Params. This should be used when a lazy-function internally contains another - * lazy-function that handles a subset or the inputs and outputs. - */ -class RemappedParams : public Params { - private: - Params &base_params_; - Span input_map_; - Span output_map_; - bool &multi_threading_enabled_; - - public: - RemappedParams(const LazyFunction &fn, - Params &base_params, - Span input_map, - Span output_map, - bool &multi_threading_enabled); - - void *try_get_input_data_ptr_impl(const int index) const override; - void *try_get_input_data_ptr_or_request_impl(const int index) override; - void *get_output_data_ptr_impl(const int index) override; - void output_set_impl(const int index) override; - bool output_was_set_impl(const int index) const override; - ValueUsage get_output_usage_impl(const int index) const override; - void set_input_unused_impl(const int index) override; - bool try_enable_multi_threading_impl() override; -}; - namespace detail { /** diff --git a/source/blender/functions/FN_lazy_function_graph.hh b/source/blender/functions/FN_lazy_function_graph.hh index 6c5090adb63..52d0eba1280 100644 --- a/source/blender/functions/FN_lazy_function_graph.hh +++ b/source/blender/functions/FN_lazy_function_graph.hh @@ -239,9 +239,6 @@ class Graph : NonCopyable, NonMovable { Span function_nodes() const; Span function_nodes(); - Span graph_inputs(); - Span graph_outputs(); - Span graph_inputs() const; Span graph_outputs() const; @@ -509,16 +506,6 @@ inline Span Graph::function_nodes() return nodes_.as_span().drop_front(2).cast(); } -inline Span Graph::graph_inputs() -{ - return graph_inputs_; -} - -inline Span Graph::graph_outputs() -{ - return graph_outputs_; -} - inline Span Graph::graph_inputs() const { return graph_inputs_; diff --git a/source/blender/functions/intern/lazy_function_execute.cc b/source/blender/functions/intern/lazy_function_execute.cc index eb446cbbea7..f50621d66ac 100644 --- a/source/blender/functions/intern/lazy_function_execute.cc +++ b/source/blender/functions/intern/lazy_function_execute.cc @@ -10,10 +10,6 @@ namespace blender::fn::lazy_function { -/* -------------------------------------------------------------------- */ -/** \name BasicParams. - * \{ */ - BasicParams::BasicParams(const LazyFunction &fn, const Span inputs, const Span outputs, @@ -73,72 +69,4 @@ bool BasicParams::try_enable_multi_threading_impl() return true; } -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name RemappedParams. - * \{ */ - -RemappedParams::RemappedParams(const LazyFunction &fn, - Params &base_params, - const Span input_map, - const Span output_map, - bool &multi_threading_enabled) - : Params(fn, multi_threading_enabled), - base_params_(base_params), - input_map_(input_map), - output_map_(output_map), - multi_threading_enabled_(multi_threading_enabled) -{ -} - -void *RemappedParams::try_get_input_data_ptr_impl(const int index) const -{ - return base_params_.try_get_input_data_ptr(input_map_[index]); -} - -void *RemappedParams::try_get_input_data_ptr_or_request_impl(const int index) -{ - return base_params_.try_get_input_data_ptr_or_request(input_map_[index]); -} - -void *RemappedParams::get_output_data_ptr_impl(const int index) -{ - return base_params_.get_output_data_ptr(output_map_[index]); -} - -void RemappedParams::output_set_impl(const int index) -{ - return base_params_.output_set(output_map_[index]); -} - -bool RemappedParams::output_was_set_impl(const int index) const -{ - return base_params_.output_was_set(output_map_[index]); -} - -lf::ValueUsage RemappedParams::get_output_usage_impl(const int index) const -{ - return base_params_.get_output_usage(output_map_[index]); -} - -void RemappedParams::set_input_unused_impl(const int index) -{ - return base_params_.set_input_unused(input_map_[index]); -} - -bool RemappedParams::try_enable_multi_threading_impl() -{ - if (multi_threading_enabled_) { - return true; - } - if (base_params_.try_enable_multi_threading()) { - multi_threading_enabled_ = true; - return true; - } - return false; -} - -/** \} */ - } // namespace blender::fn::lazy_function diff --git a/source/blender/functions/intern/lazy_function_graph_executor.cc b/source/blender/functions/intern/lazy_function_graph_executor.cc index e5a962eadc4..69780881f16 100644 --- a/source/blender/functions/intern/lazy_function_graph_executor.cc +++ b/source/blender/functions/intern/lazy_function_graph_executor.cc @@ -250,25 +250,6 @@ struct ScheduledNodes { { return this->priority_.is_empty() && this->normal_.is_empty(); } - - int64_t nodes_num() const - { - return priority_.size() + normal_.size(); - } - - /** - * Split up the scheduled nodes into two groups that can be worked on in parallel. - */ - void split_into(ScheduledNodes &other) - { - BLI_assert(this != &other); - const int64_t priority_split = priority_.size() / 2; - const int64_t normal_split = normal_.size() / 2; - other.priority_.extend(priority_.as_span().drop_front(priority_split)); - other.normal_.extend(normal_.as_span().drop_front(normal_split)); - priority_.resize(priority_split); - normal_.resize(normal_split); - } }; struct CurrentTask { @@ -813,16 +794,6 @@ class Executor { current_task.has_scheduled_nodes.store(false, std::memory_order_relaxed); } this->run_node_task(*node, current_task, local_data); - - /* If there are many nodes scheduled at the same time, it's beneficial to let multiple - * threads work on those. */ - if (current_task.scheduled_nodes.nodes_num() > 128) { - if (this->try_enable_multi_threading()) { - std::unique_ptr split_nodes = std::make_unique(); - current_task.scheduled_nodes.split_into(*split_nodes); - this->push_to_task_pool(std::move(split_nodes)); - } - } } } @@ -1258,10 +1229,10 @@ class Executor { /** * Allow other threads to steal all the nodes that are currently scheduled on this thread. */ - void push_all_scheduled_nodes_to_task_pool(CurrentTask ¤t_task) + void move_scheduled_nodes_to_task_pool(CurrentTask ¤t_task) { BLI_assert(this->use_multi_threading()); - std::unique_ptr scheduled_nodes = std::make_unique(); + ScheduledNodes *scheduled_nodes = MEM_new(__func__); { std::lock_guard lock{current_task.mutex}; if (current_task.scheduled_nodes.is_empty()) { @@ -1270,11 +1241,6 @@ class Executor { *scheduled_nodes = std::move(current_task.scheduled_nodes); current_task.has_scheduled_nodes.store(false, std::memory_order_relaxed); } - this->push_to_task_pool(std::move(scheduled_nodes)); - } - - void push_to_task_pool(std::unique_ptr scheduled_nodes) - { /* All nodes are pushed as a single task in the pool. This avoids unnecessary threading * overhead when the nodes are fast to compute. */ BLI_task_pool_push( @@ -1288,9 +1254,9 @@ class Executor { const LocalData local_data = executor.get_local_data(); executor.run_task(new_current_task, local_data); }, - scheduled_nodes.release(), + scheduled_nodes, true, - [](TaskPool * /*pool*/, void *data) { delete static_cast(data); }); + [](TaskPool * /*pool*/, void *data) { MEM_delete(static_cast(data)); }); } LocalData get_local_data() @@ -1444,7 +1410,7 @@ inline void Executor::execute_node(const FunctionNode &node, if (!this->try_enable_multi_threading()) { return; } - this->push_all_scheduled_nodes_to_task_pool(current_task); + this->move_scheduled_nodes_to_task_pool(current_task); }; lazy_threading::HintReceiver blocking_hint_receiver{blocking_hint_fn}; diff --git a/source/blender/geometry/CMakeLists.txt b/source/blender/geometry/CMakeLists.txt index 4ccc6c0ce16..eda7767b8ab 100644 --- a/source/blender/geometry/CMakeLists.txt +++ b/source/blender/geometry/CMakeLists.txt @@ -18,7 +18,6 @@ set(SRC intern/add_curves_on_mesh.cc intern/curve_constraints.cc intern/fillet_curves.cc - intern/join_geometries.cc intern/mesh_copy_selection.cc intern/mesh_merge_by_distance.cc intern/mesh_primitive_cuboid.cc @@ -44,7 +43,6 @@ set(SRC GEO_add_curves_on_mesh.hh GEO_curve_constraints.hh GEO_fillet_curves.hh - GEO_join_geometries.hh GEO_mesh_copy_selection.hh GEO_mesh_merge_by_distance.hh GEO_mesh_primitive_cuboid.hh diff --git a/source/blender/geometry/GEO_join_geometries.hh b/source/blender/geometry/GEO_join_geometries.hh deleted file mode 100644 index 73991740489..00000000000 --- a/source/blender/geometry/GEO_join_geometries.hh +++ /dev/null @@ -1,15 +0,0 @@ -/* SPDX-FileCopyrightText: 2023 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -#pragma once - -#include "BKE_anonymous_attribute_id.hh" -#include "BKE_geometry_set.hh" - -namespace blender::geometry { - -bke::GeometrySet join_geometries(Span geometries, - const bke::AnonymousAttributePropagationInfo &propagation_info); - -} diff --git a/source/blender/geometry/GEO_mesh_copy_selection.hh b/source/blender/geometry/GEO_mesh_copy_selection.hh index 6ad71e66e59..69bdc4d34cd 100644 --- a/source/blender/geometry/GEO_mesh_copy_selection.hh +++ b/source/blender/geometry/GEO_mesh_copy_selection.hh @@ -24,19 +24,19 @@ namespace blender::geometry { std::optional mesh_copy_selection( const Mesh &src_mesh, - const VArray &selection, + const fn::Field &selection, eAttrDomain selection_domain, const bke::AnonymousAttributePropagationInfo &propagation_info); std::optional mesh_copy_selection_keep_verts( const Mesh &src_mesh, - const VArray &selection, + const fn::Field &selection, eAttrDomain selection_domain, const bke::AnonymousAttributePropagationInfo &propagation_info); std::optional mesh_copy_selection_keep_edges( const Mesh &mesh, - const VArray &selection, + const fn::Field &selection, eAttrDomain selection_domain, const bke::AnonymousAttributePropagationInfo &propagation_info); diff --git a/source/blender/geometry/GEO_randomize.hh b/source/blender/geometry/GEO_randomize.hh index 7c85218d592..4727b43b3a8 100644 --- a/source/blender/geometry/GEO_randomize.hh +++ b/source/blender/geometry/GEO_randomize.hh @@ -8,19 +8,17 @@ struct Mesh; struct PointCloud; namespace blender::bke { class CurvesGeometry; -class Instances; -} // namespace blender::bke +} namespace blender::geometry { bool use_debug_randomization(); -void debug_randomize_vert_order(Mesh *mesh); +void debug_randomize_vertex_order(Mesh *mesh); void debug_randomize_edge_order(Mesh *mesh); void debug_randomize_face_order(Mesh *mesh); void debug_randomize_mesh_order(Mesh *mesh); void debug_randomize_point_order(PointCloud *pointcloud); void debug_randomize_curve_order(bke::CurvesGeometry *curves); -void debug_randomize_instance_order(bke::Instances *instances); }; // namespace blender::geometry diff --git a/source/blender/geometry/GEO_resample_curves.hh b/source/blender/geometry/GEO_resample_curves.hh index 69eadf56da5..de25669cd21 100644 --- a/source/blender/geometry/GEO_resample_curves.hh +++ b/source/blender/geometry/GEO_resample_curves.hh @@ -26,7 +26,6 @@ struct ResampleCurvesOutputAttributeIDs { * \note The values provided by the #count_field are clamped to 1 or greater. */ CurvesGeometry resample_to_count(const CurvesGeometry &src_curves, - const fn::FieldContext &field_context, const fn::Field &selection_field, const fn::Field &count_field, const ResampleCurvesOutputAttributeIDs &output_ids = {}); @@ -37,7 +36,6 @@ CurvesGeometry resample_to_count(const CurvesGeometry &src_curves, * The accuracy will depend on the curve's resolution parameter. */ CurvesGeometry resample_to_length(const CurvesGeometry &src_curves, - const fn::FieldContext &field_context, const fn::Field &selection_field, const fn::Field &segment_length_field, const ResampleCurvesOutputAttributeIDs &output_ids = {}); @@ -46,7 +44,6 @@ CurvesGeometry resample_to_length(const CurvesGeometry &src_curves, * Evaluate each selected curve to its implicit evaluated points. */ CurvesGeometry resample_to_evaluated(const CurvesGeometry &src_curves, - const fn::FieldContext &field_context, const fn::Field &selection_field, const ResampleCurvesOutputAttributeIDs &output_ids = {}); diff --git a/source/blender/geometry/GEO_uv_pack.hh b/source/blender/geometry/GEO_uv_pack.hh index 5133406a544..91078cb6eb5 100644 --- a/source/blender/geometry/GEO_uv_pack.hh +++ b/source/blender/geometry/GEO_uv_pack.hh @@ -113,7 +113,7 @@ class UVPackIsland_Params { float *progress; }; -class UVPhi; +class uv_phi; class PackIsland { public: PackIsland(); @@ -130,7 +130,7 @@ class PackIsland { int caller_index; void add_triangle(const float2 uv0, const float2 uv1, const float2 uv2); - void add_polygon(const Span uvs, MemArena *arena, Heap *heap); + void add_polygon(const blender::Span uvs, MemArena *arena, Heap *heap); void build_transformation(const float scale, const double rotation, float r_matrix[2][2]) const; void build_inverse_transformation(const float scale, @@ -148,14 +148,14 @@ class PackIsland { float2 half_diagonal_; float pre_rotate_; - void place_(const float scale, const UVPhi phi); + void place_(const float scale, const uv_phi phi); void finalize_geometry_(const UVPackIsland_Params ¶ms, MemArena *arena, Heap *heap); bool can_rotate_(const UVPackIsland_Params ¶ms) const; bool can_scale_(const UVPackIsland_Params ¶ms) const; bool can_translate_(const UVPackIsland_Params ¶ms) const; - Vector triangle_vertices_; + blender::Vector triangle_vertices_; private: void calculate_pivot_(); /* Calculate `pivot_` and `half_diagonal_` based on added triangles. */ diff --git a/source/blender/geometry/intern/fillet_curves.cc b/source/blender/geometry/intern/fillet_curves.cc index 6867c058a94..4066704d32b 100644 --- a/source/blender/geometry/intern/fillet_curves.cc +++ b/source/blender/geometry/intern/fillet_curves.cc @@ -533,14 +533,15 @@ static bke::CurvesGeometry fillet_curves( attribute.dst.finish(); } - bke::copy_attributes_group_to_group(src_attributes, - ATTR_DOMAIN_POINT, - propagation_info, - {}, - src_points_by_curve, - dst_points_by_curve, - unselected, - dst_attributes); + if (!unselected.is_empty()) { + for (auto &attribute : bke::retrieve_attributes_for_transfer( + src_attributes, dst_attributes, ATTR_DOMAIN_MASK_POINT, propagation_info)) + { + bke::curves::copy_point_data( + src_points_by_curve, dst_points_by_curve, unselected, attribute.src, attribute.dst.span); + attribute.dst.finish(); + } + } return dst_curves; } diff --git a/source/blender/geometry/intern/join_geometries.cc b/source/blender/geometry/intern/join_geometries.cc deleted file mode 100644 index bda7ac936cc..00000000000 --- a/source/blender/geometry/intern/join_geometries.cc +++ /dev/null @@ -1,210 +0,0 @@ -/* SPDX-FileCopyrightText: 2023 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -#include "BLI_array_utils.hh" - -#include "GEO_join_geometries.hh" -#include "GEO_realize_instances.hh" - -#include "BKE_instances.hh" - -namespace blender::geometry { - -using bke::AttributeIDRef; -using bke::AttributeMetaData; -using bke::GeometryComponent; -using bke::GeometrySet; - -static Map get_final_attribute_info( - const Span components, const Span ignored_attributes) -{ - Map info; - - for (const GeometryComponent *component : components) { - component->attributes()->for_all( - [&](const bke::AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { - if (ignored_attributes.contains(attribute_id.name())) { - return true; - } - if (meta_data.data_type == CD_PROP_STRING) { - return true; - } - info.add_or_modify( - attribute_id, - [&](AttributeMetaData *meta_data_final) { *meta_data_final = meta_data; }, - [&](AttributeMetaData *meta_data_final) { - meta_data_final->data_type = bke::attribute_data_type_highest_complexity( - {meta_data_final->data_type, meta_data.data_type}); - meta_data_final->domain = bke::attribute_domain_highest_priority( - {meta_data_final->domain, meta_data.domain}); - }); - return true; - }); - } - - return info; -} - -static void fill_new_attribute(const Span src_components, - const AttributeIDRef &attribute_id, - const eCustomDataType data_type, - const eAttrDomain domain, - GMutableSpan dst_span) -{ - const CPPType *cpp_type = bke::custom_data_type_to_cpp_type(data_type); - BLI_assert(cpp_type != nullptr); - - int offset = 0; - for (const GeometryComponent *component : src_components) { - const int domain_num = component->attribute_domain_size(domain); - if (domain_num == 0) { - continue; - } - GVArray read_attribute = *component->attributes()->lookup_or_default( - attribute_id, domain, data_type, nullptr); - - GVArraySpan src_span{read_attribute}; - const void *src_buffer = src_span.data(); - void *dst_buffer = dst_span[offset]; - cpp_type->copy_assign_n(src_buffer, dst_buffer, domain_num); - - offset += domain_num; - } -} - -static void join_attributes(const Span src_components, - GeometryComponent &result, - const Span ignored_attributes = {}) -{ - const Map info = get_final_attribute_info(src_components, - ignored_attributes); - - for (const MapItem item : info.items()) { - const AttributeIDRef attribute_id = item.key; - const AttributeMetaData &meta_data = item.value; - - bke::GSpanAttributeWriter write_attribute = - result.attributes_for_write()->lookup_or_add_for_write_only_span( - attribute_id, meta_data.domain, meta_data.data_type); - if (!write_attribute) { - continue; - } - fill_new_attribute( - src_components, attribute_id, meta_data.data_type, meta_data.domain, write_attribute.span); - write_attribute.finish(); - } -} - -static void join_instances(const Span src_components, - GeometrySet &result) -{ - Array offsets_data(src_components.size() + 1); - for (const int i : src_components.index_range()) { - const auto &src_component = static_cast(*src_components[i]); - offsets_data[i] = src_component.get()->instances_num(); - } - const OffsetIndices offsets = offset_indices::accumulate_counts_to_offsets(offsets_data); - - std::unique_ptr dst_instances = std::make_unique(); - dst_instances->resize(offsets.total_size()); - - MutableSpan all_transforms = dst_instances->transforms(); - MutableSpan all_handles = dst_instances->reference_handles(); - - for (const int i : src_components.index_range()) { - const auto &src_component = static_cast(*src_components[i]); - const bke::Instances &src_instances = *src_component.get(); - - const Span src_references = src_instances.references(); - Array handle_map(src_references.size()); - for (const int src_handle : src_references.index_range()) { - handle_map[src_handle] = dst_instances->add_reference(src_references[src_handle]); - } - - const IndexRange dst_range = offsets[i]; - - const Span src_handles = src_instances.reference_handles(); - array_utils::gather(handle_map.as_span(), src_handles, all_handles.slice(dst_range)); - array_utils::copy(src_instances.transforms(), all_transforms.slice(dst_range)); - } - - result.replace_instances(dst_instances.release()); - auto &dst_component = result.get_component_for_write(); - join_attributes(src_components, dst_component, {"position"}); -} - -static void join_volumes(const Span /*src_components*/, - GeometrySet & /*result*/) -{ - /* Not yet supported. Joining volume grids with the same name requires resampling of at least one - * of the grids. The cell size of the resulting volume has to be determined somehow. */ -} - -static void join_component_type(const bke::GeometryComponent::Type component_type, - const Span src_geometry_sets, - const bke::AnonymousAttributePropagationInfo &propagation_info, - GeometrySet &result) -{ - Vector components; - for (const GeometrySet &geometry_set : src_geometry_sets) { - const GeometryComponent *component = geometry_set.get_component(component_type); - if (component != nullptr && !component->is_empty()) { - components.append(component); - } - } - - if (components.is_empty()) { - return; - } - if (components.size() == 1) { - result.add(*components.first()); - return; - } - - switch (component_type) { - case bke::GeometryComponent::Type::Instance: - join_instances(components, result); - return; - case bke::GeometryComponent::Type::Volume: - join_volumes(components, result); - return; - default: - break; - } - - std::unique_ptr instances = std::make_unique(); - for (const GeometryComponent *component : components) { - GeometrySet tmp_geo; - tmp_geo.add(*component); - const int handle = instances->add_reference(bke::InstanceReference{tmp_geo}); - instances->add_instance(handle, float4x4::identity()); - } - - RealizeInstancesOptions options; - options.keep_original_ids = true; - options.realize_instance_attributes = false; - options.propagation_info = propagation_info; - GeometrySet joined_components = realize_instances( - GeometrySet::from_instances(instances.release()), options); - result.add(joined_components.get_component_for_write(component_type)); -} - -GeometrySet join_geometries(const Span geometries, - const bke::AnonymousAttributePropagationInfo &propagation_info) -{ - GeometrySet result; - static const Array supported_types({GeometryComponent::Type::Mesh, - GeometryComponent::Type::PointCloud, - GeometryComponent::Type::Instance, - GeometryComponent::Type::Volume, - GeometryComponent::Type::Curve, - GeometryComponent::Type::Edit}); - for (const GeometryComponent::Type type : supported_types) { - join_component_type(type, geometries, propagation_info, result); - } - - return result; -} - -} // namespace blender::geometry diff --git a/source/blender/geometry/intern/mesh_copy_selection.cc b/source/blender/geometry/intern/mesh_copy_selection.cc index 41f8c9aa092..98344e28f67 100644 --- a/source/blender/geometry/intern/mesh_copy_selection.cc +++ b/source/blender/geometry/intern/mesh_copy_selection.cc @@ -238,7 +238,7 @@ static void gather_vert_attributes(const Mesh &mesh_src, std::optional mesh_copy_selection( const Mesh &src_mesh, - const VArray &selection, + const fn::Field &selection_field, const eAttrDomain selection_domain, const bke::AnonymousAttributePropagationInfo &propagation_info) { @@ -248,6 +248,11 @@ std::optional mesh_copy_selection( const Span src_corner_edges = src_mesh.corner_edges(); const bke::AttributeAccessor src_attributes = src_mesh.attributes(); + const bke::MeshFieldContext context(src_mesh, selection_domain); + fn::FieldEvaluator evaluator(context, src_attributes.domain_size(selection_domain)); + evaluator.add(selection_field); + evaluator.evaluate(); + const VArray selection = evaluator.get_evaluated(0); if (selection.is_empty()) { return std::nullopt; } @@ -387,7 +392,7 @@ std::optional mesh_copy_selection( std::optional mesh_copy_selection_keep_verts( const Mesh &src_mesh, - const VArray &selection, + const fn::Field &selection_field, const eAttrDomain selection_domain, const bke::AnonymousAttributePropagationInfo &propagation_info) { @@ -397,6 +402,11 @@ std::optional mesh_copy_selection_keep_verts( const Span src_corner_edges = src_mesh.corner_edges(); const bke::AttributeAccessor src_attributes = src_mesh.attributes(); + const bke::MeshFieldContext context(src_mesh, selection_domain); + fn::FieldEvaluator evaluator(context, src_attributes.domain_size(selection_domain)); + evaluator.add(selection_field); + evaluator.evaluate(); + const VArray selection = evaluator.get_evaluated(0); if (selection.is_empty()) { return std::nullopt; } @@ -492,13 +502,18 @@ std::optional mesh_copy_selection_keep_verts( std::optional mesh_copy_selection_keep_edges( const Mesh &src_mesh, - const VArray &selection, + const fn::Field &selection_field, const eAttrDomain selection_domain, const bke::AnonymousAttributePropagationInfo &propagation_info) { const OffsetIndices src_faces = src_mesh.faces(); const bke::AttributeAccessor src_attributes = src_mesh.attributes(); + const bke::MeshFieldContext context(src_mesh, selection_domain); + fn::FieldEvaluator evaluator(context, src_attributes.domain_size(selection_domain)); + evaluator.add(selection_field); + evaluator.evaluate(); + const VArray selection = evaluator.get_evaluated(0); if (selection.is_empty()) { return std::nullopt; } diff --git a/source/blender/geometry/intern/mesh_split_edges.cc b/source/blender/geometry/intern/mesh_split_edges.cc index d49f8faa1ef..608ceccf936 100644 --- a/source/blender/geometry/intern/mesh_split_edges.cc +++ b/source/blender/geometry/intern/mesh_split_edges.cc @@ -193,19 +193,15 @@ static Vector calc_corner_groups_for_vertex(const OffsetIndices> calc_all_corner_groups( - const OffsetIndices faces, - const Span corner_verts, - const Span corner_edges, - const GroupedSpan vert_to_corner_map, - const GroupedSpan edge_to_corner_map, - const Span corner_to_face_map, - const BitSpan split_edges, - const IndexMask &affected_verts) +/* Calculate groups of corners that are contiguously connected to each input vertex. */ +static Array> calc_all_corner_groups(const OffsetIndices faces, + const Span corner_verts, + const Span corner_edges, + const GroupedSpan vert_to_corner_map, + const GroupedSpan edge_to_corner_map, + const Span corner_to_face_map, + const BitSpan split_edges, + const IndexMask &affected_verts) { Array> corner_groups(affected_verts.size(), NoInitialization()); affected_verts.foreach_index(GrainSize(512), [&](const int vert, const int mask) { @@ -597,8 +593,7 @@ void split_edges(Mesh &mesh, BKE_mesh_tag_edges_split(&mesh); - debug_randomize_vert_order(&mesh); - debug_randomize_edge_order(&mesh); + debug_randomize_mesh_order(&mesh); } } // namespace blender::geometry diff --git a/source/blender/geometry/intern/point_merge_by_distance.cc b/source/blender/geometry/intern/point_merge_by_distance.cc index 02398142658..01441669495 100644 --- a/source/blender/geometry/intern/point_merge_by_distance.cc +++ b/source/blender/geometry/intern/point_merge_by_distance.cc @@ -2,7 +2,6 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ -#include "BLI_array_utils.hh" #include "BLI_kdtree.h" #include "BLI_offset_indices.hh" #include "BLI_task.hh" @@ -51,7 +50,7 @@ PointCloud *point_merge_by_distance(const PointCloud &src_points, * finding, converting from indices into the selection to indices into the full input point * cloud. */ Array merge_indices(src_size); - array_utils::fill_index_range(merge_indices); + std::iota(merge_indices.begin(), merge_indices.end(), 0); selection.foreach_index([&](const int src_index, const int pos) { const int merge_index = selection_merge_indices[pos]; diff --git a/source/blender/geometry/intern/randomize.cc b/source/blender/geometry/intern/randomize.cc index 654225307d7..6f4bea740d2 100644 --- a/source/blender/geometry/intern/randomize.cc +++ b/source/blender/geometry/intern/randomize.cc @@ -17,9 +17,7 @@ #include "BKE_attribute_math.hh" #include "BKE_curves.hh" #include "BKE_customdata.h" -#include "BKE_geometry_set.hh" #include "BKE_global.h" -#include "BKE_instances.hh" #include "BKE_mesh.hh" #include "BLI_array.hh" @@ -68,11 +66,6 @@ static int seed_from_curves(const bke::CurvesGeometry &curves) return curves.point_num; } -static int seed_from_instances(const bke::Instances &instances) -{ - return instances.instances_num(); -} - static void reorder_customdata(CustomData &data, const Span new_by_old_map) { CustomData new_data; @@ -86,7 +79,7 @@ static void reorder_customdata(CustomData &data, const Span new_by_old_map) data = new_data; } -void debug_randomize_vert_order(Mesh *mesh) +void debug_randomize_vertex_order(Mesh *mesh) { if (mesh == nullptr || !use_debug_randomization()) { return; @@ -225,39 +218,11 @@ void debug_randomize_mesh_order(Mesh *mesh) return; } - debug_randomize_vert_order(mesh); + debug_randomize_vertex_order(mesh); debug_randomize_edge_order(mesh); debug_randomize_face_order(mesh); } -void debug_randomize_instance_order(bke::Instances *instances) -{ - if (instances == nullptr || !use_debug_randomization()) { - return; - } - - const int instances_num = instances->instances_num(); - const int seed = seed_from_instances(*instances); - const Array new_by_old_map = get_permutation(instances_num, seed); - - reorder_customdata(instances->custom_data_attributes(), new_by_old_map); - - const Span old_reference_handles = instances->reference_handles(); - const Span old_transforms = instances->transforms(); - - Vector new_reference_handles(instances_num); - Vector new_transforms(instances_num); - - for (const int old_i : new_by_old_map.index_range()) { - const int new_i = new_by_old_map[old_i]; - new_reference_handles[new_i] = old_reference_handles[old_i]; - new_transforms[new_i] = old_transforms[old_i]; - } - - instances->reference_handles().copy_from(new_reference_handles); - instances->transforms().copy_from(new_transforms); -} - bool use_debug_randomization() { return G.randomize_geometry_element_order; diff --git a/source/blender/geometry/intern/realize_instances.cc b/source/blender/geometry/intern/realize_instances.cc index 1757a4f129f..dc353a50c1b 100644 --- a/source/blender/geometry/intern/realize_instances.cc +++ b/source/blender/geometry/intern/realize_instances.cc @@ -31,9 +31,12 @@ namespace blender::geometry { using blender::bke::AttributeIDRef; using blender::bke::AttributeKind; using blender::bke::AttributeMetaData; +using blender::bke::custom_data_type_to_cpp_type; +using blender::bke::CustomDataAttributes; using blender::bke::GSpanAttributeWriter; using blender::bke::InstanceReference; using blender::bke::Instances; +using blender::bke::object_get_evaluated_geometry_set; using blender::bke::SpanAttributeWriter; /** @@ -389,37 +392,35 @@ static Vector> prepare_attribute_fallbacks( const OrderedAttributes &ordered_attributes) { Vector> attributes_to_override; - const bke::AttributeAccessor attributes = instances.attributes(); - attributes.for_all([&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { - const int attribute_index = ordered_attributes.ids.index_of_try(attribute_id); - if (attribute_index == -1) { - /* The attribute is not propagated to the final geometry. */ - return true; - } - const bke::GAttributeReader attribute = attributes.lookup(attribute_id); - if (!attribute || !attribute.varray.is_span()) { - return true; - } - GSpan span = attribute.varray.get_internal_span(); - const eCustomDataType expected_type = ordered_attributes.kinds[attribute_index].data_type; - if (meta_data.data_type != expected_type) { - const CPPType &from_type = span.type(); - const CPPType &to_type = *bke::custom_data_type_to_cpp_type(expected_type); - const bke::DataTypeConversions &conversions = bke::get_implicit_type_conversions(); - if (!conversions.is_convertible(from_type, to_type)) { - /* Ignore the attribute because it can not be converted to the desired type. */ + const CustomDataAttributes &attributes = instances.custom_data_attributes(); + attributes.foreach_attribute( + [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { + const int attribute_index = ordered_attributes.ids.index_of_try(attribute_id); + if (attribute_index == -1) { + /* The attribute is not propagated to the final geometry. */ + return true; + } + GSpan span = *attributes.get_for_read(attribute_id); + const eCustomDataType expected_type = ordered_attributes.kinds[attribute_index].data_type; + if (meta_data.data_type != expected_type) { + const CPPType &from_type = span.type(); + const CPPType &to_type = *custom_data_type_to_cpp_type(expected_type); + const bke::DataTypeConversions &conversions = bke::get_implicit_type_conversions(); + if (!conversions.is_convertible(from_type, to_type)) { + /* Ignore the attribute because it can not be converted to the desired type. */ + return true; + } + /* Convert the attribute on the instances component to the expected attribute type. */ + std::unique_ptr> temporary_array = std::make_unique>( + to_type, instances.instances_num()); + conversions.convert_to_initialized_n(span, temporary_array->as_mutable_span()); + span = temporary_array->as_span(); + gather_info.r_temporary_arrays.append(std::move(temporary_array)); + } + attributes_to_override.append({attribute_index, span}); return true; - } - /* Convert the attribute on the instances component to the expected attribute type. */ - std::unique_ptr> temporary_array = std::make_unique>( - to_type, instances.instances_num()); - conversions.convert_to_initialized_n(span, temporary_array->as_mutable_span()); - span = temporary_array->as_span(); - gather_info.r_temporary_arrays.append(std::move(temporary_array)); - } - attributes_to_override.append({attribute_index, span}); - return true; - }); + }, + ATTR_DOMAIN_INSTANCE); return attributes_to_override; } @@ -437,8 +438,8 @@ static void foreach_geometry_in_reference( switch (reference.type()) { case InstanceReference::Type::Object: { const Object &object = reference.object(); - const bke::GeometrySet object_geometry = bke::object_get_evaluated_geometry_set(object); - fn(object_geometry, base_transform, id); + const bke::GeometrySet object_geometry_set = object_get_evaluated_geometry_set(object); + fn(object_geometry_set, base_transform, id); break; } case InstanceReference::Type::Collection: { @@ -447,11 +448,11 @@ static void foreach_geometry_in_reference( offset_matrix.location() -= collection.instance_offset; int index = 0; FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN (&collection, object) { - const bke::GeometrySet object_geometry = bke::object_get_evaluated_geometry_set(*object); + const bke::GeometrySet object_geometry_set = object_get_evaluated_geometry_set(*object); const float4x4 matrix = base_transform * offset_matrix * float4x4_view(object->object_to_world); const int sub_id = noise::hash(id, index); - fn(object_geometry, matrix, sub_id); + fn(object_geometry_set, matrix, sub_id); index++; } FOREACH_COLLECTION_OBJECT_RECURSIVE_END; @@ -479,9 +480,9 @@ static void gather_realize_tasks_for_instances(GatherTasksInfo &gather_info, Span stored_instance_ids; if (gather_info.create_id_attribute_on_any_component) { - bke::AttributeReader ids = instances.attributes().lookup("id"); - if (ids) { - stored_instance_ids = ids.varray.get_internal_span(); + std::optional ids = instances.custom_data_attributes().get_for_read("id"); + if (ids.has_value()) { + stored_instance_ids = ids->typed(); } } @@ -1497,7 +1498,7 @@ static void remove_id_attribute_from_instances(bke::GeometrySet &geometry_set) { geometry_set.modify_geometry_sets([&](bke::GeometrySet &sub_geometry) { if (Instances *instances = sub_geometry.get_instances_for_write()) { - instances->attributes_for_write().remove("id"); + instances->custom_data_attributes().remove("id"); } }); } diff --git a/source/blender/geometry/intern/resample_curves.cc b/source/blender/geometry/intern/resample_curves.cc index e17fbca6ec1..b6509ccae14 100644 --- a/source/blender/geometry/intern/resample_curves.cc +++ b/source/blender/geometry/intern/resample_curves.cc @@ -2,7 +2,6 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ -#include "BLI_array_utils.hh" #include "BLI_math_color.hh" #include "BLI_math_quaternion.hh" @@ -198,25 +197,25 @@ static void copy_or_defaults_for_unselected_curves(const CurvesGeometry &src_cur { const OffsetIndices src_points_by_curve = src_curves.points_by_curve(); const OffsetIndices dst_points_by_curve = dst_curves.points_by_curve(); - array_utils::copy_group_to_group(src_points_by_curve, - dst_points_by_curve, - unselected_curves, - src_curves.positions(), - dst_curves.positions_for_write()); + bke::curves::copy_point_data(src_points_by_curve, + dst_points_by_curve, + unselected_curves, + src_curves.positions(), + dst_curves.positions_for_write()); for (const int i : attributes.src.index_range()) { - array_utils::copy_group_to_group(src_points_by_curve, - dst_points_by_curve, - unselected_curves, - attributes.src[i], - attributes.dst[i]); + bke::curves::copy_point_data(src_points_by_curve, + dst_points_by_curve, + unselected_curves, + attributes.src[i], + attributes.dst[i]); } for (const int i : attributes.src_no_interpolation.index_range()) { - array_utils::copy_group_to_group(src_points_by_curve, - dst_points_by_curve, - unselected_curves, - attributes.src_no_interpolation[i], - attributes.dst_no_interpolation[i]); + bke::curves::copy_point_data(src_points_by_curve, + dst_points_by_curve, + unselected_curves, + attributes.src_no_interpolation[i], + attributes.dst_no_interpolation[i]); } if (!attributes.dst_tangents.is_empty()) { @@ -246,7 +245,6 @@ static void normalize_curve_point_data(const IndexMaskSegment curve_selection, } static CurvesGeometry resample_to_uniform(const CurvesGeometry &src_curves, - const fn::FieldContext &field_context, const fn::Field &selection_field, const fn::Field &count_field, const ResampleCurvesOutputAttributeIDs &output_ids) @@ -263,6 +261,7 @@ static CurvesGeometry resample_to_uniform(const CurvesGeometry &src_curves, CurvesGeometry dst_curves = bke::curves::copy_only_curve_domain(src_curves); MutableSpan dst_offsets = dst_curves.offsets_for_write(); + const bke::CurvesFieldContext field_context{src_curves, ATTR_DOMAIN_CURVE}; fn::FieldEvaluator evaluator{field_context, src_curves.curves_num()}; evaluator.set_selection(selection_field); evaluator.add_with_destination(count_field, dst_offsets.drop_back(1)); @@ -395,33 +394,24 @@ static CurvesGeometry resample_to_uniform(const CurvesGeometry &src_curves, } CurvesGeometry resample_to_count(const CurvesGeometry &src_curves, - const fn::FieldContext &field_context, const fn::Field &selection_field, const fn::Field &count_field, const ResampleCurvesOutputAttributeIDs &output_ids) { - return resample_to_uniform(src_curves, - field_context, - selection_field, - get_count_input_max_one(count_field), - output_ids); + return resample_to_uniform( + src_curves, selection_field, get_count_input_max_one(count_field), output_ids); } CurvesGeometry resample_to_length(const CurvesGeometry &src_curves, - const fn::FieldContext &field_context, const fn::Field &selection_field, const fn::Field &segment_length_field, const ResampleCurvesOutputAttributeIDs &output_ids) { - return resample_to_uniform(src_curves, - field_context, - selection_field, - get_count_input_from_length(segment_length_field), - output_ids); + return resample_to_uniform( + src_curves, selection_field, get_count_input_from_length(segment_length_field), output_ids); } CurvesGeometry resample_to_evaluated(const CurvesGeometry &src_curves, - const fn::FieldContext &field_context, const fn::Field &selection_field, const ResampleCurvesOutputAttributeIDs &output_ids) { @@ -432,6 +422,7 @@ CurvesGeometry resample_to_evaluated(const CurvesGeometry &src_curves, const OffsetIndices src_evaluated_points_by_curve = src_curves.evaluated_points_by_curve(); const Span evaluated_positions = src_curves.evaluated_positions(); + const bke::CurvesFieldContext field_context{src_curves, ATTR_DOMAIN_CURVE}; fn::FieldEvaluator evaluator{field_context, src_curves.curves_num()}; evaluator.set_selection(selection_field); evaluator.evaluate(); diff --git a/source/blender/geometry/intern/set_curve_type.cc b/source/blender/geometry/intern/set_curve_type.cc index 996545de5fa..c74fea9b63d 100644 --- a/source/blender/geometry/intern/set_curve_type.cc +++ b/source/blender/geometry/intern/set_curve_type.cc @@ -7,7 +7,6 @@ #include "BKE_curves.hh" #include "BKE_curves_utils.hh" -#include "BLI_array_utils.hh" #include "BLI_task.hh" #include "GEO_set_curve_type.hh" @@ -332,7 +331,7 @@ static bke::CurvesGeometry convert_curves_to_bezier( dst_points_by_curve, selection, BEZIER_HANDLE_ALIGN, dst_types_l); bke::curves::fill_points( dst_points_by_curve, selection, BEZIER_HANDLE_ALIGN, dst_types_r); - array_utils::copy_group_to_group( + bke::curves::copy_point_data( src_points_by_curve, dst_points_by_curve, selection, src_positions, dst_positions); selection.foreach_index(GrainSize(512), [&](const int i) { @@ -345,13 +344,13 @@ static bke::CurvesGeometry convert_curves_to_bezier( }); for (bke::AttributeTransferData &attribute : generic_attributes) { - array_utils::copy_group_to_group( + bke::curves::copy_point_data( src_points_by_curve, dst_points_by_curve, selection, attribute.src, attribute.dst.span); } }; auto poly_to_bezier = [&](const IndexMask &selection) { - array_utils::copy_group_to_group( + bke::curves::copy_point_data( src_points_by_curve, dst_points_by_curve, selection, src_positions, dst_positions); bke::curves::fill_points( dst_points_by_curve, selection, BEZIER_HANDLE_VECTOR, dst_types_l); @@ -359,7 +358,7 @@ static bke::CurvesGeometry convert_curves_to_bezier( dst_points_by_curve, selection, BEZIER_HANDLE_VECTOR, dst_types_r); dst_curves.calculate_bezier_auto_handles(); for (bke::AttributeTransferData &attribute : generic_attributes) { - array_utils::copy_group_to_group( + bke::curves::copy_point_data( src_points_by_curve, dst_points_by_curve, selection, attribute.src, attribute.dst.span); } }; @@ -370,21 +369,21 @@ static bke::CurvesGeometry convert_curves_to_bezier( const Span src_handles_l = src_curves.handle_positions_left(); const Span src_handles_r = src_curves.handle_positions_right(); - array_utils::copy_group_to_group( + bke::curves::copy_point_data( src_points_by_curve, dst_points_by_curve, selection, src_positions, dst_positions); - array_utils::copy_group_to_group( + bke::curves::copy_point_data( src_points_by_curve, dst_points_by_curve, selection, src_handles_l, dst_handles_l); - array_utils::copy_group_to_group( + bke::curves::copy_point_data( src_points_by_curve, dst_points_by_curve, selection, src_handles_r, dst_handles_r); - array_utils::copy_group_to_group( + bke::curves::copy_point_data( src_points_by_curve, dst_points_by_curve, selection, src_types_l, dst_types_l); - array_utils::copy_group_to_group( + bke::curves::copy_point_data( src_points_by_curve, dst_points_by_curve, selection, src_types_r, dst_types_r); dst_curves.calculate_bezier_auto_handles(); for (bke::AttributeTransferData &attribute : generic_attributes) { - array_utils::copy_group_to_group( + bke::curves::copy_point_data( src_points_by_curve, dst_points_by_curve, selection, attribute.src, attribute.dst.span); } }; @@ -453,14 +452,25 @@ static bke::CurvesGeometry convert_curves_to_bezier( attribute.dst.finish(); } - bke::copy_attributes_group_to_group(src_attributes, - ATTR_DOMAIN_POINT, - propagation_info, - {}, - src_points_by_curve, - dst_points_by_curve, - unselected, - dst_attributes); + src_attributes.for_all( + [&](const bke::AttributeIDRef &id, const bke::AttributeMetaData meta_data) { + if (id.is_anonymous() && !propagation_info.propagate(id.anonymous_id())) { + return true; + } + if (meta_data.domain != ATTR_DOMAIN_POINT) { + return true; + } + + const GVArraySpan src = *src_attributes.lookup(id, meta_data.domain); + bke::GSpanAttributeWriter dst = dst_attributes.lookup_or_add_for_write_only_span( + id, meta_data.domain, meta_data.data_type); + + bke::curves::copy_point_data( + src_points_by_curve, dst_points_by_curve, unselected, src, dst.span); + dst.finish(); + + return true; + }); return dst_curves; } @@ -538,7 +548,7 @@ static bke::CurvesGeometry convert_curves_to_nurbs( auto poly_to_nurbs = [&](const IndexMask &selection) { index_mask::masked_fill(dst_curves.nurbs_orders_for_write(), 4, selection); - array_utils::copy_group_to_group( + bke::curves::copy_point_data( src_points_by_curve, dst_points_by_curve, selection, src_positions, dst_positions); fill_weights_if_necessary(selection); @@ -559,7 +569,7 @@ static bke::CurvesGeometry convert_curves_to_nurbs( } for (bke::AttributeTransferData &attribute : generic_attributes) { - array_utils::copy_group_to_group( + bke::curves::copy_point_data( src_points_by_curve, dst_points_by_curve, selection, attribute.src, attribute.dst.span); } }; @@ -593,19 +603,19 @@ static bke::CurvesGeometry convert_curves_to_nurbs( }; auto nurbs_to_nurbs = [&](const IndexMask &selection) { - array_utils::copy_group_to_group( + bke::curves::copy_point_data( src_points_by_curve, dst_points_by_curve, selection, src_positions, dst_positions); if (!src_curves.nurbs_weights().is_empty()) { - array_utils::copy_group_to_group(src_points_by_curve, - dst_points_by_curve, - selection, - src_curves.nurbs_weights(), - dst_curves.nurbs_weights_for_write()); + bke::curves::copy_point_data(src_points_by_curve, + dst_points_by_curve, + selection, + src_curves.nurbs_weights(), + dst_curves.nurbs_weights_for_write()); } for (bke::AttributeTransferData &attribute : generic_attributes) { - array_utils::copy_group_to_group( + bke::curves::copy_point_data( src_points_by_curve, dst_points_by_curve, selection, attribute.src, attribute.dst.span); } }; @@ -622,14 +632,25 @@ static bke::CurvesGeometry convert_curves_to_nurbs( attribute.dst.finish(); } - bke::copy_attributes_group_to_group(src_attributes, - ATTR_DOMAIN_POINT, - propagation_info, - {}, - src_points_by_curve, - dst_points_by_curve, - unselected, - dst_attributes); + src_attributes.for_all( + [&](const bke::AttributeIDRef &id, const bke::AttributeMetaData meta_data) { + if (id.is_anonymous() && !propagation_info.propagate(id.anonymous_id())) { + return true; + } + if (meta_data.domain != ATTR_DOMAIN_POINT) { + return true; + } + + const GVArraySpan src = *src_attributes.lookup(id, meta_data.domain); + bke::GSpanAttributeWriter dst = dst_attributes.lookup_or_add_for_write_only_span( + id, meta_data.domain, meta_data.data_type); + + bke::curves::copy_point_data( + src_points_by_curve, dst_points_by_curve, unselected, src, dst.span); + dst.finish(); + + return true; + }); return dst_curves; } diff --git a/source/blender/geometry/intern/subdivide_curves.cc b/source/blender/geometry/intern/subdivide_curves.cc index aabd4dfbfc0..589f88240c2 100644 --- a/source/blender/geometry/intern/subdivide_curves.cc +++ b/source/blender/geometry/intern/subdivide_curves.cc @@ -410,14 +410,15 @@ bke::CurvesGeometry subdivide_curves( subdivide_bezier, subdivide_nurbs); - bke::copy_attributes_group_to_group(src_attributes, - ATTR_DOMAIN_POINT, - propagation_info, - {}, - src_points_by_curve, - dst_points_by_curve, - unselected, - dst_attributes); + if (!unselected.is_empty()) { + for (auto &attribute : bke::retrieve_attributes_for_transfer( + src_attributes, dst_attributes, ATTR_DOMAIN_MASK_POINT, propagation_info)) + { + bke::curves::copy_point_data( + src_points_by_curve, dst_points_by_curve, unselected, attribute.src, attribute.dst.span); + attribute.dst.finish(); + } + } return dst_curves; } diff --git a/source/blender/geometry/intern/trim_curves.cc b/source/blender/geometry/intern/trim_curves.cc index 1bcd9efbb6e..fdaaa42d779 100644 --- a/source/blender/geometry/intern/trim_curves.cc +++ b/source/blender/geometry/intern/trim_curves.cc @@ -137,8 +137,10 @@ static bke::curves::CurvePoint lookup_point_bezier( return lookup_point_bezier( bezier_offsets, accumulated_lengths, sample_length, cyclic, num_curve_points); } - return lookup_point_uniform_spacing( - accumulated_lengths, sample_length, cyclic, resolution, num_curve_points); + else { + return lookup_point_uniform_spacing( + accumulated_lengths, sample_length, cyclic, resolution, num_curve_points); + } } static bke::curves::CurvePoint lookup_curve_point( @@ -173,10 +175,12 @@ static bke::curves::CurvePoint lookup_curve_point( else if (curve_type == CURVE_TYPE_POLY) { return lookup_point_polygonal(accumulated_lengths, sample_length, cyclic, num_curve_points); } - /* Handle evaluated curve. */ - BLI_assert(resolution > 0); - return lookup_point_polygonal( - accumulated_lengths, sample_length, cyclic, evaluated_points_by_curve[curve_index].size()); + else { + /* Handle evaluated curve. */ + BLI_assert(resolution > 0); + return lookup_point_polygonal( + accumulated_lengths, sample_length, cyclic, evaluated_points_by_curve[curve_index].size()); + } } /** \} */ @@ -1059,14 +1063,17 @@ bke::CurvesGeometry trim_curves(const bke::CurvesGeometry &src_curves, copy_point_skip.add("nurbs_weight"); } - bke::copy_attributes_group_to_group(src_attributes, - ATTR_DOMAIN_POINT, - propagation_info, - copy_point_skip, - src_points_by_curve, - dst_points_by_curve, - unselected, - dst_attributes); + /* Copy point domain. */ + for (auto &attribute : bke::retrieve_attributes_for_transfer(src_attributes, + dst_attributes, + ATTR_DOMAIN_MASK_POINT, + propagation_info, + copy_point_skip)) + { + bke::curves::copy_point_data( + src_points_by_curve, dst_points_by_curve, unselected, attribute.src, attribute.dst.span); + attribute.dst.finish(); + } } dst_curves.remove_attributes_based_on_types(); diff --git a/source/blender/geometry/intern/uv_pack.cc b/source/blender/geometry/intern/uv_pack.cc index 3f9260600da..6e151f12364 100644 --- a/source/blender/geometry/intern/uv_pack.cc +++ b/source/blender/geometry/intern/uv_pack.cc @@ -33,9 +33,9 @@ namespace blender::geometry { /* Store information about an island's placement such as translation, rotation and reflection. */ -class UVPhi { +class uv_phi { public: - UVPhi(); + uv_phi(); bool is_valid() const; float2 translation; @@ -43,12 +43,12 @@ class UVPhi { // bool reflect; }; -UVPhi::UVPhi() : translation(-1.0f, -1.0f), rotation(0.0f) +uv_phi::uv_phi() : translation(-1.0f, -1.0f), rotation(0.0f) { /* Initialize invalid. */ } -bool UVPhi::is_valid() const +bool uv_phi::is_valid() const { return translation.x != -1.0f; } @@ -84,10 +84,10 @@ static float dist_signed_squared_to_edge(const float2 probe, const float2 uva, c const float2 edge = uvb - uva; const float2 side = probe - uva; - const float edge_length_squared = math::length_squared(edge); + const float edge_length_squared = blender::math::length_squared(edge); /* Tolerance here is to avoid division by zero later. */ if (edge_length_squared < 1e-40f) { - return math::length_squared(side); + return blender::math::length_squared(side); } const float numerator = edge.x * side.y - edge.y * side.x; /* c.f. cross product. */ @@ -162,7 +162,7 @@ void PackIsland::add_triangle(const float2 uv0, const float2 uv1, const float2 u } } -void PackIsland::add_polygon(const Span uvs, MemArena *arena, Heap *heap) +void PackIsland::add_polygon(const blender::Span uvs, MemArena *arena, Heap *heap) { /* Internally, PackIsland uses triangles as the primitive, so we have to triangulate. */ @@ -254,7 +254,7 @@ void PackIsland::calculate_pre_rotation_(const UVPackIsland_Params ¶ms) /* TODO: Use "Rotating Calipers" directly. */ { - Array coords(triangle_vertices_.size()); + blender::Array coords(triangle_vertices_.size()); for (const int64_t i : triangle_vertices_.index_range()) { coords[i].x = triangle_vertices_[i].x * aspect_y; coords[i].y = triangle_vertices_[i].y; @@ -327,7 +327,7 @@ void PackIsland::finalize_geometry_(const UVPackIsland_Params ¶ms, MemArena /* Write back. */ triangle_vertices_.clear(); - Array convexVertices(convex_len); + blender::Array convexVertices(convex_len); for (int i = 0; i < convex_len; i++) { convexVertices[i] = source[index_map[i]]; } @@ -367,7 +367,7 @@ void PackIsland::calculate_pivot_() BLI_assert(half_diagonal_.y >= 0.0f); } -void PackIsland::place_(const float scale, const UVPhi phi) +void PackIsland::place_(const float scale, const uv_phi phi) { angle = phi.rotation + pre_rotate_; @@ -428,7 +428,7 @@ static void pack_islands_alpaca_turbo(const int64_t exclude_index, const rctf &exclude, const Span> islands, const float target_aspect_y, - MutableSpan r_phis, + MutableSpan r_phis, rctf *r_extent) { /* Exclude an initial AABB near the origin. */ @@ -460,7 +460,7 @@ static void pack_islands_alpaca_turbo(const int64_t exclude_index, } /* Place the island. */ - UVPhi &phi = r_phis[island.index]; + uv_phi &phi = r_phis[island.index]; phi.rotation = 0.0f; phi.translation.x = u0 + dsm_u * 0.5f; phi.translation.y = v0 + dsm_v * 0.5f; @@ -539,7 +539,7 @@ static void pack_islands_alpaca_rotate(const int64_t exclude_index, const rctf &exclude, const Span> islands, const float target_aspect_y, - MutableSpan r_phis, + MutableSpan r_phis, rctf *r_extent) { /* Exclude an initial AABB near the origin. */ @@ -558,7 +558,7 @@ static void pack_islands_alpaca_rotate(const int64_t exclude_index, /* Visit every island in order, except the excluded islands at the start. */ for (int64_t index = exclude_index; index < islands.size(); index++) { UVAABBIsland &island = *islands[index]; - UVPhi &phi = r_phis[island.index]; + uv_phi &phi = r_phis[island.index]; const float uvdiag_x = island.uv_diagonal.x * island.aspect_y; float min_dsm = std::min(uvdiag_x, island.uv_diagonal.y); float max_dsm = std::max(uvdiag_x, island.uv_diagonal.y); @@ -646,7 +646,7 @@ static void pack_islands_fast(const int64_t exclude_index, const Span> aabbs, const bool rotate, const float target_aspect_y, - MutableSpan r_phis, + MutableSpan r_phis, rctf *r_extent) { if (rotate) { @@ -661,10 +661,10 @@ static void pack_islands_fast(const int64_t exclude_index, static void pack_gobel(const Span> aabbs, const float scale, const int m, - MutableSpan r_phis) + MutableSpan r_phis) { for (const int64_t i : aabbs.index_range()) { - UVPhi &phi = *(UVPhi *)&r_phis[aabbs[i]->index]; + uv_phi &phi = *(uv_phi *)&r_phis[aabbs[i]->index]; phi.rotation = 0.0f; if (i == 0) { phi.translation.x = 0.5f * scale; @@ -702,7 +702,7 @@ static bool pack_islands_optimal_pack_table(const int table_count, const float large_uv, const Span> aabbs, const UVPackIsland_Params ¶ms, - MutableSpan r_phis, + MutableSpan r_phis, rctf *r_extent) { if (table_count < island_count) { @@ -715,7 +715,7 @@ static bool pack_islands_optimal_pack_table(const int table_count, *r_extent = extent; for (int i = 0; i < island_count; i++) { - UVPhi &phi = r_phis[aabbs[i]->index]; + uv_phi &phi = r_phis[aabbs[i]->index]; phi.translation.x = optimal[i * 3 + 0] * large_uv; phi.translation.y = optimal[i * 3 + 1] * large_uv; phi.rotation = optimal[i * 3 + 2]; @@ -726,7 +726,7 @@ static bool pack_islands_optimal_pack_table(const int table_count, /* Attempt to find an "Optimal" packing of the islands, e.g. assuming squares or circles. */ static void pack_islands_optimal_pack(const Span> aabbs, const UVPackIsland_Params ¶ms, - MutableSpan r_phis, + MutableSpan r_phis, rctf *r_extent) { if (params.shape_method == ED_UVPACK_SHAPE_AABB) { @@ -1059,7 +1059,7 @@ static void pack_islands_optimal_pack(const Span> /* Wrapper around #BLI_box_pack_2d. */ static void pack_island_box_pack_2d(const Span> aabbs, const UVPackIsland_Params ¶ms, - MutableSpan r_phis, + MutableSpan r_phis, rctf *r_extent) { /* Allocate storage. */ @@ -1086,7 +1086,7 @@ static void pack_island_box_pack_2d(const Span> aa /* Write back box_pack UVs. */ for (const int64_t i : aabbs.index_range()) { BoxPack *box = box_array + i; - UVPhi &phi = *(UVPhi *)&r_phis[aabbs[i]->index]; + uv_phi &phi = *(uv_phi *)&r_phis[aabbs[i]->index]; phi.rotation = 0.0f; /* #BLI_box_pack_2d never rotates. */ phi.translation.x = (box->x + box->w * 0.5f) * params.target_aspect_y; phi.translation.y = (box->y + box->h * 0.5f); @@ -1120,7 +1120,7 @@ class Occupancy { /* Write or Query an island on the bitmap. */ float trace_island(const PackIsland *island, - const UVPhi phi, + const uv_phi phi, const float scale, const float margin, const bool write) const; @@ -1128,7 +1128,7 @@ class Occupancy { int bitmap_radix; /* Width and Height of `bitmap`. */ float bitmap_scale_reciprocal; /* == 1.0f / `bitmap_scale`. */ private: - mutable Array bitmap_; + mutable blender::Array bitmap_; mutable float2 witness_; /* Witness to a previously known occupied pixel. */ mutable float witness_distance_; /* Signed distance to nearest placed island. */ @@ -1178,9 +1178,9 @@ static float signed_distance_fat_triangle(const float2 probe, return -sqrtf(-result_ssq); } BLI_assert(result_ssq >= 0.0f); - result_ssq = std::min(result_ssq, math::length_squared(probe - uv0)); - result_ssq = std::min(result_ssq, math::length_squared(probe - uv1)); - result_ssq = std::min(result_ssq, math::length_squared(probe - uv2)); + result_ssq = std::min(result_ssq, blender::math::length_squared(probe - uv0)); + result_ssq = std::min(result_ssq, blender::math::length_squared(probe - uv1)); + result_ssq = std::min(result_ssq, blender::math::length_squared(probe - uv2)); BLI_assert(result_ssq >= 0.0f); return sqrtf(result_ssq); } @@ -1276,7 +1276,7 @@ float2 PackIsland::get_diagonal_support(const float scale, } float Occupancy::trace_island(const PackIsland *island, - const UVPhi phi, + const uv_phi phi, const float scale, const float margin, const bool write) const @@ -1320,14 +1320,14 @@ float Occupancy::trace_island(const PackIsland *island, return -1.0f; /* Available. */ } -static UVPhi find_best_fit_for_island(const PackIsland *island, - const int scan_line, - Occupancy &occupancy, - const float scale, - const int angle_90_multiple, - /* TODO: const bool reflect, */ - const float margin, - const float target_aspect_y) +static uv_phi find_best_fit_for_island(const PackIsland *island, + const int scan_line, + Occupancy &occupancy, + const float scale, + const int angle_90_multiple, + /* TODO: const bool reflect, */ + const float margin, + const float target_aspect_y) { /* Discussion: Different xatlas implementation make different choices here, either * fixing the output bitmap size before packing begins, or sometimes allowing @@ -1356,7 +1356,7 @@ static UVPhi find_best_fit_for_island(const PackIsland *island, const int scan_line_x = int(scan_line * sqrt_target_aspect_y); const int scan_line_y = int(scan_line / sqrt_target_aspect_y); - UVPhi phi; + uv_phi phi; phi.rotation = DEG2RADF(angle_90_multiple * 90); // phi.reflect = reflect; float matrix[2][2]; @@ -1387,7 +1387,7 @@ static UVPhi find_best_fit_for_island(const PackIsland *island, t = t + std::max(1, int(extent)); } - return UVPhi(); /* Unable to find a place to fit. */ + return uv_phi(); /* Unable to find a place to fit. */ } static float guess_initial_scale(const Span islands, @@ -1414,8 +1414,8 @@ class UVMinimumEnclosingSquareFinder { float best_angle; rctf best_bounds; - Vector points; - Vector indices; + blender::Vector points; + blender::Vector indices; UVMinimumEnclosingSquareFinder(const float scale, const float margin, @@ -1490,7 +1490,7 @@ static bool rotate_inside_square(const Span> islan const UVPackIsland_Params ¶ms, const float scale, const float margin, - MutableSpan r_phis, + MutableSpan r_phis, rctf *r_extent) { if (island_indices.size() == 0) { @@ -1593,13 +1593,13 @@ static int64_t pack_island_xatlas(const Span> isla const float scale, const float margin, const UVPackIsland_Params ¶ms, - MutableSpan r_phis, + MutableSpan r_phis, rctf *r_extent) { if (params.shape_method == ED_UVPACK_SHAPE_AABB) { return 0; /* Not yet supported. */ } - Array phis(r_phis.size()); + blender::Array phis(r_phis.size()); Occupancy occupancy(guess_initial_scale(islands, scale, margin)); rctf extent = {0.0f, 0.0f, 0.0f, 0.0f}; @@ -1637,7 +1637,7 @@ static int64_t pack_island_xatlas(const Span> isla } PackIsland *island = islands[island_indices[i]->index]; - UVPhi phi; /* Create an identity transform. */ + uv_phi phi; /* Create an identity transform. */ if (!island->can_translate_(params)) { /* Move the pinned island into the correct coordinate system. */ @@ -1776,7 +1776,7 @@ static float pack_islands_scale_margin(const Span islands, const float scale, const float margin, const UVPackIsland_Params ¶ms, - MutableSpan r_phis) + MutableSpan r_phis) { /* #BLI_box_pack_2d produces layouts with high packing efficiency, but has `O(n^3)` * time complexity, causing poor performance if there are lots of islands. See: #102843. @@ -1886,7 +1886,7 @@ static float pack_islands_scale_margin(const Span islands, BLI_rctf_do_minmax_v(&locked_bounds, top_right); } - UVPhi &phi = r_phis[aabbs[i]->index]; /* Lock in place. */ + uv_phi &phi = r_phis[aabbs[i]->index]; /* Lock in place. */ phi.translation = pack_island->pivot_; sub_v2_v2(phi.translation, params.udim_base_offset); phi.rotation = 0.0f; @@ -1986,9 +1986,9 @@ static float pack_islands_margin_fraction(const Span &islands, float scale_high = 0.0f; float value_high = 0.0f; - Array phis_a(islands.size()); - Array phis_b(islands.size()); - Array *phis_low = nullptr; + blender::Array phis_a(islands.size()); + blender::Array phis_b(islands.size()); + blender::Array *phis_low = nullptr; /* Scaling smaller than `min_scale_roundoff` is unlikely to fit and * will destroy information in existing UVs. */ @@ -2044,7 +2044,7 @@ static float pack_islands_margin_fraction(const Span &islands, scale = std::max(scale, min_scale_roundoff); /* Evaluate our `f`. */ - Array *phis_target = (phis_low == &phis_a) ? &phis_b : &phis_a; + blender::Array *phis_target = (phis_low == &phis_a) ? &phis_b : &phis_a; const float margin = rescale_margin ? margin_fraction * scale : margin_fraction; const float max_uv = pack_islands_scale_margin(islands, scale, margin, params, *phis_target) / params.target_extent; @@ -2182,8 +2182,8 @@ class OverlapMerger { * * Technically, performance is O(n^2). In practice, should be fast enough. */ - Vector sub_islands; /* Pack these islands instead. */ - Vector merge_trace; /* Trace merge information. */ + blender::Vector sub_islands; /* Pack these islands instead. */ + blender::Vector merge_trace; /* Trace merge information. */ for (const int64_t i : islands.index_range()) { PackIsland *island = islands[i]; island->calculate_pivot_(); @@ -2304,7 +2304,7 @@ float pack_islands(const Span &islands, const UVPackIsland_Params /* Either all of the islands can scale, or none of them can. * In either case, we pack them all tight to the origin. */ - Array phis(islands.size()); + blender::Array phis(islands.size()); const float scale = 1.0f; const float max_uv = pack_islands_scale_margin(islands, scale, margin, params, phis); const float result = can_scale_count && max_uv > 1e-14f ? params.target_extent / max_uv : 1.0f; diff --git a/source/blender/geometry/intern/uv_parametrizer.cc b/source/blender/geometry/intern/uv_parametrizer.cc index a89464bd602..733617b43fc 100644 --- a/source/blender/geometry/intern/uv_parametrizer.cc +++ b/source/blender/geometry/intern/uv_parametrizer.cc @@ -4111,7 +4111,7 @@ void uv_parametrizer_pack(ParamHandle *handle, float margin, bool do_rotate, boo float matrix[2][2]; pack_island->build_transformation(island_scale, pack_island->angle, matrix); for (PVert *v = chart->verts; v; v = v->nextlink) { - geometry::mul_v2_m2_add_v2v2(v->uv, matrix, v->uv, pack_island->pre_translate); + blender::geometry::mul_v2_m2_add_v2v2(v->uv, matrix, v->uv, pack_island->pre_translate); } pack_island_vector[i] = nullptr; diff --git a/source/blender/gpencil_modifiers_legacy/intern/MOD_gpencil_legacy_shrinkwrap.cc b/source/blender/gpencil_modifiers_legacy/intern/MOD_gpencil_legacy_shrinkwrap.cc index 7331ab8ad8a..4660da2afe7 100644 --- a/source/blender/gpencil_modifiers_legacy/intern/MOD_gpencil_legacy_shrinkwrap.cc +++ b/source/blender/gpencil_modifiers_legacy/intern/MOD_gpencil_legacy_shrinkwrap.cc @@ -206,7 +206,7 @@ static void update_depsgraph(GpencilModifierData *md, CustomData_MeshMasks mask = {0}; if (BKE_shrinkwrap_needs_normals(mmd->shrink_type, mmd->shrink_mode)) { - mask.lmask |= CD_MASK_CUSTOMLOOPNORMAL; + mask.lmask |= CD_MASK_NORMAL | CD_MASK_CUSTOMLOOPNORMAL; } if (mmd->target != nullptr) { diff --git a/source/blender/gpencil_modifiers_legacy/intern/lineart/lineart_cpu.cc b/source/blender/gpencil_modifiers_legacy/intern/lineart/lineart_cpu.cc index 37423c2e60d..750edc765c6 100644 --- a/source/blender/gpencil_modifiers_legacy/intern/lineart/lineart_cpu.cc +++ b/source/blender/gpencil_modifiers_legacy/intern/lineart/lineart_cpu.cc @@ -2006,6 +2006,10 @@ static void lineart_geometry_object_load(LineartObjectInfo *ob_info, if (orig_ob->lineart.flags & OBJECT_LRT_OWN_CREASE) { crease_angle = cosf(M_PI - orig_ob->lineart.crease_threshold); } + else if (ob_info->original_me->flag & ME_AUTOSMOOTH) { + crease_angle = cosf(ob_info->original_me->smoothresh); + use_auto_smooth = true; + } else { crease_angle = la_data->conf.crease_threshold; } diff --git a/source/blender/gpencil_modifiers_legacy/intern/lineart/lineart_ops.cc b/source/blender/gpencil_modifiers_legacy/intern/lineart/lineart_ops.cc index 6b879e3c487..734744e2599 100644 --- a/source/blender/gpencil_modifiers_legacy/intern/lineart/lineart_ops.cc +++ b/source/blender/gpencil_modifiers_legacy/intern/lineart/lineart_ops.cc @@ -219,12 +219,15 @@ static void lineart_gpencil_guard_modifiers(LineartBakeJob *bj) } } -static void lineart_gpencil_bake_startjob(void *customdata, wmJobWorkerStatus *worker_status) +static void lineart_gpencil_bake_startjob(void *customdata, + bool *stop, + bool *do_update, + float *progress) { LineartBakeJob *bj = (LineartBakeJob *)customdata; - bj->stop = &worker_status->stop; - bj->do_update = &worker_status->do_update; - bj->progress = &worker_status->progress; + bj->stop = stop; + bj->do_update = do_update; + bj->progress = progress; lineart_gpencil_guard_modifiers(bj); @@ -336,8 +339,9 @@ static int lineart_gpencil_bake_common(bContext *C, return OPERATOR_RUNNING_MODAL; } - wmJobWorkerStatus worker_status = {}; - lineart_gpencil_bake_startjob(bj, &worker_status); + float pseduo_progress; + bool pseduo_do_update; + lineart_gpencil_bake_startjob(bj, nullptr, &pseduo_do_update, &pseduo_progress); BLI_linklist_free(bj->objects, nullptr); MEM_freeN(bj); diff --git a/source/blender/gpu/CMakeLists.txt b/source/blender/gpu/CMakeLists.txt index e6c3d1d1b31..d3b353bbb25 100644 --- a/source/blender/gpu/CMakeLists.txt +++ b/source/blender/gpu/CMakeLists.txt @@ -43,6 +43,7 @@ if(WITH_RENDERDOC) endif() set(INC_SYS + ${Epoxy_INCLUDE_DIRS} ) set(SRC @@ -200,7 +201,6 @@ set(VULKAN_SRC vulkan/vk_bindable_resource.cc vulkan/vk_buffer.cc vulkan/vk_command_buffer.cc - vulkan/vk_command_buffers.cc vulkan/vk_common.cc vulkan/vk_context.cc vulkan/vk_data_conversion.cc @@ -238,7 +238,6 @@ set(VULKAN_SRC vulkan/vk_bindable_resource.hh vulkan/vk_buffer.hh vulkan/vk_command_buffer.hh - vulkan/vk_command_buffers.hh vulkan/vk_common.hh vulkan/vk_context.hh vulkan/vk_data_conversion.hh @@ -326,20 +325,14 @@ set(LIB PRIVATE bf::blenlib PRIVATE bf::dna PRIVATE bf::intern::atomic + ${Epoxy_LIBRARIES} PRIVATE bf::intern::guardedalloc ) # Select Backend source based on availability if(WITH_OPENGL_BACKEND) - list(APPEND INC_SYS - ${Epoxy_INCLUDE_DIRS} - ) - list(APPEND SRC - ${OPENGL_SRC} - ) - list(APPEND LIB - ${Epoxy_LIBRARIES} - ) + list(APPEND SRC ${OPENGL_SRC}) + add_definitions(-DWITH_OPENGL_BACKEND) endif() @@ -349,25 +342,12 @@ endif() if(WITH_VULKAN_BACKEND) - - if(APPLE) - list(APPEND INC_SYS - ${MOLTENVK_INCLUDE_DIRS} - ) - - list(APPEND LIB - ${MOLTENVK_LIBRARIES} - ) - endif() - list(APPEND INC ../../../extern/vulkan_memory_allocator ) list(APPEND INC_SYS ${VULKAN_INCLUDE_DIRS} - ) - - list(APPEND INC_SYS + ${MOLTENVK_INCLUDE_DIRS} ${SHADERC_INCLUDE_DIRS} ) list(APPEND SRC @@ -377,6 +357,7 @@ if(WITH_VULKAN_BACKEND) list(APPEND LIB ${VULKAN_LIBRARIES} ${SHADERC_LIBRARIES} + ${MOLTENVK_LIBRARIES} extern_vulkan_memory_allocator ) @@ -475,7 +456,6 @@ set(GLSL_SRC shaders/common/gpu_shader_common_math.glsl shaders/common/gpu_shader_common_math_utils.glsl shaders/common/gpu_shader_common_mix_rgb.glsl - shaders/common/gpu_shader_debug_gradients_lib.glsl shaders/common/gpu_shader_math_base_lib.glsl shaders/common/gpu_shader_math_fast_lib.glsl shaders/common/gpu_shader_math_matrix_lib.glsl diff --git a/source/blender/gpu/GPU_capabilities.h b/source/blender/gpu/GPU_capabilities.h index f3bf60a6293..1f22d7f1faa 100644 --- a/source/blender/gpu/GPU_capabilities.h +++ b/source/blender/gpu/GPU_capabilities.h @@ -12,8 +12,6 @@ #pragma once -#include "BLI_sys_types.h" - #ifdef __cplusplus extern "C" { #endif @@ -36,7 +34,6 @@ int GPU_max_varying_floats(void); int GPU_max_shader_storage_buffer_bindings(void); int GPU_max_compute_shader_storage_blocks(void); int GPU_max_samplers(void); -size_t GPU_max_storage_buffer_size(void); int GPU_extensions_len(void); const char *GPU_extension_get(int i); diff --git a/source/blender/gpu/GPU_common_types.h b/source/blender/gpu/GPU_common_types.h index 85c19d3c884..ff9501a1021 100644 --- a/source/blender/gpu/GPU_common_types.h +++ b/source/blender/gpu/GPU_common_types.h @@ -12,63 +12,13 @@ extern "C" { #endif -/** - * Describes the load operation of a frame-buffer attachment at the start of a render pass. - */ typedef enum eGPULoadOp { - /** - * Clear the frame-buffer attachment using the clear value. - */ GPU_LOADACTION_CLEAR = 0, - /** - * Load the value from the attached texture. - * Cannot be used with memoryless attachments. - * Slower than `GPU_LOADACTION_CLEAR` or `GPU_LOADACTION_DONT_CARE`. - */ GPU_LOADACTION_LOAD, - /** - * Do not care about the content of the attachment when the render pass starts. - * Useful if only the values being written are important. - * Faster than `GPU_LOADACTION_CLEAR`. - */ - GPU_LOADACTION_DONT_CARE, + GPU_LOADACTION_DONT_CARE } eGPULoadOp; -/** - * Describes the store operation of a frame-buffer attachment at the end of a render pass. - */ -typedef enum eGPUStoreOp { - /** - * Do not care about the content of the attachment when the render pass ends. - * Useful if only the values being written are important. - * Cannot be used with memoryless attachments. - */ - GPU_STOREACTION_STORE = 0, - /** - * The result of the rendering for this attachment will be discarded. - * No writes to the texture memory will be done which makes it faster than - * `GPU_STOREACTION_STORE`. - * IMPORTANT: The actual values of the attachment is to be considered undefined. - * Only to be used on transient attachment that are only used within the boundaries of - * a render pass (ex.: Unneeded depth buffer result). - */ - GPU_STOREACTION_DONT_CARE, -} eGPUStoreOp; - -/** - * Describes the state of a frame-buffer attachment during a sub-pass. - * - * NOTE: Until this is correctly implemented in all backend, reading and writing from the - * same attachment will not work. Although there is no case where it would currently be useful. - */ -typedef enum GPUAttachmentState { - /** Attachment will not be written during rendering. */ - GPU_ATTACHEMENT_IGNORE = 0, - /** Attachment will be written during render sub-pass. This also works with blending. */ - GPU_ATTACHEMENT_WRITE, - /** Attachment is used as input in the fragment shader. Incompatible with depth on Metal. */ - GPU_ATTACHEMENT_READ, -} GPUAttachmentState; +typedef enum eGPUStoreOp { GPU_STOREACTION_STORE = 0, GPU_STOREACTION_DONT_CARE } eGPUStoreOp; typedef enum eGPUFrontFace { GPU_CLOCKWISE, diff --git a/source/blender/gpu/GPU_framebuffer.h b/source/blender/gpu/GPU_framebuffer.h index 0d88b110467..4ae1081dc3a 100644 --- a/source/blender/gpu/GPU_framebuffer.h +++ b/source/blender/gpu/GPU_framebuffer.h @@ -178,38 +178,6 @@ void GPU_framebuffer_bind_loadstore(GPUFrameBuffer *framebuffer, GPU_framebuffer_bind_loadstore(_fb, actions, (sizeof(actions) / sizeof(GPULoadStore))); \ } -/** - * Sub-pass config array matches attachment structure of `GPU_framebuffer_config_array`. - * This allows to explicitly specify attachment state within the next sub-pass. - * This enables a number of bandwidth optimizations specially on Tile Based Deferred Renderers - * where the attachments can be kept into tile memory and used in place for later sub-passes. - * - * IMPORTANT: When using this, the framebuffer initial state is undefined. A sub-pass transition - * need to be issued before any draw-call. - * - * Example: - * \code{.c} - * GPU_framebuffer_bind_loadstore(&fb, { - * GPU_ATTACHEMENT_WRITE, // must be depth buffer - * GPU_ATTACHEMENT_READ, // Color attachment 0 - * GPU_ATTACHEMENT_IGNORE, // Color attachment 1 - * GPU_ATTACHEMENT_WRITE} // Color attachment 2 - * }) - * \endcode - * - * \note Excess attachments will have no effect as long as they are GPU_ATTACHEMENT_IGNORE. - */ -void GPU_framebuffer_subpass_transition_array(GPUFrameBuffer *framebuffer, - const GPUAttachmentState *attachment_states, - uint attachment_len); - -#define GPU_framebuffer_subpass_transition(_fb, ...) \ - { \ - GPUAttachmentState actions[] = __VA_ARGS__; \ - GPU_framebuffer_subpass_transition_array( \ - _fb, actions, (sizeof(actions) / sizeof(GPUAttachmentState))); \ - } - /** \} */ /* -------------------------------------------------------------------- */ diff --git a/source/blender/gpu/GPU_platform.h b/source/blender/gpu/GPU_platform.h index 6507dbb281c..348a878e570 100644 --- a/source/blender/gpu/GPU_platform.h +++ b/source/blender/gpu/GPU_platform.h @@ -56,19 +56,6 @@ typedef enum eGPUSupportLevel { GPU_SUPPORT_LEVEL_UNSUPPORTED, } eGPUSupportLevel; -typedef enum GPUArchitectureType { - /* Immediate Mode Renderer (IMR). - * Typically, an IMR architecture will execute GPU work in sequence, rasterizing primitives in - * order. */ - GPU_ARCHITECTURE_IMR = 0, - - /* Tile-Based-Deferred-Renderer (TBDR). - * A TBDR architecture will typically execute the vertex stage up-front for all primitives, - * binning geometry into distinct tiled regions. Fragments will then be rasterized within - * the bounds of one tile at a time. */ - GPU_ARCHITECTURE_TBDR = 1, -} GPUArchitectureType; - #ifdef __cplusplus extern "C" { #endif @@ -88,7 +75,6 @@ const char *GPU_platform_renderer(void); const char *GPU_platform_version(void); const char *GPU_platform_support_level_key(void); const char *GPU_platform_gpu_name(void); -GPUArchitectureType GPU_platform_architecture(void); #ifdef __cplusplus } diff --git a/source/blender/gpu/GPU_shader_shared_utils.h b/source/blender/gpu/GPU_shader_shared_utils.h index e605b0d4f84..8b7235ac01e 100644 --- a/source/blender/gpu/GPU_shader_shared_utils.h +++ b/source/blender/gpu/GPU_shader_shared_utils.h @@ -29,7 +29,6 @@ */ #ifdef GPU_SHADER -# define BLI_STATIC_ASSERT(cond, msg) # define BLI_STATIC_ASSERT_ALIGN(type_, align_) # define BLI_STATIC_ASSERT_SIZE(type_, size_) # define static diff --git a/source/blender/gpu/GPU_storage_buffer.h b/source/blender/gpu/GPU_storage_buffer.h index 5d7c72fb1cb..46cc5b030dc 100644 --- a/source/blender/gpu/GPU_storage_buffer.h +++ b/source/blender/gpu/GPU_storage_buffer.h @@ -48,24 +48,10 @@ void GPU_storagebuf_clear_to_zero(GPUStorageBuf *ssbo); */ void GPU_storagebuf_clear(GPUStorageBuf *ssbo, uint32_t clear_value); -/** - * Explicitly sync updated storage buffer contents back to host within the GPU command stream. This - * ensures any changes made by the GPU are visible to the host. - * NOTE: This command is only valid for host-visible storage buffers. - */ -void GPU_storagebuf_sync_to_host(GPUStorageBuf *ssbo); - /** * Read back content of the buffer to CPU for inspection. * Slow! Only use for inspection / debugging. - * - * NOTE: If GPU_storagebuf_sync_to_host is called, this command is synchronized against that call. - * If pending GPU updates to the storage buffer are not yet visible to the host, the command will - * stall until dependent GPU work has completed. - * - * Otherwise, this command is unsynchronized and will return current visible storage buffer - * contents immediately. - * Alternatively, use appropriate barrier or GPU_finish before reading. + * NOTE: Not synchronized. Use appropriate barrier before reading. */ void GPU_storagebuf_read(GPUStorageBuf *ssbo, void *data); diff --git a/source/blender/gpu/GPU_texture.h b/source/blender/gpu/GPU_texture.h index 135aafd26da..3f76b63b35f 100644 --- a/source/blender/gpu/GPU_texture.h +++ b/source/blender/gpu/GPU_texture.h @@ -779,7 +779,7 @@ void GPU_texture_update_mipmap(GPUTexture *texture, /** * Fills the whole texture with the same data for all pixels. - * \warning Only works for 2D and 3D textures. + * \warning Only work for 2D texture for now. * \warning Only clears the MIP 0 of the texture. * \param data_format: data format of the pixel data. * \note The format is float for UNORM textures. @@ -990,21 +990,6 @@ bool GPU_texture_has_stencil_format(const GPUTexture *texture); */ bool GPU_texture_has_integer_format(const GPUTexture *texture); -/** - * Return true if the texture format is a float type. - */ -bool GPU_texture_has_float_format(const GPUTexture *tex); - -/** - * Return true if the texture format is an integer normalized type. - */ -bool GPU_texture_has_normalized_format(const GPUTexture *tex); - -/** - * Return true if the texture format is a signed type. - */ -bool GPU_texture_has_signed_format(const GPUTexture *tex); - /** * Returns the pixel dimensions of a texture's mip-map level. * \a size is expected to be a pointer to a vector of dimension matching the texture's dimension diff --git a/source/blender/gpu/dummy/dummy_backend.hh b/source/blender/gpu/dummy/dummy_backend.hh index 3cd88bacb41..72f1af8e280 100644 --- a/source/blender/gpu/dummy/dummy_backend.hh +++ b/source/blender/gpu/dummy/dummy_backend.hh @@ -30,8 +30,7 @@ class DummyBackend : public GPUBackend { GPU_BACKEND_NONE, "Unknown", "", - "", - GPU_ARCHITECTURE_IMR); + ""); } void delete_resources() override {} void samplers_update() override {} diff --git a/source/blender/gpu/dummy/dummy_framebuffer.hh b/source/blender/gpu/dummy/dummy_framebuffer.hh index a9d7dbeccd3..bff41acd9ac 100644 --- a/source/blender/gpu/dummy/dummy_framebuffer.hh +++ b/source/blender/gpu/dummy/dummy_framebuffer.hh @@ -35,9 +35,6 @@ class DummyFrameBuffer : public FrameBuffer { void attachment_set_loadstore_op(GPUAttachmentType /*type*/, GPULoadStore /*ls*/) override {} - void subpass_transition(const GPUAttachmentState /*depth_attachment_state*/, - Span /*color_attachment_states*/) override{}; - void read(eGPUFrameBufferBits /*planes*/, eGPUDataFormat /*format*/, const int /*area*/[4], diff --git a/source/blender/gpu/intern/gpu_batch_presets.cc b/source/blender/gpu/intern/gpu_batch_presets.cc index 6352d8bf347..c5292ac04c7 100644 --- a/source/blender/gpu/intern/gpu_batch_presets.cc +++ b/source/blender/gpu/intern/gpu_batch_presets.cc @@ -385,11 +385,6 @@ void gpu_batch_presets_exit() MEM_freeN(link); } - /* Reset pointers to null for subsequent initializations after tear-down. */ - g_presets_2d = {{nullptr}}; - g_presets_3d = {{nullptr}}; - presets_list = {nullptr, nullptr}; - BLI_mutex_end(&g_presets_3d.mutex); } diff --git a/source/blender/gpu/intern/gpu_capabilities.cc b/source/blender/gpu/intern/gpu_capabilities.cc index 6effcbefe01..100bed3ad05 100644 --- a/source/blender/gpu/intern/gpu_capabilities.cc +++ b/source/blender/gpu/intern/gpu_capabilities.cc @@ -207,11 +207,6 @@ bool GPU_transform_feedback_support() return GCaps.transform_feedback_support; } -size_t GPU_max_storage_buffer_size() -{ - return GCaps.max_storage_buffer_size; -} - /** \} */ /* -------------------------------------------------------------------- */ diff --git a/source/blender/gpu/intern/gpu_capabilities_private.hh b/source/blender/gpu/intern/gpu_capabilities_private.hh index 60afd22ffa3..c387d778760 100644 --- a/source/blender/gpu/intern/gpu_capabilities_private.hh +++ b/source/blender/gpu/intern/gpu_capabilities_private.hh @@ -38,7 +38,6 @@ struct GPUCapabilities { int max_varying_floats = 0; int max_shader_storage_buffer_bindings = 0; int max_compute_shader_storage_blocks = 0; - size_t max_storage_buffer_size = 0; int extensions_len = 0; const char *(*extension_get)(int); diff --git a/source/blender/gpu/intern/gpu_context.cc b/source/blender/gpu/intern/gpu_context.cc index f98f0378f4e..f8a7f33a7eb 100644 --- a/source/blender/gpu/intern/gpu_context.cc +++ b/source/blender/gpu/intern/gpu_context.cc @@ -185,7 +185,7 @@ void GPU_context_main_unlock() /** \} */ /* -------------------------------------------------------------------- */ -/** \name GPU Begin/end work blocks +/** \name GPU Begin/end work blocks * * Used to explicitly define a per-frame block within which GPU work will happen. * Used for global autoreleasepool flushing in Metal diff --git a/source/blender/gpu/intern/gpu_framebuffer.cc b/source/blender/gpu/intern/gpu_framebuffer.cc index 8ef6ee21723..d67e68be797 100644 --- a/source/blender/gpu/intern/gpu_framebuffer.cc +++ b/source/blender/gpu/intern/gpu_framebuffer.cc @@ -265,14 +265,6 @@ void GPU_framebuffer_bind_loadstore(GPUFrameBuffer *gpu_fb, fb->load_store_config_array(load_store_actions, actions_len); } -void GPU_framebuffer_subpass_transition_array(GPUFrameBuffer *gpu_fb, - const GPUAttachmentState *attachment_states, - uint attachment_len) -{ - unwrap(gpu_fb)->subpass_transition( - attachment_states[0], Span(attachment_states + 1, attachment_len - 1)); -} - void GPU_framebuffer_bind_no_srgb(GPUFrameBuffer *gpu_fb) { const bool enable_srgb = false; diff --git a/source/blender/gpu/intern/gpu_framebuffer_private.hh b/source/blender/gpu/intern/gpu_framebuffer_private.hh index adb25b0b454..336ffe9bc84 100644 --- a/source/blender/gpu/intern/gpu_framebuffer_private.hh +++ b/source/blender/gpu/intern/gpu_framebuffer_private.hh @@ -127,9 +127,6 @@ class FrameBuffer { int dst_offset_x, int dst_offset_y) = 0; - virtual void subpass_transition(const GPUAttachmentState depth_attachment_state, - Span color_attachment_states) = 0; - void load_store_config_array(const GPULoadStore *load_store_actions, uint actions_len); void attachment_set(GPUAttachmentType type, const GPUAttachment &new_attachment); diff --git a/source/blender/gpu/intern/gpu_platform.cc b/source/blender/gpu/intern/gpu_platform.cc index d6ba3cdf6df..e6720912d4a 100644 --- a/source/blender/gpu/intern/gpu_platform.cc +++ b/source/blender/gpu/intern/gpu_platform.cc @@ -13,7 +13,7 @@ #include "BLI_dynstr.h" #include "BLI_string.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "GPU_platform.h" @@ -70,8 +70,7 @@ void GPUPlatformGlobal::init(eGPUDeviceType gpu_device, eGPUBackendType backend, const char *vendor_str, const char *renderer_str, - const char *version_str, - GPUArchitectureType arch_type) + const char *version_str) { this->clear(); @@ -92,7 +91,6 @@ void GPUPlatformGlobal::init(eGPUDeviceType gpu_device, this->support_key = create_key(gpu_support_level, vendor, renderer, version); this->gpu_name = create_gpu_name(vendor, renderer, version); this->backend = backend; - this->architecture_type = arch_type; } void GPUPlatformGlobal::clear() @@ -151,12 +149,6 @@ const char *GPU_platform_gpu_name() return GPG.gpu_name; } -GPUArchitectureType GPU_platform_architecture() -{ - BLI_assert(GPG.initialized); - return GPG.architecture_type; -} - bool GPU_type_matches(eGPUDeviceType device, eGPUOSType os, eGPUDriverType driver) { return GPU_type_matches_ex(device, os, driver, GPU_BACKEND_ANY); diff --git a/source/blender/gpu/intern/gpu_platform_private.hh b/source/blender/gpu/intern/gpu_platform_private.hh index 3d2efc06a22..9ae9010e61a 100644 --- a/source/blender/gpu/intern/gpu_platform_private.hh +++ b/source/blender/gpu/intern/gpu_platform_private.hh @@ -25,7 +25,6 @@ class GPUPlatformGlobal { char *support_key = nullptr; char *gpu_name = nullptr; eGPUBackendType backend = GPU_BACKEND_NONE; - GPUArchitectureType architecture_type = GPU_ARCHITECTURE_IMR; public: void init(eGPUDeviceType gpu_device, @@ -35,8 +34,7 @@ class GPUPlatformGlobal { eGPUBackendType backend, const char *vendor_str, const char *renderer_str, - const char *version_str, - GPUArchitectureType arch_type); + const char *version_str); void clear(); }; diff --git a/source/blender/gpu/intern/gpu_shader.cc b/source/blender/gpu/intern/gpu_shader.cc index 91f5c4e046f..99fe0009373 100644 --- a/source/blender/gpu/intern/gpu_shader.cc +++ b/source/blender/gpu/intern/gpu_shader.cc @@ -10,7 +10,7 @@ #include "BLI_math_matrix.h" #include "BLI_string.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "GPU_capabilities.h" #include "GPU_debug.h" diff --git a/source/blender/gpu/intern/gpu_shader_builder_stubs.cc b/source/blender/gpu/intern/gpu_shader_builder_stubs.cc index 777959f834f..07ccafb6fb7 100644 --- a/source/blender/gpu/intern/gpu_shader_builder_stubs.cc +++ b/source/blender/gpu/intern/gpu_shader_builder_stubs.cc @@ -226,6 +226,15 @@ extern "C" void ntreeFreeLocalTree(struct bNodeTree * /*ntree*/) /** \} */ +/* -------------------------------------------------------------------- */ +/** \name Stubs of bmesh.h + * \{ */ +extern "C" void BM_face_as_array_vert_tri(BMFace * /*f*/, BMVert *[3] /*r_verts*/) +{ + BLI_assert_unreachable(); +} +/** \} */ + /* -------------------------------------------------------------------- */ /** \name Stubs of DRW_engine.h * \{ */ diff --git a/source/blender/gpu/intern/gpu_shader_create_info.cc b/source/blender/gpu/intern/gpu_shader_create_info.cc index a32d687b099..d528383f8f5 100644 --- a/source/blender/gpu/intern/gpu_shader_create_info.cc +++ b/source/blender/gpu/intern/gpu_shader_create_info.cc @@ -243,37 +243,7 @@ std::string ShaderCreateInfo::check_error() const } } - if (!this->geometry_source_.is_empty()) { - if (bool(this->builtins_ & BuiltinBits::BARYCENTRIC_COORD)) { - error += "Shader " + this->name_ + - " has geometry stage and uses barycentric coordinates. This is not allowed as " - "fallback injects a geometry stage.\n"; - } - if (bool(this->builtins_ & BuiltinBits::VIEWPORT_INDEX)) { - error += "Shader " + this->name_ + - " has geometry stage and uses multi-viewport. This is not allowed as " - "fallback injects a geometry stage.\n"; - } - if (bool(this->builtins_ & BuiltinBits::LAYER)) { - error += "Shader " + this->name_ + - " has geometry stage and uses layer output. This is not allowed as " - "fallback injects a geometry stage.\n"; - } - } - -#ifndef NDEBUG - if (bool(this->builtins_ & - (BuiltinBits::BARYCENTRIC_COORD | BuiltinBits::VIEWPORT_INDEX | BuiltinBits::LAYER))) - { - for (const StageInterfaceInfo *interface : this->vertex_out_interfaces_) { - if (interface->instance_name.is_empty()) { - error += "Shader " + this->name_ + " uses interface " + interface->name + - " that doesn't contain an instance name, but is required for the fallback " - "geometry shader.\n"; - } - } - } - +#ifdef DEBUG if (!this->is_vulkan_compatible()) { error += this->name_ + " contains a stage interface using an instance name and mixed interpolation modes. " @@ -552,8 +522,7 @@ bool gpu_shader_create_info_compile_all() if ((info->metal_backend_only_ && GPU_backend_get_type() != GPU_BACKEND_METAL) || (GPU_compute_shader_support() == false && info->compute_source_ != nullptr) || (GPU_geometry_shader_support() == false && info->geometry_source_ != nullptr) || - (GPU_shader_image_load_store_support() == false && info->has_resource_image()) || - (GPU_transform_feedback_support() == false && info->tf_type_ != GPU_SHADER_TFB_NONE)) + (GPU_shader_image_load_store_support() == false && info->has_resource_image())) { skipped++; continue; diff --git a/source/blender/gpu/intern/gpu_shader_log.cc b/source/blender/gpu/intern/gpu_shader_log.cc index 0997c89b788..96e91ccbc10 100644 --- a/source/blender/gpu/intern/gpu_shader_log.cc +++ b/source/blender/gpu/intern/gpu_shader_log.cc @@ -10,7 +10,7 @@ #include "BLI_dynstr.h" #include "BLI_string.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_vector.hh" #include "gpu_shader_dependency_private.h" diff --git a/source/blender/gpu/intern/gpu_storage_buffer.cc b/source/blender/gpu/intern/gpu_storage_buffer.cc index b0d2e550117..0d1254592fe 100644 --- a/source/blender/gpu/intern/gpu_storage_buffer.cc +++ b/source/blender/gpu/intern/gpu_storage_buffer.cc @@ -106,11 +106,6 @@ void GPU_storagebuf_copy_sub_from_vertbuf( unwrap(ssbo)->copy_sub(unwrap(src), dst_offset, src_offset, copy_size); } -void GPU_storagebuf_sync_to_host(GPUStorageBuf *ssbo) -{ - unwrap(ssbo)->async_flush_to_host(); -} - void GPU_storagebuf_read(GPUStorageBuf *ssbo, void *data) { unwrap(ssbo)->read(data); diff --git a/source/blender/gpu/intern/gpu_storage_buffer_private.hh b/source/blender/gpu/intern/gpu_storage_buffer_private.hh index c099b67fba2..39541944901 100644 --- a/source/blender/gpu/intern/gpu_storage_buffer_private.hh +++ b/source/blender/gpu/intern/gpu_storage_buffer_private.hh @@ -47,7 +47,6 @@ class StorageBuf { virtual void clear(uint32_t clear_value) = 0; virtual void copy_sub(VertBuf *src, uint dst_offset, uint src_offset, uint copy_size) = 0; virtual void read(void *data) = 0; - virtual void async_flush_to_host() = 0; }; /* Syntactic sugar. */ diff --git a/source/blender/gpu/intern/gpu_texture.cc b/source/blender/gpu/intern/gpu_texture.cc index c6420e6583a..533bf9aca67 100644 --- a/source/blender/gpu/intern/gpu_texture.cc +++ b/source/blender/gpu/intern/gpu_texture.cc @@ -920,22 +920,6 @@ bool GPU_texture_has_integer_format(const GPUTexture *tex) return (reinterpret_cast(tex)->format_flag_get() & GPU_FORMAT_INTEGER) != 0; } -bool GPU_texture_has_float_format(const GPUTexture *tex) -{ - return (reinterpret_cast(tex)->format_flag_get() & GPU_FORMAT_FLOAT) != 0; -} - -bool GPU_texture_has_normalized_format(const GPUTexture *tex) -{ - return (reinterpret_cast(tex)->format_flag_get() & - GPU_FORMAT_NORMALIZED_INTEGER) != 0; -} - -bool GPU_texture_has_signed_format(const GPUTexture *tex) -{ - return (reinterpret_cast(tex)->format_flag_get() & GPU_FORMAT_SIGNED) != 0; -} - bool GPU_texture_is_cube(const GPUTexture *tex) { return (reinterpret_cast(tex)->type_get() & GPU_TEXTURE_CUBE) != 0; diff --git a/source/blender/gpu/metal/kernels/compute_texture_update.msl b/source/blender/gpu/metal/kernels/compute_texture_update.msl index 02eb2b193f7..d46f839c386 100644 --- a/source/blender/gpu/metal/kernels/compute_texture_update.msl +++ b/source/blender/gpu/metal/kernels/compute_texture_update.msl @@ -33,16 +33,6 @@ using namespace metal; # define DIMS 3 #endif -/* `base_offset` refers to the base read offset in bytes of the input data buffer. - * Component offset adds an additional offset to read components. When clearing a buffer - * and only one source color data element is provided, the base_offset is always zero.*/ -#if IS_TEXTURE_CLEAR == 1 -# define READ_INPUT_DATA(base_offset, component_offset) (input_data[0 + component_offset]) -#else -# define READ_INPUT_DATA(base_offset, component_offset) \ - (input_data[base_offset + component_offset]) -#endif - /* Position dimensionality for thread-group. */ #if DIMS == 1 # define POSITION_TYPE uint @@ -75,7 +65,7 @@ kernel void compute_texture_update(constant TextureUpdateParams ¶ms [[buffer vec output; for (int i = 0; i < COMPONENT_COUNT_INPUT; i++) { - output[i] = OUTPUT_DATA_TYPE(READ_INPUT_DATA(index, i)); + output[i] = OUTPUT_DATA_TYPE(input_data[index + i]); } for (int i = COMPONENT_COUNT_INPUT; i < COMPONENT_COUNT_OUTPUT; i++) { output[i] = OUTPUT_DATA_TYPE(0); @@ -92,7 +82,7 @@ kernel void compute_texture_update(constant TextureUpdateParams ¶ms [[buffer vec output; for (int i = 0; i < COMPONENT_COUNT_INPUT; i++) { - output[i] = OUTPUT_DATA_TYPE(READ_INPUT_DATA(index, i)); + output[i] = OUTPUT_DATA_TYPE(input_data[index + i]); } for (int i = COMPONENT_COUNT_INPUT; i < COMPONENT_COUNT_OUTPUT; i++) { output[i] = OUTPUT_DATA_TYPE(0); @@ -112,7 +102,7 @@ kernel void compute_texture_update(constant TextureUpdateParams ¶ms [[buffer vec output; for (int i = 0; i < COMPONENT_COUNT_INPUT; i++) { - output[i] = OUTPUT_DATA_TYPE(READ_INPUT_DATA(index, i)); + output[i] = OUTPUT_DATA_TYPE(input_data[index + i]); } for (int i = COMPONENT_COUNT_INPUT; i < COMPONENT_COUNT_OUTPUT; i++) { output[i] = OUTPUT_DATA_TYPE(0); @@ -130,7 +120,7 @@ kernel void compute_texture_update(constant TextureUpdateParams ¶ms [[buffer vec output; for (int i = 0; i < COMPONENT_COUNT_INPUT; i++) { - output[i] = OUTPUT_DATA_TYPE(READ_INPUT_DATA(index, i)); + output[i] = OUTPUT_DATA_TYPE(input_data[index + i]); } for (int i = COMPONENT_COUNT_INPUT; i < COMPONENT_COUNT_OUTPUT; i++) { output[i] = OUTPUT_DATA_TYPE(0); @@ -151,7 +141,7 @@ kernel void compute_texture_update(constant TextureUpdateParams ¶ms [[buffer vec output; for (int i = 0; i < COMPONENT_COUNT_INPUT; i++) { - output[i] = OUTPUT_DATA_TYPE(READ_INPUT_DATA(index, i)); + output[i] = OUTPUT_DATA_TYPE(input_data[index + i]); } for (int i = COMPONENT_COUNT_INPUT; i < COMPONENT_COUNT_OUTPUT; i++) { output[i] = OUTPUT_DATA_TYPE(0); diff --git a/source/blender/gpu/metal/kernels/depth_2d_update_info.hh b/source/blender/gpu/metal/kernels/depth_2d_update_info.hh index 48c46a24e2c..ced537ad81a 100644 --- a/source/blender/gpu/metal/kernels/depth_2d_update_info.hh +++ b/source/blender/gpu/metal/kernels/depth_2d_update_info.hh @@ -18,13 +18,13 @@ GPU_SHADER_CREATE_INFO(depth_2d_update_info_base) .push_constant(Type::VEC2, "offset") .push_constant(Type::VEC2, "size") .push_constant(Type::INT, "mip") + .sampler(0, ImageType::FLOAT_2D, "source_data", Frequency::PASS) .depth_write(DepthWrite::ANY) .vertex_source("depth_2d_update_vert.glsl"); GPU_SHADER_CREATE_INFO(depth_2d_update_float) .metal_backend_only(true) .fragment_source("depth_2d_update_float_frag.glsl") - .sampler(0, ImageType::FLOAT_2D, "source_data", Frequency::PASS) .additional_info("depth_2d_update_info_base") .do_static_compilation(true) .depth_write(DepthWrite::ANY); @@ -33,7 +33,6 @@ GPU_SHADER_CREATE_INFO(depth_2d_update_int24) .metal_backend_only(true) .fragment_source("depth_2d_update_int24_frag.glsl") .additional_info("depth_2d_update_info_base") - .sampler(0, ImageType::INT_2D, "source_data", Frequency::PASS) .do_static_compilation(true) .depth_write(DepthWrite::ANY); @@ -41,6 +40,5 @@ GPU_SHADER_CREATE_INFO(depth_2d_update_int32) .metal_backend_only(true) .fragment_source("depth_2d_update_int32_frag.glsl") .additional_info("depth_2d_update_info_base") - .sampler(0, ImageType::INT_2D, "source_data", Frequency::PASS) .do_static_compilation(true) .depth_write(DepthWrite::ANY); diff --git a/source/blender/gpu/metal/mtl_backend.mm b/source/blender/gpu/metal/mtl_backend.mm index cc30798abe9..28f3466c477 100644 --- a/source/blender/gpu/metal/mtl_backend.mm +++ b/source/blender/gpu/metal/mtl_backend.mm @@ -194,8 +194,6 @@ void MTLBackend::platform_init(MTLContext *ctx) if (G.debug & G_DEBUG_GPU) { printf("METAL API - DETECTED GPU: %s\n", vendor); } - GPUArchitectureType architecture_type = (mtl_device.hasUnifiedMemory) ? GPU_ARCHITECTURE_TBDR : - GPU_ARCHITECTURE_IMR; /* macOS is the only supported platform, but check to ensure we are not building with Metal * enablement on another platform. */ @@ -242,15 +240,7 @@ void MTLBackend::platform_init(MTLContext *ctx) printf("Renderer: %s\n", renderer); } - GPG.init(device, - os, - driver, - support_level, - GPU_BACKEND_METAL, - vendor, - renderer, - version, - architecture_type); + GPG.init(device, os, driver, support_level, GPU_BACKEND_METAL, vendor, renderer, version); } void MTLBackend::platform_exit() @@ -436,7 +426,6 @@ void MTLBackend::capabilities_init(MTLContext *ctx) /* Maximum buffer bindings: 31. Consider required slot for uniforms/UBOs/Vertex attributes. * Can use argument buffers if a higher limit is required. */ GCaps.max_shader_storage_buffer_bindings = 14; - GCaps.max_storage_buffer_size = size_t(ctx->device.maxBufferLength); if (GCaps.compute_shader_support) { GCaps.max_work_group_count[0] = 65535; diff --git a/source/blender/gpu/metal/mtl_command_buffer.mm b/source/blender/gpu/metal/mtl_command_buffer.mm index c68130c2d0a..1a03d5ab7e9 100644 --- a/source/blender/gpu/metal/mtl_command_buffer.mm +++ b/source/blender/gpu/metal/mtl_command_buffer.mm @@ -1000,7 +1000,7 @@ void MTLComputeState::bind_compute_buffer(id buffer, } } -void MTLRenderPassState::bind_vertex_bytes(const void *bytes, uint64_t length, uint index) +void MTLRenderPassState::bind_vertex_bytes(void *bytes, uint64_t length, uint index) { /* Bytes always updated as source data may have changed. */ BLI_assert(index >= 0 && index < MTL_MAX_BUFFER_BINDINGS); @@ -1025,7 +1025,7 @@ void MTLRenderPassState::bind_vertex_bytes(const void *bytes, uint64_t length, u } } -void MTLRenderPassState::bind_fragment_bytes(const void *bytes, uint64_t length, uint index) +void MTLRenderPassState::bind_fragment_bytes(void *bytes, uint64_t length, uint index) { /* Bytes always updated as source data may have changed. */ BLI_assert(index >= 0 && index < MTL_MAX_BUFFER_BINDINGS); @@ -1050,7 +1050,7 @@ void MTLRenderPassState::bind_fragment_bytes(const void *bytes, uint64_t length, } } -void MTLComputeState::bind_compute_bytes(const void *bytes, uint64_t length, uint index) +void MTLComputeState::bind_compute_bytes(void *bytes, uint64_t length, uint index) { /* Bytes always updated as source data may have changed. */ BLI_assert(index >= 0 && index < MTL_MAX_BUFFER_BINDINGS); diff --git a/source/blender/gpu/metal/mtl_common.hh b/source/blender/gpu/metal/mtl_common.hh index faffd5e4808..e754c7132b6 100644 --- a/source/blender/gpu/metal/mtl_common.hh +++ b/source/blender/gpu/metal/mtl_common.hh @@ -7,7 +7,7 @@ /** -- Renderer Options -- */ /* Number of frames over which rolling averages are taken. */ -#define MTL_FRAME_AVERAGE_COUNT 15 +#define MTL_FRAME_AVERAGE_COUNT 5 #define MTL_MAX_DRAWABLES 3 #define MTL_MAX_SET_BYTES_SIZE 4096 #define MTL_FORCE_WAIT_IDLE 0 diff --git a/source/blender/gpu/metal/mtl_context.hh b/source/blender/gpu/metal/mtl_context.hh index 6ae13ed6893..605dfa8c593 100644 --- a/source/blender/gpu/metal/mtl_context.hh +++ b/source/blender/gpu/metal/mtl_context.hh @@ -145,8 +145,8 @@ class MTLRenderPassState { /* Buffer binding (RenderCommandEncoder). */ void bind_vertex_buffer(id buffer, uint64_t buffer_offset, uint index); void bind_fragment_buffer(id buffer, uint64_t buffer_offset, uint index); - void bind_vertex_bytes(const void *bytes, uint64_t length, uint index); - void bind_fragment_bytes(const void *bytes, uint64_t length, uint index); + void bind_vertex_bytes(void *bytes, uint64_t length, uint index); + void bind_fragment_bytes(void *bytes, uint64_t length, uint index); }; /* Metal Context Compute Pass State -- Used to track active ComputeCommandEncoder state. */ @@ -184,7 +184,7 @@ class MTLComputeState { uint64_t buffer_offset, uint index, bool writeable = false); - void bind_compute_bytes(const void *bytes, uint64_t length, uint index); + void bind_compute_bytes(void *bytes, uint64_t length, uint index); }; /* Depth Stencil State */ diff --git a/source/blender/gpu/metal/mtl_context.mm b/source/blender/gpu/metal/mtl_context.mm index 5770e77967d..2778bdd3e93 100644 --- a/source/blender/gpu/metal/mtl_context.mm +++ b/source/blender/gpu/metal/mtl_context.mm @@ -1253,7 +1253,7 @@ bool MTLContext::ensure_buffer_bindings( "[UBO] UBO (UBO Name: %s) bound at location: %d (buffer[[%d]]) with size " "%lu (Expected size " "%d) (Shader Name: %s) is too small -- binding NULL buffer. This is likely an " - "over-binding, which is not used, but we need this to avoid validation " + "over-binding, which is not used, but we need this to avoid validation " "issues", shader_interface->get_name_at_offset(ubo.name_offset), ubo_location, @@ -2624,15 +2624,15 @@ void present(MTLRenderPassDescriptor *blit_descriptor, * If latency improves, increase frames in flight to improve overall * performance. */ int perf_max_drawables = MTL_MAX_DRAWABLES; - if (MTLContext::avg_drawable_latency_us > 150000) { + if (MTLContext::avg_drawable_latency_us > 185000) { perf_max_drawables = 1; } - else if (MTLContext::avg_drawable_latency_us > 75000) { + else if (MTLContext::avg_drawable_latency_us > 85000) { perf_max_drawables = 2; } while (MTLContext::max_drawables_in_flight > min_ii(perf_max_drawables, MTL_MAX_DRAWABLES)) { - PIL_sleep_ms(1); + PIL_sleep_ms(2); } /* Present is submitted in its own CMD Buffer to ensure drawable reference released as early as @@ -2683,7 +2683,7 @@ void present(MTLRenderPassDescriptor *blit_descriptor, .count(); MTLContext::latency_resolve_average(microseconds_per_frame); - MTL_LOG_INFO("Frame Latency: %f ms (Rolling avg: %f ms Drawables: %d)", + MTL_LOG_INFO("Frame Latency: %f ms (Rolling avg: %f ms Drawables: %d)", ((float)microseconds_per_frame) / 1000.0f, ((float)MTLContext::avg_drawable_latency_us) / 1000.0f, perf_max_drawables); diff --git a/source/blender/gpu/metal/mtl_framebuffer.hh b/source/blender/gpu/metal/mtl_framebuffer.hh index b4f009f4405..dbe8fa4c0b4 100644 --- a/source/blender/gpu/metal/mtl_framebuffer.hh +++ b/source/blender/gpu/metal/mtl_framebuffer.hh @@ -153,9 +153,6 @@ class MTLFrameBuffer : public FrameBuffer { int dst_offset_x, int dst_offset_y) override; - void subpass_transition(const GPUAttachmentState /*depth_attachment_state*/, - Span /*color_attachment_states*/) override{}; - void apply_state(); /* State. */ diff --git a/source/blender/gpu/metal/mtl_storage_buffer.hh b/source/blender/gpu/metal/mtl_storage_buffer.hh index af114c1e768..168838d86b2 100644 --- a/source/blender/gpu/metal/mtl_storage_buffer.hh +++ b/source/blender/gpu/metal/mtl_storage_buffer.hh @@ -54,10 +54,6 @@ class MTLStorageBuf : public StorageBuf { /** Usage type. */ GPUUsageType usage_; - /* Synchronization event for host reads. */ - id gpu_write_fence_ = nil; - uint64_t host_read_signal_value_ = 0; - public: MTLStorageBuf(size_t size, GPUUsageType usage, const char *name); ~MTLStorageBuf(); @@ -72,7 +68,6 @@ class MTLStorageBuf : public StorageBuf { void clear(uint32_t clear_value) override; void copy_sub(VertBuf *src, uint dst_offset, uint src_offset, uint copy_size) override; void read(void *data) override; - void async_flush_to_host() override; void init(); diff --git a/source/blender/gpu/metal/mtl_storage_buffer.mm b/source/blender/gpu/metal/mtl_storage_buffer.mm index 40006459f77..a07618c0f48 100644 --- a/source/blender/gpu/metal/mtl_storage_buffer.mm +++ b/source/blender/gpu/metal/mtl_storage_buffer.mm @@ -8,7 +8,6 @@ #include "BLI_string.h" -#include "GPU_state.h" #include "gpu_backend.hh" #include "gpu_context_private.hh" @@ -20,8 +19,6 @@ #include "mtl_uniform_buffer.hh" #include "mtl_vertex_buffer.hh" -#include "PIL_time.h" - namespace blender::gpu { /* -------------------------------------------------------------------- */ @@ -74,11 +71,6 @@ MTLStorageBuf::~MTLStorageBuf() has_data_ = false; } - if (gpu_write_fence_ != nil) { - [gpu_write_fence_ release]; - gpu_write_fence_ = nil; - } - /* Ensure SSBO is not bound to active CTX. * SSBO bindings are reset upon Context-switch so we do not need * to check deactivated context's. */ @@ -180,7 +172,6 @@ void MTLStorageBuf::update(const void *data) toBuffer:dst_buf destinationOffset:0 size:size_in_bytes_]; - staging_buf->free(); } else { /* Upload data. */ @@ -307,9 +298,6 @@ void MTLStorageBuf::copy_sub(VertBuf *src_, uint dst_offset, uint src_offset, ui if (dst->metal_buffer_ == nullptr) { dst->init(); } - if (copy_size == 0) { - return; - } if (src->vbo_ == nullptr) { src->bind(); } @@ -332,40 +320,6 @@ void MTLStorageBuf::copy_sub(VertBuf *src_, uint dst_offset, uint src_offset, ui size:copy_size]; } -void MTLStorageBuf::async_flush_to_host() -{ - bool device_only = (usage_ == GPU_USAGE_DEVICE_ONLY); - BLI_assert_msg(!device_only, - "Storage buffers with usage GPU_USAGE_DEVICE_ONLY cannot have their data " - "synchronized to the host."); - if (device_only) { - return; - } - - MTLContext *ctx = MTLContext::get(); - BLI_assert(ctx); - - if (gpu_write_fence_ == nil) { - gpu_write_fence_ = [ctx->device newSharedEvent]; - } - - if (metal_buffer_ == nullptr) { - this->init(); - } - - /* For discrete memory systems, explicitly flush GPU-resident memory back to host. */ - id storage_buf_mtl = this->metal_buffer_->get_metal_buffer(); - if (storage_buf_mtl.storageMode == MTLStorageModeManaged) { - id blit_encoder = ctx->main_command_buffer.ensure_begin_blit_encoder(); - [blit_encoder synchronizeResource:storage_buf_mtl]; - } - - /* Encode event signal and flush command buffer to ensure GPU work is in the pipeline for future - * reads. */ - ctx->main_command_buffer.encode_signal_event(gpu_write_fence_, ++host_read_signal_value_); - GPU_flush(); -} - void MTLStorageBuf::read(void *data) { if (data == nullptr) { @@ -376,71 +330,19 @@ void MTLStorageBuf::read(void *data) this->init(); } - /* Device-only storage buffers cannot be read directly and require staging. This path should only - be used for unit testing. */ - bool device_only = (usage_ == GPU_USAGE_DEVICE_ONLY); - if (device_only) { - /** Read storage buffer contents via staging buffer. */ + /* Managed buffers need to be explicitly flushed back to host. */ + if (metal_buffer_->get_resource_options() & MTLResourceStorageModeManaged) { /* Fetch active context. */ MTLContext *ctx = static_cast(unwrap(GPU_context_active_get())); BLI_assert(ctx); - /* Prepare staging buffer. */ - gpu::MTLBuffer *staging_buf = MTLContext::get_global_memory_manager()->allocate(size_in_bytes_, - true); - id staging_buf_mtl = staging_buf->get_metal_buffer(); - BLI_assert(staging_buf_mtl != nil); - - /* Ensure destination buffer. */ - id storage_buf_mtl = this->metal_buffer_->get_metal_buffer(); - BLI_assert(storage_buf_mtl != nil); - + /* Ensure GPU updates are flushed back to CPU. */ id blit_encoder = ctx->main_command_buffer.ensure_begin_blit_encoder(); - [blit_encoder copyFromBuffer:storage_buf_mtl - sourceOffset:0 - toBuffer:staging_buf_mtl - destinationOffset:0 - size:size_in_bytes_]; - if (staging_buf_mtl.storageMode == MTLStorageModeManaged) { - [blit_encoder synchronizeResource:staging_buf_mtl]; - } - - /* Device-only reads will always stall the GPU pipe. */ - GPU_finish(); - MTL_LOG_WARNING( - "Device-only storage buffer being read. This will stall the GPU pipeline. Ensure this " - "path is only used in testing."); - - /* Read contents back to data. */ - memcpy(data, staging_buf->get_host_ptr(), size_in_bytes_); - staging_buf->free(); + [blit_encoder synchronizeResource:metal_buffer_->get_metal_buffer()]; } - else { - /** Direct storage buffer read. */ - /* If we have a synchronization event from a prior memory sync, ensure memory is fully synced. - * Otherwise, assume read is asynchronous. */ - if (gpu_write_fence_ != nil) { - /* Ensure the GPU updates are visible to the host before reading. */ - while (gpu_write_fence_.signaledValue < host_read_signal_value_) { - PIL_sleep_ms(1); - } - } - /* Managed buffers need to be explicitly flushed back to host. */ - if (metal_buffer_->get_resource_options() & MTLResourceStorageModeManaged) { - /* Fetch active context. */ - MTLContext *ctx = static_cast(unwrap(GPU_context_active_get())); - BLI_assert(ctx); - - /* Ensure GPU updates are flushed back to CPU. */ - id blit_encoder = - ctx->main_command_buffer.ensure_begin_blit_encoder(); - [blit_encoder synchronizeResource:metal_buffer_->get_metal_buffer()]; - } - - /* Read data. NOTE: Unless explicitly synchronized with GPU work, results may not be ready. */ - memcpy(data, metal_buffer_->get_host_ptr(), size_in_bytes_); - } + /* Read data. NOTE: Unless explicitly synchronized with GPU work, results may not be ready. */ + memcpy(data, metal_buffer_->get_host_ptr(), size_in_bytes_); } id MTLStorageBuf::get_metal_buffer() diff --git a/source/blender/gpu/metal/mtl_texture.hh b/source/blender/gpu/metal/mtl_texture.hh index aa04b150cc7..a509051104d 100644 --- a/source/blender/gpu/metal/mtl_texture.hh +++ b/source/blender/gpu/metal/mtl_texture.hh @@ -42,26 +42,20 @@ struct TextureUpdateRoutineSpecialisation { /* Number of channels the destination texture has (min=1, max=4). */ int component_count_output; - /* Whether the update routine is a clear, and only the first texel of the input data buffer will - * be read. */ - bool is_clear; - bool operator==(const TextureUpdateRoutineSpecialisation &other) const { return ((input_data_type == other.input_data_type) && (output_data_type == other.output_data_type) && (component_count_input == other.component_count_input) && - (component_count_output == other.component_count_output) && - (is_clear == other.is_clear)); + (component_count_output == other.component_count_output)); } uint64_t hash() const { blender::DefaultHash string_hasher; - return (uint64_t)string_hasher(this->input_data_type + this->output_data_type + - std::to_string((this->component_count_input << 9) | - (this->component_count_output << 5) | - (this->is_clear ? 1 : 0))); + return (uint64_t)string_hasher( + this->input_data_type + this->output_data_type + + std::to_string((this->component_count_input << 8) + this->component_count_output)); } }; @@ -354,7 +348,8 @@ class MTLTexture : public Texture { uint dst_slice, int width, int height); - GPUFrameBuffer *get_blit_framebuffer(int dst_slice, uint dst_mip); + GPUFrameBuffer *get_blit_framebuffer(uint dst_slice, uint dst_mip); + /* Texture Update function Utilities. */ /* Metal texture updating does not provide the same range of functionality for type conversion * and format compatibility as are available in OpenGL. To achieve the same level of diff --git a/source/blender/gpu/metal/mtl_texture.mm b/source/blender/gpu/metal/mtl_texture.mm index 795cd7e453d..4aa9c8b1954 100644 --- a/source/blender/gpu/metal/mtl_texture.mm +++ b/source/blender/gpu/metal/mtl_texture.mm @@ -198,22 +198,7 @@ void gpu::MTLTexture::bake_mip_swizzle_view() mip_texture_base_level_, min_ii(mip_texture_max_level_, (int)texture_.mipmapLevelCount), range_len); -#ifndef NDEBUG - mip_swizzle_view_.label = [NSString - stringWithFormat: - @"MipSwizzleView_%s__format=%u_type=%u_baselevel=%u_numlevels=%u_swizzle='%c%c%c%c'", - [[texture_ label] UTF8String], - (uint)texture_view_pixel_format, - (uint)texture_view_texture_type, - (uint)mip_texture_base_level_, - (uint)range_len, - tex_swizzle_mask_[0], - tex_swizzle_mask_[1], - tex_swizzle_mask_[2], - tex_swizzle_mask_[3]]; -#else mip_swizzle_view_.label = [texture_ label]; -#endif texture_view_dirty_flags_ = TEXTURE_VIEW_NOT_DIRTY; } } @@ -415,7 +400,7 @@ void gpu::MTLTexture::blit(gpu::MTLTexture *dst, } } -GPUFrameBuffer *gpu::MTLTexture::get_blit_framebuffer(int dst_slice, uint dst_mip) +GPUFrameBuffer *gpu::MTLTexture::get_blit_framebuffer(uint dst_slice, uint dst_mip) { /* Check if layer has changed. */ @@ -569,9 +554,7 @@ void gpu::MTLTexture::update_sub( tex_data_format_to_msl_type_str(type), /* INPUT DATA FORMAT */ tex_data_format_to_msl_texture_template_type(type), /* TEXTURE DATA FORMAT */ num_channels, - destination_num_channels, - false /* Not a clear. */ - }; + destination_num_channels}; /* Determine whether we can do direct BLIT or not. */ bool can_use_direct_blit = true; @@ -1206,7 +1189,7 @@ void gpu::MTLTexture::generate_mipmap() } /* Ensure mipmaps. */ - this->ensure_mipmaps(mtl_max_mips_); + this->ensure_mipmaps(9999); /* Ensure texture is baked. */ this->ensure_baked(); @@ -1308,103 +1291,14 @@ void gpu::MTLTexture::clear(eGPUDataFormat data_format, const void *data) /* Ensure texture is baked. */ this->ensure_baked(); - /* If render-pass clear is not supported, use compute-based clear. */ - bool do_render_pass_clear = true; - if (ELEM(type_, GPU_TEXTURE_1D, GPU_TEXTURE_1D_ARRAY)) { - do_render_pass_clear = false; - } - - if (do_render_pass_clear) { - /* Create clear frame-buffer for fast clear. */ - GPUFrameBuffer *prev_fb = GPU_framebuffer_active_get(); - FrameBuffer *fb = unwrap(this->get_blit_framebuffer(-1, 0)); - fb->bind(true); - fb->clear_attachment(this->attachment_type(0), data_format, data); - GPU_framebuffer_bind(prev_fb); - } - else { - /** Perform compute-based clear. */ - /* Prepare specialization struct (For texture clear routine). */ - int num_channels = to_component_len(format_); - TextureUpdateRoutineSpecialisation compute_specialization_kernel = { - tex_data_format_to_msl_type_str(data_format), /* INPUT DATA FORMAT */ - tex_data_format_to_msl_texture_template_type(data_format), /* TEXTURE DATA FORMAT */ - num_channels, - num_channels, - true /* Operation is a clear. */ - }; - - /* Determine size of source data clear. */ - uint clear_data_size = to_bytesize(format_, data_format); - - /* Fetch active context. */ - MTLContext *ctx = static_cast(unwrap(GPU_context_active_get())); - BLI_assert(ctx); - - /* Determine writeable texture handle. */ - id texture_handle = texture_; - - /* Begin compute encoder. */ - id compute_encoder = - ctx->main_command_buffer.ensure_begin_compute_encoder(); - - /* Perform clear operation based on texture type. */ - switch (type_) { - case GPU_TEXTURE_1D: { - id pso = texture_update_1d_get_kernel( - compute_specialization_kernel); - TextureUpdateParams params = {0, - {w_, 1, 1}, - {0, 0, 0}, - ((ctx->pipeline_state.unpack_row_length == 0) ? - w_ : - ctx->pipeline_state.unpack_row_length)}; - - /* Bind resources via compute state for optimal state caching performance. */ - MTLComputeState &cs = ctx->main_command_buffer.get_compute_state(); - cs.bind_pso(pso); - cs.bind_compute_bytes(¶ms, sizeof(params), 0); - cs.bind_compute_bytes(data, clear_data_size, 1); - cs.bind_compute_texture(texture_handle, 0); - [compute_encoder dispatchThreads:MTLSizeMake(w_, 1, 1) /* Width, Height, Layer */ - threadsPerThreadgroup:MTLSizeMake(64, 1, 1)]; - } break; - case GPU_TEXTURE_1D_ARRAY: { - id pso = texture_update_1d_array_get_kernel( - compute_specialization_kernel); - TextureUpdateParams params = {0, - {w_, h_, 1}, - {0, 0, 0}, - ((ctx->pipeline_state.unpack_row_length == 0) ? - w_ : - ctx->pipeline_state.unpack_row_length)}; - - /* Bind resources via compute state for optimal state caching performance. */ - MTLComputeState &cs = ctx->main_command_buffer.get_compute_state(); - cs.bind_pso(pso); - cs.bind_compute_bytes(¶ms, sizeof(params), 0); - cs.bind_compute_bytes(data, clear_data_size, 1); - cs.bind_compute_texture(texture_handle, 0); - [compute_encoder dispatchThreads:MTLSizeMake(w_, h_, 1) /* Width, layers, nil */ - threadsPerThreadgroup:MTLSizeMake(8, 8, 1)]; - } break; - default: { - MTL_LOG_ERROR( - "gpu::MTLTexture::clear requires compute pass for texture" - "type: %d, but this is not yet supported", - (int)type_); - } break; - } - - /* Textures which use MTLStorageModeManaged need to have updated contents - * synced back to CPU to avoid an automatic flush overwriting contents. */ - id blit_encoder = ctx->main_command_buffer.ensure_begin_blit_encoder(); - if (texture_.storageMode == MTLStorageModeManaged) { - [blit_encoder synchronizeResource:texture_]; - } - [blit_encoder optimizeContentsForGPUAccess:texture_]; - } + /* Create clear framebuffer. */ + GPUFrameBuffer *prev_fb = GPU_framebuffer_active_get(); + FrameBuffer *fb = unwrap(this->get_blit_framebuffer(0, 0)); + fb->bind(true); + fb->clear_attachment(this->attachment_type(0), data_format, data); + GPU_framebuffer_bind(prev_fb); } + static MTLTextureSwizzle swizzle_to_mtl(const char swizzle) { switch (swizzle) { @@ -1493,7 +1387,7 @@ void *gpu::MTLTexture::read(int mip, eGPUDataFormat type) int extent[3] = {1, 1, 1}; this->mip_size_get(mip, extent); - size_t sample_len = extent[0] * max_ii(extent[1], 1) * max_ii(extent[2], 1); + size_t sample_len = extent[0] * extent[1] * extent[2]; size_t sample_size = to_bytesize(format_, type); size_t texture_size = sample_len * sample_size; int num_channels = to_component_len(format_); @@ -1585,7 +1479,7 @@ void gpu::MTLTexture::read_internal(int mip, /* Determine size of output data. */ size_t bytes_per_row = desired_output_bpp * width; size_t bytes_per_image = bytes_per_row * height; - size_t total_bytes = bytes_per_image * max_ii(depth, 1); + size_t total_bytes = bytes_per_image * depth; if (can_use_simple_read) { /* DEBUG check that if direct copy is being used, then both the expected output size matches @@ -1693,57 +1587,6 @@ void gpu::MTLTexture::read_internal(int mip, copy_successful = true; } } break; - - case GPU_TEXTURE_1D_ARRAY: { - if (can_use_simple_read) { - /* Use Blit Encoder READ. */ - id enc = ctx->main_command_buffer.ensure_begin_blit_encoder(); - if (G.debug & G_DEBUG_GPU) { - [enc insertDebugSignpost:@"GPUTextureRead1DArray"]; - } - - int base_slice = y_off; - int final_slice = base_slice + height; - size_t texture_array_relative_offset = 0; - - for (int array_slice = base_slice; array_slice < final_slice; array_slice++) { - [enc copyFromTexture:read_texture - sourceSlice:base_slice - sourceLevel:mip - sourceOrigin:MTLOriginMake(x_off, 0, 0) - sourceSize:MTLSizeMake(width, 1, 1) - toBuffer:destination_buffer - destinationOffset:texture_array_relative_offset - destinationBytesPerRow:bytes_per_row - destinationBytesPerImage:bytes_per_row]; - texture_array_relative_offset += bytes_per_row; - } - copy_successful = true; - } - else { - /* Use Compute READ. */ - id compute_encoder = - ctx->main_command_buffer.ensure_begin_compute_encoder(); - id pso = texture_read_1d_array_get_kernel( - compute_specialization_kernel); - TextureReadParams params = { - mip, - {width, height, 1}, - {x_off, y_off, 0}, - }; - - /* Bind resources via compute state for optimal state caching performance. */ - MTLComputeState &cs = ctx->main_command_buffer.get_compute_state(); - cs.bind_pso(pso); - cs.bind_compute_bytes(¶ms, sizeof(params), 0); - cs.bind_compute_buffer(destination_buffer, 0, 1, true); - cs.bind_compute_texture(read_texture, 0); - [compute_encoder dispatchThreads:MTLSizeMake(width, height, 1) /* Width, Height, Layer */ - threadsPerThreadgroup:MTLSizeMake(8, 8, 1)]; - copy_successful = true; - } - } break; - case GPU_TEXTURE_2D: { if (can_use_simple_read) { /* Use Blit Encoder READ. */ @@ -1882,19 +1725,12 @@ void gpu::MTLTexture::read_internal(int mip, } } break; - case GPU_TEXTURE_CUBE: case GPU_TEXTURE_CUBE_ARRAY: { - BLI_assert_msg(z_off == 0 || type_ == GPU_TEXTURE_CUBE_ARRAY, - "z_off > 0 is only supported by TEXTURE CUBE ARRAY reads."); - BLI_assert_msg(depth <= 6 || type_ == GPU_TEXTURE_CUBE_ARRAY, - "depth > 6 is only supported by TEXTURE CUBE ARRAY reads. "); if (can_use_simple_read) { id enc = ctx->main_command_buffer.ensure_begin_blit_encoder(); if (G.debug & G_DEBUG_GPU) { [enc insertDebugSignpost:@"GPUTextureReadCubeArray"]; } - - /* NOTE: Depth should have a minimum value of 1 as we read at least one slice. */ int base_slice = z_off; int final_slice = base_slice + depth; size_t texture_array_relative_offset = 0; @@ -1921,7 +1757,7 @@ void gpu::MTLTexture::read_internal(int mip, } break; default: - MTL_LOG_ERROR( + MTL_LOG_WARNING( "gpu::MTLTexture::read_internal simple-copy not yet supported for texture " "type: %d", (int)type_); diff --git a/source/blender/gpu/metal/mtl_texture_util.mm b/source/blender/gpu/metal/mtl_texture_util.mm index 858a46b98a6..6747ffdb4d2 100644 --- a/source/blender/gpu/metal/mtl_texture_util.mm +++ b/source/blender/gpu/metal/mtl_texture_util.mm @@ -96,7 +96,7 @@ MTLPixelFormat gpu_texture_format_to_metal(eGPUTextureFormat tex_format) return MTLPixelFormatR16Float; case GPU_R16: return MTLPixelFormatR16Unorm; - /* Special formats texture & render-buffer. */ + /* Special formats texture & renderbuffer */ case GPU_RGB10_A2: return MTLPixelFormatRGB10A2Unorm; case GPU_RGB10_A2UI: @@ -225,7 +225,6 @@ size_t get_mtl_format_bytesize(MTLPixelFormat tex_format) case MTLPixelFormatR8Uint: case MTLPixelFormatR8Sint: case MTLPixelFormatR8Unorm: - case MTLPixelFormatR8Snorm: return 1; case MTLPixelFormatR32Uint: case MTLPixelFormatR32Sint: @@ -289,7 +288,6 @@ int get_mtl_format_num_components(MTLPixelFormat tex_format) case MTLPixelFormatDepth32Float_Stencil8: case MTLPixelFormatRG16Snorm: case MTLPixelFormatRG16Unorm: - case MTLPixelFormatRG8Snorm: return 2; case MTLPixelFormatR8Uint: @@ -413,9 +411,7 @@ id gpu::MTLTexture::mtl_texture_update_impl( [NSNumber numberWithInt:specialization_params.component_count_input], @"COMPONENT_COUNT_OUTPUT" : [NSNumber numberWithInt:specialization_params.component_count_output], - @"TEX_TYPE" : [NSNumber numberWithInt:((int)(texture_type))], - @"IS_TEXTURE_CLEAR" : - [NSNumber numberWithInt:((int)(specialization_params.is_clear ? 1 : 0))] + @"TEX_TYPE" : [NSNumber numberWithInt:((int)(texture_type))] }; /* Prepare shader library for conversion routine. */ @@ -665,7 +661,7 @@ void gpu::MTLTexture::update_sub_depth_2d( /** \} */ /* -------------------------------------------------------------------- */ -/** \name Texture data read routines +/** \name Texture data read routines * \{ */ id gpu::MTLTexture::mtl_texture_read_impl( diff --git a/source/blender/gpu/opengl/gl_backend.cc b/source/blender/gpu/opengl/gl_backend.cc index 04333721005..76cff63a62b 100644 --- a/source/blender/gpu/opengl/gl_backend.cc +++ b/source/blender/gpu/opengl/gl_backend.cc @@ -49,6 +49,8 @@ void GLBackend::platform_init() #ifdef _WIN32 os = GPU_OS_WIN; +#elif defined(__APPLE__) + os = GPU_OS_MAC; #else os = GPU_OS_UNIX; #endif @@ -196,15 +198,7 @@ void GLBackend::platform_init() } } - GPG.init(device, - os, - driver, - support_level, - GPU_BACKEND_OPENGL, - vendor, - renderer, - version, - GPU_ARCHITECTURE_IMR); + GPG.init(device, os, driver, support_level, GPU_BACKEND_OPENGL, vendor, renderer, version); } void GLBackend::platform_exit() @@ -301,12 +295,9 @@ static void detect_workarounds() /* Although an OpenGL 4.3 feature, our implementation requires shader_draw_parameters_support. * NOTE: we should untangle this by checking both features for clarity. */ GLContext::multi_draw_indirect_support = false; - /* Turn off extensions. */ - GLContext::layered_rendering_support = false; + /* Turn off vendor specific extensions. */ GLContext::native_barycentric_support = false; - GLContext::framebuffer_fetch_support = false; - GLContext::texture_barrier_support = false; /* Do not alter OpenGL 4.3 features. * These code paths should be removed. */ @@ -317,6 +308,7 @@ static void detect_workarounds() GLContext::debug_layer_support = false; GLContext::fixed_restart_index_support = false; GLContext::geometry_shader_invocations = false; + GLContext::layered_rendering_support = false; GLContext::texture_cube_map_array_support = false; GLContext::texture_gather_support = false; GLContext::texture_storage_support = false; @@ -396,6 +388,17 @@ static void detect_workarounds() GCaps.use_hq_normals_workaround = true; } } + /* There is an issue with the #glBlitFramebuffer on MacOS with radeon pro graphics. + * Blitting depth with#GL_DEPTH24_STENCIL8 is buggy so the workaround is to use + * #GPU_DEPTH32F_STENCIL8. Then Blitting depth will work but blitting stencil will + * still be broken. */ + if (GPU_type_matches(GPU_DEVICE_ATI, GPU_OS_MAC, GPU_DRIVER_OFFICIAL)) { + if (strstr(renderer, "AMD Radeon Pro") || strstr(renderer, "AMD Radeon R9") || + strstr(renderer, "AMD Radeon RX")) + { + GCaps.depth_blitting_workaround = true; + } + } /* Limit this fix to older hardware with GL < 4.5. This means Broadwell GPUs are * covered since they only support GL 4.4 on windows. * This fixes some issues with workbench anti-aliasing on Win + Intel GPU. (see #76273) */ @@ -470,6 +473,11 @@ static void detect_workarounds() } } + /* Disable TF on macOS. */ + if (GPU_type_matches(GPU_DEVICE_ANY, GPU_OS_MAC, GPU_DRIVER_ANY)) { + GCaps.transform_feedback_support = false; + } + /* Right now draw shader parameters are broken on Qualcomm devices * regardless of driver version */ if (GPU_type_matches(GPU_DEVICE_QUALCOMM, GPU_OS_WIN, GPU_DRIVER_ANY)) { @@ -493,6 +501,21 @@ static void detect_workarounds() GLContext::debug_layer_workaround = true; } + /* Broken glGenerateMipmap on macOS 10.15.7 security update. */ + if (GPU_type_matches(GPU_DEVICE_INTEL, GPU_OS_MAC, GPU_DRIVER_ANY) && + strstr(renderer, "HD Graphics 4000")) + { + GLContext::generate_mipmap_workaround = true; + } + + /* Certain Intel/AMD based platforms don't clear the viewport textures. Always clearing leads to + * noticeable performance regressions on other platforms as well. */ + if (GPU_type_matches(GPU_DEVICE_ANY, GPU_OS_MAC, GPU_DRIVER_ANY) || + GPU_type_matches(GPU_DEVICE_INTEL, GPU_OS_ANY, GPU_DRIVER_ANY)) + { + GCaps.clear_viewport_workaround = true; + } + /* There is an issue in AMD official driver where we cannot use multi bind when using images. AMD * is aware of the issue, but hasn't released a fix. */ if (GPU_type_matches(GPU_DEVICE_ATI, GPU_OS_ANY, GPU_DRIVER_OFFICIAL)) { @@ -511,6 +534,7 @@ GLint GLContext::max_cubemap_size = 0; GLint GLContext::max_ubo_binds = 0; GLint GLContext::max_ubo_size = 0; GLint GLContext::max_ssbo_binds = 0; +GLint GLContext::max_ssbo_size = 0; /** Extensions. */ @@ -520,7 +544,6 @@ bool GLContext::copy_image_support = false; bool GLContext::debug_layer_support = false; bool GLContext::direct_state_access_support = false; bool GLContext::explicit_location_support = false; -bool GLContext::framebuffer_fetch_support = false; bool GLContext::geometry_shader_invocations = false; bool GLContext::fixed_restart_index_support = false; bool GLContext::layered_rendering_support = false; @@ -530,7 +553,6 @@ bool GLContext::multi_bind_image_support = false; bool GLContext::multi_draw_indirect_support = false; bool GLContext::shader_draw_parameters_support = false; bool GLContext::stencil_texturing_support = false; -bool GLContext::texture_barrier_support = false; bool GLContext::texture_cube_map_array_support = false; bool GLContext::texture_filter_anisotropic_support = false; bool GLContext::texture_gather_support = false; @@ -559,7 +581,14 @@ void GLBackend::capabilities_init() glGetIntegerv(GL_MAX_ELEMENTS_INDICES, &GCaps.max_batch_indices); glGetIntegerv(GL_MAX_ELEMENTS_VERTICES, &GCaps.max_batch_vertices); glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &GCaps.max_vertex_attribs); - glGetIntegerv(GL_MAX_VARYING_FLOATS, &GCaps.max_varying_floats); + if (GPU_type_matches(GPU_DEVICE_APPLE, GPU_OS_MAC, GPU_DRIVER_OFFICIAL)) { + /* Due to a bug, querying GL_MAX_VARYING_FLOATS is emitting GL_INVALID_ENUM. + * Force use minimum required value. */ + GCaps.max_varying_floats = 32; + } + else { + glGetIntegerv(GL_MAX_VARYING_FLOATS, &GCaps.max_varying_floats); + } glGetIntegerv(GL_NUM_EXTENSIONS, &GCaps.extensions_len); GCaps.extension_get = gl_extension_get; @@ -585,9 +614,6 @@ void GLBackend::capabilities_init() glGetIntegerv(GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS, &GCaps.max_shader_storage_buffer_bindings); glGetIntegerv(GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS, &GCaps.max_compute_shader_storage_blocks); - int64_t max_ssbo_size; - glGetInteger64v(GL_MAX_SHADER_STORAGE_BLOCK_SIZE, &max_ssbo_size); - GCaps.max_storage_buffer_size = size_t(max_ssbo_size); } GCaps.transform_feedback_support = true; GCaps.texture_view_support = epoxy_gl_version() >= 43 || @@ -606,6 +632,7 @@ void GLBackend::capabilities_init() GLContext::max_ssbo_binds = min_ii(GLContext::max_ssbo_binds, max_ssbo_binds); glGetIntegerv(GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS, &max_ssbo_binds); GLContext::max_ssbo_binds = min_ii(GLContext::max_ssbo_binds, max_ssbo_binds); + glGetIntegerv(GL_MAX_SHADER_STORAGE_BLOCK_SIZE, &GLContext::max_ssbo_size); GLContext::base_instance_support = epoxy_has_gl_extension("GL_ARB_base_instance"); GLContext::clear_texture_support = epoxy_has_gl_extension("GL_ARB_clear_texture"); GLContext::copy_image_support = epoxy_has_gl_extension("GL_ARB_copy_image"); @@ -616,8 +643,6 @@ void GLBackend::capabilities_init() GLContext::explicit_location_support = epoxy_gl_version() >= 43; GLContext::geometry_shader_invocations = epoxy_has_gl_extension("GL_ARB_gpu_shader5"); GLContext::fixed_restart_index_support = epoxy_has_gl_extension("GL_ARB_ES3_compatibility"); - GLContext::framebuffer_fetch_support = epoxy_has_gl_extension("GL_EXT_shader_framebuffer_fetch"); - GLContext::texture_barrier_support = epoxy_has_gl_extension("GL_ARB_texture_barrier"); GLContext::layered_rendering_support = epoxy_has_gl_extension( "GL_ARB_shader_viewport_layer_array"); GLContext::native_barycentric_support = epoxy_has_gl_extension( @@ -637,9 +662,6 @@ void GLBackend::capabilities_init() GLContext::vertex_attrib_binding_support = epoxy_has_gl_extension( "GL_ARB_vertex_attrib_binding"); - /* Disabled until it is proven to work. */ - GLContext::framebuffer_fetch_support = false; - detect_workarounds(); /* Disable this feature entirely when not debugging. */ diff --git a/source/blender/gpu/opengl/gl_batch.cc b/source/blender/gpu/opengl/gl_batch.cc index f34291e9f27..86f1a1148eb 100644 --- a/source/blender/gpu/opengl/gl_batch.cc +++ b/source/blender/gpu/opengl/gl_batch.cc @@ -219,6 +219,15 @@ GLuint GLVaoCache::base_instance_vao_get(GPUBatch *batch, int i_first) /* Trigger update. */ base_instance_ = 0; } + /** + * There seems to be a nasty bug when drawing using the same VAO reconfiguring (#71147). + * We just use a throwaway VAO for that. Note that this is likely to degrade performance. + */ +#ifdef __APPLE__ + glDeleteVertexArrays(1, &vao_base_instance_); + vao_base_instance_ = 0; + base_instance_ = 0; +#endif if (vao_base_instance_ == 0) { glGenVertexArrays(1, &vao_base_instance_); @@ -309,12 +318,18 @@ void GLBatch::draw(int v_first, int v_count, int i_first, int i_count) } } else { +#ifdef __APPLE__ + glDisable(GL_PRIMITIVE_RESTART); +#endif if (GLContext::base_instance_support) { glDrawArraysInstancedBaseInstance(gl_type, v_first, v_count, i_count, i_first); } else { glDrawArraysInstanced(gl_type, v_first, v_count, i_count); } +#ifdef __APPLE__ + glEnable(GL_PRIMITIVE_RESTART); +#endif } } diff --git a/source/blender/gpu/opengl/gl_compute.cc b/source/blender/gpu/opengl/gl_compute.cc index 5d6e9d40c02..e3694b0c486 100644 --- a/source/blender/gpu/opengl/gl_compute.cc +++ b/source/blender/gpu/opengl/gl_compute.cc @@ -16,12 +16,6 @@ void GLCompute::dispatch(int group_x_len, int group_y_len, int group_z_len) { GL_CHECK_RESOURCES("Compute"); - /* Sometime we reference a dispatch size but we want to skip it by setting one dimension to 0. - * Avoid error being reported on some implementation for these case. */ - if (group_x_len == 0 || group_y_len == 0 || group_z_len == 0) { - return; - } - glDispatchCompute(group_x_len, group_y_len, group_z_len); } diff --git a/source/blender/gpu/opengl/gl_context.cc b/source/blender/gpu/opengl/gl_context.cc index b1e1ceb6b52..9b3dd040990 100644 --- a/source/blender/gpu/opengl/gl_context.cc +++ b/source/blender/gpu/opengl/gl_context.cc @@ -304,6 +304,7 @@ void GLContext::vao_cache_unregister(GLVaoCache *cache) void GLContext::memory_statistics_get(int *r_total_mem, int *r_free_mem) { + /* TODO(merwin): use Apple's platform API to get this info. */ if (epoxy_has_gl_extension("GL_NVX_gpu_memory_info")) { /* Returned value in Kb. */ glGetIntegerv(GL_GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX, r_total_mem); diff --git a/source/blender/gpu/opengl/gl_context.hh b/source/blender/gpu/opengl/gl_context.hh index 0b65020e825..3db611a0c9b 100644 --- a/source/blender/gpu/opengl/gl_context.hh +++ b/source/blender/gpu/opengl/gl_context.hh @@ -43,6 +43,7 @@ class GLContext : public Context { static GLint max_cubemap_size; static GLint max_ubo_size; static GLint max_ubo_binds; + static GLint max_ssbo_size; static GLint max_ssbo_binds; /** Extensions. */ @@ -55,7 +56,6 @@ class GLContext : public Context { static bool explicit_location_support; static bool geometry_shader_invocations; static bool fixed_restart_index_support; - static bool framebuffer_fetch_support; static bool layered_rendering_support; static bool native_barycentric_support; static bool multi_bind_support; @@ -63,7 +63,6 @@ class GLContext : public Context { static bool multi_draw_indirect_support; static bool shader_draw_parameters_support; static bool stencil_texturing_support; - static bool texture_barrier_support; static bool texture_cube_map_array_support; static bool texture_filter_anisotropic_support; static bool texture_gather_support; diff --git a/source/blender/gpu/opengl/gl_debug.cc b/source/blender/gpu/opengl/gl_debug.cc index 687ad1feced..7bca77f2252 100644 --- a/source/blender/gpu/opengl/gl_debug.cc +++ b/source/blender/gpu/opengl/gl_debug.cc @@ -71,12 +71,12 @@ static void APIENTRY debug_callback(GLenum /*source*/, if (TRIM_NVIDIA_BUFFER_INFO && STRPREFIX(message, "Buffer detailed info") && GPU_type_matches(GPU_DEVICE_NVIDIA, GPU_OS_ANY, GPU_DRIVER_OFFICIAL)) { - /* Suppress buffer infos flooding the output. */ + /** Suppress buffer infos flooding the output. */ return; } if (TRIM_SHADER_STATS_INFO && STRPREFIX(message, "Shader Stats")) { - /* Suppress buffer infos flooding the output. */ + /** Suppress buffer infos flooding the output. */ return; } @@ -94,7 +94,7 @@ static void APIENTRY debug_callback(GLenum /*source*/, CLG_Severity clog_severity; if (GPU_debug_group_match(GPU_DEBUG_SHADER_COMPILATION_GROUP)) { - /* Do not duplicate shader compilation error/warnings. */ + /** Do not duplicate shader compilation error/warnings. */ return; } diff --git a/source/blender/gpu/opengl/gl_framebuffer.cc b/source/blender/gpu/opengl/gl_framebuffer.cc index db7f986c3da..2bdbd0fea08 100644 --- a/source/blender/gpu/opengl/gl_framebuffer.cc +++ b/source/blender/gpu/opengl/gl_framebuffer.cc @@ -226,110 +226,6 @@ void GLFrameBuffer::update_attachments() } } -void GLFrameBuffer::subpass_transition(const GPUAttachmentState depth_attachment_state, - Span color_attachment_states) -{ - /* NOTE: Depth is not supported as input attachment because the Metal API doesn't support it and - * because depth is not compatible with the framebuffer fetch implementation. */ - BLI_assert(depth_attachment_state != GPU_ATTACHEMENT_READ); - GPU_depth_mask(depth_attachment_state == GPU_ATTACHEMENT_WRITE); - - bool any_read = false; - for (auto attachment : color_attachment_states.index_range()) { - if (attachment == GPU_ATTACHEMENT_READ) { - any_read = true; - break; - } - } - - if (GLContext::framebuffer_fetch_support) { - if (any_read) { - glFramebufferFetchBarrierEXT(); - } - } - else if (GLContext::texture_barrier_support) { - if (any_read) { - glTextureBarrier(); - } - - GLenum attachments[GPU_FB_MAX_COLOR_ATTACHMENT] = {GL_NONE}; - for (int i : color_attachment_states.index_range()) { - GPUAttachmentType type = GPU_FB_COLOR_ATTACHMENT0 + i; - GPUTexture *attach_tex = this->attachments_[type].tex; - if (color_attachment_states[i] == GPU_ATTACHEMENT_READ) { - tmp_detached_[type] = this->attachments_[type]; /* Bypass feedback loop check. */ - GPU_texture_bind_ex(attach_tex, GPUSamplerState::default_sampler(), i); - } - else { - tmp_detached_[type] = GPU_ATTACHMENT_NONE; - } - bool attach_write = color_attachment_states[i] == GPU_ATTACHEMENT_WRITE; - attachments[i] = (attach_tex && attach_write) ? to_gl(type) : GL_NONE; - } - /* We have to use `glDrawBuffers` instead of `glColorMaski` because the later is overwritten - * by the `GLStateManager`. */ - /* WATCH(fclem): This modifies the frame-buffer state without setting `dirty_attachments_`. */ - glDrawBuffers(ARRAY_SIZE(attachments), attachments); - } - else { - /* The only way to have correct visibility without extensions and ensure defined behavior, is - * to unbind the textures and update the frame-buffer. This is a slow operation but that's all - * we can do to emulate the sub-pass input. */ - /* TODO(@fclem): Could avoid the frame-buffer reconfiguration by creating multiple - * frame-buffers internally. */ - for (int i : color_attachment_states.index_range()) { - GPUAttachmentType type = GPU_FB_COLOR_ATTACHMENT0 + i; - - if (color_attachment_states[i] == GPU_ATTACHEMENT_WRITE) { - if (tmp_detached_[type].tex != nullptr) { - /* Re-attach previous read attachments. */ - this->attachment_set(type, tmp_detached_[type]); - tmp_detached_[type] = GPU_ATTACHMENT_NONE; - } - } - else if (color_attachment_states[i] == GPU_ATTACHEMENT_READ) { - tmp_detached_[type] = this->attachments_[type]; - unwrap(tmp_detached_[type].tex)->detach_from(this); - GPU_texture_bind_ex(tmp_detached_[type].tex, GPUSamplerState::default_sampler(), i); - } - } - if (dirty_attachments_) { - this->update_attachments(); - } - } -} - -void GLFrameBuffer::attachment_set_loadstore_op(GPUAttachmentType type, GPULoadStore ls) -{ - BLI_assert(context_->active_fb == this); - - /* TODO(fclem): Add support for other ops. */ - if (ls.load_action == eGPULoadOp::GPU_LOADACTION_CLEAR) { - if (tmp_detached_[type].tex != nullptr) { - /* #GPULoadStore is used to define the frame-buffer before it is used for rendering. - * Binding back unattached attachment makes its state undefined. This is described by the - * documentation and the user-land code should specify a sub-pass at the start of the drawing - * to explicitly set attachment state. */ - if (GLContext::framebuffer_fetch_support) { - /* NOOP. */ - } - else if (GLContext::texture_barrier_support) { - /* Reset default attachment state. */ - for (int i : IndexRange(ARRAY_SIZE(tmp_detached_))) { - tmp_detached_[i] = GPU_ATTACHMENT_NONE; - } - glDrawBuffers(ARRAY_SIZE(gl_attachments_), gl_attachments_); - } - else { - tmp_detached_[type] = GPU_ATTACHMENT_NONE; - this->attachment_set(type, tmp_detached_[type]); - this->update_attachments(); - } - } - clear_attachment(type, GPU_DATA_FLOAT, ls.clear_value); - } -} - void GLFrameBuffer::apply_state() { if (dirty_state_ == false) { @@ -468,8 +364,6 @@ void GLFrameBuffer::clear_attachment(GPUAttachmentType type, /* Save and restore the state. */ eGPUWriteMask write_mask = GPU_write_mask_get(); GPU_color_mask(true, true, true, true); - bool depth_mask = GPU_depth_mask_get(); - GPU_depth_mask(true); context_->state_manager->apply_state(); @@ -510,7 +404,6 @@ void GLFrameBuffer::clear_attachment(GPUAttachmentType type, } GPU_write_mask(write_mask); - GPU_depth_mask(depth_mask); } void GLFrameBuffer::clear_multi(const float (*clear_cols)[4]) diff --git a/source/blender/gpu/opengl/gl_framebuffer.hh b/source/blender/gpu/opengl/gl_framebuffer.hh index 70987f8d56c..61e88fa2724 100644 --- a/source/blender/gpu/opengl/gl_framebuffer.hh +++ b/source/blender/gpu/opengl/gl_framebuffer.hh @@ -34,8 +34,6 @@ class GLFrameBuffer : public FrameBuffer { GLStateManager *state_manager_ = nullptr; /** Copy of the GL state. Contains ONLY color attachments enums for slot binding. */ GLenum gl_attachments_[GPU_FB_MAX_COLOR_ATTACHMENT] = {0}; - /** List of attachment that are associated with this frame-buffer but temporarily detached. */ - GPUAttachment tmp_detached_[GPU_FB_MAX_ATTACHMENT]; /** Internal frame-buffers are immutable. */ bool immutable_ = false; /** True is the frame-buffer has its first color target using the GPU_SRGB8_A8 format. */ @@ -79,10 +77,7 @@ class GLFrameBuffer : public FrameBuffer { const void *clear_value) override; /* Attachment load-stores are currently no-op's in OpenGL. */ - void attachment_set_loadstore_op(GPUAttachmentType type, GPULoadStore ls) override; - - void subpass_transition(const GPUAttachmentState depth_attachment_state, - Span color_attachment_states) override; + void attachment_set_loadstore_op(GPUAttachmentType /*type*/, GPULoadStore /*ls*/) override{}; void read(eGPUFrameBufferBits planes, eGPUDataFormat format, diff --git a/source/blender/gpu/opengl/gl_immediate.cc b/source/blender/gpu/opengl/gl_immediate.cc index 0f6ee4e4327..69e3df3168c 100644 --- a/source/blender/gpu/opengl/gl_immediate.cc +++ b/source/blender/gpu/opengl/gl_immediate.cc @@ -147,8 +147,13 @@ void GLImmediate::end() /* Update matrices. */ GPU_shader_bind(shader); +#ifdef __APPLE__ + glDisable(GL_PRIMITIVE_RESTART); +#endif glDrawArrays(to_gl(prim_type), 0, vertex_len); - +#ifdef __APPLE__ + glEnable(GL_PRIMITIVE_RESTART); +#endif /* These lines are causing crash on startup on some old GPU + drivers. * They are not required so just comment them. (#55722) */ // glBindBuffer(GL_ARRAY_BUFFER, 0); diff --git a/source/blender/gpu/opengl/gl_shader.cc b/source/blender/gpu/opengl/gl_shader.cc index 04978684f58..57e6aa26233 100644 --- a/source/blender/gpu/opengl/gl_shader.cc +++ b/source/blender/gpu/opengl/gl_shader.cc @@ -110,139 +110,9 @@ static const char *to_string(const Type &type) return "ivec4"; case Type::BOOL: return "bool"; - /* Alias special types. */ - case Type::UCHAR: - case Type::USHORT: - return "uint"; - case Type::UCHAR2: - case Type::USHORT2: - return "uvec2"; - case Type::UCHAR3: - case Type::USHORT3: - return "uvec3"; - case Type::UCHAR4: - case Type::USHORT4: - return "uvec4"; - case Type::CHAR: - case Type::SHORT: - return "int"; - case Type::CHAR2: - case Type::SHORT2: - return "ivec2"; - case Type::CHAR3: - case Type::SHORT3: - return "ivec3"; - case Type::CHAR4: - case Type::SHORT4: - return "ivec4"; - case Type::VEC3_101010I2: - return "vec3"; + default: + return "unknown"; } - BLI_assert_unreachable(); - return "unknown"; -} - -static const int to_component_count(const Type &type) -{ - switch (type) { - case Type::FLOAT: - case Type::UINT: - case Type::INT: - case Type::BOOL: - return 1; - case Type::VEC2: - case Type::UVEC2: - case Type::IVEC2: - return 2; - case Type::VEC3: - case Type::UVEC3: - case Type::IVEC3: - return 3; - case Type::VEC4: - case Type::UVEC4: - case Type::IVEC4: - return 4; - case Type::MAT3: - return 9; - case Type::MAT4: - return 16; - /* Alias special types. */ - case Type::UCHAR: - case Type::USHORT: - return 1; - case Type::UCHAR2: - case Type::USHORT2: - return 2; - case Type::UCHAR3: - case Type::USHORT3: - return 3; - case Type::UCHAR4: - case Type::USHORT4: - return 4; - case Type::CHAR: - case Type::SHORT: - return 1; - case Type::CHAR2: - case Type::SHORT2: - return 2; - case Type::CHAR3: - case Type::SHORT3: - return 3; - case Type::CHAR4: - case Type::SHORT4: - return 4; - case Type::VEC3_101010I2: - return 3; - } - BLI_assert_unreachable(); - return -1; -} - -static const Type to_component_type(const Type &type) -{ - switch (type) { - case Type::FLOAT: - case Type::VEC2: - case Type::VEC3: - case Type::VEC4: - case Type::MAT3: - case Type::MAT4: - return Type::FLOAT; - case Type::UINT: - case Type::UVEC2: - case Type::UVEC3: - case Type::UVEC4: - return Type::UINT; - case Type::INT: - case Type::IVEC2: - case Type::IVEC3: - case Type::IVEC4: - case Type::BOOL: - return Type::INT; - /* Alias special types. */ - case Type::UCHAR: - case Type::UCHAR2: - case Type::UCHAR3: - case Type::UCHAR4: - case Type::USHORT: - case Type::USHORT2: - case Type::USHORT3: - case Type::USHORT4: - return Type::UINT; - case Type::CHAR: - case Type::CHAR2: - case Type::CHAR3: - case Type::CHAR4: - case Type::SHORT: - case Type::SHORT2: - case Type::SHORT3: - case Type::SHORT4: - return Type::INT; - case Type::VEC3_101010I2: - return Type::FLOAT; - } - BLI_assert_unreachable(); - return Type::FLOAT; } static const char *to_string(const eGPUTextureFormat &type) @@ -670,6 +540,12 @@ std::string GLShader::vertex_interface_declare(const ShaderCreateInfo &info) con } ss << "in " << to_string(attr.type) << " " << attr.name << ";\n"; } + /* NOTE(D4490): Fix a bug where shader without any vertex attributes do not behave correctly. */ + if (GPU_type_matches_ex(GPU_DEVICE_APPLE, GPU_OS_MAC, GPU_DRIVER_ANY, GPU_BACKEND_OPENGL) && + info.vertex_inputs_.is_empty()) + { + ss << "in float gpu_dummy_workaround;\n"; + } ss << "\n/* Interfaces. */\n"; for (const StageInterfaceInfo *iface : info.vertex_out_interfaces_) { print_interface(ss, "out", *iface); @@ -705,7 +581,7 @@ std::string GLShader::vertex_interface_declare(const ShaderCreateInfo &info) con std::string GLShader::fragment_interface_declare(const ShaderCreateInfo &info) const { std::stringstream ss; - std::string pre_main, post_main; + std::string pre_main; ss << "\n/* Interfaces. */\n"; const Vector &in_interfaces = info.geometry_source_.is_empty() ? @@ -729,6 +605,7 @@ std::string GLShader::fragment_interface_declare(const ShaderCreateInfo &info) c ss << "#define gpu_position_at_vertex(v) gpu_pos[v]\n"; } else if (epoxy_has_gl_extension("GL_AMD_shader_explicit_vertex_parameter")) { + std::cout << "native" << std::endl; /* NOTE(fclem): This won't work with geometry shader. Hopefully, we don't need geometry * shader workaround if this extension/feature is detected. */ ss << "\n/* Stable Barycentric Coordinates. */\n"; @@ -761,63 +638,12 @@ std::string GLShader::fragment_interface_declare(const ShaderCreateInfo &info) c if (epoxy_has_gl_extension("GL_ARB_conservative_depth")) { ss << "layout(" << to_string(info.depth_write_) << ") out float gl_FragDepth;\n"; } - ss << "\n/* Sub-pass Inputs. */\n"; for (const ShaderCreateInfo::SubpassIn &input : info.subpass_inputs_) { - if (GLContext::framebuffer_fetch_support) { - /* Declare as inout but do not write to it. */ - ss << "layout(location = " << std::to_string(input.index) << ") inout " - << to_string(input.type) << " " << input.name << ";\n"; - } - else { - std::string image_name = "gpu_subpass_img_"; - image_name += std::to_string(input.index); - - /* Declare global for input. */ - ss << to_string(input.type) << " " << input.name << ";\n"; - - /* IMPORTANT: We assume that the frame-buffer will be layered or not based on the layer - * built-in flag. */ - bool is_layered_fb = bool(info.builtins_ & BuiltinBits::LAYER); - - /* Start with invalid value to detect failure cases. */ - ImageType image_type = ImageType::FLOAT_BUFFER; - switch (to_component_type(input.type)) { - case Type::FLOAT: - image_type = is_layered_fb ? ImageType::FLOAT_2D_ARRAY : ImageType::FLOAT_2D; - break; - case Type::INT: - image_type = is_layered_fb ? ImageType::INT_2D_ARRAY : ImageType::INT_2D; - break; - case Type::UINT: - image_type = is_layered_fb ? ImageType::UINT_2D_ARRAY : ImageType::UINT_2D; - break; - default: - break; - } - /* Declare image. */ - using Resource = ShaderCreateInfo::Resource; - /* NOTE(fclem): Using the attachment index as resource index might be problematic as it might - * collide with other resources. */ - Resource res(Resource::BindType::SAMPLER, input.index); - res.sampler.type = image_type; - res.sampler.sampler = GPUSamplerState::default_sampler(); - res.sampler.name = image_name; - print_resource(ss, res, false); - - char swizzle[] = "xyzw"; - swizzle[to_component_count(input.type)] = '\0'; - - std::string texel_co = (is_layered_fb) ? "ivec3(gl_FragCoord.xy, gpu_Layer)" : - "ivec2(gl_FragCoord.xy)"; - - std::stringstream ss_pre; - /* Populate the global before main using imageLoad. */ - ss_pre << " " << input.name << " = texelFetch(" << image_name << ", " << texel_co << ", 0)." - << swizzle << ";\n"; - - pre_main += ss_pre.str(); - } + /* TODO(fclem): Add GL_EXT_shader_framebuffer_fetch support and fallback using imageLoad. + * For now avoid compilation failure. */ + ss << "const " << to_string(input.type) << " " << input.name << " = " << to_string(input.type) + << "(0);\n"; } ss << "\n/* Outputs. */\n"; for (const ShaderCreateInfo::FragOut &output : info.fragment_outputs_) { @@ -837,7 +663,8 @@ std::string GLShader::fragment_interface_declare(const ShaderCreateInfo &info) c } ss << "\n"; - if (!pre_main.empty() || !post_main.empty()) { + if (pre_main.empty() == false) { + std::string post_main; ss << main_function_wrapper(pre_main, post_main); } return ss.str(); @@ -1064,9 +891,6 @@ static char *glsl_patch_default_get() if (GLContext::native_barycentric_support) { STR_CONCAT(patch, slen, "#extension GL_AMD_shader_explicit_vertex_parameter: enable\n"); } - if (GLContext::framebuffer_fetch_support) { - STR_CONCAT(patch, slen, "#extension GL_EXT_shader_framebuffer_fetch: enable\n"); - } /* Fallbacks. */ if (!GLContext::shader_draw_parameters_support) { diff --git a/source/blender/gpu/opengl/gl_shader.hh b/source/blender/gpu/opengl/gl_shader.hh index 65f55c0c19e..3863dab0849 100644 --- a/source/blender/gpu/opengl/gl_shader.hh +++ b/source/blender/gpu/opengl/gl_shader.hh @@ -69,7 +69,7 @@ class GLShader : public Shader { void uniform_float(int location, int comp_len, int array_size, const float *data) override; void uniform_int(int location, int comp_len, int array_size, const int *data) override; - /* Unused: SSBO vertex fetch draw parameters. */ + /* Unusued: SSBO vertex fetch draw parameters. */ bool get_uses_ssbo_vertex_fetch() const override { return false; diff --git a/source/blender/gpu/opengl/gl_shader_log.cc b/source/blender/gpu/opengl/gl_shader_log.cc index 0e637763868..e0d0973e005 100644 --- a/source/blender/gpu/opengl/gl_shader_log.cc +++ b/source/blender/gpu/opengl/gl_shader_log.cc @@ -39,7 +39,10 @@ const char *GLLogParser::parse_line(const char *log_line, GPULogItem &log_item) } if ((log_item.cursor.row != -1) && (log_item.cursor.column != -1)) { - if (GPU_type_matches(GPU_DEVICE_NVIDIA, GPU_OS_ANY, GPU_DRIVER_OFFICIAL)) { + if (GPU_type_matches(GPU_DEVICE_NVIDIA, GPU_OS_ANY, GPU_DRIVER_OFFICIAL) || + GPU_type_matches(GPU_DEVICE_INTEL, GPU_OS_MAC, GPU_DRIVER_OFFICIAL) || + GPU_type_matches(GPU_DEVICE_APPLE, GPU_OS_MAC, GPU_DRIVER_OFFICIAL)) + { /* 0:line */ log_item.cursor.row = log_item.cursor.column; log_item.cursor.column = -1; diff --git a/source/blender/gpu/opengl/gl_storage_buffer.cc b/source/blender/gpu/opengl/gl_storage_buffer.cc index 9bc42456104..0cfa7743bb3 100644 --- a/source/blender/gpu/opengl/gl_storage_buffer.cc +++ b/source/blender/gpu/opengl/gl_storage_buffer.cc @@ -8,7 +8,6 @@ #include "BLI_string.h" -#include "GPU_capabilities.h" #include "gpu_backend.hh" #include "gpu_context_private.hh" @@ -28,7 +27,7 @@ GLStorageBuf::GLStorageBuf(size_t size, GPUUsageType usage, const char *name) { usage_ = usage; /* Do not create UBO GL buffer here to allow allocation from any thread. */ - BLI_assert(size <= GPU_max_storage_buffer_size()); + BLI_assert(size <= GLContext::max_ssbo_size); } GLStorageBuf::~GLStorageBuf() @@ -161,11 +160,6 @@ void GLStorageBuf::copy_sub(VertBuf *src_, uint dst_offset, uint src_offset, uin } } -void GLStorageBuf::async_flush_to_host() -{ - GPU_memory_barrier(GPU_BARRIER_BUFFER_UPDATE); -} - void GLStorageBuf::read(void *data) { if (ssbo_id_ == 0) { diff --git a/source/blender/gpu/opengl/gl_storage_buffer.hh b/source/blender/gpu/opengl/gl_storage_buffer.hh index d24e9ae713f..20144d0c8f0 100644 --- a/source/blender/gpu/opengl/gl_storage_buffer.hh +++ b/source/blender/gpu/opengl/gl_storage_buffer.hh @@ -37,7 +37,6 @@ class GLStorageBuf : public StorageBuf { void clear(uint32_t clear_value) override; void copy_sub(VertBuf *src, uint dst_offset, uint src_offset, uint copy_size) override; void read(void *data) override; - void async_flush_to_host() override; /* Special internal function to bind SSBOs to indirect argument targets. */ void bind_as(GLenum target); diff --git a/source/blender/gpu/opengl/gl_texture.cc b/source/blender/gpu/opengl/gl_texture.cc index 2bcf02e8660..038561eeadb 100644 --- a/source/blender/gpu/opengl/gl_texture.cc +++ b/source/blender/gpu/opengl/gl_texture.cc @@ -40,7 +40,7 @@ GLTexture::GLTexture(const char *name) : Texture(name) GLTexture::~GLTexture() { if (framebuffer_) { - GPU_framebuffer_free(wrap(framebuffer_)); + GPU_framebuffer_free(framebuffer_); } GLContext *ctx = GLContext::get(); if (ctx != nullptr && is_bound_) { @@ -400,7 +400,7 @@ void GLTexture::clear(eGPUDataFormat data_format, const void *data) /* Fallback for older GL. */ GPUFrameBuffer *prev_fb = GPU_framebuffer_active_get(); - FrameBuffer *fb = this->framebuffer_get(); + FrameBuffer *fb = reinterpret_cast(this->framebuffer_get()); fb->bind(true); fb->clear_attachment(this->attachment_type(0), data_format, data); @@ -431,11 +431,8 @@ void GLTexture::copy_to(Texture *dst_) } else { /* Fallback for older GL. */ - GPU_framebuffer_blit(wrap(src->framebuffer_get()), - 0, - wrap(dst->framebuffer_get()), - 0, - to_framebuffer_bits(format_)); + GPU_framebuffer_blit( + src->framebuffer_get(), 0, dst->framebuffer_get(), 0, to_framebuffer_bits(format_)); } has_pixels_ = true; @@ -531,14 +528,16 @@ void GLTexture::mip_range_set(int min, int max) } } -FrameBuffer *GLTexture::framebuffer_get() +GPUFrameBuffer *GLTexture::framebuffer_get() { if (framebuffer_) { return framebuffer_; } - BLI_assert(!(type_ & GPU_TEXTURE_1D)); - framebuffer_ = unwrap(GPU_framebuffer_create(name_)); - framebuffer_->attachment_set(this->attachment_type(0), GPU_ATTACHMENT_TEXTURE(wrap(this))); + BLI_assert(!(type_ & (GPU_TEXTURE_ARRAY | GPU_TEXTURE_CUBE | GPU_TEXTURE_1D | GPU_TEXTURE_3D))); + /* TODO(fclem): cleanup this. Don't use GPU object but blender::gpu ones. */ + GPUTexture *gputex = reinterpret_cast(static_cast(this)); + framebuffer_ = GPU_framebuffer_create(name_); + GPU_framebuffer_texture_attach(framebuffer_, gputex, 0, 0); has_pixels_ = true; return framebuffer_; } @@ -736,6 +735,7 @@ bool GLTexture::proxy_check(int mip) } if (GPU_type_matches(GPU_DEVICE_ATI, GPU_OS_WIN, GPU_DRIVER_ANY) || + GPU_type_matches(GPU_DEVICE_NVIDIA, GPU_OS_MAC, GPU_DRIVER_OFFICIAL) || GPU_type_matches(GPU_DEVICE_ATI, GPU_OS_UNIX, GPU_DRIVER_OFFICIAL)) { /* Some AMD drivers have a faulty `GL_PROXY_TEXTURE_..` check. @@ -747,6 +747,13 @@ bool GLTexture::proxy_check(int mip) return true; } + if ((type_ == GPU_TEXTURE_CUBE_ARRAY) && + GPU_type_matches(GPU_DEVICE_ANY, GPU_OS_MAC, GPU_DRIVER_ANY)) + { + /* Special fix for #79703. */ + return true; + } + GLenum gl_proxy = to_gl_proxy(type_); GLenum internal_format = to_gl_internal_format(format_); GLenum gl_format = to_gl_data_format(format_); @@ -810,10 +817,7 @@ void GLTexture::check_feedback_loop() if (fb_[i] == fb) { GPUAttachmentType type = fb_attachment_[i]; GPUAttachment attachment = fb->attachments_[type]; - /* Check for when texture is used with texture barrier. */ - GPUAttachment attachment_read = fb->tmp_detached_[type]; - if (attachment.mip <= mip_max_ && attachment.mip >= mip_min_ && - attachment_read.tex == nullptr) { + if (attachment.mip <= mip_max_ && attachment.mip >= mip_min_) { char msg[256]; SNPRINTF(msg, "Feedback loop: Trying to bind a texture (%s) with mip range %d-%d but mip %d is " diff --git a/source/blender/gpu/opengl/gl_texture.hh b/source/blender/gpu/opengl/gl_texture.hh index 2490956d09c..bb37e10c452 100644 --- a/source/blender/gpu/opengl/gl_texture.hh +++ b/source/blender/gpu/opengl/gl_texture.hh @@ -14,6 +14,8 @@ #include "gpu_texture_private.hh" +struct GPUFrameBuffer; + namespace blender { namespace gpu { @@ -45,7 +47,7 @@ class GLTexture : public Texture { /** opengl identifier for texture. */ GLuint tex_id_ = 0; /** Legacy workaround for texture copy. Created when using framebuffer_get(). */ - FrameBuffer *framebuffer_ = nullptr; + GPUFrameBuffer *framebuffer_ = nullptr; /** True if this texture is bound to at least one texture unit. */ /* TODO(fclem): How do we ensure thread safety here? */ bool is_bound_ = false; @@ -122,7 +124,7 @@ class GLTexture : public Texture { void stencil_texture_mode_set(bool use_stencil); void update_sub_direct_state_access( int mip, int offset[3], int extent[3], GLenum gl_format, GLenum gl_type, const void *data); - FrameBuffer *framebuffer_get(); + GPUFrameBuffer *framebuffer_get(); MEM_CXX_CLASS_ALLOC_FUNCS("GLTexture") }; @@ -207,7 +209,7 @@ inline GLenum to_gl_internal_format(eGPUTextureFormat format) return GL_R16F; case GPU_R16: return GL_R16; - /* Special formats texture & render-buffer. */ + /* Special formats texture & renderbuffer */ case GPU_RGB10_A2: return GL_RGB10_A2; case GPU_RGB10_A2UI: @@ -423,7 +425,7 @@ inline GLenum to_gl_data_format(eGPUTextureFormat format) case GPU_R16UI: case GPU_R16I: return GL_RED_INTEGER; - /* Special formats texture & render-buffer. */ + /* Special formats texture & renderbuffer */ case GPU_RGB10_A2UI: case GPU_RGB10_A2: case GPU_SRGB8_A8: diff --git a/source/blender/gpu/shaders/common/gpu_shader_common_math.glsl b/source/blender/gpu/shaders/common/gpu_shader_common_math.glsl index 9ab6a465cbf..344842ca30b 100644 --- a/source/blender/gpu/shaders/common/gpu_shader_common_math.glsl +++ b/source/blender/gpu/shaders/common/gpu_shader_common_math.glsl @@ -228,14 +228,3 @@ void math_smoothmax(float a, float b, float c, out float result) math_smoothmin(-a, -b, c, result); result = -result; } - -/* TODO(fclem): Fix dependency hell one EEVEE legacy is removed. */ -float math_reduce_max(vec3 a) -{ - return max(a.x, max(a.y, a.z)); -} - -float math_average(vec3 a) -{ - return (a.x + a.y + a.z) * (1.0 / 3.0); -} diff --git a/source/blender/gpu/shaders/common/gpu_shader_debug_gradients_lib.glsl b/source/blender/gpu/shaders/common/gpu_shader_debug_gradients_lib.glsl deleted file mode 100644 index defe44ebe60..00000000000 --- a/source/blender/gpu/shaders/common/gpu_shader_debug_gradients_lib.glsl +++ /dev/null @@ -1,28 +0,0 @@ -/* SPDX-FileCopyrightText: 2023 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -/* - * For debugging purpose mainly. - * From https://www.shadertoy.com/view/4dsSzr - * By Morgan McGuire @morgan3d, http://graphicscodex.com - * Reuse permitted under the BSD license. - */ -vec3 neon_gradient(float t) -{ - float tt = abs(0.43 - t) * 1.7; - return clamp(vec3(t * 1.3 + 0.1, tt * tt, (1.0 - t) * 1.7), 0.0, 1.0); -} -vec3 heatmap_gradient(float t) -{ - float a = pow(t, 1.5) * 0.8 + 0.2; - float b = smoothstep(0.0, 0.35, t) + t * 0.5; - float c = smoothstep(0.5, 1.0, t); - float d = max(1.0 - t * 1.7, t * 7.0 - 6.0); - return clamp(a * vec3(b, c, d), vec3(0.0), vec3(1.0)); -} -vec3 hue_gradient(float t) -{ - vec3 p = abs(fract(t + vec3(1.0, 2.0 / 3.0, 1.0 / 3.0)) * 6.0 - 3.0); - return (clamp(p - 1.0, 0.0, 1.0)); -} diff --git a/source/blender/gpu/shaders/common/gpu_shader_math_base_lib.glsl b/source/blender/gpu/shaders/common/gpu_shader_math_base_lib.glsl index 7596c0b9f84..8aa2842076c 100644 --- a/source/blender/gpu/shaders/common/gpu_shader_math_base_lib.glsl +++ b/source/blender/gpu/shaders/common/gpu_shader_math_base_lib.glsl @@ -163,31 +163,6 @@ float safe_rcp(float a) return (a != 0.0) ? (1.0 / a) : 0.0; } -/** - * Safe square root function. Returns `sqrt(a)`. - * If `a` is less or equal to 0 then the result will be 0. - */ -float safe_sqrt(float a) -{ - return sqrt(max(0.0, a)); -} - -/** - * Safe `arccosine` function. Returns `acos(a)`. - * If `a` is greater than 1, returns 0. - * If `a` is less than -1, returns PI. - */ -float safe_acos(float a) -{ - if (a <= -1.0) { - return M_PI; - } - else if (a >= 1.0) { - return 0.0; - } - return acos(a); -} - /** * Return true if the difference between`a` and `b` is below the `epsilon` value. */ diff --git a/source/blender/gpu/shaders/common/gpu_shader_math_matrix_lib.glsl b/source/blender/gpu/shaders/common/gpu_shader_math_matrix_lib.glsl index adb600cb665..1ccecfc9bc6 100644 --- a/source/blender/gpu/shaders/common/gpu_shader_math_matrix_lib.glsl +++ b/source/blender/gpu/shaders/common/gpu_shader_math_matrix_lib.glsl @@ -246,16 +246,6 @@ mat4x4 from_loc_rot_scale(vec3 location, EulerXYZ rotation, vec3 scale); */ // mat4x4 from_normalized_axis_data(vec3 location, vec3 forward, vec3 up); /* TODO. */ -/** - * Create a rotation matrix from only one \a up axis. - * The other axes are chosen to always be orthogonal. The resulting matrix is a basis matrix. - * \note `up` must be normalized. - * \note This can be used to create a tangent basis from a normal vector. - * \note The output of this function is not given to be same across blender version. Prefer using - * `from_orthonormal_axes` for more stable output. - */ -mat3x3 from_up_axis(vec3 up); - /** \} */ /* -------------------------------------------------------------------- */ @@ -1026,20 +1016,6 @@ mat4x4 from_loc_rot_scale(vec3 location, AxisAngle rotation, vec3 scale) return ret; } -mat3x3 from_up_axis(vec3 up) -{ - /* Duff, Tom, et al. "Building an orthonormal basis, revisited." JCGT 6.1 (2017). */ - float z_sign = sign(up.z); - float a = -1.0 / (z_sign + up.z); - float b = up.x * up.y * a; - - mat3x3 basis; - basis[0] = vec3(1.0 + z_sign * square(up.x) * a, z_sign * b, -z_sign * up.x); - basis[1] = vec3(b, z_sign + square(up.y) * a, -up.y); - basis[2] = up; - return basis; -} - void detail_normalized_to_eul2(mat3 mat, out EulerXYZ eul1, out EulerXYZ eul2) { float cy = hypot(mat[0][0], mat[0][1]); diff --git a/source/blender/gpu/shaders/common/gpu_shader_math_vector_lib.glsl b/source/blender/gpu/shaders/common/gpu_shader_math_vector_lib.glsl index dc02f7d4a05..8d56d0f5b61 100644 --- a/source/blender/gpu/shaders/common/gpu_shader_math_vector_lib.glsl +++ b/source/blender/gpu/shaders/common/gpu_shader_math_vector_lib.glsl @@ -184,43 +184,6 @@ bool is_equal(vec2 a, vec2 b, const float epsilon); bool is_equal(vec3 a, vec3 b, const float epsilon); bool is_equal(vec4 a, vec4 b, const float epsilon); -/** - * Return the maximum component of a vector. - */ -float reduce_max(vec2 a); -float reduce_max(vec3 a); -float reduce_max(vec4 a); -int reduce_max(ivec2 a); -int reduce_max(ivec3 a); -int reduce_max(ivec4 a); - -/** - * Return the minimum component of a vector. - */ -float reduce_min(vec2 a); -float reduce_min(vec3 a); -float reduce_min(vec4 a); -int reduce_min(ivec2 a); -int reduce_min(ivec3 a); -int reduce_min(ivec4 a); - -/** - * Return the sum of the components of a vector. - */ -float reduce_add(vec2 a); -float reduce_add(vec3 a); -float reduce_add(vec4 a); -int reduce_add(ivec2 a); -int reduce_add(ivec3 a); -int reduce_add(ivec4 a); - -/** - * Return the average of the components of a vector. - */ -float average(vec2 a); -float average(vec3 a); -float average(vec4 a); - # endif /* GPU_METAL */ /* ---------------------------------------------------------------------- */ @@ -627,94 +590,6 @@ bool is_equal(vec4 a, vec4 b, const float epsilon) return all(lessThanEqual(abs(a - b), vec4(epsilon))); } -float reduce_max(vec2 a) -{ - return max(a.x, a.y); -} -float reduce_max(vec3 a) -{ - return max(a.x, max(a.y, a.z)); -} -float reduce_max(vec4 a) -{ - return max(max(a.x, a.y), max(a.z, a.w)); -} -int reduce_max(ivec2 a) -{ - return max(a.x, a.y); -} -int reduce_max(ivec3 a) -{ - return max(a.x, max(a.y, a.z)); -} -int reduce_max(ivec4 a) -{ - return max(max(a.x, a.y), max(a.z, a.w)); -} - -float reduce_min(vec2 a) -{ - return min(a.x, a.y); -} -float reduce_min(vec3 a) -{ - return min(a.x, min(a.y, a.z)); -} -float reduce_min(vec4 a) -{ - return min(min(a.x, a.y), min(a.z, a.w)); -} -int reduce_min(ivec2 a) -{ - return min(a.x, a.y); -} -int reduce_min(ivec3 a) -{ - return min(a.x, min(a.y, a.z)); -} -int reduce_min(ivec4 a) -{ - return min(min(a.x, a.y), min(a.z, a.w)); -} - -float reduce_add(vec2 a) -{ - return a.x + a.y; -} -float reduce_add(vec3 a) -{ - return a.x + a.y + a.z; -} -float reduce_add(vec4 a) -{ - return a.x + a.y + a.z + a.w; -} -int reduce_add(ivec2 a) -{ - return a.x + a.y; -} -int reduce_add(ivec3 a) -{ - return a.x + a.y + a.z; -} -int reduce_add(ivec4 a) -{ - return a.x + a.y + a.z + a.w; -} - -float average(vec2 a) -{ - return reduce_add(a) * (1.0 / 2.0); -} -float average(vec3 a) -{ - return reduce_add(a) * (1.0 / 3.0); -} -float average(vec4 a) -{ - return reduce_add(a) * (1.0 / 4.0); -} - # define ASSERT_UNIT_EPSILON 0.0002 /* Checks are flipped so NAN doesn't assert because we're making sure the value was diff --git a/source/blender/gpu/shaders/common/gpu_shader_utildefines_lib.glsl b/source/blender/gpu/shaders/common/gpu_shader_utildefines_lib.glsl index 5c97caa72cd..7907a59d7de 100644 --- a/source/blender/gpu/shaders/common/gpu_shader_utildefines_lib.glsl +++ b/source/blender/gpu/shaders/common/gpu_shader_utildefines_lib.glsl @@ -36,9 +36,6 @@ #define in_range_exclusive(val, min_v, max_v) (all(greaterThan(val, min_v)) && all(lessThan(val, max_v))) #define in_texture_range(texel, tex) (all(greaterThanEqual(texel, ivec2(0))) && all(lessThan(texel, textureSize(tex, 0).xy))) #define in_image_range(texel, tex) (all(greaterThanEqual(texel, ivec2(0))) && all(lessThan(texel, imageSize(tex).xy))) - -#define weighted_sum(val0, val1, val2, val3, weights) ((val0 * weights[0] + val1 * weights[1] + val2 * weights[2] + val3 * weights[3]) * safe_rcp(weights[0] + weights[1] + weights[2] + weights[3])) -#define weighted_sum_array(val, weights) ((val[0] * weights[0] + val[1] * weights[1] + val[2] * weights[2] + val[3] * weights[3]) * safe_rcp(weights[0] + weights[1] + weights[2] + weights[3])) /* clang-format on */ bool flag_test(uint flag, uint val) @@ -74,7 +71,7 @@ void set_flag_from_test(inout int value, bool test, int flag) } /* Keep define to match C++ implementation. */ -#define SET_FLAG_FROM_TEST(value, test, flag) set_flag_from_test(value, test, flag) +#define SET_FLAG_FROM_TEST(value, test, flag) flag_test(value, test, flag) /** * Pack two 16-bit uint into one 32-bit uint. diff --git a/source/blender/gpu/shaders/gpu_shader_codegen_lib.glsl b/source/blender/gpu/shaders/gpu_shader_codegen_lib.glsl index db1bf1e75a0..e013364e913 100644 --- a/source/blender/gpu/shaders/gpu_shader_codegen_lib.glsl +++ b/source/blender/gpu/shaders/gpu_shader_codegen_lib.glsl @@ -101,11 +101,11 @@ vec4 tangent_get(vec4 attr, mat3 normalmat) /* Assumes GPU_VEC4 is color data. So converting to luminance like cycles. */ #define float_from_vec4(v) dot(v.rgb, vec3(0.2126, 0.7152, 0.0722)) -#define float_from_vec3(v) ((v.r + v.g + v.b) * (1.0 / 3.0)) +#define float_from_vec3(v) avg(v.rgb) #define float_from_vec2(v) v.r -#define vec2_from_vec4(v) vec2(((v.r + v.g + v.b) * (1.0 / 3.0)), v.a) -#define vec2_from_vec3(v) vec2(((v.r + v.g + v.b) * (1.0 / 3.0)), 1.0) +#define vec2_from_vec4(v) vec2(avg(v.rgb), v.a) +#define vec2_from_vec3(v) vec2(avg(v.rgb), 1.0) #define vec2_from_float(v) vec2(v) #define vec3_from_vec4(v) v.rgb diff --git a/source/blender/gpu/shaders/infos/gpu_shader_test_info.hh b/source/blender/gpu/shaders/infos/gpu_shader_test_info.hh index 7bceb1d76db..87fd2f30015 100644 --- a/source/blender/gpu/shaders/infos/gpu_shader_test_info.hh +++ b/source/blender/gpu/shaders/infos/gpu_shader_test_info.hh @@ -98,9 +98,3 @@ GPU_SHADER_CREATE_INFO(eevee_shadow_test) .additional_info("gpu_shader_test") .additional_info("eevee_shared") .do_static_compilation(true); - -GPU_SHADER_CREATE_INFO(eevee_occupancy_test) - .fragment_source("eevee_occupancy_test.glsl") - .additional_info("gpu_shader_test") - .additional_info("eevee_shared") - .do_static_compilation(true); diff --git a/source/blender/gpu/shaders/material/gpu_shader_material_attribute.glsl b/source/blender/gpu/shaders/material/gpu_shader_material_attribute.glsl index b8bb947fcf3..d4650d0977f 100644 --- a/source/blender/gpu/shaders/material/gpu_shader_material_attribute.glsl +++ b/source/blender/gpu/shaders/material/gpu_shader_material_attribute.glsl @@ -2,8 +2,6 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ -#pragma BLENDER_REQUIRE(gpu_shader_common_math.glsl) - void node_attribute_color(vec4 attr, out vec4 out_attr) { out_attr = attr_load_color_post(attr); @@ -65,6 +63,6 @@ void node_attribute( { outcol = vec4(attr.xyz, 1.0); outvec = attr.xyz; - outf = math_average(attr.xyz); + outf = avg(attr.xyz); outalpha = attr.w; } diff --git a/source/blender/gpu/shaders/material/gpu_shader_material_camera.glsl b/source/blender/gpu/shaders/material/gpu_shader_material_camera.glsl index 595d8226e15..384eabba204 100644 --- a/source/blender/gpu/shaders/material/gpu_shader_material_camera.glsl +++ b/source/blender/gpu/shaders/material/gpu_shader_material_camera.glsl @@ -2,12 +2,9 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ -#pragma BLENDER_REQUIRE(gpu_shader_material_transform_utils.glsl) - void camera(out vec3 outview, out float outdepth, out float outdist) { - vec3 vP; - point_transform_world_to_view(g_data.P, vP); + vec3 vP = transform_point(ViewMatrix, g_data.P); vP.z = -vP.z; outdepth = abs(vP.z); outdist = length(vP); diff --git a/source/blender/gpu/shaders/material/gpu_shader_material_displacement.glsl b/source/blender/gpu/shaders/material/gpu_shader_material_displacement.glsl index 8eab5e45127..69c722db2fe 100644 --- a/source/blender/gpu/shaders/material/gpu_shader_material_displacement.glsl +++ b/source/blender/gpu/shaders/material/gpu_shader_material_displacement.glsl @@ -2,15 +2,12 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ -#pragma BLENDER_REQUIRE(gpu_shader_material_transform_utils.glsl) - void node_displacement_object(float height, float midlevel, float scale, vec3 N, out vec3 result) { - vec3 lN; - direction_transform_world_to_object(N, lN); - vec3 l_displacement = (height - midlevel) * scale * normalize(lN); + N = transform_direction(ModelMatrixInverse, N); + result = (height - midlevel) * scale * normalize(N); /* Apply object scale and orientation. */ - direction_transform_object_to_world(l_displacement, result); + result = transform_direction(ModelMatrix, result); } void node_displacement_world(float height, float midlevel, float scale, vec3 N, out vec3 result) diff --git a/source/blender/gpu/shaders/material/gpu_shader_material_eevee_specular.glsl b/source/blender/gpu/shaders/material/gpu_shader_material_eevee_specular.glsl index fc131847db7..14367021ca7 100644 --- a/source/blender/gpu/shaders/material/gpu_shader_material_eevee_specular.glsl +++ b/source/blender/gpu/shaders/material/gpu_shader_material_eevee_specular.glsl @@ -18,7 +18,7 @@ void node_eevee_specular(vec4 diffuse, { N = safe_normalize(N); CN = safe_normalize(CN); - vec3 V = coordinate_incoming(g_data.P); + vec3 V = cameraVec(g_data.P); ClosureEmission emission_data; emission_data.weight = weight; diff --git a/source/blender/gpu/shaders/material/gpu_shader_material_fresnel.glsl b/source/blender/gpu/shaders/material/gpu_shader_material_fresnel.glsl index e73366cae6e..6e610e6ad52 100644 --- a/source/blender/gpu/shaders/material/gpu_shader_material_fresnel.glsl +++ b/source/blender/gpu/shaders/material/gpu_shader_material_fresnel.glsl @@ -33,7 +33,7 @@ float fresnel_dielectric(vec3 Incoming, vec3 Normal, float eta) void node_fresnel(float ior, vec3 N, out float result) { N = normalize(N); - vec3 V = coordinate_incoming(g_data.P); + vec3 V = cameraVec(g_data.P); float eta = max(ior, 0.00001); result = fresnel_dielectric(V, N, (FrontFacing) ? eta : 1.0 / eta); diff --git a/source/blender/gpu/shaders/material/gpu_shader_material_glass.glsl b/source/blender/gpu/shaders/material/gpu_shader_material_glass.glsl index fe08957caf6..33c46a7bfa1 100644 --- a/source/blender/gpu/shaders/material/gpu_shader_material_glass.glsl +++ b/source/blender/gpu/shaders/material/gpu_shader_material_glass.glsl @@ -11,7 +11,7 @@ void node_bsdf_glass(vec4 color, out Closure result) { N = safe_normalize(N); - vec3 V = coordinate_incoming(g_data.P); + vec3 V = cameraVec(g_data.P); float NV = dot(N, V); vec2 bsdf = bsdf_lut(NV, roughness, ior, do_multiscatter != 0.0); diff --git a/source/blender/gpu/shaders/material/gpu_shader_material_glossy.glsl b/source/blender/gpu/shaders/material/gpu_shader_material_glossy.glsl index 100731b0b83..149f8106212 100644 --- a/source/blender/gpu/shaders/material/gpu_shader_material_glossy.glsl +++ b/source/blender/gpu/shaders/material/gpu_shader_material_glossy.glsl @@ -13,7 +13,7 @@ void node_bsdf_glossy(vec4 color, out Closure result) { N = safe_normalize(N); - vec3 V = coordinate_incoming(g_data.P); + vec3 V = cameraVec(g_data.P); float NV = dot(N, V); vec2 split_sum = brdf_lut(NV, roughness); diff --git a/source/blender/gpu/shaders/material/gpu_shader_material_layer_weight.glsl b/source/blender/gpu/shaders/material/gpu_shader_material_layer_weight.glsl index b5258196b70..5488e8e63cb 100644 --- a/source/blender/gpu/shaders/material/gpu_shader_material_layer_weight.glsl +++ b/source/blender/gpu/shaders/material/gpu_shader_material_layer_weight.glsl @@ -10,7 +10,7 @@ void node_layer_weight(float blend, vec3 N, out float fresnel, out float facing) /* fresnel */ float eta = max(1.0 - blend, 0.00001); - vec3 V = coordinate_incoming(g_data.P); + vec3 V = cameraVec(g_data.P); fresnel = fresnel_dielectric(V, N, (FrontFacing) ? 1.0 / eta : eta); diff --git a/source/blender/gpu/shaders/material/gpu_shader_material_principled.glsl b/source/blender/gpu/shaders/material/gpu_shader_material_principled.glsl index 5955b5d7967..f00bf176f84 100644 --- a/source/blender/gpu/shaders/material/gpu_shader_material_principled.glsl +++ b/source/blender/gpu/shaders/material/gpu_shader_material_principled.glsl @@ -2,9 +2,6 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ -#pragma BLENDER_REQUIRE(gpu_shader_math_fast_lib.glsl) -#pragma BLENDER_REQUIRE(gpu_shader_common_math.glsl) - vec3 tint_from_color(vec3 color) { float lum = dot(color, vec3(0.3, 0.6, 0.1)); /* luminance approx. */ @@ -82,7 +79,7 @@ void node_bsdf_principled(vec4 base_color, N = safe_normalize(N); CN = safe_normalize(CN); - vec3 V = coordinate_incoming(g_data.P); + vec3 V = cameraVec(g_data.P); float NV = dot(N, V); ClosureTransparency transparency_data; @@ -100,7 +97,7 @@ void node_bsdf_principled(vec4 base_color, vec3 sheen_color = sheen_weight * sheen_tint.rgb * principled_sheen(NV, sheen_roughness); diffuse_data.color = weight * sheen_color; /* Attenuate lower layers */ - weight *= (1.0 - math_reduce_max(sheen_color)); + weight *= (1.0 - max_v3(sheen_color)); } else { diffuse_data.color = vec3(0.0); @@ -121,7 +118,7 @@ void node_bsdf_principled(vec4 base_color, if (!all(equal(coat_tint.rgb, vec3(1.0)))) { float coat_neta = 1.0 / coat_ior; - float NT = sqrt_fast(1.0 - coat_neta * coat_neta * (1 - NV * NV)); + float NT = fast_sqrt(1.0 - coat_neta * coat_neta * (1 - NV * NV)); /* Tint lower layers. */ coat_tint.rgb = mix(vec3(1.0), pow(coat_tint.rgb, vec3(1.0 / NT)), coat_weight); } @@ -205,7 +202,7 @@ void node_bsdf_principled(vec4 base_color, reflection_data.color += weight * reflectance; /* Attenuate lower layers */ - weight *= (1.0 - math_reduce_max(reflectance)); + weight *= (1.0 - max_v3(reflectance)); } /* Diffuse component */ @@ -217,10 +214,10 @@ void node_bsdf_principled(vec4 base_color, /* Adjust the weight of picking the closure. */ reflection_data.color *= coat_tint.rgb; - reflection_data.weight = math_average(reflection_data.color); + reflection_data.weight = avg(reflection_data.color); reflection_data.color *= safe_rcp(reflection_data.weight); - diffuse_data.weight = math_average(diffuse_data.color); + diffuse_data.weight = avg(diffuse_data.color); diffuse_data.color *= safe_rcp(diffuse_data.weight); /* Ref. #98190: Defines are optimizations for old compilers. diff --git a/source/blender/gpu/shaders/material/gpu_shader_material_tangent.glsl b/source/blender/gpu/shaders/material/gpu_shader_material_tangent.glsl index 955bd55345b..8ca0879f488 100644 --- a/source/blender/gpu/shaders/material/gpu_shader_material_tangent.glsl +++ b/source/blender/gpu/shaders/material/gpu_shader_material_tangent.glsl @@ -2,8 +2,6 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ -#pragma BLENDER_REQUIRE(gpu_shader_material_transform_utils.glsl) - void tangent_orco_x(vec3 orco_in, out vec3 orco_out) { orco_out = orco_in.xzy * vec3(0.0, -0.5, 0.5) + vec3(0.0, 0.25, -0.25); @@ -26,6 +24,6 @@ void node_tangentmap(vec4 attr_tangent, out vec3 tangent) void node_tangent(vec3 orco, out vec3 T) { - direction_transform_object_to_world(orco, T); + T = transform_direction(ModelMatrix, orco); T = cross(g_data.N, normalize(cross(T, g_data.N))); } diff --git a/source/blender/gpu/shaders/material/gpu_shader_material_tex_sky.glsl b/source/blender/gpu/shaders/material/gpu_shader_material_tex_sky.glsl index e1919fcbaf2..f146263b541 100644 --- a/source/blender/gpu/shaders/material/gpu_shader_material_tex_sky.glsl +++ b/source/blender/gpu/shaders/material/gpu_shader_material_tex_sky.glsl @@ -166,8 +166,7 @@ void node_tex_sky_nishita(vec3 co, } else { /* evaluate longitudinal position on the map */ - const float tau = 6.28318530717958647692; - float x = (spherical.y + M_PI + sun_rotation) / tau; + float x = (spherical.y + M_PI + sun_rotation) / M_2PI; if (x > 1.0) { x -= 1.0; } diff --git a/source/blender/gpu/shaders/material/gpu_shader_material_texture_coordinates.glsl b/source/blender/gpu/shaders/material/gpu_shader_material_texture_coordinates.glsl index 06c2f733bce..b087796395e 100644 --- a/source/blender/gpu/shaders/material/gpu_shader_material_texture_coordinates.glsl +++ b/source/blender/gpu/shaders/material/gpu_shader_material_texture_coordinates.glsl @@ -2,8 +2,6 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ -#pragma BLENDER_REQUIRE(gpu_shader_material_transform_utils.glsl) - void node_tex_coord_position(out vec3 out_pos) { out_pos = g_data.P; @@ -21,15 +19,9 @@ void node_tex_coord(mat4 obmatinv, out vec3 reflection) { generated = attr_orco; - normal_transform_world_to_object(g_data.N, normal); + normal = normal_world_to_object(g_data.N); uv = attr_uv.xyz; - bool valid_mat = (obmatinv[3][3] != 0.0); - if (valid_mat) { - object = (obmatinv * vec4(g_data.P, 1.0)).xyz; - } - else { - point_transform_world_to_object(g_data.P, object); - } + object = transform_point((obmatinv[3][3] == 0.0) ? ModelMatrixInverse : obmatinv, g_data.P); camera = coordinate_camera(g_data.P); window = coordinate_screen(g_data.P); reflection = coordinate_reflect(g_data.P, g_data.N); diff --git a/source/blender/gpu/shaders/material/gpu_shader_material_transform_utils.glsl b/source/blender/gpu/shaders/material/gpu_shader_material_transform_utils.glsl index aa33f4bf2a0..1ff74c17745 100644 --- a/source/blender/gpu/shaders/material/gpu_shader_material_transform_utils.glsl +++ b/source/blender/gpu/shaders/material/gpu_shader_material_transform_utils.glsl @@ -2,78 +2,74 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ -/* Requires all common matrices declared. */ - void normal_transform_object_to_world(vec3 vin, out vec3 vout) { - /* Expansion of NormalMatrix. */ - vout = vin * mat3(ModelMatrixInverse); + vout = normal_object_to_world(vin); } void normal_transform_world_to_object(vec3 vin, out vec3 vout) { - /* Expansion of NormalMatrixInverse. */ - vout = vin * mat3(ModelMatrix); + vout = normal_world_to_object(vin); } void direction_transform_object_to_world(vec3 vin, out vec3 vout) { - vout = mat3x3(ModelMatrix) * vin; + vout = transform_direction(ModelMatrix, vin); } void direction_transform_object_to_view(vec3 vin, out vec3 vout) { - vout = mat3x3(ModelMatrix) * vin; - vout = mat3x3(ViewMatrix) * vout; + vout = transform_direction(ModelMatrix, vin); + vout = transform_direction(ViewMatrix, vout); } void direction_transform_view_to_world(vec3 vin, out vec3 vout) { - vout = mat3x3(ViewMatrixInverse) * vin; + vout = transform_direction(ViewMatrixInverse, vin); } void direction_transform_view_to_object(vec3 vin, out vec3 vout) { - vout = mat3x3(ViewMatrixInverse) * vin; - vout = mat3x3(ModelMatrixInverse) * vout; + vout = transform_direction(ViewMatrixInverse, vin); + vout = transform_direction(ModelMatrixInverse, vout); } void direction_transform_world_to_view(vec3 vin, out vec3 vout) { - vout = mat3x3(ViewMatrix) * vin; + vout = transform_direction(ViewMatrix, vin); } void direction_transform_world_to_object(vec3 vin, out vec3 vout) { - vout = mat3x3(ModelMatrixInverse) * vin; + vout = transform_direction(ModelMatrixInverse, vin); } void point_transform_object_to_world(vec3 vin, out vec3 vout) { - vout = (ModelMatrix * vec4(vin, 1.0)).xyz; + vout = point_object_to_world(vin); } void point_transform_object_to_view(vec3 vin, out vec3 vout) { - vout = (ViewMatrix * (ModelMatrix * vec4(vin, 1.0))).xyz; + vout = point_object_to_view(vin); } void point_transform_view_to_world(vec3 vin, out vec3 vout) { - vout = (ViewMatrixInverse * vec4(vin, 1.0)).xyz; + vout = point_view_to_world(vin); } void point_transform_view_to_object(vec3 vin, out vec3 vout) { - vout = (ModelMatrixInverse * (ViewMatrixInverse * vec4(vin, 1.0))).xyz; + vout = point_view_to_object(vin); } void point_transform_world_to_view(vec3 vin, out vec3 vout) { - vout = (ViewMatrix * vec4(vin, 1.0)).xyz; + vout = point_world_to_view(vin); } void point_transform_world_to_object(vec3 vin, out vec3 vout) { - vout = (ModelMatrixInverse * vec4(vin, 1.0)).xyz; + vout = point_world_to_object(vin); } diff --git a/source/blender/gpu/shaders/material/gpu_shader_material_vector_displacement.glsl b/source/blender/gpu/shaders/material/gpu_shader_material_vector_displacement.glsl index abd201cd9fb..f3423e8cf09 100644 --- a/source/blender/gpu/shaders/material/gpu_shader_material_vector_displacement.glsl +++ b/source/blender/gpu/shaders/material/gpu_shader_material_vector_displacement.glsl @@ -2,27 +2,22 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ -#pragma BLENDER_REQUIRE(gpu_shader_material_transform_utils.glsl) - void node_vector_displacement_tangent( vec4 vector, float midlevel, float scale, vec4 T, out vec3 result) { - vec3 oN, oT, oB; - normal_transform_world_to_object(g_data.N, oN); - normal_transform_world_to_object(T.xyz, oT); - oN = normalize(oN); - oT = normalize(oT); - oB = T.w * safe_normalize(cross(oN, oT)); + vec3 oN = normalize(normal_world_to_object(g_data.N)); + vec3 oT = normalize(normal_world_to_object(T.xyz)); + vec3 oB = T.w * safe_normalize(cross(oN, oT)); - vec3 disp = (vector.xyz - midlevel) * scale; - disp = disp.x * oT + disp.y * oN + disp.z * oB; - point_transform_object_to_world(disp, result); + result = (vector.xyz - midlevel) * scale; + result = result.x * oT + result.y * oN + result.z * oB; + result = transform_point(ModelMatrix, result); } void node_vector_displacement_object(vec4 vector, float midlevel, float scale, out vec3 result) { - vec3 disp = (vector.xyz - midlevel) * scale; - point_transform_object_to_world(disp, result); + result = (vector.xyz - midlevel) * scale; + result = transform_point(ModelMatrix, result); } void node_vector_displacement_world(vec4 vector, float midlevel, float scale, out vec3 result) diff --git a/source/blender/gpu/shaders/metal/mtl_shader_defines.msl b/source/blender/gpu/shaders/metal/mtl_shader_defines.msl index 8d78b0fd45c..22685368d95 100644 --- a/source/blender/gpu/shaders/metal/mtl_shader_defines.msl +++ b/source/blender/gpu/shaders/metal/mtl_shader_defines.msl @@ -980,183 +980,57 @@ inline void _texture_write_internal_fast(thread _mtl_combined_image_sampler_3d -S _texture_image_atomic_or_internal(thread _mtl_combined_image_sampler_1d tex, - int coord, - S data) -{ - return tex.texture->atomic_fetch_or(uint(coord), vec(data)).x; -} - -template -S _texture_image_atomic_or_internal(thread _mtl_combined_image_sampler_1d_array tex, - int2 coord, - S data) -{ - return tex.texture->atomic_fetch_or(uint(coord.x), uint(coord.y), vec(data)).x; -} - -template -S _texture_image_atomic_or_internal(thread _mtl_combined_image_sampler_2d tex, - int2 coord, - S data) -{ - return tex.texture->atomic_fetch_or(uint2(coord.xy), vec(data)).x; -} - -template -S _texture_image_atomic_or_internal(thread _mtl_combined_image_sampler_2d_array tex, - int3 coord, - S data) -{ - return tex.texture->atomic_fetch_or(uint2(coord.xy), uint(coord.z), vec(data)).x; -} - -template -S _texture_image_atomic_or_internal(thread _mtl_combined_image_sampler_3d tex, - int3 coord, - S data) -{ - return tex.texture->atomic_fetch_or(uint3(coord), vec(data)).x; -} - -/* Atomic XOR. */ -template -S _texture_image_atomic_xor_internal(thread _mtl_combined_image_sampler_1d tex, - int coord, - S data) -{ - return tex.texture->atomic_fetch_xor(uint(coord), vec(data)).x; -} - -template -S _texture_image_atomic_xor_internal(thread _mtl_combined_image_sampler_1d_array tex, - int2 coord, - S data) -{ - return tex.texture->atomic_fetch_xor(uint(coord.x), uint(coord.y), vec(data)).x; -} - -template -S _texture_image_atomic_xor_internal(thread _mtl_combined_image_sampler_2d tex, - int2 coord, - S data) -{ - return tex.texture->atomic_fetch_xor(uint2(coord.xy), vec(data)).x; -} - -template -S _texture_image_atomic_xor_internal(thread _mtl_combined_image_sampler_2d_array tex, - int3 coord, - S data) -{ - return tex.texture->atomic_fetch_xor(uint2(coord.xy), uint(coord.z), vec(data)).x; -} - -template -S _texture_image_atomic_xor_internal(thread _mtl_combined_image_sampler_3d tex, - int3 coord, - S data) -{ - return tex.texture->atomic_fetch_xor(uint3(coord), vec(data)).x; -} /* Atomic Min. */ template -S _texture_image_atomic_min_internal(thread _mtl_combined_image_sampler_1d tex, - int coord, - S data) +vec _texture_image_atomic_min_internal(thread _mtl_combined_image_sampler_1d tex, + int coord, + vec data) { - return tex.texture->atomic_fetch_min(uint(coord), vec(data)).x; + return tex.texture->atomic_fetch_min(uint(coord), data); } template -S _texture_image_atomic_min_internal(thread _mtl_combined_image_sampler_1d_array tex, - int2 coord, - S data) +vec _texture_image_atomic_min_internal(thread _mtl_combined_image_sampler_1d_array tex, + int2 coord, + vec data) { - return tex.texture->atomic_fetch_min(uint(coord.x), uint(coord.y), vec(data)).x; + return tex.texture->atomic_fetch_min(uint(coord.x), uint(coord.y), data); } template -S _texture_image_atomic_min_internal(thread _mtl_combined_image_sampler_2d tex, - int2 coord, - S data) +vec _texture_image_atomic_min_internal(thread _mtl_combined_image_sampler_2d tex, + int2 coord, + vec data) { - return tex.texture->atomic_fetch_min(uint2(coord.xy), vec(data)).x; + return tex.texture->atomic_fetch_min(uint2(coord.xy), data); } template -S _texture_image_atomic_min_internal(thread _mtl_combined_image_sampler_2d_array tex, - int3 coord, - S data) +vec _texture_image_atomic_min_internal(thread _mtl_combined_image_sampler_2d_array tex, + int3 coord, + vec data) { - return tex.texture->atomic_fetch_min(uint2(coord.xy), uint(coord.z), vec(data)).x; + return tex.texture->atomic_fetch_min(uint2(coord.xy), uint(coord.z), data); } template -S _texture_image_atomic_min_internal(thread _mtl_combined_image_sampler_3d tex, - int3 coord, - S data) +vec _texture_image_atomic_min_internal(thread _mtl_combined_image_sampler_3d tex, + int3 coord, + vec data) { - return tex.texture->atomic_fetch_min(uint3(coord), vec(data)).x; -} - -/* Atomic Add. */ -template -S _texture_image_atomic_add_internal(thread _mtl_combined_image_sampler_1d tex, - int coord, - S data) -{ - return tex.texture->atomic_fetch_add(uint(coord), vec(data)).x; -} - -template -S _texture_image_atomic_add_internal(thread _mtl_combined_image_sampler_1d_array tex, - int2 coord, - S data) -{ - return tex.texture->atomic_fetch_add(uint(coord.x), uint(coord.y), vec(data)).x; -} - -template -S _texture_image_atomic_add_internal(thread _mtl_combined_image_sampler_2d tex, - int2 coord, - S data) -{ - return tex.texture->atomic_fetch_add(uint2(coord.xy), vec(data)).x; -} - -template -S _texture_image_atomic_add_internal(thread _mtl_combined_image_sampler_2d_array tex, - int3 coord, - S data) -{ - return tex.texture->atomic_fetch_add(uint2(coord.xy), uint(coord.z), vec(data)).x; -} - -template -S _texture_image_atomic_add_internal(thread _mtl_combined_image_sampler_3d tex, - int3 coord, - S data) -{ - return tex.texture->atomic_fetch_add(uint3(coord), vec(data)).x; + return tex.texture->atomic_fetch_min(uint3(coord), data); } /* Atomic Exchange. */ -template -S _texture_image_atomic_exchange_internal(thread _mtl_combined_image_sampler_1d tex, - int coord, - S data) +template +vec _texture_image_atomic_exchange_internal(thread _mtl_combined_image_sampler_1d tex, + int coord, + vec data) { - return tex.texture->atomic_exchange(uint(coord), vec(data)).x; + return tex.texture->atomic_exchange(uint(coord), data); } template @@ -1164,31 +1038,31 @@ S _texture_image_atomic_exchange_internal(thread _mtl_combined_image_sampler_1d_ int2 coord, S data) { - return tex.texture->atomic_exchange(uint(coord.x), uint(coord.y), vec(data)).x; + return tex.texture->atomic_exchange(uint(coord.x), uint(coord.y), data); } -template +template S _texture_image_atomic_exchange_internal(thread _mtl_combined_image_sampler_2d tex, int2 coord, S data) { - return tex.texture->atomic_exchange(uint2(coord.xy), vec(data)).x; + return tex.texture->atomic_exchange(uint2(coord.xy), data); } -template +template S _texture_image_atomic_exchange_internal(thread _mtl_combined_image_sampler_2d_array tex, int3 coord, S data) { - return tex.texture->atomic_exchange(uint2(coord.xy), uint(coord.z), vec(data)).x; + return tex.texture->atomic_exchange(uint2(coord.xy), uint(coord.z), data); } -template +template S _texture_image_atomic_exchange_internal(thread _mtl_combined_image_sampler_3d tex, int3 coord, S data) { - return tex.texture->atomic_exchange(uint3(coord), vec(data)).x; + return tex.texture->atomic_exchange(uint3(coord), data); } #else diff --git a/source/blender/gpu/tests/buffer_texture_test.cc b/source/blender/gpu/tests/buffer_texture_test.cc index c509ba1bfd9..ec47861ebc9 100644 --- a/source/blender/gpu/tests/buffer_texture_test.cc +++ b/source/blender/gpu/tests/buffer_texture_test.cc @@ -42,7 +42,7 @@ static void test_buffer_texture() /* Construct SSBO. */ GPUStorageBuf *ssbo = GPU_storagebuf_create_ex( - 4 * sizeof(float), nullptr, GPU_USAGE_STATIC, __func__); + 4 * sizeof(float), nullptr, GPU_USAGE_DEVICE_ONLY, __func__); GPU_storagebuf_bind(ssbo, GPU_shader_get_ssbo_binding(shader, "data_out")); /* Dispatch compute task. */ diff --git a/source/blender/gpu/tests/framebuffer_test.cc b/source/blender/gpu/tests/framebuffer_test.cc index b8e882ef2be..491a759f8c6 100644 --- a/source/blender/gpu/tests/framebuffer_test.cc +++ b/source/blender/gpu/tests/framebuffer_test.cc @@ -336,15 +336,12 @@ static void test_framebuffer_subpass_input() const int2 size(1, 1); eGPUTextureUsage usage = GPU_TEXTURE_USAGE_ATTACHMENT | GPU_TEXTURE_USAGE_HOST_READ; - GPUTexture *texture_a = GPU_texture_create_2d( - __func__, UNPACK2(size), 1, GPU_R32I, usage, nullptr); - GPUTexture *texture_b = GPU_texture_create_2d( + GPUTexture *texture = GPU_texture_create_2d( __func__, UNPACK2(size), 1, GPU_R32I, usage, nullptr); GPUFrameBuffer *framebuffer = GPU_framebuffer_create(__func__); - GPU_framebuffer_ensure_config( - &framebuffer, - {GPU_ATTACHMENT_NONE, GPU_ATTACHMENT_TEXTURE(texture_a), GPU_ATTACHMENT_TEXTURE(texture_b)}); + GPU_framebuffer_ensure_config(&framebuffer, + {GPU_ATTACHMENT_NONE, GPU_ATTACHMENT_TEXTURE(texture)}); GPU_framebuffer_bind(framebuffer); const float4 clear_color(0.0f); @@ -364,7 +361,7 @@ static void test_framebuffer_subpass_input() create_info_read.vertex_source("gpu_framebuffer_subpass_input_test.glsl"); create_info_read.fragment_source("gpu_framebuffer_subpass_input_test.glsl"); create_info_read.subpass_in(0, Type::INT, "in_value", 0); - create_info_read.fragment_out(1, Type::INT, "out_value"); + create_info_read.fragment_out(0, Type::INT, "out_value"); GPUShader *shader_read = GPU_shader_create_from_info( reinterpret_cast(&create_info_read)); @@ -376,16 +373,10 @@ static void test_framebuffer_subpass_input() GPU_vertbuf_data_alloc(verts, 3); GPUBatch *batch = GPU_batch_create_ex(GPU_PRIM_TRIS, verts, nullptr, GPU_BATCH_OWNS_VBO); - /* Metal Raster Order Group does not need that. */ - GPU_framebuffer_subpass_transition( - framebuffer, {GPU_ATTACHEMENT_IGNORE, GPU_ATTACHEMENT_WRITE, GPU_ATTACHEMENT_IGNORE}); - GPU_batch_set_shader(batch, shader_write); GPU_batch_draw(batch); - /* Metal Raster Order Group does not need that. */ - GPU_framebuffer_subpass_transition( - framebuffer, {GPU_ATTACHEMENT_IGNORE, GPU_ATTACHEMENT_READ, GPU_ATTACHEMENT_WRITE}); + /* TODO(fclem): Vulkan might want to introduce an explicit sync event here. */ GPU_batch_set_shader(batch, shader_read); GPU_batch_draw(batch); @@ -394,17 +385,12 @@ static void test_framebuffer_subpass_input() GPU_finish(); - int *read_data_a = static_cast(GPU_texture_read(texture_a, GPU_DATA_INT, 0)); - EXPECT_EQ(*read_data_a, 0xDEADBEEF); - MEM_freeN(read_data_a); - - int *read_data_b = static_cast(GPU_texture_read(texture_b, GPU_DATA_INT, 0)); - EXPECT_EQ(*read_data_b, 0xDEADC0DE); - MEM_freeN(read_data_b); + int *read_data = static_cast(GPU_texture_read(texture, GPU_DATA_INT, 0)); + EXPECT_EQ(*read_data, 0xDEADC0DE); + MEM_freeN(read_data); GPU_framebuffer_free(framebuffer); - GPU_texture_free(texture_a); - GPU_texture_free(texture_b); + GPU_texture_free(texture); GPU_shader_free(shader_write); GPU_shader_free(shader_read); diff --git a/source/blender/gpu/tests/shader_test.cc b/source/blender/gpu/tests/shader_test.cc index bf9d17c6dea..ffbf5778ca0 100644 --- a/source/blender/gpu/tests/shader_test.cc +++ b/source/blender/gpu/tests/shader_test.cc @@ -461,7 +461,6 @@ GPU_TEST(math_lib) static void test_eevee_lib() { gpu_shader_lib_test("eevee_shadow_test.glsl", "eevee_shared"); - gpu_shader_lib_test("eevee_occupancy_test.glsl"); } GPU_TEST(eevee_lib) diff --git a/source/blender/gpu/tests/texture_test.cc b/source/blender/gpu/tests/texture_test.cc index af459b34baa..0a3f11eafa2 100644 --- a/source/blender/gpu/tests/texture_test.cc +++ b/source/blender/gpu/tests/texture_test.cc @@ -63,39 +63,13 @@ static void test_texture_read() } GPU_TEST(texture_read) -static void test_texture_1d() -{ - const int SIZE = 32; - GPU_render_begin(); - - eGPUTextureUsage usage = GPU_TEXTURE_USAGE_ATTACHMENT | GPU_TEXTURE_USAGE_HOST_READ | - GPU_TEXTURE_USAGE_SHADER_WRITE; - GPUTexture *tex = GPU_texture_create_1d("tex", SIZE, 1, GPU_RGBA32F, usage, nullptr); - float4 clear_color(0.9f, 0.7f, 0.2f, 1.0f); - GPU_texture_clear(tex, GPU_DATA_FLOAT, clear_color); - - GPU_memory_barrier(GPU_BARRIER_TEXTURE_UPDATE); - - float4 *data = (float4 *)GPU_texture_read(tex, GPU_DATA_FLOAT, 0); - for (int index : IndexRange(SIZE)) { - EXPECT_EQ(clear_color, data[index]); - } - MEM_freeN(data); - - GPU_texture_free(tex); - - GPU_render_end(); -} -GPU_TEST(texture_1d) - static void test_texture_1d_array() { const int LAYERS = 8; const int SIZE = 32; GPU_render_begin(); - eGPUTextureUsage usage = GPU_TEXTURE_USAGE_ATTACHMENT | GPU_TEXTURE_USAGE_HOST_READ | - GPU_TEXTURE_USAGE_SHADER_WRITE; + eGPUTextureUsage usage = GPU_TEXTURE_USAGE_ATTACHMENT | GPU_TEXTURE_USAGE_HOST_READ; GPUTexture *tex = GPU_texture_create_1d_array( "tex", SIZE, LAYERS, 1, GPU_RGBA32F, usage, nullptr); float4 clear_color(1.0f, 0.5f, 0.2f, 1.0f); @@ -241,30 +215,6 @@ static void test_texture_cube_array() } GPU_TEST(texture_cube_array) -static void test_texture_3d() -{ - const int SIZE = 32; - GPU_render_begin(); - - eGPUTextureUsage usage = GPU_TEXTURE_USAGE_ATTACHMENT | GPU_TEXTURE_USAGE_HOST_READ; - GPUTexture *tex = GPU_texture_create_3d("tex", SIZE, SIZE, SIZE, 1, GPU_RGBA32F, usage, nullptr); - float4 clear_color(1.0f, 0.5f, 0.2f, 1.0f); - GPU_texture_clear(tex, GPU_DATA_FLOAT, clear_color); - - GPU_memory_barrier(GPU_BARRIER_TEXTURE_UPDATE); - - float4 *data = (float4 *)GPU_texture_read(tex, GPU_DATA_FLOAT, 0); - for (int index : IndexRange(SIZE * SIZE * SIZE)) { - EXPECT_EQ(clear_color, data[index]); - } - MEM_freeN(data); - - GPU_texture_free(tex); - - GPU_render_end(); -} -GPU_TEST(texture_3d) - static void test_texture_copy() { const int SIZE = 128; diff --git a/source/blender/gpu/vulkan/vk_backend.cc b/source/blender/gpu/vulkan/vk_backend.cc index a354994c7f6..c1eb184fb54 100644 --- a/source/blender/gpu/vulkan/vk_backend.cc +++ b/source/blender/gpu/vulkan/vk_backend.cc @@ -50,8 +50,7 @@ void VKBackend::platform_init() GPU_BACKEND_VULKAN, "", "", - "", - GPU_ARCHITECTURE_IMR); + ""); } void VKBackend::platform_init(const VKDevice &device) @@ -73,33 +72,13 @@ void VKBackend::platform_init(const VKDevice &device) GPU_BACKEND_VULKAN, vendor_name.c_str(), properties.deviceName, - driver_version.c_str(), - GPU_ARCHITECTURE_IMR); + driver_version.c_str()); } void VKBackend::detect_workarounds(VKDevice &device) { VKWorkarounds workarounds; - if (G.debug & G_DEBUG_GPU_FORCE_WORKAROUNDS) { - printf("\n"); - printf("VK: Forcing workaround usage and disabling features and extensions.\n"); - printf(" Vendor: %s\n", device.vendor_name().c_str()); - printf(" Device: %s\n", device.physical_device_properties_get().deviceName); - printf(" Driver: %s\n", device.driver_version().c_str()); - /* Force workarounds. */ - workarounds.not_aligned_pixel_formats = true; - workarounds.shader_output_layer = true; - workarounds.shader_output_viewport_index = true; - - return; - } - - workarounds.shader_output_layer = - !device.physical_device_vulkan_12_features_get().shaderOutputLayer; - workarounds.shader_output_viewport_index = - !device.physical_device_vulkan_12_features_get().shaderOutputViewportIndex; - /* AMD GPUs don't support texture formats that use are aligned to 24 or 48 bits. */ if (GPU_type_matches(GPU_DEVICE_ATI, GPU_OS_ANY, GPU_DRIVER_ANY)) { workarounds.not_aligned_pixel_formats = true; @@ -129,8 +108,9 @@ void VKBackend::compute_dispatch(int groups_x_len, int groups_y_len, int groups_ VKContext &context = *VKContext::get(); context.state_manager_get().apply_bindings(); context.bind_compute_pipeline(); - VKCommandBuffers &command_buffers = context.command_buffers_get(); - command_buffers.dispatch(groups_x_len, groups_y_len, groups_z_len); + VKCommandBuffer &command_buffer = context.command_buffer_get(); + command_buffer.dispatch(groups_x_len, groups_y_len, groups_z_len); + command_buffer.submit(); } void VKBackend::compute_dispatch_indirect(StorageBuf *indirect_buf) @@ -140,8 +120,9 @@ void VKBackend::compute_dispatch_indirect(StorageBuf *indirect_buf) context.state_manager_get().apply_bindings(); context.bind_compute_pipeline(); VKStorageBuffer &indirect_buffer = *unwrap(indirect_buf); - VKCommandBuffers &command_buffers = context.command_buffers_get(); - command_buffers.dispatch(indirect_buffer); + VKCommandBuffer &command_buffer = context.command_buffer_get(); + command_buffer.dispatch(indirect_buffer); + command_buffer.submit(); } Context *VKBackend::context_alloc(void *ghost_window, void *ghost_context) @@ -268,7 +249,6 @@ void VKBackend::capabilities_init(VKDevice &device) GCaps.max_varying_floats = limits.maxVertexOutputComponents; GCaps.max_shader_storage_buffer_bindings = limits.maxPerStageDescriptorStorageBuffers; GCaps.max_compute_shader_storage_blocks = limits.maxPerStageDescriptorStorageBuffers; - GCaps.max_storage_buffer_size = size_t(limits.maxStorageBufferRange); detect_workarounds(device); } diff --git a/source/blender/gpu/vulkan/vk_batch.cc b/source/blender/gpu/vulkan/vk_batch.cc index 9baf255471d..9f94042c666 100644 --- a/source/blender/gpu/vulkan/vk_batch.cc +++ b/source/blender/gpu/vulkan/vk_batch.cc @@ -47,21 +47,21 @@ void VKBatch::draw(int vertex_first, int vertex_count, int instance_first, int i draw_setup(); VKContext &context = *VKContext::get(); - VKCommandBuffers &command_buffers = context.command_buffers_get(); + VKCommandBuffer &command_buffer = context.command_buffer_get(); VKIndexBuffer *index_buffer = index_buffer_get(); const bool draw_indexed = index_buffer != nullptr; if (draw_indexed) { - command_buffers.draw_indexed(index_buffer->index_len_get(), - instance_count, - index_buffer->index_start_get(), - vertex_first, - instance_first); + command_buffer.draw_indexed(index_buffer->index_len_get(), + instance_count, + index_buffer->index_start_get(), + vertex_first, + instance_first); } else { - command_buffers.draw(vertex_first, vertex_count, instance_first, instance_count); + command_buffer.draw(vertex_first, vertex_count, instance_first, instance_count); } - command_buffers.submit(); + command_buffer.submit(); } void VKBatch::draw_indirect(GPUStorageBuf *indirect_buf, intptr_t offset) @@ -79,15 +79,14 @@ void VKBatch::multi_draw_indirect(GPUStorageBuf *indirect_buf, VKStorageBuffer &indirect_buffer = *unwrap(unwrap(indirect_buf)); VKContext &context = *VKContext::get(); const bool draw_indexed = index_buffer_get() != nullptr; - VKCommandBuffers &command_buffers = context.command_buffers_get(); + VKCommandBuffer &command_buffer = context.command_buffer_get(); if (draw_indexed) { - command_buffers.draw_indexed_indirect(indirect_buffer, offset, count, stride); + command_buffer.draw_indexed_indirect(indirect_buffer, offset, count, stride); } else { - command_buffers.draw_indirect(indirect_buffer, offset, count, stride); + command_buffer.draw_indirect(indirect_buffer, offset, count, stride); } - - command_buffers.submit(); + command_buffer.submit(); } VKVertexBuffer *VKBatch::vertex_buffer_get(int index) diff --git a/source/blender/gpu/vulkan/vk_buffer.cc b/source/blender/gpu/vulkan/vk_buffer.cc index 5096da7c804..c0f6048f40b 100644 --- a/source/blender/gpu/vulkan/vk_buffer.cc +++ b/source/blender/gpu/vulkan/vk_buffer.cc @@ -98,8 +98,8 @@ void VKBuffer::update(const void *data) const void VKBuffer::clear(VKContext &context, uint32_t clear_value) { - VKCommandBuffers &command_buffers = context.command_buffers_get(); - command_buffers.fill(*this, clear_value); + VKCommandBuffer &command_buffer = context.command_buffer_get(); + command_buffer.fill(*this, clear_value); } void VKBuffer::read(void *data) const diff --git a/source/blender/gpu/vulkan/vk_command_buffer.cc b/source/blender/gpu/vulkan/vk_command_buffer.cc index cf0704082b4..07216d5abf0 100644 --- a/source/blender/gpu/vulkan/vk_command_buffer.cc +++ b/source/blender/gpu/vulkan/vk_command_buffer.cc @@ -7,17 +7,27 @@ */ #include "vk_command_buffer.hh" -#include "vk_backend.hh" +#include "vk_buffer.hh" +#include "vk_context.hh" +#include "vk_device.hh" +#include "vk_framebuffer.hh" +#include "vk_index_buffer.hh" +#include "vk_memory.hh" +#include "vk_pipeline.hh" +#include "vk_storage_buffer.hh" +#include "vk_texture.hh" +#include "vk_vertex_buffer.hh" + +#include "BLI_assert.h" namespace blender::gpu { VKCommandBuffer::~VKCommandBuffer() { - if (vk_command_buffer_ != VK_NULL_HANDLE) { - VKDevice &device = VKBackend::get().device_get(); - vkFreeCommandBuffers( - device.device_get(), device.vk_command_pool_get(), 1, &vk_command_buffer_); - vk_command_buffer_ = VK_NULL_HANDLE; + if (vk_device_ != VK_NULL_HANDLE) { + VK_ALLOCATION_CALLBACKS; + vkDestroyFence(vk_device_, vk_fence_, vk_allocation_callbacks); + vk_fence_ = VK_NULL_HANDLE; } } @@ -26,19 +36,49 @@ bool VKCommandBuffer::is_initialized() const return vk_command_buffer_ != VK_NULL_HANDLE; } -void VKCommandBuffer::init(VkCommandBuffer vk_command_buffer) +void VKCommandBuffer::init(const VKDevice &device) { if (is_initialized()) { return; } - vk_command_buffer_ = vk_command_buffer; + vk_device_ = device.device_get(); + vk_queue_ = device.queue_get(); + + /* When a the last GHOST context is destroyed the device is deallocate. A moment later the GPU + * context is destroyed. The first step is to activate it. Activating would retrieve the device + * from GHOST which in that case is a #VK_NULL_HANDLE. */ + if (vk_device_ == VK_NULL_HANDLE) { + return; + } + + VkCommandBufferAllocateInfo alloc_info = {}; + alloc_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + alloc_info.commandPool = device.vk_command_pool_get(); + alloc_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + alloc_info.commandBufferCount = 1; + vkAllocateCommandBuffers(vk_device_, &alloc_info, &vk_command_buffer_); + + submission_id_.reset(); state.stage = Stage::Initial; + + if (vk_fence_ == VK_NULL_HANDLE) { + VK_ALLOCATION_CALLBACKS; + VkFenceCreateInfo fenceInfo{}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + vkCreateFence(vk_device_, &fenceInfo, vk_allocation_callbacks, &vk_fence_); + } + else { + vkResetFences(vk_device_, 1, &vk_fence_); + } } void VKCommandBuffer::begin_recording() { + ensure_no_active_framebuffer(); if (is_in_stage(Stage::Submitted)) { + vkWaitForFences(vk_device_, 1, &vk_fence_, VK_TRUE, FenceTimeout); + vkResetFences(vk_device_, 1, &vk_fence_); stage_transfer(Stage::Submitted, Stage::Executed); } if (is_in_stage(Stage::Executed)) { @@ -50,18 +90,356 @@ void VKCommandBuffer::begin_recording() begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; vkBeginCommandBuffer(vk_command_buffer_, &begin_info); stage_transfer(Stage::Initial, Stage::Recording); - state.recorded_command_counts = 0; } void VKCommandBuffer::end_recording() { + ensure_no_active_framebuffer(); vkEndCommandBuffer(vk_command_buffer_); stage_transfer(Stage::Recording, Stage::BetweenRecordingAndSubmitting); } -void VKCommandBuffer::commands_submitted() +void VKCommandBuffer::bind(const VKPipeline &pipeline, VkPipelineBindPoint bind_point) { + vkCmdBindPipeline(vk_command_buffer_, bind_point, pipeline.vk_handle()); +} + +void VKCommandBuffer::bind(const VKDescriptorSet &descriptor_set, + const VkPipelineLayout vk_pipeline_layout, + VkPipelineBindPoint bind_point) +{ + VkDescriptorSet vk_descriptor_set = descriptor_set.vk_handle(); + vkCmdBindDescriptorSets( + vk_command_buffer_, bind_point, vk_pipeline_layout, 0, 1, &vk_descriptor_set, 0, 0); +} + +void VKCommandBuffer::bind(const uint32_t binding, + const VKVertexBuffer &vertex_buffer, + const VkDeviceSize offset) +{ + bind(binding, vertex_buffer.vk_handle(), offset); +} + +void VKCommandBuffer::bind(const uint32_t binding, const VKBufferWithOffset &vertex_buffer) +{ + bind(binding, vertex_buffer.buffer.vk_handle(), vertex_buffer.offset); +} + +void VKCommandBuffer::bind(const uint32_t binding, + const VkBuffer &vk_vertex_buffer, + const VkDeviceSize offset) +{ + validate_framebuffer_exists(); + ensure_active_framebuffer(); + vkCmdBindVertexBuffers(vk_command_buffer_, binding, 1, &vk_vertex_buffer, &offset); +} + +void VKCommandBuffer::bind(const VKBufferWithOffset &index_buffer, VkIndexType index_type) +{ + validate_framebuffer_exists(); + ensure_active_framebuffer(); + vkCmdBindIndexBuffer( + vk_command_buffer_, index_buffer.buffer.vk_handle(), index_buffer.offset, index_type); +} + +void VKCommandBuffer::begin_render_pass(VKFrameBuffer &framebuffer) +{ + validate_framebuffer_not_exists(); + state.framebuffer_ = &framebuffer; +} + +void VKCommandBuffer::end_render_pass(const VKFrameBuffer &framebuffer) +{ + UNUSED_VARS_NDEBUG(framebuffer); + BLI_assert(state.framebuffer_ == nullptr || state.framebuffer_ == &framebuffer); + ensure_no_active_framebuffer(); + state.framebuffer_ = nullptr; +} + +void VKCommandBuffer::push_constants(const VKPushConstants &push_constants, + const VkPipelineLayout vk_pipeline_layout, + const VkShaderStageFlags vk_shader_stages) +{ + BLI_assert(push_constants.layout_get().storage_type_get() == + VKPushConstants::StorageType::PUSH_CONSTANTS); + vkCmdPushConstants(vk_command_buffer_, + vk_pipeline_layout, + vk_shader_stages, + push_constants.offset(), + push_constants.layout_get().size_in_bytes(), + push_constants.data()); +} + +void VKCommandBuffer::fill(VKBuffer &buffer, uint32_t clear_data) +{ + ensure_no_active_framebuffer(); + vkCmdFillBuffer(vk_command_buffer_, buffer.vk_handle(), 0, buffer.size_in_bytes(), clear_data); +} + +void VKCommandBuffer::copy(VKBuffer &dst_buffer, + VKTexture &src_texture, + Span regions) +{ + ensure_no_active_framebuffer(); + vkCmdCopyImageToBuffer(vk_command_buffer_, + src_texture.vk_image_handle(), + src_texture.current_layout_get(), + dst_buffer.vk_handle(), + regions.size(), + regions.data()); +} + +void VKCommandBuffer::copy(VKTexture &dst_texture, + VKBuffer &src_buffer, + Span regions) +{ + ensure_no_active_framebuffer(); + vkCmdCopyBufferToImage(vk_command_buffer_, + src_buffer.vk_handle(), + dst_texture.vk_image_handle(), + dst_texture.current_layout_get(), + regions.size(), + regions.data()); +} + +void VKCommandBuffer::copy(VKTexture &dst_texture, + VKTexture &src_texture, + Span regions) +{ + ensure_no_active_framebuffer(); + vkCmdCopyImage(vk_command_buffer_, + src_texture.vk_image_handle(), + src_texture.current_layout_get(), + dst_texture.vk_image_handle(), + dst_texture.current_layout_get(), + regions.size(), + regions.data()); +} + +void VKCommandBuffer::copy(VKBuffer &dst_buffer, VkBuffer src_buffer, Span regions) +{ + ensure_no_active_framebuffer(); + vkCmdCopyBuffer( + vk_command_buffer_, src_buffer, dst_buffer.vk_handle(), regions.size(), regions.data()); +} + +void VKCommandBuffer::blit(VKTexture &dst_texture, + VKTexture &src_texture, + Span regions) +{ + blit(dst_texture, + dst_texture.current_layout_get(), + src_texture, + src_texture.current_layout_get(), + regions); +} + +void VKCommandBuffer::blit(VKTexture &dst_texture, + VkImageLayout dst_layout, + VKTexture &src_texture, + VkImageLayout src_layout, + Span regions) +{ + ensure_no_active_framebuffer(); + vkCmdBlitImage(vk_command_buffer_, + src_texture.vk_image_handle(), + src_layout, + dst_texture.vk_image_handle(), + dst_layout, + regions.size(), + regions.data(), + VK_FILTER_NEAREST); +} + +void VKCommandBuffer::clear(VkImage vk_image, + VkImageLayout vk_image_layout, + const VkClearColorValue &vk_clear_color, + Span ranges) +{ + ensure_no_active_framebuffer(); + vkCmdClearColorImage(vk_command_buffer_, + vk_image, + vk_image_layout, + &vk_clear_color, + ranges.size(), + ranges.data()); +} + +void VKCommandBuffer::clear(VkImage vk_image, + VkImageLayout vk_image_layout, + const VkClearDepthStencilValue &vk_clear_value, + Span ranges) +{ + ensure_no_active_framebuffer(); + vkCmdClearDepthStencilImage(vk_command_buffer_, + vk_image, + vk_image_layout, + &vk_clear_value, + ranges.size(), + ranges.data()); +} + +void VKCommandBuffer::clear(Span attachments, Span areas) +{ + validate_framebuffer_exists(); + ensure_active_framebuffer(); + vkCmdClearAttachments( + vk_command_buffer_, attachments.size(), attachments.data(), areas.size(), areas.data()); +} + +void VKCommandBuffer::draw(int v_first, int v_count, int i_first, int i_count) +{ + validate_framebuffer_exists(); + ensure_active_framebuffer(); + vkCmdDraw(vk_command_buffer_, v_count, i_count, v_first, i_first); + state.draw_counts++; +} + +void VKCommandBuffer::draw_indexed( + int index_count, int instance_count, int first_index, int vertex_offset, int first_instance) +{ + validate_framebuffer_exists(); + ensure_active_framebuffer(); + vkCmdDrawIndexed( + vk_command_buffer_, index_count, instance_count, first_index, vertex_offset, first_instance); + state.draw_counts++; +} + +void VKCommandBuffer::draw_indirect(const VKStorageBuffer &buffer, + VkDeviceSize offset, + uint32_t draw_count, + uint32_t stride) +{ + validate_framebuffer_exists(); + ensure_active_framebuffer(); + vkCmdDrawIndirect(vk_command_buffer_, buffer.vk_handle(), offset, draw_count, stride); + state.draw_counts++; +} + +void VKCommandBuffer::draw_indexed_indirect(const VKStorageBuffer &buffer, + + VkDeviceSize offset, + uint32_t draw_count, + uint32_t stride) +{ + validate_framebuffer_exists(); + ensure_active_framebuffer(); + vkCmdDrawIndexedIndirect(vk_command_buffer_, buffer.vk_handle(), offset, draw_count, stride); + state.draw_counts++; +} + +void VKCommandBuffer::pipeline_barrier(VkPipelineStageFlags source_stages, + VkPipelineStageFlags destination_stages) +{ + if (state.framebuffer_) { + ensure_active_framebuffer(); + } + vkCmdPipelineBarrier(vk_command_buffer_, + source_stages, + destination_stages, + 0, + 0, + nullptr, + 0, + nullptr, + 0, + nullptr); +} + +void VKCommandBuffer::pipeline_barrier(Span image_memory_barriers) +{ + ensure_no_active_framebuffer(); + vkCmdPipelineBarrier(vk_command_buffer_, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_DEPENDENCY_BY_REGION_BIT, + 0, + nullptr, + 0, + nullptr, + image_memory_barriers.size(), + image_memory_barriers.data()); +} + +void VKCommandBuffer::dispatch(int groups_x_len, int groups_y_len, int groups_z_len) +{ + ensure_no_active_framebuffer(); + vkCmdDispatch(vk_command_buffer_, groups_x_len, groups_y_len, groups_z_len); +} + +void VKCommandBuffer::dispatch(VKStorageBuffer &command_buffer) +{ + ensure_no_active_framebuffer(); + vkCmdDispatchIndirect(vk_command_buffer_, command_buffer.vk_handle(), 0); +} + +void VKCommandBuffer::submit() +{ + ensure_no_active_framebuffer(); + end_recording(); + submit_commands(); + begin_recording(); +} + +void VKCommandBuffer::submit_commands() +{ + VkSubmitInfo submit_info = {}; + submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submit_info.commandBufferCount = 1; + submit_info.pCommandBuffers = &vk_command_buffer_; + + vkQueueSubmit(vk_queue_, 1, &submit_info, vk_fence_); + submission_id_.next(); stage_transfer(Stage::BetweenRecordingAndSubmitting, Stage::Submitted); } +/* -------------------------------------------------------------------- */ +/** \name FrameBuffer/RenderPass state tracking + * \{ */ + +void VKCommandBuffer::validate_framebuffer_not_exists() +{ + BLI_assert_msg(state.framebuffer_ == nullptr && state.framebuffer_active_ == false, + "State error: expected no framebuffer being tracked."); +} + +void VKCommandBuffer::validate_framebuffer_exists() +{ + BLI_assert_msg(state.framebuffer_, "State error: expected framebuffer being tracked."); +} + +void VKCommandBuffer::ensure_no_active_framebuffer() +{ + state.checks_++; + if (state.framebuffer_ && state.framebuffer_active_) { + vkCmdEndRenderPass(vk_command_buffer_); + state.framebuffer_active_ = false; + state.switches_++; + } +} + +void VKCommandBuffer::ensure_active_framebuffer() +{ + BLI_assert(state.framebuffer_); + state.checks_++; + if (!state.framebuffer_active_) { + VkRenderPassBeginInfo render_pass_begin_info = {}; + render_pass_begin_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + state.framebuffer_->vk_render_pass_ensure(); + render_pass_begin_info.renderPass = state.framebuffer_->vk_render_pass_get(); + render_pass_begin_info.framebuffer = state.framebuffer_->vk_framebuffer_get(); + render_pass_begin_info.renderArea = state.framebuffer_->vk_render_areas_get()[0]; + /* We don't use clear ops, but vulkan wants to have at least one. */ + VkClearValue clear_value = {}; + render_pass_begin_info.clearValueCount = 1; + render_pass_begin_info.pClearValues = &clear_value; + + vkCmdBeginRenderPass(vk_command_buffer_, &render_pass_begin_info, VK_SUBPASS_CONTENTS_INLINE); + state.framebuffer_active_ = true; + state.switches_++; + } +} + +/** \} */ + } // namespace blender::gpu diff --git a/source/blender/gpu/vulkan/vk_command_buffer.hh b/source/blender/gpu/vulkan/vk_command_buffer.hh index f63e2633be7..30b63b7696b 100644 --- a/source/blender/gpu/vulkan/vk_command_buffer.hh +++ b/source/blender/gpu/vulkan/vk_command_buffer.hh @@ -14,11 +14,35 @@ #include "BLI_utility_mixins.hh" namespace blender::gpu { +class VKBuffer; +struct VKBufferWithOffset; +class VKDescriptorSet; +class VKFrameBuffer; +class VKIndexBuffer; +class VKPipeline; +class VKPushConstants; +class VKStorageBuffer; +class VKTexture; +class VKVertexBuffer; class VKDevice; /** Command buffer to keep track of the life-time of a command buffer. */ class VKCommandBuffer : NonCopyable, NonMovable { + /** Not owning handle to the command buffer and device. Handle is owned by `GHOST_ContextVK`. */ + VkDevice vk_device_ = VK_NULL_HANDLE; VkCommandBuffer vk_command_buffer_ = VK_NULL_HANDLE; + VkQueue vk_queue_ = VK_NULL_HANDLE; + + /** + * Timeout to use when waiting for fences in nanoseconds. + * + * Currently added as the fence will halt when there are no commands in the command buffer for + * the second time. This should be solved and this timeout should be removed. + */ + static constexpr uint64_t FenceTimeout = UINT64_MAX; + /** Owning handles */ + VkFence vk_fence_ = VK_NULL_HANDLE; + VKSubmissionID submission_id_; private: enum class Stage { @@ -53,17 +77,24 @@ class VKCommandBuffer : NonCopyable, NonMovable { * it. */ struct { + /* Reference to the last_framebuffer where begin_render_pass was called for. */ + VKFrameBuffer *framebuffer_ = nullptr; + /* Is last_framebuffer_ currently bound. Each call should ensure the correct state. */ + bool framebuffer_active_ = false; + /* Amount of times a check has been requested. */ + uint64_t checks_ = 0; + /* Amount of times a check required to change the render pass. */ + uint64_t switches_ = 0; + + /* Number of times a vkDraw command has been recorded. */ + uint64_t draw_counts = 0; + /** * Current stage of the command buffer to keep track of inconsistencies & incorrect usage. */ Stage stage = Stage::Initial; - /** - * The number of command added to the command buffer since last submission. - */ - uint64_t recorded_command_counts = 0; } state; - bool is_in_stage(Stage stage) { return state.stage == stage; @@ -103,29 +134,123 @@ class VKCommandBuffer : NonCopyable, NonMovable { public: virtual ~VKCommandBuffer(); bool is_initialized() const; - void init(VkCommandBuffer vk_command_buffer); + void init(const VKDevice &vk_device); void begin_recording(); void end_recording(); + void bind(const VKPipeline &vk_pipeline, VkPipelineBindPoint bind_point); + void bind(const VKDescriptorSet &descriptor_set, + const VkPipelineLayout vk_pipeline_layout, + VkPipelineBindPoint bind_point); + void bind(const uint32_t binding, + const VKVertexBuffer &vertex_buffer, + const VkDeviceSize offset); + /* Bind the given buffer as a vertex buffer. */ + void bind(const uint32_t binding, const VKBufferWithOffset &vertex_buffer); + void bind(const uint32_t binding, const VkBuffer &vk_vertex_buffer, const VkDeviceSize offset); + /* Bind the given buffer as an index buffer. */ + void bind(const VKBufferWithOffset &index_buffer, VkIndexType index_type); + + void begin_render_pass(VKFrameBuffer &framebuffer); + void end_render_pass(const VKFrameBuffer &framebuffer); + /** - * Receive the vulkan handle of the command buffer. + * Add a push constant command to the command buffer. + * + * Only valid when the storage type of push_constants is StorageType::PUSH_CONSTANTS. */ - VkCommandBuffer vk_command_buffer() const + void push_constants(const VKPushConstants &push_constants, + const VkPipelineLayout vk_pipeline_layout, + const VkShaderStageFlags vk_shader_stages); + void dispatch(int groups_x_len, int groups_y_len, int groups_z_len); + void dispatch(VKStorageBuffer &command_buffer); + /** Copy the contents of a texture MIP level to the dst buffer. */ + void copy(VKBuffer &dst_buffer, VKTexture &src_texture, Span regions); + void copy(VKTexture &dst_texture, VKBuffer &src_buffer, Span regions); + void copy(VKTexture &dst_texture, VKTexture &src_texture, Span regions); + void copy(VKBuffer &dst_buffer, VkBuffer src_buffer, Span regions); + void blit(VKTexture &dst_texture, VKTexture &src_texture, Span regions); + void blit(VKTexture &dst_texture, + VkImageLayout dst_layout, + VKTexture &src_texture, + VkImageLayout src_layout, + Span regions); + void pipeline_barrier(VkPipelineStageFlags source_stages, + VkPipelineStageFlags destination_stages); + void pipeline_barrier(Span image_memory_barriers); + + /** + * Clear color image resource. + */ + void clear(VkImage vk_image, + VkImageLayout vk_image_layout, + const VkClearColorValue &vk_clear_color, + Span ranges); + + /** + * Clear depth/stencil aspect of an image resource. + */ + void clear(VkImage vk_image, + VkImageLayout vk_image_layout, + const VkClearDepthStencilValue &vk_clear_color, + Span ranges); + + /** + * Clear attachments of the active framebuffer. + */ + void clear(Span attachments, Span areas); + void fill(VKBuffer &buffer, uint32_t data); + + void draw(int v_first, int v_count, int i_first, int i_count); + void draw_indexed( + int index_count, int instance_count, int first_index, int vertex_offset, int first_instance); + + void draw_indirect(const VKStorageBuffer &buffer, + VkDeviceSize offset, + uint32_t draw_count, + uint32_t stride); + void draw_indexed_indirect(const VKStorageBuffer &buffer, + VkDeviceSize offset, + uint32_t draw_count, + uint32_t stride); + + /** + * Stop recording commands, encode + send the recordings to Vulkan, wait for the until the + * commands have been executed and start the command buffer to accept recordings again. + */ + void submit(); + + const VKSubmissionID &submission_id_get() const { - return vk_command_buffer_; + return submission_id_; } - bool has_recorded_commands() const - { - return state.recorded_command_counts != 0; - } + private: + void submit_commands(); - void command_recorded() - { - state.recorded_command_counts++; - } + /** + * Validate that there isn't a framebuffer being tracked (bound or not bound). + * + * Raises an assert in debug when a framebuffer is being tracked. + */ + void validate_framebuffer_not_exists(); - void commands_submitted(); + /** + * Validate that there is a framebuffer being tracked (bound or not bound). + * + * Raises an assert in debug when no framebuffer is being tracked. + */ + void validate_framebuffer_exists(); + + /** + * Ensure that there is no framebuffer being tracked or the tracked framebuffer isn't bound. + */ + void ensure_no_active_framebuffer(); + + /** + * Ensure that the tracked framebuffer is bound. + */ + void ensure_active_framebuffer(); }; } // namespace blender::gpu diff --git a/source/blender/gpu/vulkan/vk_command_buffers.cc b/source/blender/gpu/vulkan/vk_command_buffers.cc deleted file mode 100644 index 0bdac1ccd0b..00000000000 --- a/source/blender/gpu/vulkan/vk_command_buffers.cc +++ /dev/null @@ -1,614 +0,0 @@ -/* SPDX-FileCopyrightText: 2023 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -/** \file - * \ingroup gpu - */ - -#include "vk_command_buffers.hh" -#include "vk_backend.hh" -#include "vk_device.hh" -#include "vk_framebuffer.hh" -#include "vk_memory.hh" -#include "vk_pipeline.hh" -#include "vk_storage_buffer.hh" -#include "vk_texture.hh" -#include "vk_vertex_buffer.hh" - -#include "BLI_assert.h" - -namespace blender::gpu { - -VKCommandBuffers::~VKCommandBuffers() -{ - if (vk_fence_ != VK_NULL_HANDLE) { - VK_ALLOCATION_CALLBACKS; - const VKDevice &device = VKBackend::get().device_get(); - vkDestroyFence(device.device_get(), vk_fence_, vk_allocation_callbacks); - vk_fence_ = VK_NULL_HANDLE; - } -} - -void VKCommandBuffers::init(const VKDevice &device) -{ - if (initialized_) { - return; - } - initialized_ = true; - - /* When a the last GHOST context is destroyed the device is deallocate. A moment later the GPU - * context is destroyed. The first step is to activate it. Activating would retrieve the device - * from GHOST which in that case is a #VK_NULL_HANDLE. */ - if (!device.is_initialized()) { - return; - } - - init_command_buffers(device); - init_fence(device); - submission_id_.reset(); -} - -static void init_command_buffer(VKCommandBuffer &command_buffer, - VkCommandBuffer vk_command_buffer, - const char *name) -{ - command_buffer.init(vk_command_buffer); - command_buffer.begin_recording(); - debug::object_label(vk_command_buffer, name); -} - -void VKCommandBuffers::init_command_buffers(const VKDevice &device) -{ - VkCommandBuffer vk_command_buffers[4] = {VK_NULL_HANDLE}; - VkCommandBufferAllocateInfo alloc_info = {}; - alloc_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; - alloc_info.commandPool = device.vk_command_pool_get(); - alloc_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; - alloc_info.commandBufferCount = (uint32_t)Type::Max; - vkAllocateCommandBuffers(device.device_get(), &alloc_info, vk_command_buffers); - - init_command_buffer(command_buffer_get(Type::DataTransfer), - vk_command_buffers[(int)Type::DataTransfer], - "Data Transfer Command Buffer"); - init_command_buffer(command_buffer_get(Type::Compute), - vk_command_buffers[(int)Type::Compute], - "Compute Command Buffer"); - init_command_buffer(command_buffer_get(Type::Graphics), - vk_command_buffers[(int)Type::Graphics], - "Graphics Command Buffer"); -} - -void VKCommandBuffers::init_fence(const VKDevice &device) -{ - if (vk_fence_ == VK_NULL_HANDLE) { - VK_ALLOCATION_CALLBACKS; - VkFenceCreateInfo fenceInfo{}; - fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; - vkCreateFence(device.device_get(), &fenceInfo, vk_allocation_callbacks, &vk_fence_); - } -} - -static void submit_command_buffers(const VKDevice &device, - MutableSpan command_buffers, - VkFence vk_fence, - uint64_t timeout) -{ - BLI_assert(ELEM(command_buffers.size(), 1, 2)); - VkCommandBuffer handles[2]; - int num_command_buffers = 0; - for (VKCommandBuffer *command_buffer : command_buffers) { - command_buffer->end_recording(); - handles[num_command_buffers++] = command_buffer->vk_command_buffer(); - } - - VkSubmitInfo submit_info = {}; - submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; - submit_info.commandBufferCount = num_command_buffers; - submit_info.pCommandBuffers = handles; - - vkQueueSubmit(device.queue_get(), 1, &submit_info, vk_fence); - - vkWaitForFences(device.device_get(), 1, &vk_fence, VK_TRUE, timeout); - vkResetFences(device.device_get(), 1, &vk_fence); - - for (VKCommandBuffer *command_buffer : command_buffers) { - command_buffer->commands_submitted(); - command_buffer->begin_recording(); - } -} - -void VKCommandBuffers::submit() -{ - const VKDevice &device = VKBackend::get().device_get(); - VKCommandBuffer &data_transfer = command_buffer_get(Type::DataTransfer); - VKCommandBuffer &compute = command_buffer_get(Type::Compute); - VKCommandBuffer &graphics = command_buffer_get(Type::Graphics); - - const bool has_transfer_work = data_transfer.has_recorded_commands(); - const bool has_compute_work = compute.has_recorded_commands(); - const bool has_graphics_work = graphics.has_recorded_commands(); - BLI_assert_msg((has_compute_work && !has_graphics_work) || - (!has_compute_work && has_graphics_work) || - !(has_compute_work || has_graphics_work), - "Cannot determine dependency when compute and graphics work needs to be done"); - - VKCommandBuffer *command_buffers[2] = {nullptr, nullptr}; - int command_buffer_index = 0; - - if (has_transfer_work) { - command_buffers[command_buffer_index++] = &data_transfer; - } - - if (has_compute_work) { - command_buffers[command_buffer_index++] = &compute; - submit_command_buffers(device, - MutableSpan(command_buffers, command_buffer_index), - vk_fence_, - FenceTimeout); - } - else if (has_graphics_work) { - VKFrameBuffer *framebuffer = framebuffer_; - end_render_pass(*framebuffer); - command_buffers[command_buffer_index++] = &graphics; - submit_command_buffers(device, - MutableSpan(command_buffers, command_buffer_index), - vk_fence_, - FenceTimeout); - begin_render_pass(*framebuffer); - } - /* Check needs to go as last, transfer work is already included with the compute/graphics - * submission. */ - else if (has_transfer_work) { - submit_command_buffers(device, - MutableSpan(command_buffers, command_buffer_index), - vk_fence_, - FenceTimeout); - } - - const bool reset_submission_id = has_compute_work || has_graphics_work; - if (reset_submission_id) { - submission_id_.next(); - } -} - -void VKCommandBuffers::ensure_no_compute_commands() -{ - if (command_buffer_get(Type::Compute).has_recorded_commands()) { - submit(); - } -} - -void VKCommandBuffers::ensure_no_draw_commands() -{ - if (command_buffer_get(Type::Graphics).has_recorded_commands()) { - submit(); - } -} - -void VKCommandBuffers::validate_framebuffer_not_exists() -{ - BLI_assert_msg(framebuffer_ == nullptr && framebuffer_bound_ == false, - "State error: expected no framebuffer being tracked."); -} - -void VKCommandBuffers::validate_framebuffer_exists() -{ - BLI_assert_msg(framebuffer_, "State error: expected framebuffer being tracked."); -} - -void VKCommandBuffers::ensure_active_framebuffer() -{ - BLI_assert(framebuffer_); - if (framebuffer_ && !framebuffer_bound_) { - VkRenderPassBeginInfo render_pass_begin_info = {}; - render_pass_begin_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; - framebuffer_->vk_render_pass_ensure(); - render_pass_begin_info.renderPass = framebuffer_->vk_render_pass_get(); - render_pass_begin_info.framebuffer = framebuffer_->vk_framebuffer_get(); - render_pass_begin_info.renderArea = framebuffer_->vk_render_areas_get()[0]; - /* We don't use clear ops, but vulkan wants to have at least one. */ - VkClearValue clear_value = {}; - render_pass_begin_info.clearValueCount = 1; - render_pass_begin_info.pClearValues = &clear_value; - - VKCommandBuffer &command_buffer = command_buffer_get(Type::Graphics); - vkCmdBeginRenderPass( - command_buffer.vk_command_buffer(), &render_pass_begin_info, VK_SUBPASS_CONTENTS_INLINE); - framebuffer_bound_ = true; - } -} - -/** - * \name Vulkan commands - * \{ - */ - -void VKCommandBuffers::bind(const VKPipeline &vk_pipeline, VkPipelineBindPoint bind_point) -{ - Type type; - if (bind_point == VK_PIPELINE_BIND_POINT_COMPUTE) { - ensure_no_draw_commands(); - type = Type::Compute; - } - else if (bind_point == VK_PIPELINE_BIND_POINT_GRAPHICS) { - ensure_no_compute_commands(); - type = Type::Graphics; - } - - VKCommandBuffer &command_buffer = command_buffer_get(type); - vkCmdBindPipeline(command_buffer.vk_command_buffer(), bind_point, vk_pipeline.vk_handle()); - command_buffer.command_recorded(); -} - -void VKCommandBuffers::bind(const VKDescriptorSet &descriptor_set, - const VkPipelineLayout vk_pipeline_layout, - VkPipelineBindPoint bind_point) -{ - Type type; - if (bind_point == VK_PIPELINE_BIND_POINT_COMPUTE) { - ensure_no_draw_commands(); - type = Type::Compute; - } - if (bind_point == VK_PIPELINE_BIND_POINT_GRAPHICS) { - ensure_no_compute_commands(); - type = Type::Graphics; - } - - VKCommandBuffer &command_buffer = command_buffer_get(type); - VkDescriptorSet vk_descriptor_set = descriptor_set.vk_handle(); - vkCmdBindDescriptorSets(command_buffer.vk_command_buffer(), - bind_point, - vk_pipeline_layout, - 0, - 1, - &vk_descriptor_set, - 0, - 0); - command_buffer.command_recorded(); -} - -void VKCommandBuffers::bind(const uint32_t binding, - const VKVertexBuffer &vertex_buffer, - const VkDeviceSize offset) -{ - bind(binding, vertex_buffer.vk_handle(), offset); -} - -void VKCommandBuffers::bind(const uint32_t binding, const VKBufferWithOffset &vertex_buffer) -{ - bind(binding, vertex_buffer.buffer.vk_handle(), vertex_buffer.offset); -} - -void VKCommandBuffers::bind(const uint32_t binding, - const VkBuffer &vk_vertex_buffer, - const VkDeviceSize offset) -{ - ensure_no_compute_commands(); - VKCommandBuffer &command_buffer = command_buffer_get(Type::Graphics); - - validate_framebuffer_exists(); - ensure_active_framebuffer(); - vkCmdBindVertexBuffers( - command_buffer.vk_command_buffer(), binding, 1, &vk_vertex_buffer, &offset); - command_buffer.command_recorded(); -} - -void VKCommandBuffers::bind(const VKBufferWithOffset &index_buffer, VkIndexType index_type) -{ - ensure_no_compute_commands(); - VKCommandBuffer &command_buffer = command_buffer_get(Type::Graphics); - - validate_framebuffer_exists(); - ensure_active_framebuffer(); - vkCmdBindIndexBuffer(command_buffer.vk_command_buffer(), - index_buffer.buffer.vk_handle(), - index_buffer.offset, - index_type); - command_buffer.command_recorded(); -} - -void VKCommandBuffers::begin_render_pass(VKFrameBuffer &framebuffer) -{ - ensure_no_compute_commands(); - BLI_assert(framebuffer_ == nullptr); - framebuffer_ = &framebuffer; - framebuffer_bound_ = false; -} - -void VKCommandBuffers::ensure_no_active_framebuffer() -{ - if (framebuffer_ && framebuffer_bound_) { - VKCommandBuffer &command_buffer = command_buffer_get(Type::Graphics); - vkCmdEndRenderPass(command_buffer.vk_command_buffer()); - command_buffer.command_recorded(); - framebuffer_bound_ = false; - } -} - -void VKCommandBuffers::end_render_pass(const VKFrameBuffer &framebuffer) -{ - ensure_no_compute_commands(); - UNUSED_VARS_NDEBUG(framebuffer); - BLI_assert(framebuffer_ == nullptr || framebuffer_ == &framebuffer); - ensure_no_active_framebuffer(); - framebuffer_ = nullptr; -} - -void VKCommandBuffers::push_constants(const VKPushConstants &push_constants, - const VkPipelineLayout vk_pipeline_layout, - const VkShaderStageFlags vk_shader_stages) -{ - Type type; - if (vk_shader_stages == VK_SHADER_STAGE_COMPUTE_BIT) { - ensure_no_draw_commands(); - type = Type::Compute; - } - else { - ensure_no_compute_commands(); - type = Type::Graphics; - } - - VKCommandBuffer &command_buffer = command_buffer_get(type); - BLI_assert(push_constants.layout_get().storage_type_get() == - VKPushConstants::StorageType::PUSH_CONSTANTS); - vkCmdPushConstants(command_buffer.vk_command_buffer(), - vk_pipeline_layout, - vk_shader_stages, - push_constants.offset(), - push_constants.layout_get().size_in_bytes(), - push_constants.data()); - command_buffer.command_recorded(); -} - -void VKCommandBuffers::dispatch(int groups_x_len, int groups_y_len, int groups_z_len) -{ - ensure_no_draw_commands(); - - VKCommandBuffer &command_buffer = command_buffer_get(Type::Compute); - vkCmdDispatch(command_buffer.vk_command_buffer(), groups_x_len, groups_y_len, groups_z_len); - command_buffer.command_recorded(); -} - -void VKCommandBuffers::dispatch(VKStorageBuffer &command_storage_buffer) -{ - ensure_no_draw_commands(); - - VKCommandBuffer &command_buffer = command_buffer_get(Type::Compute); - vkCmdDispatchIndirect(command_buffer.vk_command_buffer(), command_storage_buffer.vk_handle(), 0); - command_buffer.command_recorded(); -} - -void VKCommandBuffers::copy(VKBuffer &dst_buffer, - VKTexture &src_texture, - Span regions) -{ - VKCommandBuffer &command_buffer = command_buffer_get(Type::DataTransfer); - vkCmdCopyImageToBuffer(command_buffer.vk_command_buffer(), - src_texture.vk_image_handle(), - src_texture.current_layout_get(), - dst_buffer.vk_handle(), - regions.size(), - regions.data()); - command_buffer.command_recorded(); -} - -void VKCommandBuffers::copy(VKTexture &dst_texture, - VKBuffer &src_buffer, - Span regions) -{ - VKCommandBuffer &command_buffer = command_buffer_get(Type::DataTransfer); - vkCmdCopyBufferToImage(command_buffer.vk_command_buffer(), - src_buffer.vk_handle(), - dst_texture.vk_image_handle(), - dst_texture.current_layout_get(), - regions.size(), - regions.data()); - command_buffer.command_recorded(); -} - -void VKCommandBuffers::copy(VKTexture &dst_texture, - VKTexture &src_texture, - Span regions) -{ - VKCommandBuffer &command_buffer = command_buffer_get(Type::DataTransfer); - vkCmdCopyImage(command_buffer.vk_command_buffer(), - src_texture.vk_image_handle(), - src_texture.current_layout_get(), - dst_texture.vk_image_handle(), - dst_texture.current_layout_get(), - regions.size(), - regions.data()); - command_buffer.command_recorded(); -} - -void VKCommandBuffers::copy(VKBuffer &dst_buffer, VkBuffer src_buffer, Span regions) -{ - VKCommandBuffer &command_buffer = command_buffer_get(Type::DataTransfer); - vkCmdCopyBuffer(command_buffer.vk_command_buffer(), - src_buffer, - dst_buffer.vk_handle(), - regions.size(), - regions.data()); - command_buffer.command_recorded(); -} - -void VKCommandBuffers::blit(VKTexture &dst_texture, - VKTexture &src_texture, - Span regions) -{ - blit(dst_texture, - dst_texture.current_layout_get(), - src_texture, - src_texture.current_layout_get(), - regions); -} - -void VKCommandBuffers::blit(VKTexture &dst_texture, - VkImageLayout dst_layout, - VKTexture &src_texture, - VkImageLayout src_layout, - Span regions) -{ - VKCommandBuffer &command_buffer = command_buffer_get(Type::DataTransfer); - vkCmdBlitImage(command_buffer.vk_command_buffer(), - src_texture.vk_image_handle(), - src_layout, - dst_texture.vk_image_handle(), - dst_layout, - regions.size(), - regions.data(), - VK_FILTER_NEAREST); - command_buffer.command_recorded(); -} - -void VKCommandBuffers::pipeline_barrier(VkPipelineStageFlags source_stages, - VkPipelineStageFlags destination_stages) -{ - /* TODO: Command isn't used.*/ - VKCommandBuffer &command_buffer = command_buffer_get(Type::DataTransfer); - vkCmdPipelineBarrier(command_buffer.vk_command_buffer(), - source_stages, - destination_stages, - 0, - 0, - nullptr, - 0, - nullptr, - 0, - nullptr); - command_buffer.command_recorded(); -} - -void VKCommandBuffers::pipeline_barrier(Span image_memory_barriers) -{ - VKCommandBuffer &command_buffer = command_buffer_get(Type::DataTransfer); - vkCmdPipelineBarrier(command_buffer.vk_command_buffer(), - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, - VK_DEPENDENCY_BY_REGION_BIT, - 0, - nullptr, - 0, - nullptr, - image_memory_barriers.size(), - image_memory_barriers.data()); - command_buffer.command_recorded(); -} - -void VKCommandBuffers::clear(VkImage vk_image, - VkImageLayout vk_image_layout, - const VkClearColorValue &vk_clear_color, - Span ranges) -{ - VKCommandBuffer &command_buffer = command_buffer_get(Type::DataTransfer); - vkCmdClearColorImage(command_buffer.vk_command_buffer(), - vk_image, - vk_image_layout, - &vk_clear_color, - ranges.size(), - ranges.data()); - command_buffer.command_recorded(); -} - -void VKCommandBuffers::clear(VkImage vk_image, - VkImageLayout vk_image_layout, - const VkClearDepthStencilValue &vk_clear_depth_stencil, - Span ranges) -{ - VKCommandBuffer &command_buffer = command_buffer_get(Type::DataTransfer); - vkCmdClearDepthStencilImage(command_buffer.vk_command_buffer(), - vk_image, - vk_image_layout, - &vk_clear_depth_stencil, - ranges.size(), - ranges.data()); - command_buffer.command_recorded(); -} - -void VKCommandBuffers::clear(Span attachments, Span areas) -{ - ensure_no_compute_commands(); - VKCommandBuffer &command_buffer = command_buffer_get(Type::Graphics); - validate_framebuffer_exists(); - ensure_active_framebuffer(); - vkCmdClearAttachments(command_buffer.vk_command_buffer(), - attachments.size(), - attachments.data(), - areas.size(), - areas.data()); - command_buffer.command_recorded(); -} - -void VKCommandBuffers::fill(VKBuffer &buffer, uint32_t clear_data) -{ - VKCommandBuffer &command_buffer = command_buffer_get(Type::DataTransfer); - vkCmdFillBuffer(command_buffer.vk_command_buffer(), - buffer.vk_handle(), - 0, - buffer.size_in_bytes(), - clear_data); - command_buffer.command_recorded(); -} - -void VKCommandBuffers::draw(int v_first, int v_count, int i_first, int i_count) -{ - ensure_no_compute_commands(); - validate_framebuffer_exists(); - ensure_active_framebuffer(); - - VKCommandBuffer &command_buffer = command_buffer_get(Type::Graphics); - vkCmdDraw(command_buffer.vk_command_buffer(), v_count, i_count, v_first, i_first); - command_buffer.command_recorded(); -} - -void VKCommandBuffers::draw_indexed( - int index_count, int instance_count, int first_index, int vertex_offset, int first_instance) -{ - ensure_no_compute_commands(); - validate_framebuffer_exists(); - ensure_active_framebuffer(); - - VKCommandBuffer &command_buffer = command_buffer_get(Type::Graphics); - vkCmdDrawIndexed(command_buffer.vk_command_buffer(), - index_count, - instance_count, - first_index, - vertex_offset, - first_instance); - command_buffer.command_recorded(); -} - -void VKCommandBuffers::draw_indirect(const VKStorageBuffer &buffer, - VkDeviceSize offset, - uint32_t draw_count, - uint32_t stride) -{ - ensure_no_compute_commands(); - validate_framebuffer_exists(); - ensure_active_framebuffer(); - - VKCommandBuffer &command_buffer = command_buffer_get(Type::Graphics); - vkCmdDrawIndirect( - command_buffer.vk_command_buffer(), buffer.vk_handle(), offset, draw_count, stride); - command_buffer.command_recorded(); -} - -void VKCommandBuffers::draw_indexed_indirect(const VKStorageBuffer &buffer, - VkDeviceSize offset, - uint32_t draw_count, - uint32_t stride) -{ - ensure_no_compute_commands(); - validate_framebuffer_exists(); - ensure_active_framebuffer(); - - VKCommandBuffer &command_buffer = command_buffer_get(Type::Graphics); - vkCmdDrawIndexedIndirect( - command_buffer.vk_command_buffer(), buffer.vk_handle(), offset, draw_count, stride); - command_buffer.command_recorded(); -} - -/** \} */ - -} // namespace blender::gpu diff --git a/source/blender/gpu/vulkan/vk_command_buffers.hh b/source/blender/gpu/vulkan/vk_command_buffers.hh deleted file mode 100644 index 834e3a8f391..00000000000 --- a/source/blender/gpu/vulkan/vk_command_buffers.hh +++ /dev/null @@ -1,200 +0,0 @@ -/* SPDX-FileCopyrightText: 2023 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -/** \file - * \ingroup gpu - */ - -#pragma once - -#include "vk_command_buffer.hh" - -namespace blender::gpu { -class VKFrameBuffer; -class VKStorageBuffer; -class VKBuffer; -class VKVertexBuffer; -class VKIndexBuffer; -class VKTexture; -class VKPushConstants; -struct VKBufferWithOffset; -class VKPipeline; -class VKDescriptorSet; - -class VKCommandBuffers : public NonCopyable, NonMovable { - enum class Type { - DataTransfer = 0, - Compute = 1, - Graphics = 2, - Max = 3, - }; - - bool initialized_ = false; - /** - * Timeout to use when waiting for fences in nanoseconds. - * - * Currently added as the fence will halt when there are no commands in the command buffer for - * the second time. This should be solved and this timeout should be removed. - */ - static constexpr uint64_t FenceTimeout = UINT64_MAX; - - /* Fence for CPU GPU synchronization when submitting the command buffers. */ - VkFence vk_fence_ = VK_NULL_HANDLE; - - /** - * Active framebuffer for graphics command buffer. - */ - VKFrameBuffer *framebuffer_ = nullptr; - bool framebuffer_bound_ = false; - - /* TODO: General command buffer should not be used, but is added to help during the transition.*/ - VKCommandBuffer buffers_[(int)Type::Max]; - VKSubmissionID submission_id_; - - public: - ~VKCommandBuffers(); - - void init(const VKDevice &device); - - /** - * Have these command buffers already been initialized? - */ - bool is_initialized() const - { - return initialized_; - } - - void bind(const VKPipeline &vk_pipeline, VkPipelineBindPoint bind_point); - void bind(const VKDescriptorSet &descriptor_set, - const VkPipelineLayout vk_pipeline_layout, - VkPipelineBindPoint bind_point); - void bind(const uint32_t binding, - const VKVertexBuffer &vertex_buffer, - const VkDeviceSize offset); - /* Bind the given buffer as a vertex buffer. */ - void bind(const uint32_t binding, const VKBufferWithOffset &vertex_buffer); - void bind(const uint32_t binding, const VkBuffer &vk_vertex_buffer, const VkDeviceSize offset); - /* Bind the given buffer as an index buffer. */ - void bind(const VKBufferWithOffset &index_buffer, VkIndexType index_type); - - void begin_render_pass(VKFrameBuffer &framebuffer); - void end_render_pass(const VKFrameBuffer &framebuffer); - - /** - * Add a push constant command to the command buffer. - * - * Only valid when the storage type of push_constants is StorageType::PUSH_CONSTANTS. - */ - void push_constants(const VKPushConstants &push_constants, - const VkPipelineLayout vk_pipeline_layout, - const VkShaderStageFlags vk_shader_stages); - void dispatch(int groups_x_len, int groups_y_len, int groups_z_len); - void dispatch(VKStorageBuffer &command_buffer); - /** Copy the contents of a texture MIP level to the dst buffer. */ - void copy(VKBuffer &dst_buffer, VKTexture &src_texture, Span regions); - void copy(VKTexture &dst_texture, VKBuffer &src_buffer, Span regions); - void copy(VKTexture &dst_texture, VKTexture &src_texture, Span regions); - void copy(VKBuffer &dst_buffer, VkBuffer src_buffer, Span regions); - void blit(VKTexture &dst_texture, VKTexture &src_texture, Span regions); - void blit(VKTexture &dst_texture, - VkImageLayout dst_layout, - VKTexture &src_texture, - VkImageLayout src_layout, - Span regions); - void pipeline_barrier(VkPipelineStageFlags source_stages, - VkPipelineStageFlags destination_stages); - void pipeline_barrier(Span image_memory_barriers); - - /** - * Clear color image resource. - */ - void clear(VkImage vk_image, - VkImageLayout vk_image_layout, - const VkClearColorValue &vk_clear_color, - Span ranges); - - /** - * Clear depth/stencil aspect of an image resource. - */ - void clear(VkImage vk_image, - VkImageLayout vk_image_layout, - const VkClearDepthStencilValue &vk_clear_color, - Span ranges); - - /** - * Clear attachments of the active framebuffer. - */ - void clear(Span attachments, Span areas); - void fill(VKBuffer &buffer, uint32_t data); - - void draw(int v_first, int v_count, int i_first, int i_count); - void draw_indexed( - int index_count, int instance_count, int first_index, int vertex_offset, int first_instance); - - void draw_indirect(const VKStorageBuffer &buffer, - VkDeviceSize offset, - uint32_t draw_count, - uint32_t stride); - void draw_indexed_indirect(const VKStorageBuffer &buffer, - VkDeviceSize offset, - uint32_t draw_count, - uint32_t stride); - - void submit(); - - const VKSubmissionID &submission_id_get() const - { - return submission_id_; - } - - private: - void init_fence(const VKDevice &device); - void init_command_buffers(const VKDevice &device); - - VKCommandBuffer &command_buffer_get(Type type) - { - return buffers_[(int)type]; - } - - /** - * Ensure that no compute commands are scheduled. - * - * To ensure correct operation all compute commands should be flushed when adding a new draw - * command. - */ - void ensure_no_compute_commands(); - - /** - * Ensire that no draw_commands are scheduled. - * - * To ensure correct operation all draw commands should be flushed when adding a new compute - * command. - */ - void ensure_no_draw_commands(); - - /** - * Validate that there isn't a framebuffer being tracked (bound or not bound). - * - * Raises an assert in debug when a framebuffer is being tracked. - */ - void validate_framebuffer_not_exists(); - - /** - * Validate that there is a framebuffer being tracked (bound or not bound). - * - * Raises an assert in debug when no framebuffer is being tracked. - */ - void validate_framebuffer_exists(); - - /** - * Ensure that the tracked framebuffer is bound. - */ - void ensure_active_framebuffer(); - /** - * Ensure that the tracked framebuffer is bound. - */ - void ensure_no_active_framebuffer(); -}; - -} // namespace blender::gpu diff --git a/source/blender/gpu/vulkan/vk_context.cc b/source/blender/gpu/vulkan/vk_context.cc index ee1ceb46649..01d5bf73032 100644 --- a/source/blender/gpu/vulkan/vk_context.cc +++ b/source/blender/gpu/vulkan/vk_context.cc @@ -51,10 +51,10 @@ void VKContext::sync_backbuffer() { if (ghost_context_) { VKDevice &device = VKBackend::get().device_; - if (!command_buffers_.is_initialized()) { - command_buffers_.init(device); + if (!command_buffer_.is_initialized()) { + command_buffer_.init(device); + command_buffer_.begin_recording(); device.init_dummy_buffer(*this); - device.init_dummy_color_attachment(); } device.descriptor_pools_get().reset(); } @@ -121,12 +121,12 @@ void VKContext::end_frame() void VKContext::flush() { - command_buffers_.submit(); + command_buffer_.submit(); } void VKContext::finish() { - command_buffers_.submit(); + command_buffer_.submit(); } void VKContext::memory_statistics_get(int * /*total_mem*/, int * /*free_mem*/) {} @@ -155,8 +155,7 @@ void VKContext::activate_framebuffer(VKFrameBuffer &framebuffer) BLI_assert(active_fb == nullptr); active_fb = &framebuffer; framebuffer.update_size(); - framebuffer.update_srgb(); - command_buffers_get().begin_render_pass(framebuffer); + command_buffer_.begin_render_pass(framebuffer); } VKFrameBuffer *VKContext::active_framebuffer_get() const @@ -173,7 +172,7 @@ void VKContext::deactivate_framebuffer() { VKFrameBuffer *framebuffer = active_framebuffer_get(); BLI_assert(framebuffer != nullptr); - command_buffers_get().end_render_pass(*framebuffer); + command_buffer_.end_render_pass(*framebuffer); active_fb = nullptr; } @@ -238,12 +237,6 @@ void VKContext::swap_buffers_post_callback() void VKContext::swap_buffers_pre_handler(const GHOST_VulkanSwapChainData &swap_chain_data) { - /* - * Ensure no graphics/compute commands are scheduled. They could use the back buffer, which - * layout is altered here. - */ - command_buffers_get().submit(); - VKFrameBuffer &framebuffer = *unwrap(back_left); VKTexture wrapper("display_texture"); @@ -253,10 +246,11 @@ void VKContext::swap_buffers_pre_handler(const GHOST_VulkanSwapChainData &swap_c wrapper.layout_ensure(*this, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); framebuffer.color_attachment_layout_ensure(*this, 0, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); VKTexture *color_attachment = unwrap(unwrap(framebuffer.color_tex(0))); + color_attachment->layout_ensure(*this, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); VkImageBlit image_blit = {}; - image_blit.srcOffsets[0] = {0, color_attachment->height_get() - 1, 0}; - image_blit.srcOffsets[1] = {color_attachment->width_get(), 0, 1}; + image_blit.srcOffsets[0] = {0, int32_t(swap_chain_data.extent.height) - 1, 0}; + image_blit.srcOffsets[1] = {int32_t(swap_chain_data.extent.width), 0, 1}; image_blit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; image_blit.srcSubresource.mipLevel = 0; image_blit.srcSubresource.baseArrayLayer = 0; @@ -270,13 +264,13 @@ void VKContext::swap_buffers_pre_handler(const GHOST_VulkanSwapChainData &swap_c image_blit.dstSubresource.baseArrayLayer = 0; image_blit.dstSubresource.layerCount = 1; - command_buffers_get().blit(wrapper, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - *color_attachment, - VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - Span(&image_blit, 1)); + command_buffer_.blit(wrapper, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + *color_attachment, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + Span(&image_blit, 1)); wrapper.layout_ensure(*this, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); - command_buffers_get().submit(); + command_buffer_.submit(); } void VKContext::swap_buffers_post_handler() diff --git a/source/blender/gpu/vulkan/vk_context.hh b/source/blender/gpu/vulkan/vk_context.hh index 67c2d0715d6..b8cc2430323 100644 --- a/source/blender/gpu/vulkan/vk_context.hh +++ b/source/blender/gpu/vulkan/vk_context.hh @@ -12,7 +12,7 @@ #include "GHOST_Types.h" -#include "vk_command_buffers.hh" +#include "vk_command_buffer.hh" #include "vk_common.hh" #include "vk_debug.hh" #include "vk_descriptor_pools.hh" @@ -25,7 +25,7 @@ class VKStateManager; class VKContext : public Context, NonCopyable { private: - VKCommandBuffers command_buffers_; + VKCommandBuffer command_buffer_; VkExtent2D vk_extent_ = {}; VkFormat swap_chain_format_ = {}; @@ -69,9 +69,9 @@ class VKContext : public Context, NonCopyable { return static_cast(Context::get()); } - VKCommandBuffers &command_buffers_get() + VKCommandBuffer &command_buffer_get() { - return command_buffers_; + return command_buffer_; } VKStateManager &state_manager_get() const; diff --git a/source/blender/gpu/vulkan/vk_data_conversion.cc b/source/blender/gpu/vulkan/vk_data_conversion.cc index 9521ccd971b..fd088d192bb 100644 --- a/source/blender/gpu/vulkan/vk_data_conversion.cc +++ b/source/blender/gpu/vulkan/vk_data_conversion.cc @@ -933,6 +933,28 @@ void convert_host_to_device(void *dst_buffer, convert_buffer(dst_buffer, src_buffer, buffer_size, device_format, conversion_type); } +void convert_host_to_device(void *dst_buffer, + const void *src_buffer, + uint2 src_size, + uint src_row_length, + eGPUDataFormat host_format, + eGPUTextureFormat device_format) +{ + const uint8_t *src = static_cast(src_buffer); + uint8_t *dst = static_cast(dst_buffer); + ConversionType conversion_type = host_to_device(host_format, device_format); + size_t src_row_len = src_row_length * to_bytesize(device_format, host_format); + size_t dst_row_len = src_size.x * to_bytesize(device_format); + + for (uint row : IndexRange(src_size.y)) { + convert_buffer(&dst[dst_row_len * row], + &src[src_row_len * row], + src_size.x, + device_format, + conversion_type); + } +} + void convert_device_to_host(void *dst_buffer, const void *src_buffer, size_t buffer_size, diff --git a/source/blender/gpu/vulkan/vk_data_conversion.hh b/source/blender/gpu/vulkan/vk_data_conversion.hh index 77dca1fbf61..a1ff0deaf9b 100644 --- a/source/blender/gpu/vulkan/vk_data_conversion.hh +++ b/source/blender/gpu/vulkan/vk_data_conversion.hh @@ -33,6 +33,27 @@ void convert_host_to_device(void *dst_buffer, eGPUDataFormat host_format, eGPUTextureFormat device_format); +/** + * Convert host buffer to device buffer with row length. + * + * \param dst_buffer: device buffer. + * \param src_buffer: host buffer. + * \param src_size: size of the host buffer. + * \param src_row_length: Length of a single row of the buffer (in pixels). + * \param host_format: format of the host buffer. + * \param device_format: format of the device buffer. + * + * \note Will assert when the host_format/device_format combination isn't valid + * (#validate_data_format) or supported. Some combinations aren't supported in Vulkan due to + * platform incompatibility. + */ +void convert_host_to_device(void *dst_buffer, + const void *src_buffer, + uint2 src_size, + uint src_row_length, + eGPUDataFormat host_format, + eGPUTextureFormat device_format); + /** * Convert device buffer to host buffer. * diff --git a/source/blender/gpu/vulkan/vk_debug.cc b/source/blender/gpu/vulkan/vk_debug.cc index 3001aa5a084..b5d0778e434 100644 --- a/source/blender/gpu/vulkan/vk_debug.cc +++ b/source/blender/gpu/vulkan/vk_debug.cc @@ -261,17 +261,6 @@ messenger_callback(VkDebugUtilsMessageSeverityFlagBitsEXT message_severity, const VkDebugUtilsMessengerCallbackDataEXT *callback_data, void *user_data) { - /* - * Some message IDs are turned of globally to reduce console flooding. - * - * - 0xec321b6c: `VUID-VkBufferCreateInfo-size-06409` is disabled as all allocations are reported - * to be larger than the maximum allowed buffer size, although the buffer-size is 4GB. Detected - * on Mesa 23.0.4. - */ - if (ELEM(callback_data->messageIdNumber, 0xec321b6c)) { - return VK_FALSE; - } - VKDebuggingTools &debugging_tools = *reinterpret_cast(user_data); if (debugging_tools.is_ignore(callback_data->messageIdNumber)) { return VK_FALSE; diff --git a/source/blender/gpu/vulkan/vk_descriptor_set.cc b/source/blender/gpu/vulkan/vk_descriptor_set.cc index cceef036260..5b6c400bb5f 100644 --- a/source/blender/gpu/vulkan/vk_descriptor_set.cc +++ b/source/blender/gpu/vulkan/vk_descriptor_set.cc @@ -181,6 +181,7 @@ void VKDescriptorSetTracker::update(VKContext &context) } /* TODO: Based on the actual usage we should use * VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL/VK_IMAGE_LAYOUT_GENERAL. */ + binding.texture->ensure_allocated(); binding.texture->layout_ensure(context, VK_IMAGE_LAYOUT_GENERAL); VkDescriptorImageInfo image_info = {}; image_info.sampler = binding.vk_sampler; diff --git a/source/blender/gpu/vulkan/vk_device.cc b/source/blender/gpu/vulkan/vk_device.cc index 1f3a1d91681..befb83c3ec9 100644 --- a/source/blender/gpu/vulkan/vk_device.cc +++ b/source/blender/gpu/vulkan/vk_device.cc @@ -27,10 +27,6 @@ void VKDevice::deinit() vkDestroyCommandPool(vk_device_, vk_command_pool_, vk_allocation_callbacks); dummy_buffer_.free(); - if (dummy_color_attachment_.has_value()) { - delete &(*dummy_color_attachment_).get(); - dummy_color_attachment_.reset(); - } sampler_.free(); destroy_discarded_resources(); vmaDestroyAllocator(mem_allocator_); @@ -89,17 +85,11 @@ void VKDevice::init_physical_device_properties() void VKDevice::init_physical_device_features() { BLI_assert(vk_physical_device_ != VK_NULL_HANDLE); - VkPhysicalDeviceFeatures2 features = {}; features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; vk_physical_device_vulkan_11_features_.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES; - vk_physical_device_vulkan_12_features_.sType = - VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES; - features.pNext = &vk_physical_device_vulkan_11_features_; - vk_physical_device_vulkan_11_features_.pNext = &vk_physical_device_vulkan_12_features_; - vkGetPhysicalDeviceFeatures2(vk_physical_device_, &features); vk_physical_device_features_ = features.features; } @@ -145,19 +135,6 @@ void VKDevice::init_dummy_buffer(VKContext &context) dummy_buffer_.clear(context, 0); } -void VKDevice::init_dummy_color_attachment() -{ - if (dummy_color_attachment_.has_value()) { - return; - } - - GPUTexture *texture = GPU_texture_create_2d( - "dummy_attachment", 1, 1, 1, GPU_R32F, GPU_TEXTURE_USAGE_ATTACHMENT, nullptr); - BLI_assert(texture); - VKTexture &vk_texture = *unwrap(unwrap(texture)); - dummy_color_attachment_ = std::make_optional(std::reference_wrapper(vk_texture)); -} - /* -------------------------------------------------------------------- */ /** \name Platform/driver/device information * \{ */ diff --git a/source/blender/gpu/vulkan/vk_device.hh b/source/blender/gpu/vulkan/vk_device.hh index 7df585b737a..ed027340f9f 100644 --- a/source/blender/gpu/vulkan/vk_device.hh +++ b/source/blender/gpu/vulkan/vk_device.hh @@ -28,18 +28,6 @@ struct VKWorkarounds { * If set to true we should work around this issue by using a different texture format. */ bool not_aligned_pixel_formats = false; - - /** - * Is the workaround for devices that don't support - * #VkPhysicalDeviceVulkan12Features::shaderOutputViewportIndex enabled. - */ - bool shader_output_viewport_index = false; - - /** - * Is the workaround for devices that don't support - * #VkPhysicalDeviceVulkan12Features::shaderOutputLayer enabled. - */ - bool shader_output_layer = false; }; class VKDevice : public NonCopyable { @@ -75,7 +63,6 @@ class VKDevice : public NonCopyable { /** Features support. */ VkPhysicalDeviceFeatures vk_physical_device_features_ = {}; VkPhysicalDeviceVulkan11Features vk_physical_device_vulkan_11_features_ = {}; - VkPhysicalDeviceVulkan12Features vk_physical_device_vulkan_12_features_ = {}; /** Functions of vk_ext_debugutils for this device/instance. */ debug::VKDebuggingTools debugging_tools_; @@ -85,7 +72,6 @@ class VKDevice : public NonCopyable { /** Buffer to bind to unbound resource locations. */ VKBuffer dummy_buffer_; - std::optional> dummy_color_attachment_; Vector> discarded_images_; Vector> discarded_buffers_; @@ -114,11 +100,6 @@ class VKDevice : public NonCopyable { return vk_physical_device_vulkan_11_features_; } - const VkPhysicalDeviceVulkan12Features &physical_device_vulkan_12_features_get() const - { - return vk_physical_device_vulkan_12_features_; - } - VkInstance instance_get() const { return vk_instance_; @@ -177,7 +158,6 @@ class VKDevice : public NonCopyable { * Dummy buffer can only be initialized after the command buffer of the context is retrieved. */ void init_dummy_buffer(VKContext &context); - void init_dummy_color_attachment(); void deinit(); eGPUDeviceType device_type() const; @@ -203,12 +183,6 @@ class VKDevice : public NonCopyable { return dummy_buffer_; } - VKTexture &dummy_color_attachment_get() const - { - BLI_assert(dummy_color_attachment_.has_value()); - return (*dummy_color_attachment_).get(); - } - void discard_image(VkImage vk_image, VmaAllocation vma_allocation); void discard_image_view(VkImageView vk_image_view); void discard_buffer(VkBuffer vk_buffer, VmaAllocation vma_allocation); diff --git a/source/blender/gpu/vulkan/vk_framebuffer.cc b/source/blender/gpu/vulkan/vk_framebuffer.cc index b4835ce5b83..159d033c658 100644 --- a/source/blender/gpu/vulkan/vk_framebuffer.cc +++ b/source/blender/gpu/vulkan/vk_framebuffer.cc @@ -22,8 +22,6 @@ namespace blender::gpu { VKFrameBuffer::VKFrameBuffer(const char *name) : FrameBuffer(name) { size_set(1, 1); - srgb_ = false; - enabled_srgb_ = false; } VKFrameBuffer::~VKFrameBuffer() @@ -33,7 +31,7 @@ VKFrameBuffer::~VKFrameBuffer() /** \} */ -void VKFrameBuffer::bind(bool enabled_srgb) +void VKFrameBuffer::bind(bool /*enabled_srgb*/) { VKContext &context = *VKContext::get(); /* Updating attachments can issue pipeline barriers, this should be done outside the render pass. @@ -46,8 +44,6 @@ void VKFrameBuffer::bind(bool enabled_srgb) } context.activate_framebuffer(*this); - enabled_srgb_ = enabled_srgb; - Shader::set_framebuffer_srgb_target(enabled_srgb && srgb_); } Array VKFrameBuffer::vk_viewports_get() const @@ -75,10 +71,10 @@ Array VKFrameBuffer::vk_render_areas_get() const if (scissor_test_get()) { int scissor_rect[4]; scissor_get(scissor_rect); - render_area.offset.x = clamp_i(scissor_rect[0], 0, width_); - render_area.offset.y = clamp_i(scissor_rect[1], 0, height_); - render_area.extent.width = clamp_i(scissor_rect[2], 1, width_ - scissor_rect[0]); - render_area.extent.height = clamp_i(scissor_rect[3], 1, height_ - scissor_rect[1]); + render_area.offset.x = scissor_rect[0]; + render_area.offset.y = scissor_rect[1]; + render_area.extent.width = scissor_rect[2]; + render_area.extent.height = scissor_rect[3]; } else { render_area.offset.x = 0; @@ -146,8 +142,8 @@ void VKFrameBuffer::clear(const Vector &attachments) const clear_rect.layerCount = 1; VKContext &context = *VKContext::get(); - VKCommandBuffers &command_buffers = context.command_buffers_get(); - command_buffers.clear(attachments, Span(&clear_rect, 1)); + VKCommandBuffer &command_buffer = context.command_buffer_get(); + command_buffer.clear(attachments, Span(&clear_rect, 1)); } void VKFrameBuffer::clear(const eGPUFrameBufferBits buffers, @@ -171,6 +167,7 @@ void VKFrameBuffer::clear(const eGPUFrameBufferBits buffers, << "PERFORMANCE: impact clearing depth texture in render pass that doesn't allow " "depth writes.\n"; } + depth_texture->ensure_allocated(); depth_attachment_layout_ensure(context, VK_IMAGE_LAYOUT_GENERAL); depth_texture->clear_depth_stencil(buffers, clear_depth, clear_stencil); } @@ -217,18 +214,6 @@ void VKFrameBuffer::attachment_set_loadstore_op(GPUAttachmentType /*type*/, GPUL /** \} */ -/* -------------------------------------------------------------------- */ -/** \name Sub-pass transition - * \{ */ - -void VKFrameBuffer::subpass_transition(const GPUAttachmentState /*depth_attachment_state*/, - Span /*color_attachment_states*/) -{ - NOT_YET_IMPLEMENTED; -} - -/** \} */ - /* -------------------------------------------------------------------- */ /** \name Read back * \{ */ @@ -240,17 +225,14 @@ void VKFrameBuffer::read(eGPUFrameBufferBits plane, int slot, void *r_data) { - VKContext &context = *VKContext::get(); VKTexture *texture = nullptr; switch (plane) { case GPU_COLOR_BIT: - color_attachment_layout_ensure(context, slot, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); - texture = unwrap(unwrap(color_tex(slot))); + texture = unwrap(unwrap(attachments_[GPU_FB_COLOR_ATTACHMENT0 + slot].tex)); break; case GPU_DEPTH_BIT: - depth_attachment_layout_ensure(context, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); - texture = unwrap(unwrap(depth_tex())); + texture = unwrap(unwrap(attachments_[GPU_FB_DEPTH_ATTACHMENT].tex)); break; default: @@ -270,26 +252,44 @@ void VKFrameBuffer::read(eGPUFrameBufferBits plane, /** \name Blit operations * \{ */ -static void blit_aspect(VKCommandBuffers &command_buffer, - VKTexture &dst_texture, - VKTexture &src_texture, - int dst_offset_x, - int dst_offset_y, - VkImageAspectFlagBits image_aspect) +void VKFrameBuffer::blit_to(eGPUFrameBufferBits planes, + int src_slot, + FrameBuffer *dst, + int dst_slot, + int dst_offset_x, + int dst_offset_y) { - /* Prefer texture copy, as some platforms don't support using D32_SFLOAT_S8_UINT to be used as - * a blit destination. */ - if (dst_offset_x == 0 && dst_offset_y == 0 && - dst_texture.format_get() == src_texture.format_get() && - src_texture.width_get() == dst_texture.width_get() && - src_texture.height_get() == dst_texture.height_get()) - { - src_texture.copy_to(dst_texture, image_aspect); + BLI_assert(dst); + BLI_assert(planes == GPU_COLOR_BIT); + UNUSED_VARS_NDEBUG(planes); + + VKContext &context = *VKContext::get(); + if (!context.has_active_framebuffer()) { + BLI_assert_unreachable(); return; } + /* Retrieve source texture. */ + const GPUAttachment &src_attachment = attachments_[GPU_FB_COLOR_ATTACHMENT0 + src_slot]; + if (src_attachment.tex == nullptr) { + return; + } + color_attachment_layout_ensure(context, src_slot, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); + VKTexture &src_texture = *unwrap(unwrap(src_attachment.tex)); + + /* Retrieve destination texture. */ + VKFrameBuffer &dst_framebuffer = *unwrap(dst); + dst_framebuffer.color_attachment_layout_ensure( + context, dst_slot, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + const GPUAttachment &dst_attachment = + dst_framebuffer.attachments_[GPU_FB_COLOR_ATTACHMENT0 + dst_slot]; + if (dst_attachment.tex == nullptr) { + return; + } + VKTexture &dst_texture = *unwrap(unwrap(dst_attachment.tex)); + VkImageBlit image_blit = {}; - image_blit.srcSubresource.aspectMask = image_aspect; + image_blit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; image_blit.srcSubresource.mipLevel = 0; image_blit.srcSubresource.baseArrayLayer = 0; image_blit.srcSubresource.layerCount = 1; @@ -300,86 +300,18 @@ static void blit_aspect(VKCommandBuffers &command_buffer, image_blit.srcOffsets[1].y = src_texture.height_get(); image_blit.srcOffsets[1].z = 1; - image_blit.dstSubresource.aspectMask = image_aspect; + image_blit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; image_blit.dstSubresource.mipLevel = 0; image_blit.dstSubresource.baseArrayLayer = 0; image_blit.dstSubresource.layerCount = 1; - image_blit.dstOffsets[0].x = min_ii(dst_offset_x, dst_texture.width_get()); - image_blit.dstOffsets[0].y = min_ii(dst_offset_y, dst_texture.height_get()); + image_blit.dstOffsets[0].x = dst_offset_x; + image_blit.dstOffsets[0].y = dst_offset_y; image_blit.dstOffsets[0].z = 0; - image_blit.dstOffsets[1].x = min_ii(dst_offset_x + src_texture.width_get(), - dst_texture.width_get()); - image_blit.dstOffsets[1].y = min_ii(dst_offset_y + src_texture.height_get(), - dst_texture.height_get()); + image_blit.dstOffsets[1].x = dst_offset_x + src_texture.width_get(); + image_blit.dstOffsets[1].y = dst_offset_y + src_texture.height_get(); image_blit.dstOffsets[1].z = 1; - command_buffer.blit(dst_texture, src_texture, Span(&image_blit, 1)); -} - -void VKFrameBuffer::blit_to(eGPUFrameBufferBits planes, - int src_slot, - FrameBuffer *dst, - int dst_slot, - int dst_offset_x, - int dst_offset_y) -{ - BLI_assert(dst); - BLI_assert_msg(ELEM(planes, GPU_COLOR_BIT, GPU_DEPTH_BIT), - "VKFrameBuffer::blit_to only supports a single color or depth aspect."); - UNUSED_VARS_NDEBUG(planes); - - VKContext &context = *VKContext::get(); - VKCommandBuffers &command_buffers = context.command_buffers_get(); - if (!context.has_active_framebuffer()) { - BLI_assert_unreachable(); - return; - } - - VKFrameBuffer &dst_framebuffer = *unwrap(dst); - if (planes & GPU_COLOR_BIT) { - const GPUAttachment &src_attachment = attachments_[GPU_FB_COLOR_ATTACHMENT0 + src_slot]; - const GPUAttachment &dst_attachment = - dst_framebuffer.attachments_[GPU_FB_COLOR_ATTACHMENT0 + dst_slot]; - if (src_attachment.tex && dst_attachment.tex) { - VKTexture &src_texture = *unwrap(unwrap(src_attachment.tex)); - VKTexture &dst_texture = *unwrap(unwrap(dst_attachment.tex)); - color_attachment_layout_ensure(context, src_slot, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); - dst_framebuffer.color_attachment_layout_ensure( - context, dst_slot, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); - - blit_aspect(command_buffers, - dst_texture, - src_texture, - dst_offset_x, - dst_offset_y, - VK_IMAGE_ASPECT_COLOR_BIT); - } - } - - if (planes & GPU_DEPTH_BIT) { - /* Retrieve source texture. */ - const GPUAttachment &src_attachment = attachments_[GPU_FB_DEPTH_STENCIL_ATTACHMENT].tex ? - attachments_[GPU_FB_DEPTH_STENCIL_ATTACHMENT] : - attachments_[GPU_FB_DEPTH_ATTACHMENT]; - const GPUAttachment &dst_attachment = - dst_framebuffer.attachments_[GPU_FB_DEPTH_STENCIL_ATTACHMENT].tex ? - dst_framebuffer.attachments_[GPU_FB_DEPTH_STENCIL_ATTACHMENT] : - dst_framebuffer.attachments_[GPU_FB_DEPTH_ATTACHMENT]; - if (src_attachment.tex && dst_attachment.tex) { - VKTexture &src_texture = *unwrap(unwrap(src_attachment.tex)); - VKTexture &dst_texture = *unwrap(unwrap(dst_attachment.tex)); - depth_attachment_layout_ensure(context, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); - dst_framebuffer.depth_attachment_layout_ensure(context, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); - - blit_aspect(command_buffers, - dst_texture, - src_texture, - dst_offset_x, - dst_offset_y, - VK_IMAGE_ASPECT_DEPTH_BIT); - } - } + context.command_buffer_get().blit(dst_texture, src_texture, Span(&image_blit, 1)); } /** \} */ @@ -416,8 +348,6 @@ void VKFrameBuffer::render_pass_create() bool has_depth_attachment = false; bool found_attachment = false; - const VKImageView &dummy_attachment = - VKBackend::get().device_get().dummy_color_attachment_get().image_view_get(); int depth_location = -1; for (int type = GPU_FB_MAX_ATTACHMENT - 1; type >= 0; type--) { @@ -441,32 +371,23 @@ void VKFrameBuffer::render_pass_create() int attachment_location = type >= GPU_FB_COLOR_ATTACHMENT0 ? type - GPU_FB_COLOR_ATTACHMENT0 : depth_location; - const bool is_depth_attachment = ELEM( - type, GPU_FB_DEPTH_ATTACHMENT, GPU_FB_DEPTH_STENCIL_ATTACHMENT); if (attachment.tex) { - BLI_assert_msg(!is_depth_attachment || !has_depth_attachment, - "There can only be one depth/stencil attachment."); - has_depth_attachment |= is_depth_attachment; - /* Ensure texture is allocated to ensure the image view. */ VKTexture &texture = *static_cast(unwrap(attachment.tex)); - const bool use_stencil = false; - const bool use_srgb = srgb_ && enabled_srgb_; + texture.ensure_allocated(); image_views_.append(VKImageView(texture, eImageViewUsage::Attachment, IndexRange(max_ii(attachment.layer, 0), 1), IndexRange(attachment.mip, 1), - use_stencil, - use_srgb, + false, name_)); - const VKImageView &image_view = image_views_.last(); - image_views[attachment_location] = image_view.vk_handle(); + image_views[attachment_location] = image_views_.last().vk_handle(); VkAttachmentDescription &attachment_description = attachment_descriptions[attachment_location]; attachment_description.flags = 0; - attachment_description.format = image_view.vk_format(); + attachment_description.format = to_vk_format(texture.format_get()); attachment_description.samples = VK_SAMPLE_COUNT_1_BIT; attachment_description.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; attachment_description.storeOp = VK_ATTACHMENT_STORE_OP_STORE; @@ -476,31 +397,18 @@ void VKFrameBuffer::render_pass_create() attachment_description.finalLayout = texture.current_layout_get(); /* Create the attachment reference. */ + const bool is_depth_attachment = ELEM( + type, GPU_FB_DEPTH_ATTACHMENT, GPU_FB_DEPTH_STENCIL_ATTACHMENT); + + BLI_assert_msg(!is_depth_attachment || !has_depth_attachment, + "There can only be one depth/stencil attachment."); + has_depth_attachment |= is_depth_attachment; VkAttachmentReference &attachment_reference = attachment_references[attachment_location]; attachment_reference.attachment = attachment_location; attachment_reference.layout = is_depth_attachment ? VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_GENERAL; } - else if (!is_depth_attachment) { - image_views[attachment_location] = dummy_attachment.vk_handle(); - - VkAttachmentDescription &attachment_description = - attachment_descriptions[attachment_location]; - attachment_description.flags = 0; - attachment_description.format = VK_FORMAT_R32_SFLOAT; - attachment_description.samples = VK_SAMPLE_COUNT_1_BIT; - attachment_description.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; - attachment_description.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - attachment_description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; - attachment_description.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - attachment_description.initialLayout = VK_IMAGE_LAYOUT_GENERAL; - attachment_description.finalLayout = VK_IMAGE_LAYOUT_GENERAL; - - VkAttachmentReference &attachment_reference = attachment_references[attachment_location]; - attachment_reference.attachment = VK_ATTACHMENT_UNUSED; - attachment_reference.layout = VK_IMAGE_LAYOUT_UNDEFINED; - } } /* Update the size, viewport & scissor based on the first attachment. */ @@ -520,6 +428,7 @@ void VKFrameBuffer::render_pass_create() scissor_reset(); /* Create render pass. */ + const int attachment_len = has_depth_attachment ? depth_location + 1 : depth_location; const int color_attachment_len = depth_location; VkSubpassDescription subpass = {}; @@ -584,6 +493,7 @@ void VKFrameBuffer::color_attachment_layout_ensure(VKContext &context, return; } + color_texture->ensure_allocated(); color_texture->layout_ensure(context, requested_layout); dirty_attachments_ = true; } @@ -621,28 +531,6 @@ void VKFrameBuffer::update_size() size_set(1, 1); } -void VKFrameBuffer::update_srgb() -{ - for (int i : IndexRange(GPU_FB_MAX_COLOR_ATTACHMENT)) { - VKTexture *texture = unwrap(unwrap(color_tex(i))); - if (texture) { - srgb_ = (texture->format_flag_get() & GPU_FORMAT_SRGB) != 0; - return; - } - } -} - -int VKFrameBuffer::color_attachments_resource_size() const -{ - int size = 0; - for (int color_slot : IndexRange(GPU_FB_MAX_COLOR_ATTACHMENT)) { - if (color_tex(color_slot) != nullptr) { - size = max_ii(color_slot + 1, size); - } - } - return size; -} - /** \} */ } // namespace blender::gpu diff --git a/source/blender/gpu/vulkan/vk_framebuffer.hh b/source/blender/gpu/vulkan/vk_framebuffer.hh index a18c4c69ce4..0f64718667c 100644 --- a/source/blender/gpu/vulkan/vk_framebuffer.hh +++ b/source/blender/gpu/vulkan/vk_framebuffer.hh @@ -34,10 +34,6 @@ class VKFrameBuffer : public FrameBuffer { Vector image_views_; - /** Is the first attachment an SRGB texture. */ - bool srgb_; - bool enabled_srgb_; - public: /** * Create a conventional framebuffer to attach texture to. @@ -59,9 +55,6 @@ class VKFrameBuffer : public FrameBuffer { void attachment_set_loadstore_op(GPUAttachmentType type, GPULoadStore /*ls*/) override; - void subpass_transition(const GPUAttachmentState depth_attachment_state, - Span color_attachment_states) override; - void read(eGPUFrameBufferBits planes, eGPUDataFormat format, const int area[4], @@ -113,17 +106,6 @@ class VKFrameBuffer : public FrameBuffer { */ void update_size(); - void update_srgb(); - - /** - * Return the number of color attachments of this frame buffer, including unused color - * attachments. - * - * Frame-buffers can have unused attachments. When higher attachment slots are being used, unused - * lower attachment slots will be counted as they are required resources in render-passes. - */ - int color_attachments_resource_size() const; - private: void update_attachments(); void render_pass_free(); diff --git a/source/blender/gpu/vulkan/vk_image_view.cc b/source/blender/gpu/vulkan/vk_image_view.cc index e1dc489d3d0..9c90e26bc73 100644 --- a/source/blender/gpu/vulkan/vk_image_view.cc +++ b/source/blender/gpu/vulkan/vk_image_view.cc @@ -15,26 +15,11 @@ namespace blender::gpu { -static VkFormat to_non_srgb_format(const VkFormat format) -{ - switch (format) { - case VK_FORMAT_R8G8B8_SRGB: - return VK_FORMAT_R8G8B8_UNORM; - case VK_FORMAT_R8G8B8A8_SRGB: - return VK_FORMAT_R8G8B8A8_UNORM; - - default: - break; - } - return format; -} - VKImageView::VKImageView(VKTexture &texture, eImageViewUsage usage, IndexRange layer_range, IndexRange mip_range, bool use_stencil, - bool use_srgb, StringRefNull name) { const VkImageAspectFlagBits allowed_bits = static_cast( @@ -43,17 +28,12 @@ VKImageView::VKImageView(VKTexture &texture, VkImageAspectFlagBits image_aspect = static_cast( (to_vk_image_aspect_flag_bits(texture.format_get()) & allowed_bits)); - vk_format_ = to_vk_format(texture.format_get()); - if (texture.format_flag_get() & GPU_FORMAT_SRGB && !use_srgb) { - vk_format_ = to_non_srgb_format(vk_format_); - } - VK_ALLOCATION_CALLBACKS VkImageViewCreateInfo image_view_info = {}; image_view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; image_view_info.image = texture.vk_image_handle(); image_view_info.viewType = to_vk_image_view_type(texture.type_get(), usage); - image_view_info.format = vk_format_; + image_view_info.format = to_vk_format(texture.format_get()); image_view_info.components = texture.vk_component_mapping_get(); image_view_info.subresourceRange.aspectMask = image_aspect; image_view_info.subresourceRange.baseMipLevel = mip_range.first(); @@ -71,8 +51,6 @@ VKImageView::VKImageView(VKImageView &&other) { vk_image_view_ = other.vk_image_view_; other.vk_image_view_ = VK_NULL_HANDLE; - vk_format_ = other.vk_format_; - other.vk_format_ = VK_FORMAT_UNDEFINED; } VKImageView::~VKImageView() @@ -82,7 +60,6 @@ VKImageView::~VKImageView() device.discard_image_view(vk_image_view_); vk_image_view_ = VK_NULL_HANDLE; } - vk_format_ = VK_FORMAT_UNDEFINED; } } // namespace blender::gpu diff --git a/source/blender/gpu/vulkan/vk_image_view.hh b/source/blender/gpu/vulkan/vk_image_view.hh index 3ed1e693f74..679dc1d2af9 100644 --- a/source/blender/gpu/vulkan/vk_image_view.hh +++ b/source/blender/gpu/vulkan/vk_image_view.hh @@ -18,7 +18,6 @@ class VKTexture; class VKImageView : NonCopyable { VkImageView vk_image_view_ = VK_NULL_HANDLE; - VkFormat vk_format_ = VK_FORMAT_UNDEFINED; public: VKImageView(VKTexture &texture, @@ -26,7 +25,6 @@ class VKImageView : NonCopyable { IndexRange layer_range, IndexRange mip_range, bool use_stencil, - bool use_srgb, StringRefNull name); VKImageView(VKImageView &&other); @@ -37,11 +35,6 @@ class VKImageView : NonCopyable { BLI_assert(vk_image_view_ != VK_NULL_HANDLE); return vk_image_view_; } - - VkFormat vk_format() const - { - return vk_format_; - } }; } // namespace blender::gpu diff --git a/source/blender/gpu/vulkan/vk_immediate.cc b/source/blender/gpu/vulkan/vk_immediate.cc index 04e205551bc..47cd3a4d332 100644 --- a/source/blender/gpu/vulkan/vk_immediate.cc +++ b/source/blender/gpu/vulkan/vk_immediate.cc @@ -33,7 +33,7 @@ uchar *VKImmediate::begin() void VKImmediate::end() { BLI_assert_msg(prim_type != GPU_PRIM_NONE, "Illegal state: not between an immBegin/End pair."); - if (vertex_idx == 0) { + if (vertex_len == 0) { return; } @@ -44,7 +44,7 @@ void VKImmediate::end() */ uchar *data = static_cast(active_resource()->mapped_memory_get()) + subbuffer_offset_get(); - convert_in_place(data, vertex_format, vertex_idx); + convert_in_place(data, vertex_format, vertex_len); } VKContext &context = *VKContext::get(); @@ -56,7 +56,8 @@ void VKImmediate::end() context.bind_graphics_pipeline(prim_type, vertex_attributes_); vertex_attributes_.bind(context); - context.command_buffers_get().draw(0, vertex_idx, 0, 1); + context.command_buffer_get().draw(0, vertex_len, 0, 1); + context.command_buffer_get().submit(); buffer_offset_ += current_subbuffer_len_; current_subbuffer_len_ = 0; diff --git a/source/blender/gpu/vulkan/vk_index_buffer.cc b/source/blender/gpu/vulkan/vk_index_buffer.cc index fcbf7a1830a..e46e1e8b287 100644 --- a/source/blender/gpu/vulkan/vk_index_buffer.cc +++ b/source/blender/gpu/vulkan/vk_index_buffer.cc @@ -37,7 +37,7 @@ void VKIndexBuffer::upload_data() void VKIndexBuffer::bind(VKContext &context) { - context.command_buffers_get().bind(buffer_with_offset(), to_vk_index_type(index_type_)); + context.command_buffer_get().bind(buffer_with_offset(), to_vk_index_type(index_type_)); } void VKIndexBuffer::bind_as_ssbo(uint binding) @@ -63,7 +63,8 @@ void VKIndexBuffer::bind(int binding, shader::ShaderCreateInfo::Resource::BindTy void VKIndexBuffer::read(uint32_t *data) const { VKContext &context = *VKContext::get(); - context.flush(); + VKCommandBuffer &command_buffer = context.command_buffer_get(); + command_buffer.submit(); buffer_.read(data); } diff --git a/source/blender/gpu/vulkan/vk_pipeline.cc b/source/blender/gpu/vulkan/vk_pipeline.cc index bb355408677..4d5bd3b51b7 100644 --- a/source/blender/gpu/vulkan/vk_pipeline.cc +++ b/source/blender/gpu/vulkan/vk_pipeline.cc @@ -205,12 +205,12 @@ void VKPipeline::update_and_bind(VKContext &context, VkPipelineLayout vk_pipeline_layout, VkPipelineBindPoint vk_pipeline_bind_point) { - VKCommandBuffers &command_buffers = context.command_buffers_get(); - command_buffers.bind(*this, vk_pipeline_bind_point); + VKCommandBuffer &command_buffer = context.command_buffer_get(); + command_buffer.bind(*this, vk_pipeline_bind_point); push_constants_.update(context); if (descriptor_set_.has_layout()) { descriptor_set_.update(context); - command_buffers.bind( + command_buffer.bind( *descriptor_set_.active_descriptor_set(), vk_pipeline_layout, vk_pipeline_bind_point); } } diff --git a/source/blender/gpu/vulkan/vk_pipeline_state.cc b/source/blender/gpu/vulkan/vk_pipeline_state.cc index 1115869ebeb..94d7ab0529e 100644 --- a/source/blender/gpu/vulkan/vk_pipeline_state.cc +++ b/source/blender/gpu/vulkan/vk_pipeline_state.cc @@ -78,8 +78,13 @@ void VKPipelineStateManager::force_state(const GPUState &state, void VKPipelineStateManager::finalize_color_blend_state(const VKFrameBuffer &framebuffer) { color_blend_attachments.clear(); - color_blend_attachments.append_n_times(color_blend_attachment_template, - framebuffer.color_attachments_resource_size()); + for (int color_slot = 0; color_slot < GPU_FB_MAX_COLOR_ATTACHMENT; color_slot++) { + VKTexture *texture = unwrap(unwrap(framebuffer.color_tex(color_slot))); + if (texture) { + color_blend_attachments.append(color_blend_attachment_template); + } + } + pipeline_color_blend_state.attachmentCount = color_blend_attachments.size(); pipeline_color_blend_state.pAttachments = color_blend_attachments.data(); } diff --git a/source/blender/gpu/vulkan/vk_pixel_buffer.hh b/source/blender/gpu/vulkan/vk_pixel_buffer.hh index e7a2ee93e78..2e498a2af9a 100644 --- a/source/blender/gpu/vulkan/vk_pixel_buffer.hh +++ b/source/blender/gpu/vulkan/vk_pixel_buffer.hh @@ -23,11 +23,6 @@ class VKPixelBuffer : public PixelBuffer { void unmap() override; int64_t get_native_handle() override; size_t get_size() override; - - VKBuffer &buffer_get() - { - return buffer_; - } }; } // namespace blender::gpu diff --git a/source/blender/gpu/vulkan/vk_push_constants.cc b/source/blender/gpu/vulkan/vk_push_constants.cc index 2c06664776c..920e33467f8 100644 --- a/source/blender/gpu/vulkan/vk_push_constants.cc +++ b/source/blender/gpu/vulkan/vk_push_constants.cc @@ -147,7 +147,7 @@ VKPushConstants &VKPushConstants::operator=(VKPushConstants &&other) void VKPushConstants::update(VKContext &context) { VKShader *shader = static_cast(context.shader); - VKCommandBuffers &command_buffers = context.command_buffers_get(); + VKCommandBuffer &command_buffer = context.command_buffer_get(); VKPipeline &pipeline = shader->pipeline_get(); BLI_assert_msg(&pipeline.push_constants_get() == this, "Invalid state detected. Push constants doesn't belong to the active shader of " @@ -159,10 +159,7 @@ void VKPushConstants::update(VKContext &context) break; case VKPushConstants::StorageType::PUSH_CONSTANTS: - command_buffers.push_constants(*this, - shader->vk_pipeline_layout_get(), - shader->is_graphics_shader() ? VK_SHADER_STAGE_ALL_GRAPHICS : - VK_SHADER_STAGE_COMPUTE_BIT); + command_buffer.push_constants(*this, shader->vk_pipeline_layout_get(), VK_SHADER_STAGE_ALL); break; case VKPushConstants::StorageType::UNIFORM_BUFFER: diff --git a/source/blender/gpu/vulkan/vk_resource_tracker.cc b/source/blender/gpu/vulkan/vk_resource_tracker.cc index f017d336ad4..ad039df5457 100644 --- a/source/blender/gpu/vulkan/vk_resource_tracker.cc +++ b/source/blender/gpu/vulkan/vk_resource_tracker.cc @@ -12,8 +12,8 @@ namespace blender::gpu { bool VKSubmissionTracker::is_changed(VKContext &context) { - VKCommandBuffers &command_buffers = context.command_buffers_get(); - const VKSubmissionID ¤t_id = command_buffers.submission_id_get(); + VKCommandBuffer &command_buffer = context.command_buffer_get(); + const VKSubmissionID ¤t_id = command_buffer.submission_id_get(); if (last_known_id_ != current_id) { last_known_id_ = current_id; return true; diff --git a/source/blender/gpu/vulkan/vk_resource_tracker.hh b/source/blender/gpu/vulkan/vk_resource_tracker.hh index eadb615e524..d5856a11584 100644 --- a/source/blender/gpu/vulkan/vk_resource_tracker.hh +++ b/source/blender/gpu/vulkan/vk_resource_tracker.hh @@ -15,7 +15,7 @@ namespace blender::gpu { class VKContext; -class VKCommandBuffers; +class VKCommandBuffer; /** * In vulkan multiple commands can be in flight simultaneously. @@ -81,7 +81,7 @@ struct VKSubmissionID { return id_ != other.id_; } - friend class VKCommandBuffers; + friend class VKCommandBuffer; }; /** diff --git a/source/blender/gpu/vulkan/vk_shader.cc b/source/blender/gpu/vulkan/vk_shader.cc index c684160bfa9..c198dc05c5c 100644 --- a/source/blender/gpu/vulkan/vk_shader.cc +++ b/source/blender/gpu/vulkan/vk_shader.cc @@ -17,7 +17,7 @@ #include "vk_shader_interface.hh" #include "vk_shader_log.hh" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_vector.hh" #include "BKE_global.h" @@ -494,8 +494,6 @@ static char *glsl_patch_get() return patch; } - const VKWorkarounds &workarounds = VKBackend::get().device_get().workarounds_get(); - size_t slen = 0; /* Version need to go first. */ STR_CONCAT(patch, slen, "#version 450\n"); @@ -513,12 +511,8 @@ static char *glsl_patch_get() /* TODO(fclem): This creates a validation error and should be already part of Vulkan 1.2. */ STR_CONCAT(patch, slen, "#extension GL_ARB_shader_viewport_layer_array: enable\n"); - if (!workarounds.shader_output_layer) { - STR_CONCAT(patch, slen, "#define gpu_Layer gl_Layer\n"); - } - if (!workarounds.shader_output_viewport_index) { - STR_CONCAT(patch, slen, "#define gpu_ViewportIndex gl_ViewportIndex\n"); - } + STR_CONCAT(patch, slen, "#define gpu_Layer gl_Layer\n"); + STR_CONCAT(patch, slen, "#define gpu_ViewportIndex gl_ViewportIndex\n"); STR_CONCAT(patch, slen, "#define DFDX_SIGN 1.0\n"); STR_CONCAT(patch, slen, "#define DFDY_SIGN 1.0\n"); @@ -676,14 +670,6 @@ bool VKShader::finalize(const shader::ShaderCreateInfo *info) return false; } - if (do_geometry_shader_injection(info)) { - std::string source = workaround_geometry_shader_source_create(*info); - Vector sources; - sources.append("version"); - sources.append(source.c_str()); - geometry_shader_from_glsl(sources); - } - VKShaderInterface *vk_interface = new VKShaderInterface(); vk_interface->init(*info); @@ -744,8 +730,7 @@ bool VKShader::finalize_pipeline_layout(VkDevice vk_device, if (push_constants_layout.storage_type_get() == VKPushConstants::StorageType::PUSH_CONSTANTS) { push_constant_range.offset = 0; push_constant_range.size = push_constants_layout.size_in_bytes(); - push_constant_range.stageFlags = is_graphics_shader() ? VK_SHADER_STAGE_ALL_GRAPHICS : - VK_SHADER_STAGE_COMPUTE_BIT; + push_constant_range.stageFlags = VK_SHADER_STAGE_ALL; pipeline_info.pushConstantRangeCount = 1; pipeline_info.pPushConstantRanges = &push_constant_range; } @@ -857,22 +842,20 @@ static VkDescriptorType descriptor_type(const shader::ShaderCreateInfo::Resource } static VkDescriptorSetLayoutBinding create_descriptor_set_layout_binding( - const VKDescriptorSet::Location location, - const shader::ShaderCreateInfo::Resource &resource, - VkShaderStageFlagBits vk_shader_stages) + const VKDescriptorSet::Location location, const shader::ShaderCreateInfo::Resource &resource) { VkDescriptorSetLayoutBinding binding = {}; binding.binding = location; binding.descriptorType = descriptor_type(resource); binding.descriptorCount = 1; - binding.stageFlags = vk_shader_stages; + binding.stageFlags = VK_SHADER_STAGE_ALL; binding.pImmutableSamplers = nullptr; return binding; } static VkDescriptorSetLayoutBinding create_descriptor_set_layout_binding( - const VKPushConstants::Layout &push_constants_layout, VkShaderStageFlagBits vk_shader_stages) + const VKPushConstants::Layout &push_constants_layout) { BLI_assert(push_constants_layout.storage_type_get() == VKPushConstants::StorageType::UNIFORM_BUFFER); @@ -880,7 +863,7 @@ static VkDescriptorSetLayoutBinding create_descriptor_set_layout_binding( binding.binding = push_constants_layout.descriptor_set_location_get(); binding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; binding.descriptorCount = 1; - binding.stageFlags = vk_shader_stages; + binding.stageFlags = VK_SHADER_STAGE_ALL; binding.pImmutableSamplers = nullptr; return binding; @@ -889,29 +872,26 @@ static VkDescriptorSetLayoutBinding create_descriptor_set_layout_binding( static void add_descriptor_set_layout_bindings( const VKShaderInterface &interface, const Vector &resources, - Vector &r_bindings, - VkShaderStageFlagBits vk_shader_stages) + Vector &r_bindings) { for (const shader::ShaderCreateInfo::Resource &resource : resources) { const VKDescriptorSet::Location location = interface.descriptor_set_location(resource); - r_bindings.append(create_descriptor_set_layout_binding(location, resource, vk_shader_stages)); + r_bindings.append(create_descriptor_set_layout_binding(location, resource)); } /* Add push constants to the descriptor when push constants are stored in an uniform buffer. */ const VKPushConstants::Layout &push_constants_layout = interface.push_constants_layout_get(); if (push_constants_layout.storage_type_get() == VKPushConstants::StorageType::UNIFORM_BUFFER) { - r_bindings.append( - create_descriptor_set_layout_binding(push_constants_layout, vk_shader_stages)); + r_bindings.append(create_descriptor_set_layout_binding(push_constants_layout)); } } static VkDescriptorSetLayoutCreateInfo create_descriptor_set_layout( const VKShaderInterface &interface, const Vector &resources, - Vector &r_bindings, - VkShaderStageFlagBits vk_shader_stages) + Vector &r_bindings) { - add_descriptor_set_layout_bindings(interface, resources, r_bindings, vk_shader_stages); + add_descriptor_set_layout_bindings(interface, resources, r_bindings); VkDescriptorSetLayoutCreateInfo set_info = {}; set_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; set_info.flags = 0; @@ -948,11 +928,8 @@ bool VKShader::finalize_descriptor_set_layouts(VkDevice vk_device, all_resources.extend(info.batch_resources_); Vector bindings; - const VkShaderStageFlagBits vk_shader_stages = is_graphics_shader() ? - VK_SHADER_STAGE_ALL_GRAPHICS : - VK_SHADER_STAGE_COMPUTE_BIT; VkDescriptorSetLayoutCreateInfo layout_info = create_descriptor_set_layout( - shader_interface, all_resources, bindings, vk_shader_stages); + shader_interface, all_resources, bindings); if (vkCreateDescriptorSetLayout(vk_device, &layout_info, vk_allocation_callbacks, &layout_) != VK_SUCCESS) { @@ -1065,7 +1042,6 @@ std::string VKShader::vertex_interface_declare(const shader::ShaderCreateInfo &i { std::stringstream ss; std::string post_main; - const VKWorkarounds &workarounds = VKBackend::get().device_get().workarounds_get(); ss << "\n/* Inputs. */\n"; for (const ShaderCreateInfo::VertIn &attr : info.vertex_inputs_) { @@ -1077,13 +1053,6 @@ std::string VKShader::vertex_interface_declare(const shader::ShaderCreateInfo &i for (const StageInterfaceInfo *iface : info.vertex_out_interfaces_) { print_interface(ss, "out", *iface, location); } - if (workarounds.shader_output_layer && bool(info.builtins_ & BuiltinBits::LAYER)) { - ss << "layout(location=" << (location++) << ") out int gpu_Layer;\n "; - } - if (workarounds.shader_output_viewport_index && - bool(info.builtins_ & BuiltinBits::VIEWPORT_INDEX)) { - ss << "layout(location=" << (location++) << ") out int gpu_ViewportIndex;\n"; - } if (bool(info.builtins_ & BuiltinBits::BARYCENTRIC_COORD)) { /* Need this for stable barycentric. */ ss << "flat out vec4 gpu_pos_flat;\n"; @@ -1104,7 +1073,6 @@ std::string VKShader::fragment_interface_declare(const shader::ShaderCreateInfo { std::stringstream ss; std::string pre_main; - const VKWorkarounds &workarounds = VKBackend::get().device_get().workarounds_get(); ss << "\n/* Interfaces. */\n"; const Vector &in_interfaces = info.geometry_source_.is_empty() ? @@ -1114,14 +1082,6 @@ std::string VKShader::fragment_interface_declare(const shader::ShaderCreateInfo for (const StageInterfaceInfo *iface : in_interfaces) { print_interface(ss, "in", *iface, location); } - if (workarounds.shader_output_layer && bool(info.builtins_ & BuiltinBits::LAYER)) { - ss << "#define gpu_Layer gl_Layer\n"; - } - if (workarounds.shader_output_viewport_index && - bool(info.builtins_ & BuiltinBits::VIEWPORT_INDEX)) { - ss << "#define gpu_ViewportIndex gl_ViewportIndex\n"; - } - if (bool(info.builtins_ & BuiltinBits::BARYCENTRIC_COORD)) { std::cout << "native" << std::endl; /* NOTE(fclem): This won't work with geometry shader. Hopefully, we don't need geometry @@ -1153,19 +1113,6 @@ std::string VKShader::fragment_interface_declare(const shader::ShaderCreateInfo ss << "layout(early_fragment_tests) in;\n"; } ss << "layout(" << to_string(info.depth_write_) << ") out float gl_FragDepth;\n"; - - ss << "\n/* Sub-pass Inputs. */\n"; - for (const ShaderCreateInfo::SubpassIn &input : info.subpass_inputs_) { - /* Declare global for input. */ - ss << to_string(input.type) << " " << input.name << ";\n"; - - std::stringstream ss_pre; - /* Populate the global before main. */ - ss_pre << " " << input.name << " = " << to_string(input.type) << "(0);\n"; - - pre_main += ss_pre.str(); - } - ss << "\n/* Outputs. */\n"; for (const ShaderCreateInfo::FragOut &output : info.fragment_outputs_) { ss << "layout(location = " << output.index; @@ -1263,102 +1210,6 @@ std::string VKShader::compute_layout_declare(const shader::ShaderCreateInfo &inf return ss.str(); } -/* -------------------------------------------------------------------- */ -/** \name Passthrough geometry shader emulation - * - * \{ */ - -std::string VKShader::workaround_geometry_shader_source_create( - const shader::ShaderCreateInfo &info) -{ - std::stringstream ss; - const VKWorkarounds &workarounds = VKBackend::get().device_get().workarounds_get(); - - const bool do_layer_workaround = workarounds.shader_output_layer && - bool(info.builtins_ & BuiltinBits::LAYER); - const bool do_viewport_workaround = workarounds.shader_output_viewport_index && - bool(info.builtins_ & BuiltinBits::VIEWPORT_INDEX); - const bool do_barycentric_workaround = bool(info.builtins_ & BuiltinBits::BARYCENTRIC_COORD); - - shader::ShaderCreateInfo info_modified = info; - info_modified.geometry_out_interfaces_ = info_modified.vertex_out_interfaces_; - /** - * NOTE(@fclem): Assuming we will render TRIANGLES. This will not work with other primitive - * types. In this case, it might not trigger an error on some implementations. - */ - info_modified.geometry_layout(PrimitiveIn::TRIANGLES, PrimitiveOut::TRIANGLE_STRIP, 3); - - ss << geometry_layout_declare(info_modified); - ss << geometry_interface_declare(info_modified); - int location = 0; - for (const StageInterfaceInfo *iface : info.vertex_out_interfaces_) { - for (const StageInterfaceInfo::InOut &inout : iface->inouts) { - location += get_location_count(inout.type); - } - } - - if (do_layer_workaround) { - ss << "layout(location=" << (location++) << ") in int gpu_Layer[];\n"; - } - if (do_viewport_workaround) { - ss << "layout(location=" << (location++) << ") in int gpu_ViewportIndex[];\n"; - } - if (do_barycentric_workaround) { - ss << "flat out vec4 gpu_pos[3];\n"; - ss << "smooth out vec3 gpu_BaryCoord;\n"; - ss << "noperspective out vec3 gpu_BaryCoordNoPersp;\n"; - } - ss << "\n"; - - ss << "void main()\n"; - ss << "{\n"; - if (do_layer_workaround) { - ss << " gl_Layer = gpu_Layer[0];\n"; - } - if (do_viewport_workaround) { - ss << " gl_ViewportIndex = gpu_ViewportIndex[0];\n"; - } - if (do_barycentric_workaround) { - ss << " gpu_pos[0] = gl_in[0].gl_Position;\n"; - ss << " gpu_pos[1] = gl_in[1].gl_Position;\n"; - ss << " gpu_pos[2] = gl_in[2].gl_Position;\n"; - } - for (auto i : IndexRange(3)) { - for (StageInterfaceInfo *iface : info_modified.vertex_out_interfaces_) { - for (auto &inout : iface->inouts) { - ss << " " << iface->instance_name << "_out." << inout.name; - ss << " = " << iface->instance_name << "_in[" << i << "]." << inout.name << ";\n"; - } - } - if (do_barycentric_workaround) { - ss << " gpu_BaryCoordNoPersp = gpu_BaryCoord ="; - ss << " vec3(" << int(i == 0) << ", " << int(i == 1) << ", " << int(i == 2) << ");\n"; - } - ss << " gl_Position = gl_in[" << i << "].gl_Position;\n"; - ss << " EmitVertex();\n"; - } - ss << "}\n"; - return ss.str(); -} - -bool VKShader::do_geometry_shader_injection(const shader::ShaderCreateInfo *info) -{ - const VKWorkarounds &workarounds = VKBackend::get().device_get().workarounds_get(); - BuiltinBits builtins = info->builtins_; - if (bool(builtins & BuiltinBits::BARYCENTRIC_COORD)) { - return true; - } - if (workarounds.shader_output_layer && bool(builtins & BuiltinBits::LAYER)) { - return true; - } - if (workarounds.shader_output_viewport_index && bool(builtins & BuiltinBits::VIEWPORT_INDEX)) { - return true; - } - return false; -} - -/** \} */ - int VKShader::program_handle_get() const { return -1; diff --git a/source/blender/gpu/vulkan/vk_shader.hh b/source/blender/gpu/vulkan/vk_shader.hh index 3d8cf635f2b..76702d58725 100644 --- a/source/blender/gpu/vulkan/vk_shader.hh +++ b/source/blender/gpu/vulkan/vk_shader.hh @@ -60,7 +60,7 @@ class VKShader : public Shader { std::string geometry_layout_declare(const shader::ShaderCreateInfo &info) const override; std::string compute_layout_declare(const shader::ShaderCreateInfo &info) const override; - /* Unused: SSBO vertex fetch draw parameters. */ + /* Unusued: SSBO vertex fetch draw parameters. */ bool get_uses_ssbo_vertex_fetch() const override { return false; @@ -85,16 +85,6 @@ class VKShader : public Shader { const GPUPrimType prim_type, const VKVertexAttributeObject &vertex_attribute_object); - bool is_graphics_shader() const - { - return !is_compute_shader(); - } - - bool is_compute_shader() const - { - return compute_module_ != VK_NULL_HANDLE; - } - private: Vector compile_glsl_to_spirv(Span sources, shaderc_shader_kind kind); void build_shader_module(Span spirv_module, VkShaderModule *r_shader_module); @@ -106,12 +96,15 @@ class VKShader : public Shader { const shader::ShaderCreateInfo &info); bool finalize_pipeline_layout(VkDevice vk_device, const VKShaderInterface &shader_interface); - /** - * \brief features available on newer implementation such as native barycentric coordinates - * and layered rendering, necessitate a geometry shader to work on older hardware. - */ - std::string workaround_geometry_shader_source_create(const shader::ShaderCreateInfo &info); - bool do_geometry_shader_injection(const shader::ShaderCreateInfo *info); + bool is_graphics_shader() const + { + return !is_compute_shader(); + } + + bool is_compute_shader() const + { + return compute_module_ != VK_NULL_HANDLE; + } }; static inline VKShader &unwrap(Shader &shader) diff --git a/source/blender/gpu/vulkan/vk_state_manager.cc b/source/blender/gpu/vulkan/vk_state_manager.cc index 1234dff12f1..3619624ca7f 100644 --- a/source/blender/gpu/vulkan/vk_state_manager.cc +++ b/source/blender/gpu/vulkan/vk_state_manager.cc @@ -52,9 +52,10 @@ void VKStateManager::force_state() void VKStateManager::issue_barrier(eGPUBarrier /*barrier_bits*/) { VKContext &context = *VKContext::get(); + VKCommandBuffer &command_buffer = context.command_buffer_get(); /* TODO: Pipeline barriers should be added. We might be able to extract it from * the actual pipeline, later on, but for now we submit the work as barrier. */ - context.flush(); + command_buffer.submit(); } void VKStateManager::texture_bind(Texture *tex, GPUSamplerState /*sampler*/, int unit) diff --git a/source/blender/gpu/vulkan/vk_storage_buffer.cc b/source/blender/gpu/vulkan/vk_storage_buffer.cc index 978ca5bcfe5..873c04d699e 100644 --- a/source/blender/gpu/vulkan/vk_storage_buffer.cc +++ b/source/blender/gpu/vulkan/vk_storage_buffer.cc @@ -86,21 +86,17 @@ void VKStorageBuffer::copy_sub(VertBuf *src, uint dst_offset, uint src_offset, u region.size = copy_size; VKContext &context = *VKContext::get(); - VKCommandBuffers &command_buffers = context.command_buffers_get(); - command_buffers.copy(buffer_, src_vertex_buffer.vk_handle(), Span(®ion, 1)); - context.flush(); -} - -void VKStorageBuffer::async_flush_to_host() -{ - GPU_memory_barrier(GPU_BARRIER_BUFFER_UPDATE); + VKCommandBuffer &command_buffer = context.command_buffer_get(); + command_buffer.copy(buffer_, src_vertex_buffer.vk_handle(), Span(®ion, 1)); + command_buffer.submit(); } void VKStorageBuffer::read(void *data) { ensure_allocated(); VKContext &context = *VKContext::get(); - context.flush(); + VKCommandBuffer &command_buffer = context.command_buffer_get(); + command_buffer.submit(); buffer_.read(data); } diff --git a/source/blender/gpu/vulkan/vk_storage_buffer.hh b/source/blender/gpu/vulkan/vk_storage_buffer.hh index 8c6d317fd4e..889f94570b3 100644 --- a/source/blender/gpu/vulkan/vk_storage_buffer.hh +++ b/source/blender/gpu/vulkan/vk_storage_buffer.hh @@ -33,7 +33,6 @@ class VKStorageBuffer : public StorageBuf, public VKBindableResource { void clear(uint32_t clear_value) override; void copy_sub(VertBuf *src, uint dst_offset, uint src_offset, uint copy_size) override; void read(void *data) override; - void async_flush_to_host() override; VkBuffer vk_handle() const { diff --git a/source/blender/gpu/vulkan/vk_texture.cc b/source/blender/gpu/vulkan/vk_texture.cc index 29882563347..7291758e7f3 100644 --- a/source/blender/gpu/vulkan/vk_texture.cc +++ b/source/blender/gpu/vulkan/vk_texture.cc @@ -25,7 +25,7 @@ namespace blender::gpu { VKTexture::~VKTexture() { - if (vk_image_ != VK_NULL_HANDLE && allocation_ != VK_NULL_HANDLE) { + if (is_allocated() && !is_texture_view()) { VKDevice &device = VKBackend::get().device_get(); device.discard_image(vk_image_, allocation_); @@ -48,8 +48,10 @@ void VKTexture::generate_mipmap() return; } + ensure_allocated(); + VKContext &context = *VKContext::get(); - VKCommandBuffers &command_buffers = context.command_buffers_get(); + VKCommandBuffer &command_buffer = context.command_buffer_get(); layout_ensure(context, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); for (int src_mipmap : IndexRange(mipmaps_ - 1)) { @@ -92,14 +94,14 @@ void VKTexture::generate_mipmap() image_blit.dstSubresource.baseArrayLayer = 0; image_blit.dstSubresource.layerCount = vk_layer_count(1); - command_buffers.blit(*this, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - *this, - VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - Span(&image_blit, 1)); + command_buffer.blit(*this, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + *this, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + Span(&image_blit, 1)); /* TODO: Until we do actual command encoding we need to submit each transfer operation * individually. */ - context.flush(); + command_buffer.submit(); } /* Ensure that all mipmap levels are in `VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL`. * All MIP-levels are except the last one. */ @@ -110,26 +112,6 @@ void VKTexture::generate_mipmap() current_layout_set(VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); } -void VKTexture::copy_to(VKTexture &dst_texture, VkImageAspectFlagBits vk_image_aspect) -{ - VKContext &context = *VKContext::get(); - layout_ensure(context, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); - dst_texture.layout_ensure(context, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); - - VkImageCopy region = {}; - region.srcSubresource.aspectMask = vk_image_aspect; - region.srcSubresource.mipLevel = 0; - region.srcSubresource.layerCount = vk_layer_count(1); - region.dstSubresource.aspectMask = vk_image_aspect; - region.dstSubresource.mipLevel = 0; - region.dstSubresource.layerCount = vk_layer_count(1); - region.extent = vk_extent_3d(0); - - VKCommandBuffers &command_buffers = context.command_buffers_get(); - command_buffers.copy(dst_texture, *this, Span(®ion, 1)); - context.flush(); -} - void VKTexture::copy_to(Texture *tex) { VKTexture *dst = unwrap(tex); @@ -140,15 +122,35 @@ void VKTexture::copy_to(Texture *tex) BLI_assert(!is_texture_view()); UNUSED_VARS_NDEBUG(src); - copy_to(*dst, to_vk_image_aspect_flag_bits(format_)); + VKContext &context = *VKContext::get(); + ensure_allocated(); + layout_ensure(context, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); + dst->ensure_allocated(); + dst->layout_ensure(context, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + + VkImageCopy region = {}; + region.srcSubresource.aspectMask = to_vk_image_aspect_flag_bits(format_); + region.srcSubresource.mipLevel = 0; + region.srcSubresource.layerCount = vk_layer_count(1); + region.dstSubresource.aspectMask = to_vk_image_aspect_flag_bits(format_); + region.dstSubresource.mipLevel = 0; + region.dstSubresource.layerCount = vk_layer_count(1); + region.extent = vk_extent_3d(0); + + VKCommandBuffer &command_buffer = context.command_buffer_get(); + command_buffer.copy(*dst, *this, Span(®ion, 1)); + command_buffer.submit(); } void VKTexture::clear(eGPUDataFormat format, const void *data) { BLI_assert(!is_texture_view()); + if (!is_allocated()) { + allocate(); + } VKContext &context = *VKContext::get(); - VKCommandBuffers &command_buffers = context.command_buffers_get(); + VKCommandBuffer &command_buffer = context.command_buffer_get(); VkClearColorValue clear_color = to_vk_clear_color_value(format, data); VkImageSubresourceRange range = {0}; range.aspectMask = to_vk_image_aspect_flag_bits(format_); @@ -156,7 +158,7 @@ void VKTexture::clear(eGPUDataFormat format, const void *data) range.layerCount = VK_REMAINING_ARRAY_LAYERS; layout_ensure(context, VK_IMAGE_LAYOUT_GENERAL); - command_buffers.clear( + command_buffer.clear( vk_image_, current_layout_get(), clear_color, Span(&range, 1)); } @@ -166,8 +168,11 @@ void VKTexture::clear_depth_stencil(const eGPUFrameBufferBits buffers, { BLI_assert(buffers & (GPU_DEPTH_BIT | GPU_STENCIL_BIT)); + if (!is_allocated()) { + allocate(); + } VKContext &context = *VKContext::get(); - VKCommandBuffers &command_buffers = context.command_buffers_get(); + VKCommandBuffer &command_buffer = context.command_buffer_get(); VkClearDepthStencilValue clear_depth_stencil; clear_depth_stencil.depth = clear_depth; clear_depth_stencil.stencil = clear_stencil; @@ -177,10 +182,10 @@ void VKTexture::clear_depth_stencil(const eGPUFrameBufferBits buffers, range.layerCount = VK_REMAINING_ARRAY_LAYERS; layout_ensure(context, VK_IMAGE_LAYOUT_GENERAL); - command_buffers.clear(vk_image_, - current_layout_get(), - clear_depth_stencil, - Span(&range, 1)); + command_buffer.clear(vk_image_, + current_layout_get(), + clear_depth_stencil, + Span(&range, 1)); } void VKTexture::swizzle_set(const char swizzle_mask[4]) @@ -225,9 +230,9 @@ void VKTexture::read_sub(int mip, eGPUDataFormat format, const int area[4], void region.imageSubresource.mipLevel = mip; region.imageSubresource.layerCount = vk_layer_count(1); - VKCommandBuffers &command_buffers = context.command_buffers_get(); - command_buffers.copy(staging_buffer, *this, Span(®ion, 1)); - context.flush(); + VKCommandBuffer &command_buffer = context.command_buffer_get(); + command_buffer.copy(staging_buffer, *this, Span(®ion, 1)); + command_buffer.submit(); convert_device_to_host(r_data, staging_buffer.mapped_memory_get(), sample_len, format, format_); } @@ -250,9 +255,13 @@ void VKTexture::update_sub( int mip, int offset[3], int extent_[3], eGPUDataFormat format, const void *data) { BLI_assert(!is_texture_view()); + if (!is_allocated()) { + allocate(); + } /* Vulkan images cannot be directly mapped to host memory and requires a staging buffer. */ VKContext &context = *VKContext::get(); + VKBuffer staging_buffer; int layers = vk_layer_count(1); int3 extent = int3(extent_[0], max_ii(extent_[1], 1), max_ii(extent_[2], 1)); size_t sample_len = extent.x * extent.y * extent.z; @@ -266,15 +275,26 @@ void VKTexture::update_sub( extent.z = 1; } - VKBuffer staging_buffer; staging_buffer.create(device_memory_size, GPU_USAGE_DYNAMIC, VK_BUFFER_USAGE_TRANSFER_SRC_BIT); - convert_host_to_device(staging_buffer.mapped_memory_get(), data, sample_len, format, format_); + + uint buffer_row_length = context.state_manager_get().texture_unpack_row_length_get(); + if (buffer_row_length) { + /* Use custom row length #GPU_texture_unpack_row_length */ + convert_host_to_device(staging_buffer.mapped_memory_get(), + data, + uint2(extent), + buffer_row_length, + format, + format_); + } + else { + convert_host_to_device(staging_buffer.mapped_memory_get(), data, sample_len, format, format_); + } VkBufferImageCopy region = {}; region.imageExtent.width = extent.x; region.imageExtent.height = extent.y; region.imageExtent.depth = extent.z; - region.bufferRowLength = context.state_manager_get().texture_unpack_row_length_get(); region.imageOffset.x = offset[0]; region.imageOffset.y = offset[1]; region.imageOffset.z = offset[2]; @@ -283,9 +303,9 @@ void VKTexture::update_sub( region.imageSubresource.layerCount = layers; layout_ensure(context, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); - VKCommandBuffers &command_buffers = context.command_buffers_get(); - command_buffers.copy(*this, staging_buffer, Span(®ion, 1)); - context.flush(); + VKCommandBuffer &command_buffer = context.command_buffer_get(); + command_buffer.copy(*this, staging_buffer, Span(®ion, 1)); + command_buffer.submit(); } void VKTexture::update_sub(int /*offset*/[3], @@ -305,6 +325,10 @@ uint VKTexture::gl_bindcode_get() const bool VKTexture::init_internal() { + /* Initialization can only happen after the usage is known. By the current API this isn't set + * at this moment, so we cannot initialize here. The initialization is postponed until the + * allocation of the texture on the device. */ + const VKDevice &device = VKBackend::get().device_get(); const VKWorkarounds &workarounds = device.workarounds_get(); if (format_ == GPU_DEPTH_COMPONENT24 && workarounds.not_aligned_pixel_formats) { @@ -314,10 +338,7 @@ bool VKTexture::init_internal() format_ = GPU_DEPTH32F_STENCIL8; } - if (!allocate()) { - return false; - } - + /* TODO: return false when texture format isn't supported. */ return true; } @@ -339,9 +360,9 @@ bool VKTexture::init_internal(GPUVertBuf *vbo) VKContext &context = *VKContext::get(); layout_ensure(context, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); - VKCommandBuffers &command_buffers = context.command_buffers_get(); - command_buffers.copy(*this, vertex_buffer->buffer_, Span(®ion, 1)); - context.flush(); + VKCommandBuffer &command_buffer = context.command_buffer_get(); + command_buffer.copy(*this, vertex_buffer->buffer_, Span(®ion, 1)); + command_buffer.submit(); return true; } @@ -367,6 +388,23 @@ bool VKTexture::is_texture_view() const return source_texture_ != nullptr; } +void VKTexture::ensure_allocated() +{ + if (is_texture_view()) { + source_texture_->ensure_allocated(); + return; + } + + if (!is_allocated()) { + allocate(); + } +} + +bool VKTexture::is_allocated() const +{ + return (vk_image_ != VK_NULL_HANDLE && allocation_ != VK_NULL_HANDLE) || is_texture_view(); +} + static VkImageUsageFlagBits to_vk_image_usage(const eGPUTextureUsage usage, const eGPUTextureFormatFlag format_flag) { @@ -411,9 +449,7 @@ static VkImageUsageFlagBits to_vk_image_usage(const eGPUTextureUsage usage, return result; } -static VkImageCreateFlagBits to_vk_image_create(const eGPUTextureType texture_type, - const eGPUTextureFormatFlag format_flag, - const eGPUTextureUsage usage) +static VkImageCreateFlagBits to_vk_image_create(const eGPUTextureType texture_type) { VkImageCreateFlagBits result = static_cast(0); @@ -421,24 +457,20 @@ static VkImageCreateFlagBits to_vk_image_create(const eGPUTextureType texture_ty result = static_cast(result | VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT); } - /* sRGB textures needs to be mutable as they can be used as non-sRGB frame-buffer attachments. */ - if (usage & GPU_TEXTURE_USAGE_ATTACHMENT && format_flag & GPU_FORMAT_SRGB) { - result = static_cast(result | VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT); - } - return result; } bool VKTexture::allocate() { BLI_assert(vk_image_ == VK_NULL_HANDLE); + BLI_assert(!is_allocated()); BLI_assert(!is_texture_view()); VKContext &context = *VKContext::get(); const VKDevice &device = VKBackend::get().device_get(); VkImageCreateInfo image_info = {}; image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; - image_info.flags = to_vk_image_create(type_, format_flag_, usage_get()); + image_info.flags = to_vk_image_create(type_); image_info.imageType = to_vk_image_type(type_); image_info.extent = vk_extent_3d(0); image_info.mipLevels = max_ii(mipmaps_, 1); @@ -492,6 +524,9 @@ bool VKTexture::allocate() void VKTexture::bind(int binding, shader::ShaderCreateInfo::Resource::BindType bind_type) { + if (!is_allocated()) { + allocate(); + } VKContext &context = *VKContext::get(); VKShader *shader = static_cast(context.shader); const VKShaderInterface &shader_interface = shader->interface_get(); @@ -557,7 +592,7 @@ void VKTexture::layout_ensure(VKContext &context, barrier.subresourceRange.levelCount = uint32_t(mipmap_range.size()); barrier.subresourceRange.baseArrayLayer = 0; barrier.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS; - context.command_buffers_get().pipeline_barrier(Span(&barrier, 1)); + context.command_buffer_get().pipeline_barrier(Span(&barrier, 1)); } /** \} */ @@ -576,13 +611,8 @@ void VKTexture::image_view_ensure() void VKTexture::image_view_update() { - image_view_.emplace(VKImageView(*this, - eImageViewUsage::ShaderBinding, - layer_range(), - mip_map_range(), - use_stencil_, - true, - name_)); + image_view_.emplace(VKImageView( + *this, eImageViewUsage::ShaderBinding, layer_range(), mip_map_range(), use_stencil_, name_)); } IndexRange VKTexture::mip_map_range() const diff --git a/source/blender/gpu/vulkan/vk_texture.hh b/source/blender/gpu/vulkan/vk_texture.hh index 818a5c88604..443cacc5441 100644 --- a/source/blender/gpu/vulkan/vk_texture.hh +++ b/source/blender/gpu/vulkan/vk_texture.hh @@ -56,7 +56,6 @@ class VKTexture : public Texture, public VKBindableResource { void generate_mipmap() override; void copy_to(Texture *tex) override; - void copy_to(VKTexture &dst_texture, VkImageAspectFlagBits vk_image_aspect); void clear(eGPUDataFormat format, const void *data) override; void clear_depth_stencil(const eGPUFrameBufferBits buffer, float clear_depth, @@ -86,6 +85,8 @@ class VKTexture : public Texture, public VKBindableResource { return vk_image_; } + void ensure_allocated(); + protected: bool init_internal() override; bool init_internal(GPUVertBuf *vbo) override; @@ -95,6 +96,9 @@ class VKTexture : public Texture, public VKBindableResource { /** Is this texture a view of another texture. */ bool is_texture_view() const; + /** Is this texture already allocated on device. */ + bool is_allocated() const; + /** * Allocate the texture of the device. Result is `true` when texture is successfully allocated * on the device. diff --git a/source/blender/gpu/vulkan/vk_vertex_attribute_object.cc b/source/blender/gpu/vulkan/vk_vertex_attribute_object.cc index 205cb71b8c6..d60f5dcbcb5 100644 --- a/source/blender/gpu/vulkan/vk_vertex_attribute_object.cc +++ b/source/blender/gpu/vulkan/vk_vertex_attribute_object.cc @@ -80,12 +80,12 @@ void VKVertexAttributeObject::bind_vbos(VKContext &context) BLI_assert(vbos[attribute.binding]); VKVertexBuffer &vbo = *vbos[attribute.binding]; vbo.upload(); - context.command_buffers_get().bind(attribute.binding, vbo, 0); + context.command_buffer_get().bind(attribute.binding, vbo, 0); } else { const VKBuffer &buffer = VKBackend::get().device_get().dummy_buffer_get(); const VKBufferWithOffset buffer_with_offset = {buffer, 0}; - context.command_buffers_get().bind(attribute.binding, buffer_with_offset); + context.command_buffer_get().bind(attribute.binding, buffer_with_offset); } } } @@ -104,12 +104,12 @@ void VKVertexAttributeObject::bind_buffers(VKContext &context) if (attribute.binding < buffers.size()) { VKBufferWithOffset &buffer = buffers[attribute.binding]; - context.command_buffers_get().bind(attribute.binding, buffer); + context.command_buffer_get().bind(attribute.binding, buffer); } else { const VKBuffer &buffer = VKBackend::get().device_get().dummy_buffer_get(); const VKBufferWithOffset buffer_with_offset = {buffer, 0}; - context.command_buffers_get().bind(attribute.binding, buffer_with_offset); + context.command_buffer_get().bind(attribute.binding, buffer_with_offset); } } } diff --git a/source/blender/gpu/vulkan/vk_vertex_buffer.cc b/source/blender/gpu/vulkan/vk_vertex_buffer.cc index d741f57a59c..f220f5742e1 100644 --- a/source/blender/gpu/vulkan/vk_vertex_buffer.cc +++ b/source/blender/gpu/vulkan/vk_vertex_buffer.cc @@ -89,7 +89,8 @@ void VKVertexBuffer::update_sub(uint /*start*/, uint /*len*/, const void * /*dat void VKVertexBuffer::read(void *data) const { VKContext &context = *VKContext::get(); - context.flush(); + VKCommandBuffer &command_buffer = context.command_buffer_get(); + command_buffer.submit(); buffer_.read(data); } diff --git a/source/blender/imbuf/intern/indexer.cc b/source/blender/imbuf/intern/indexer.cc index 55ba73f2a10..01d4e731265 100644 --- a/source/blender/imbuf/intern/indexer.cc +++ b/source/blender/imbuf/intern/indexer.cc @@ -17,7 +17,7 @@ #include "BLI_math_base.h" #include "BLI_path_util.h" #include "BLI_string.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_threads.h" #include "BLI_utildefines.h" #ifdef _WIN32 diff --git a/source/blender/imbuf/intern/oiio/openimageio_support.cc b/source/blender/imbuf/intern/oiio/openimageio_support.cc index 0fa42f14c1d..0f928ff78d5 100644 --- a/source/blender/imbuf/intern/oiio/openimageio_support.cc +++ b/source/blender/imbuf/intern/oiio/openimageio_support.cc @@ -308,7 +308,7 @@ bool imb_oiio_write(const WriteContext &ctx, const char *filepath, const ImageSp } else { /* If we are moving from an 1-channel format to n-channel we need to - * ensure the original data is copied into the higher channels. */ + * ensure the orignal data is copied into the higher channels. */ if (ctx.ibuf->channels == 1 && file_spec.nchannels > 1) { final_buf = ImageBuf(file_spec, InitializePixels::No); ImageBufAlgo::paste(final_buf, 0, 0, 0, 0, orig_buf); diff --git a/source/blender/imbuf/intern/openexr/openexr_api.cpp b/source/blender/imbuf/intern/openexr/openexr_api.cpp index fa67a8ff727..cd9903a57fb 100644 --- a/source/blender/imbuf/intern/openexr/openexr_api.cpp +++ b/source/blender/imbuf/intern/openexr/openexr_api.cpp @@ -66,7 +66,7 @@ #include #if defined(WIN32) -# include "utfconv.hh" +# include "utfconv.h" # include #else # include @@ -78,7 +78,7 @@ #include "BLI_fileops.h" #include "BLI_math_color.h" #include "BLI_mmap.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_threads.h" #include "BKE_idprop.h" @@ -157,7 +157,14 @@ class IMMapStream : public Imf::IStream { if (file < 0) { throw IEX_NAMESPACE::InputExc("file not found"); } + const size_t file_size = BLI_file_descriptor_size(file); + if (UNLIKELY(file_size == size_t(-1))) { + close(file); + throw IEX_NAMESPACE::InputExc("file size could not be accessed"); + } + _exrpos = 0; + _exrsize = file_size; imb_mmap_lock(); _mmap_file = BLI_mmap_open(file); imb_mmap_unlock(); @@ -166,7 +173,6 @@ class IMMapStream : public Imf::IStream { throw IEX_NAMESPACE::InputExc("BLI_mmap_open failed"); } _exrbuf = (uchar *)BLI_mmap_get_pointer(_mmap_file); - _exrsize = BLI_mmap_get_length(_mmap_file); } ~IMMapStream() override diff --git a/source/blender/imbuf/intern/readimage.cc b/source/blender/imbuf/intern/readimage.cc index 7ba5ad3908a..3f9022c2730 100644 --- a/source/blender/imbuf/intern/readimage.cc +++ b/source/blender/imbuf/intern/readimage.cc @@ -124,6 +124,11 @@ ImBuf *IMB_loadifffile(int file, int flags, char colorspace[IM_MAX_SPACE], const return nullptr; } + const size_t size = BLI_file_descriptor_size(file); + if (size == size_t(-1)) { + return nullptr; + } + imb_mmap_lock(); BLI_mmap_file *mmap_file = BLI_mmap_open(file); imb_mmap_unlock(); @@ -133,7 +138,6 @@ ImBuf *IMB_loadifffile(int file, int flags, char colorspace[IM_MAX_SPACE], const } mem = static_cast(BLI_mmap_get_pointer(mmap_file)); - const size_t size = BLI_mmap_get_length(mmap_file); ibuf = IMB_ibImageFromMemory(mem, size, flags, colorspace, descr); diff --git a/source/blender/imbuf/intern/thumbs.cc b/source/blender/imbuf/intern/thumbs.cc index f3e7018a18f..80714d6fdc1 100644 --- a/source/blender/imbuf/intern/thumbs.cc +++ b/source/blender/imbuf/intern/thumbs.cc @@ -18,7 +18,7 @@ #include "BLI_hash_md5.h" #include "BLI_path_util.h" #include "BLI_string.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_system.h" #include "BLI_threads.h" #include "BLI_utildefines.h" @@ -47,7 +47,7 @@ /* For SHGetSpecialFolderPath, has to be done before BLI_winstuff * because 'near' is disabled through BLI_windstuff */ # include "BLI_winstuff.h" -# include "utfconv.hh" +# include "utfconv.h" # include /* #chdir */ # include #endif diff --git a/source/blender/imbuf/intern/webp.cc b/source/blender/imbuf/intern/webp.cc index 8fbe95b32c0..9a76f6a9457 100644 --- a/source/blender/imbuf/intern/webp.cc +++ b/source/blender/imbuf/intern/webp.cc @@ -88,6 +88,11 @@ ImBuf *imb_load_filepath_thumbnail_webp(const char *filepath, return nullptr; } + const size_t data_size = BLI_file_descriptor_size(file); + if (UNLIKELY(data_size == size_t(-1))) { + return nullptr; + } + imb_mmap_lock(); BLI_mmap_file *mmap_file = BLI_mmap_open(file); imb_mmap_unlock(); @@ -97,7 +102,6 @@ ImBuf *imb_load_filepath_thumbnail_webp(const char *filepath, } const uchar *data = static_cast(BLI_mmap_get_pointer(mmap_file)); - const size_t data_size = BLI_mmap_get_length(mmap_file); WebPDecoderConfig config; if (!data || !WebPInitDecoderConfig(&config) || diff --git a/source/blender/io/alembic/exporter/abc_archive.cc b/source/blender/io/alembic/exporter/abc_archive.cc index c1335b0ef76..c5fe2bf2d92 100644 --- a/source/blender/io/alembic/exporter/abc_archive.cc +++ b/source/blender/io/alembic/exporter/abc_archive.cc @@ -19,7 +19,7 @@ # include "BLI_path_util.h" # include "BLI_string.h" -# include "utfconv.hh" +# include "utfconv.h" #endif namespace blender::io::alembic { diff --git a/source/blender/io/alembic/exporter/abc_export_capi.cc b/source/blender/io/alembic/exporter/abc_export_capi.cc index f4ed5b93292..7c540d1e10b 100644 --- a/source/blender/io/alembic/exporter/abc_export_capi.cc +++ b/source/blender/io/alembic/exporter/abc_export_capi.cc @@ -70,7 +70,12 @@ static void report_job_duration(const ExportJobData *data) std::cout << '\n'; } -static void export_startjob(void *customdata, wmJobWorkerStatus *worker_status) +static void export_startjob(void *customdata, + /* Cannot be const, this function implements wm_jobs_start_callback. + * NOLINTNEXTLINE: readability-non-const-parameter. */ + bool *stop, + bool *do_update, + float *progress) { ExportJobData *data = static_cast(customdata); data->was_canceled = false; @@ -80,8 +85,8 @@ static void export_startjob(void *customdata, wmJobWorkerStatus *worker_status) WM_set_locked_interface(data->wm, true); G.is_break = false; - worker_status->progress = 0.0f; - worker_status->do_update = true; + *progress = 0.0f; + *do_update = true; build_depsgraph(data->depsgraph, data->params.visible_objects_only); SubdivModifierDisabler subdiv_disabler(data->depsgraph); @@ -135,7 +140,7 @@ static void export_startjob(void *customdata, wmJobWorkerStatus *worker_status) for (; frame_it != frames_end; frame_it++) { double frame = *frame_it; - if (G.is_break || worker_status->stop) { + if (G.is_break || (stop != nullptr && *stop)) { break; } @@ -149,8 +154,8 @@ static void export_startjob(void *customdata, wmJobWorkerStatus *worker_status) iter.set_export_subset(export_subset); iter.iterate_and_write(); - worker_status->progress += progress_per_frame; - worker_status->do_update = true; + *progress += progress_per_frame; + *do_update = true; } } else { @@ -168,8 +173,8 @@ static void export_startjob(void *customdata, wmJobWorkerStatus *worker_status) data->export_ok = !data->was_canceled; - worker_status->progress = 1.0f; - worker_status->do_update = true; + *progress = 1.0f; + *do_update = true; } static void export_endjob(void *customdata) @@ -225,8 +230,11 @@ bool ABC_export(Scene *scene, WM_jobs_start(CTX_wm_manager(C), wm_job); } else { - wmJobWorkerStatus worker_status = {}; - blender::io::alembic::export_startjob(job, &worker_status); + /* Fake a job context, so that we don't need null pointer checks while exporting. */ + bool stop = false, do_update = false; + float progress = 0.0f; + + blender::io::alembic::export_startjob(job, &stop, &do_update, &progress); blender::io::alembic::export_endjob(job); export_ok = job->export_ok; diff --git a/source/blender/io/alembic/exporter/abc_writer_abstract.cc b/source/blender/io/alembic/exporter/abc_writer_abstract.cc index 80563d5cf9e..de49a49abc9 100644 --- a/source/blender/io/alembic/exporter/abc_writer_abstract.cc +++ b/source/blender/io/alembic/exporter/abc_writer_abstract.cc @@ -99,7 +99,7 @@ const Imath::Box3d &ABCAbstractWriter::bounding_box() const void ABCAbstractWriter::update_bounding_box(Object *object) { - const std::optional bb = BKE_object_boundbox_get(object); + const BoundBox *bb = BKE_object_boundbox_get(object); if (!bb) { if (object->type != OB_CAMERA) { diff --git a/source/blender/io/alembic/exporter/abc_writer_mesh.cc b/source/blender/io/alembic/exporter/abc_writer_mesh.cc index 58e6ac0d358..16f2c52bf3b 100644 --- a/source/blender/io/alembic/exporter/abc_writer_mesh.cc +++ b/source/blender/io/alembic/exporter/abc_writer_mesh.cc @@ -10,7 +10,6 @@ #include "abc_hierarchy_iterator.h" #include "intern/abc_axis_conversion.h" -#include "BLI_array_utils.hh" #include "BLI_assert.h" #include "BLI_math_vector.h" @@ -64,7 +63,8 @@ namespace blender::io::alembic { static void get_vertices(Mesh *mesh, std::vector &points); static void get_topology(Mesh *mesh, std::vector &face_verts, - std::vector &loop_counts); + std::vector &loop_counts, + bool &r_has_flat_shaded_face); static void get_edge_creases(Mesh *mesh, std::vector &indices, std::vector &lengths, @@ -72,7 +72,9 @@ static void get_edge_creases(Mesh *mesh, static void get_vert_creases(Mesh *mesh, std::vector &indices, std::vector &sharpnesses); -static void get_loop_normals(const Mesh *mesh, std::vector &normals); +static void get_loop_normals(Mesh *mesh, + std::vector &normals, + bool has_flat_shaded_poly); ABCGenericMeshWriter::ABCGenericMeshWriter(const ABCWriterConstructorArgs &args) : ABCAbstractWriter(args), is_subd_(false) @@ -213,9 +215,10 @@ void ABCGenericMeshWriter::write_mesh(HierarchyContext &context, Mesh *mesh) std::vector points, normals; std::vector face_verts, loop_counts; std::vector velocities; + bool has_flat_shaded_poly = false; get_vertices(mesh, points); - get_topology(mesh, face_verts, loop_counts); + get_topology(mesh, face_verts, loop_counts, has_flat_shaded_poly); if (!frame_has_been_written_ && args_.export_params->face_sets) { write_face_sets(context.object, mesh, abc_poly_mesh_schema_); @@ -246,7 +249,7 @@ void ABCGenericMeshWriter::write_mesh(HierarchyContext &context, Mesh *mesh) } if (args_.export_params->normals) { - get_loop_normals(mesh, normals); + get_loop_normals(mesh, normals, has_flat_shaded_poly); ON3fGeomParam::Sample normals_sample; if (!normals.empty()) { @@ -279,9 +282,10 @@ void ABCGenericMeshWriter::write_subd(HierarchyContext &context, Mesh *mesh) std::vector points; std::vector face_verts, loop_counts; std::vector edge_crease_indices, edge_crease_lengths, vert_crease_indices; + bool has_flat_poly = false; get_vertices(mesh, points); - get_topology(mesh, face_verts, loop_counts); + get_topology(mesh, face_verts, loop_counts, has_flat_poly); get_edge_creases(mesh, edge_crease_indices, edge_crease_lengths, edge_crease_sharpness); get_vert_creases(mesh, vert_crease_indices, vert_crease_sharpness); @@ -446,10 +450,20 @@ static void get_vertices(Mesh *mesh, std::vector &points) static void get_topology(Mesh *mesh, std::vector &face_verts, - std::vector &loop_counts) + std::vector &loop_counts, + bool &r_has_flat_shaded_face) { const OffsetIndices faces = mesh->faces(); const Span corner_verts = mesh->corner_verts(); + const bke::AttributeAccessor attributes = mesh->attributes(); + const VArray sharp_faces = *attributes.lookup_or_default( + "sharp_face", ATTR_DOMAIN_FACE, false); + for (const int i : sharp_faces.index_range()) { + if (sharp_faces[i]) { + r_has_flat_shaded_face = true; + break; + } + } face_verts.clear(); loop_counts.clear(); @@ -521,47 +535,36 @@ static void get_vert_creases(Mesh *mesh, } } -static void get_loop_normals(const Mesh *mesh, std::vector &normals) +static void get_loop_normals(Mesh *mesh, + std::vector &normals, + bool has_flat_shaded_poly) { normals.clear(); - switch (mesh->normals_domain()) { - case blender::bke::MeshNormalDomain::Point: { - /* If all faces are smooth shaded, and there are no custom normals, we don't need to - * export normals at all. This is also done by other software, see #71246. */ - break; - } - case blender::bke::MeshNormalDomain::Face: { - normals.resize(mesh->totloop); - MutableSpan dst_normals(reinterpret_cast(normals.data()), normals.size()); + /* If all polygons are smooth shaded, and there are no custom normals, we don't need to export + * normals at all. This is also done by other software, see #71246. */ + if (!has_flat_shaded_poly && !CustomData_has_layer(&mesh->loop_data, CD_CUSTOMLOOPNORMAL) && + (mesh->flag & ME_AUTOSMOOTH) == 0) + { + return; + } - const OffsetIndices faces = mesh->faces(); - const Span face_normals = mesh->face_normals(); - threading::parallel_for(faces.index_range(), 1024, [&](const IndexRange range) { - for (const int i : range) { - float3 y_up; - copy_yup_from_zup(y_up, face_normals[i]); - dst_normals.slice(faces[i]).fill(y_up); - } - }); - break; - } - case blender::bke::MeshNormalDomain::Corner: { - normals.resize(mesh->totloop); - MutableSpan dst_normals(reinterpret_cast(normals.data()), normals.size()); + BKE_mesh_calc_normals_split(mesh); + const float(*lnors)[3] = static_cast( + CustomData_get_layer(&mesh->loop_data, CD_NORMAL)); + BLI_assert_msg(lnors != nullptr, "BKE_mesh_calc_normals_split() should have computed CD_NORMAL"); - /* NOTE: data needs to be written in the reverse order. */ - const OffsetIndices faces = mesh->faces(); - const Span corner_normals = mesh->corner_normals(); - threading::parallel_for(faces.index_range(), 1024, [&](const IndexRange range) { - for (const int i : range) { - const IndexRange face = faces[i]; - for (const int i : face.index_range()) { - copy_yup_from_zup(dst_normals[face.last(i)], corner_normals[face[i]]); - } - } - }); - break; + normals.resize(mesh->totloop); + + /* NOTE: data needs to be written in the reverse order. */ + int abc_index = 0; + const OffsetIndices faces = mesh->faces(); + + for (const int i : faces.index_range()) { + const IndexRange face = faces[i]; + for (int j = face.size() - 1; j >= 0; j--, abc_index++) { + int blender_index = face[j]; + copy_yup_from_zup(normals[abc_index].getValue(), lnors[blender_index]); } } } diff --git a/source/blender/io/alembic/intern/abc_reader_archive.cc b/source/blender/io/alembic/intern/abc_reader_archive.cc index d79bdf25a9b..46c74941fa1 100644 --- a/source/blender/io/alembic/intern/abc_reader_archive.cc +++ b/source/blender/io/alembic/intern/abc_reader_archive.cc @@ -16,7 +16,7 @@ #include "BLI_string.h" #ifdef WIN32 -# include "utfconv.hh" +# include "utfconv.h" #endif #include diff --git a/source/blender/io/alembic/intern/abc_reader_mesh.cc b/source/blender/io/alembic/intern/abc_reader_mesh.cc index 9826c1dd3dd..085315d9411 100644 --- a/source/blender/io/alembic/intern/abc_reader_mesh.cc +++ b/source/blender/io/alembic/intern/abc_reader_mesh.cc @@ -289,6 +289,7 @@ static void process_loop_normals(CDStreamConfig &config, const N3fArraySamplePtr } } + mesh->flag |= ME_AUTOSMOOTH; BKE_mesh_set_custom_normals(mesh, lnors); MEM_freeN(lnors); @@ -311,6 +312,7 @@ static void process_vertex_normals(CDStreamConfig &config, copy_zup_from_yup(vert_normals[index], vertex_normals[index].getValue()); } + config.mesh->flag |= ME_AUTOSMOOTH; BKE_mesh_set_custom_normals_from_verts(config.mesh, vert_normals); MEM_freeN(vert_normals); } diff --git a/source/blender/io/alembic/intern/alembic_capi.cc b/source/blender/io/alembic/intern/alembic_capi.cc index 09cdc030033..7d69c7066d7 100644 --- a/source/blender/io/alembic/intern/alembic_capi.cc +++ b/source/blender/io/alembic/intern/alembic_capi.cc @@ -101,7 +101,7 @@ static void add_object_path(ListBase *object_paths, const IObject &object) BLI_addtail(object_paths, abc_path); } -// #define USE_NURBS +//#define USE_NURBS /* NOTE: this function is similar to visit_objects below, need to keep them in * sync. */ @@ -453,15 +453,15 @@ static void report_job_duration(const ImportJobData *data) std::cout << '\n'; } -static void import_startjob(void *user_data, wmJobWorkerStatus *worker_status) +static void import_startjob(void *user_data, bool *stop, bool *do_update, float *progress) { SCOPE_TIMER("Alembic import, objects reading and creation"); ImportJobData *data = static_cast(user_data); - data->stop = &worker_status->stop; - data->do_update = &worker_status->do_update; - data->progress = &worker_status->progress; + data->stop = stop; + data->do_update = do_update; + data->progress = progress; data->start_time = blender::timeit::Clock::now(); WM_set_locked_interface(data->wm, true); @@ -721,8 +721,11 @@ bool ABC_import(bContext *C, WM_jobs_start(CTX_wm_manager(C), wm_job); } else { - wmJobWorkerStatus worker_status = {}; - import_startjob(job, &worker_status); + /* Fake a job context, so that we don't need null pointer checks while importing. */ + bool stop = false, do_update = false; + float progress = 0.0f; + + import_startjob(job, &stop, &do_update, &progress); import_endjob(job); import_ok = job->import_ok; diff --git a/source/blender/io/collada/AnimationImporter.cpp b/source/blender/io/collada/AnimationImporter.cpp index 80a49af58d8..bc4933d810a 100644 --- a/source/blender/io/collada/AnimationImporter.cpp +++ b/source/blender/io/collada/AnimationImporter.cpp @@ -18,7 +18,7 @@ #include "BLI_listbase.h" #include "BLI_math_matrix.h" #include "BLI_string.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLT_translation.h" diff --git a/source/blender/io/collada/GeometryExporter.cpp b/source/blender/io/collada/GeometryExporter.cpp index 1b85a0f8c5c..e72b414b83c 100644 --- a/source/blender/io/collada/GeometryExporter.cpp +++ b/source/blender/io/collada/GeometryExporter.cpp @@ -620,19 +620,22 @@ void GeometryExporter::create_normals(std::vector &normals, const Span vert_normals = me->vert_normals(); const blender::OffsetIndices faces = me->faces(); const Span corner_verts = me->corner_verts(); + const float(*lnors)[3] = nullptr; + bool use_custom_normals = false; const bke::AttributeAccessor attributes = me->attributes(); const VArray sharp_faces = *attributes.lookup_or_default( "sharp_face", ATTR_DOMAIN_FACE, false); - blender::Span corner_normals; - if (me->normals_domain() == blender::bke::MeshNormalDomain::Corner) { - corner_normals = me->corner_normals(); + BKE_mesh_calc_normals_split(me); + if (CustomData_has_layer(&me->loop_data, CD_NORMAL)) { + lnors = (float(*)[3])CustomData_get_layer(&me->loop_data, CD_NORMAL); + use_custom_normals = true; } for (const int face_index : faces.index_range()) { const IndexRange face = faces[face_index]; - bool use_vert_normals = !corner_normals.is_empty() || !sharp_faces[face_index]; + bool use_vert_normals = use_custom_normals || !sharp_faces[face_index]; if (!use_vert_normals) { /* For flat faces use face normal as vertex normal: */ @@ -650,8 +653,8 @@ void GeometryExporter::create_normals(std::vector &normals, if (use_vert_normals) { float normalized[3]; - if (!corner_normals.is_empty()) { - normalize_v3_v3(normalized, corner_normals[corner]); + if (use_custom_normals) { + normalize_v3_v3(normalized, lnors[corner]); } else { copy_v3_v3(normalized, vert_normals[corner_verts[corner]]); diff --git a/source/blender/io/collada/MeshImporter.cpp b/source/blender/io/collada/MeshImporter.cpp index 6486e4ee789..8b41b4bf72f 100644 --- a/source/blender/io/collada/MeshImporter.cpp +++ b/source/blender/io/collada/MeshImporter.cpp @@ -1173,6 +1173,7 @@ bool MeshImporter::write_geometry(const COLLADAFW::Geometry *geom) } else { BKE_mesh_set_custom_normals(me, reinterpret_cast(loop_normals.data())); + me->flag |= ME_AUTOSMOOTH; } } diff --git a/source/blender/io/gpencil/intern/gpencil_io_export_pdf.cc b/source/blender/io/gpencil/intern/gpencil_io_export_pdf.cc index daa602097c3..5b23f1aeeee 100644 --- a/source/blender/io/gpencil/intern/gpencil_io_export_pdf.cc +++ b/source/blender/io/gpencil/intern/gpencil_io_export_pdf.cc @@ -30,7 +30,7 @@ #include "ED_view3d.hh" #ifdef WIN32 -# include "utfconv.hh" +# include "utfconv.h" #endif #include "UI_view2d.hh" diff --git a/source/blender/io/gpencil/intern/gpencil_io_export_svg.cc b/source/blender/io/gpencil/intern/gpencil_io_export_svg.cc index c9146d26a56..1ef2a83f7de 100644 --- a/source/blender/io/gpencil/intern/gpencil_io_export_svg.cc +++ b/source/blender/io/gpencil/intern/gpencil_io_export_svg.cc @@ -30,7 +30,7 @@ #include "ED_view3d.hh" #ifdef WIN32 -# include "utfconv.hh" +# include "utfconv.h" #endif #include "UI_view2d.hh" diff --git a/source/blender/io/stl/importer/stl_import_mesh.cc b/source/blender/io/stl/importer/stl_import_mesh.cc index ec7d8af1869..ad035501011 100644 --- a/source/blender/io/stl/importer/stl_import_mesh.cc +++ b/source/blender/io/stl/importer/stl_import_mesh.cc @@ -92,6 +92,7 @@ Mesh *STLMeshHelper::to_mesh() if (use_custom_normals_ && loop_normals_.size() == mesh->totloop) { BKE_mesh_set_custom_normals(mesh, reinterpret_cast(loop_normals_.data())); + mesh->flag |= ME_AUTOSMOOTH; } return mesh; diff --git a/source/blender/io/usd/hydra/mesh.cc b/source/blender/io/usd/hydra/mesh.cc index 7dac3b15bc9..581fc46e8cb 100644 --- a/source/blender/io/usd/hydra/mesh.cc +++ b/source/blender/io/usd/hydra/mesh.cc @@ -8,7 +8,6 @@ #include "BLI_string.h" -#include "BKE_attribute.h" #include "BKE_material.h" #include "BKE_mesh.hh" #include "BKE_mesh_runtime.hh" @@ -232,10 +231,9 @@ void MeshData::write_submeshes(const Mesh *mesh) const Span corner_verts = mesh->corner_verts(); const Span looptris = mesh->looptris(); - Span corner_normals; - if (mesh->normals_domain() == blender::bke::MeshNormalDomain::Corner) { - corner_normals = mesh->corner_normals(); - } + Array corner_normals(mesh->totloop); + BKE_mesh_calc_normals_split_ex( + mesh, nullptr, reinterpret_cast(corner_normals.data())); const float2 *uv_map = static_cast( CustomData_get_layer(&mesh->loop_data, CD_PROP_FLOAT2)); diff --git a/source/blender/io/usd/hydra/usd_scene_delegate.cc b/source/blender/io/usd/hydra/usd_scene_delegate.cc index c069626a6f9..90acbd271a6 100644 --- a/source/blender/io/usd/hydra/usd_scene_delegate.cc +++ b/source/blender/io/usd/hydra/usd_scene_delegate.cc @@ -9,13 +9,9 @@ #include "BLI_string.h" #include "BKE_appdir.h" -#include "BKE_report.h" #include "DEG_depsgraph_query.hh" -#include "WM_api.hh" -#include "WM_types.hh" - #include "usd.h" #include "usd.hh" @@ -54,14 +50,6 @@ void USDSceneDelegate::populate(Depsgraph *depsgraph) params.export_textures = false; /* Don't copy all textures, is slow. */ params.evaluation_mode = DEG_get_mode(depsgraph); - /* NOTE: Since the reports list will be `nullptr` here, reports generated by export code from - * this call will only be printed to console. */ - wmJobWorkerStatus worker_status = {}; - ReportList worker_reports = {}; - BKE_reports_init(&worker_reports, RPT_PRINT | RPT_STORE); - worker_status.reports = &worker_reports; - params.worker_status = &worker_status; - /* Create clean directory for export. */ BLI_delete(temp_dir_.c_str(), true, true); BLI_dir_create_recursive(temp_dir_.c_str()); @@ -74,8 +62,6 @@ void USDSceneDelegate::populate(Depsgraph *depsgraph) stage_ = io::usd::export_to_stage(params, depsgraph, temp_file_.c_str()); delegate_ = std::make_unique(render_index_, delegate_id_); delegate_->Populate(stage_->GetPseudoRoot()); - - WM_reports_from_reports_move(nullptr, &worker_reports); } } // namespace blender::io::hydra diff --git a/source/blender/io/usd/intern/usd_asset_utils.cc b/source/blender/io/usd/intern/usd_asset_utils.cc index 5e9a79db6ff..db547e9385f 100644 --- a/source/blender/io/usd/intern/usd_asset_utils.cc +++ b/source/blender/io/usd/intern/usd_asset_utils.cc @@ -10,7 +10,6 @@ #include #include "BKE_main.h" -#include "BKE_report.h" #include "BLI_fileops.h" #include "BLI_path_util.h" @@ -53,18 +52,17 @@ static std::pair split_udim_pattern(const std::string /* Return the asset file base name, with special handling of * package relative paths. */ -static std::string get_asset_base_name(const char *src_path, ReportList *reports) +static std::string get_asset_base_name(const char *src_path) { char base_name[FILE_MAXFILE]; if (pxr::ArIsPackageRelativePath(src_path)) { std::pair split = pxr::ArSplitPackageRelativePathInner(src_path); if (split.second.empty()) { - BKE_reportf(reports, - RPT_WARNING, - "%s: Couldn't determine package-relative file name from path %s", - __func__, - src_path); + WM_reportf(RPT_WARNING, + "%s: Couldn't determine package-relative file name from path %s", + __func__, + src_path); return src_path; } BLI_path_split_file_part(split.second.c_str(), base_name, sizeof(base_name)); @@ -79,10 +77,9 @@ static std::string get_asset_base_name(const char *src_path, ReportList *reports /* Copy an asset to a destination directory. */ static std::string copy_asset_to_directory(const char *src_path, const char *dest_dir_path, - eUSDTexNameCollisionMode name_collision_mode, - ReportList *reports) + eUSDTexNameCollisionMode name_collision_mode) { - std::string base_name = get_asset_base_name(src_path, reports); + std::string base_name = get_asset_base_name(src_path); char dest_file_path[FILE_MAX]; BLI_path_join(dest_file_path, sizeof(dest_file_path), dest_dir_path, base_name.c_str()); @@ -92,13 +89,8 @@ static std::string copy_asset_to_directory(const char *src_path, return dest_file_path; } - if (!copy_asset(src_path, dest_file_path, name_collision_mode, reports)) { - BKE_reportf(reports, - RPT_WARNING, - "%s: Couldn't copy file %s to %s", - __func__, - src_path, - dest_file_path); + if (!copy_asset(src_path, dest_file_path, name_collision_mode)) { + WM_reportf(RPT_WARNING, "%s: Couldn't copy file %s to %s", __func__, src_path, dest_file_path); return src_path; } @@ -107,13 +99,12 @@ static std::string copy_asset_to_directory(const char *src_path, static std::string copy_udim_asset_to_directory(const char *src_path, const char *dest_dir_path, - eUSDTexNameCollisionMode name_collision_mode, - ReportList *reports) + eUSDTexNameCollisionMode name_collision_mode) { /* Get prefix and suffix from udim pattern. */ std::pair splitPath = split_udim_pattern(src_path); if (splitPath.first.empty() || splitPath.second.empty()) { - BKE_reportf(reports, RPT_ERROR, "%s: Couldn't split UDIM pattern %s", __func__, src_path); + WM_reportf(RPT_ERROR, "%s: Couldn't split UDIM pattern %s", __func__, src_path); return src_path; } @@ -127,11 +118,11 @@ static std::string copy_udim_asset_to_directory(const char *src_path, for (int i = UDIM_START_TILE; i < UDIM_END_TILE; ++i) { const std::string src_udim = splitPath.first + std::to_string(i) + splitPath.second; if (asset_exists(src_udim.c_str())) { - copy_asset_to_directory(src_udim.c_str(), dest_dir_path, name_collision_mode, reports); + copy_asset_to_directory(src_udim.c_str(), dest_dir_path, name_collision_mode); } } - const std::string src_file_name = get_asset_base_name(src_path, reports); + const std::string src_file_name = get_asset_base_name(src_path); char ret_udim_path[FILE_MAX]; BLI_path_join(ret_udim_path, sizeof(ret_udim_path), dest_dir_path, src_file_name.c_str()); @@ -140,17 +131,14 @@ static std::string copy_udim_asset_to_directory(const char *src_path, * path has the former. */ splitPath = split_udim_pattern(ret_udim_path); if (splitPath.first.empty() || splitPath.second.empty()) { - BKE_reportf(reports, RPT_ERROR, "%s: Couldn't split UDIM pattern %s", __func__, ret_udim_path); + WM_reportf(RPT_ERROR, "%s: Couldn't split UDIM pattern %s", __func__, ret_udim_path); return ret_udim_path; } return splitPath.first + UDIM_PATTERN + splitPath.second; } -bool copy_asset(const char *src, - const char *dst, - eUSDTexNameCollisionMode name_collision_mode, - ReportList *reports) +bool copy_asset(const char *src, const char *dst, eUSDTexNameCollisionMode name_collision_mode) { if (!(src && dst)) { return false; @@ -161,7 +149,7 @@ bool copy_asset(const char *src, if (name_collision_mode != USD_TEX_NAME_COLLISION_OVERWRITE) { if (!ar.Resolve(dst).IsEmpty()) { /* The asset exists, so this is a no-op. */ - BKE_reportf(reports, RPT_INFO, "%s: Will not overwrite existing asset %s", __func__, dst); + WM_reportf(RPT_INFO, "%s: Will not overwrite existing asset %s", __func__, dst); return true; } } @@ -169,96 +157,86 @@ bool copy_asset(const char *src, pxr::ArResolvedPath src_path = ar.Resolve(src); if (src_path.IsEmpty()) { - BKE_reportf(reports, RPT_ERROR, "%s: Can't resolve path %s", __func__, src); + WM_reportf(RPT_ERROR, "%s: Can't resolve path %s", __func__, src); return false; } pxr::ArResolvedPath dst_path = ar.ResolveForNewAsset(dst); if (dst_path.IsEmpty()) { - BKE_reportf(reports, RPT_ERROR, "%s: Can't resolve path %s for writing", __func__, dst); + WM_reportf(RPT_ERROR, "%s: Can't resolve path %s for writing", __func__, dst); return false; } if (src_path == dst_path) { - BKE_reportf(reports, - RPT_ERROR, - "%s: Can't copy %s. The source and destination paths are the same", - __func__, - src_path.GetPathString().c_str()); + WM_reportf(RPT_ERROR, + "%s: Can't copy %s. The source and destination paths are the same", + __func__, + src_path.GetPathString().c_str()); return false; } std::string why_not; if (!ar.CanWriteAssetToPath(dst_path, &why_not)) { - BKE_reportf(reports, - RPT_ERROR, - "%s: Can't write to asset %s: %s", - __func__, - dst_path.GetPathString().c_str(), - why_not.c_str()); + WM_reportf(RPT_ERROR, + "%s: Can't write to asset %s: %s", + __func__, + dst_path.GetPathString().c_str(), + why_not.c_str()); return false; } std::shared_ptr src_asset = ar.OpenAsset(src_path); if (!src_asset) { - BKE_reportf(reports, - RPT_ERROR, - "%s: Can't open source asset %s", - __func__, - src_path.GetPathString().c_str()); + WM_reportf( + RPT_ERROR, "%s: Can't open source asset %s", __func__, src_path.GetPathString().c_str()); return false; } const size_t size = src_asset->GetSize(); if (size == 0) { - BKE_reportf(reports, - RPT_WARNING, - "%s: Will not copy zero size source asset %s", - __func__, - src_path.GetPathString().c_str()); + WM_reportf(RPT_WARNING, + "%s: Will not copy zero size source asset %s", + __func__, + src_path.GetPathString().c_str()); return false; } std::shared_ptr buf = src_asset->GetBuffer(); if (!buf) { - BKE_reportf(reports, - RPT_ERROR, - "%s: Null buffer for source asset %s", - __func__, - src_path.GetPathString().c_str()); + WM_reportf(RPT_ERROR, + "%s: Null buffer for source asset %s", + __func__, + src_path.GetPathString().c_str()); return false; } std::shared_ptr dst_asset = ar.OpenAssetForWrite( dst_path, pxr::ArResolver::WriteMode::Replace); if (!dst_asset) { - BKE_reportf(reports, - RPT_ERROR, - "%s: Can't open destination asset %s for writing", - __func__, - src_path.GetPathString().c_str()); + WM_reportf(RPT_ERROR, + "%s: Can't open destination asset %s for writing", + __func__, + src_path.GetPathString().c_str()); return false; } size_t bytes_written = dst_asset->Write(src_asset->GetBuffer().get(), src_asset->GetSize(), 0); if (bytes_written == 0) { - BKE_reportf(reports, - RPT_ERROR, - "%s: Error writing to destination asset %s", - __func__, - dst_path.GetPathString().c_str()); + WM_reportf(RPT_ERROR, + "%s: Error writing to destination asset %s", + __func__, + dst_path.GetPathString().c_str()); } if (!dst_asset->Close()) { - BKE_reportf(reports, - RPT_ERROR, - "%s: Couldn't close destination asset %s", - __func__, - dst_path.GetPathString().c_str()); + WM_reportf(RPT_ERROR, + "%s: Couldn't close destination asset %s", + __func__, + dst_path.GetPathString().c_str()); return false; } @@ -272,15 +250,11 @@ bool asset_exists(const char *path) std::string import_asset(const char *src, const char *import_dir, - eUSDTexNameCollisionMode name_collision_mode, - ReportList *reports) + eUSDTexNameCollisionMode name_collision_mode) { if (import_dir[0] == '\0') { - BKE_reportf(reports, - RPT_ERROR, - "%s: Texture import directory path empty, couldn't import %s", - __func__, - src); + WM_reportf( + RPT_ERROR, "%s: Texture import directory path empty, couldn't import %s", __func__, src); return src; } @@ -293,15 +267,14 @@ std::string import_asset(const char *src, basepath = BKE_main_blendfile_path_from_global(); if (!basepath || basepath[0] == '\0') { - BKE_reportf(reports, - RPT_ERROR, - "%s: import directory is relative " - "but the blend file path is empty. " - "Please save the blend file before importing the USD " - "or provide an absolute import directory path. " - "Can't import %s", - __func__, - src); + WM_reportf(RPT_ERROR, + "%s: import directory is relative " + "but the blend file path is empty. " + "Please save the blend file before importing the USD " + "or provide an absolute import directory path. " + "Can't import %s", + __func__, + src); return src; } BLI_path_abs(dest_dir_path, basepath); @@ -310,19 +283,16 @@ std::string import_asset(const char *src, BLI_path_normalize(dest_dir_path); if (!BLI_dir_create_recursive(dest_dir_path)) { - BKE_reportf(reports, - RPT_ERROR, - "%s: Couldn't create texture import directory %s", - __func__, - dest_dir_path); + WM_reportf( + RPT_ERROR, "%s: Couldn't create texture import directory %s", __func__, dest_dir_path); return src; } if (is_udim_path(src)) { - return copy_udim_asset_to_directory(src, dest_dir_path, name_collision_mode, reports); + return copy_udim_asset_to_directory(src, dest_dir_path, name_collision_mode); } - return copy_asset_to_directory(src, dest_dir_path, name_collision_mode, reports); + return copy_asset_to_directory(src, dest_dir_path, name_collision_mode); } bool is_udim_path(const std::string &path) diff --git a/source/blender/io/usd/intern/usd_asset_utils.h b/source/blender/io/usd/intern/usd_asset_utils.h index b5c1b1d3733..2061342a398 100644 --- a/source/blender/io/usd/intern/usd_asset_utils.h +++ b/source/blender/io/usd/intern/usd_asset_utils.h @@ -17,14 +17,9 @@ namespace blender::io::usd { * \param src: source path of the asset to copy * \param dst: destination path of the copy * \param name_collision_mode: behavior when `dst` already exists - * \param reports: the storage for potential warning or error reports (generated using BKE_report - * API). * \return true if the copy succeeded, false otherwise */ -bool copy_asset(const char *src, - const char *dst, - eUSDTexNameCollisionMode name_collision_mode, - ReportList *reports); +bool copy_asset(const char *src, const char *dst, eUSDTexNameCollisionMode name_collision_mode); /** * Invoke the USD asset resolver to determine if the @@ -46,14 +41,11 @@ bool asset_exists(const char *path); * \param src: source path of the asset to import * \param import_dir: path to the destination directory * \param name_collision_mode: behavior when a file of the same name already exists - * \param reports: the storage for potential warning or error reports (generated using BKE_report - * API). * \return path to copied file or the original `src` path if there was an error */ std::string import_asset(const char *src, const char *import_dir, - eUSDTexNameCollisionMode name_collision_mode, - ReportList *reports); + eUSDTexNameCollisionMode name_collision_mode); /** * Check if the given path contains a UDIM token. diff --git a/source/blender/io/usd/intern/usd_capi_export.cc b/source/blender/io/usd/intern/usd_capi_export.cc index cfdc5e1fcbb..d1788f0a83e 100644 --- a/source/blender/io/usd/intern/usd_capi_export.cc +++ b/source/blender/io/usd/intern/usd_capi_export.cc @@ -8,7 +8,6 @@ #include "usd_hook.h" #include -#include #include #include #include @@ -29,7 +28,6 @@ #include "BKE_blender_version.h" #include "BKE_context.h" #include "BKE_global.h" -#include "BKE_report.h" #include "BKE_scene.h" #include "BLI_fileops.h" @@ -110,8 +108,6 @@ static bool prim_path_valid(const char *path) /** * Perform validation of export parameter settings. * \return true if the parameters are valid; returns false otherwise. - * - * \warning Do not call from worker thread, only from main thread (i.e. before starting the wmJob). */ static bool export_params_valid(const USDExportParams ¶ms) { @@ -136,11 +132,7 @@ static void ensure_root_prim(pxr::UsdStageRefPtr stage, const USDExportParams &p return; } - for (auto path : pxr::SdfPath(params.root_prim_path).GetPrefixes()) { - auto xform = pxr::UsdGeomXform::Define(stage, path); - /* Tag generated prims to allow filtering on import */ - xform.GetPrim().SetCustomDataByKey(pxr::TfToken("Blender:generated"), pxr::VtValue(true)); - } + pxr::UsdGeomXform::Define(stage, pxr::SdfPath(params.root_prim_path)); } static void report_job_duration(const ExportJobData *data) @@ -191,36 +183,35 @@ static bool perform_usdz_conversion(const ExportJobData *data) if (BLI_exists(data->usdz_filepath)) { result = BLI_delete(data->usdz_filepath, false, false); if (result != 0) { - BKE_reportf(data->params.worker_status->reports, - RPT_ERROR, - "USD Export: Unable to delete existing usdz file %s", - data->usdz_filepath); + WM_reportf( + RPT_ERROR, "USD Export: Unable to delete existing usdz file %s", data->usdz_filepath); return false; } } result = BLI_path_move(usdz_temp_full_path, data->usdz_filepath); if (result != 0) { - BKE_reportf(data->params.worker_status->reports, - RPT_ERROR, - "USD Export: Couldn't move new usdz file from temporary location %s to %s", - usdz_temp_full_path, - data->usdz_filepath); + WM_reportf(RPT_ERROR, + "USD Export: Couldn't move new usdz file from temporary location %s to %s", + usdz_temp_full_path, + data->usdz_filepath); return false; } return true; } -pxr::UsdStageRefPtr export_to_stage(const USDExportParams ¶ms, - Depsgraph *depsgraph, - const char *filepath) +static pxr::UsdStageRefPtr export_to_stage(const USDExportParams ¶ms, + Depsgraph *depsgraph, + const char *filepath, + bool *stop, + bool *do_update, + float *progress) { pxr::UsdStageRefPtr usd_stage = pxr::UsdStage::CreateNew(filepath); if (!usd_stage) { return usd_stage; } - wmJobWorkerStatus *worker_status = params.worker_status; Scene *scene = DEG_get_input_scene(depsgraph); Main *bmain = DEG_get_bmain(depsgraph); @@ -251,7 +242,7 @@ pxr::UsdStageRefPtr export_to_stage(const USDExportParams ¶ms, float progress_per_frame = 1.0f / std::max(1, (scene->r.efra - scene->r.sfra + 1)); for (float frame = scene->r.sfra; frame <= scene->r.efra; frame++) { - if (G.is_break || worker_status->stop) { + if (G.is_break || (stop != nullptr && *stop)) { break; } @@ -263,8 +254,12 @@ pxr::UsdStageRefPtr export_to_stage(const USDExportParams ¶ms, iter.set_export_frame(frame); iter.iterate_and_write(); - worker_status->progress += progress_per_frame; - worker_status->do_update = true; + if (progress) { + *progress += progress_per_frame; + } + if (do_update) { + *do_update = true; + } } } else { @@ -284,7 +279,7 @@ pxr::UsdStageRefPtr export_to_stage(const USDExportParams ¶ms, } } - call_export_hooks(usd_stage, depsgraph, params.worker_status->reports); + call_export_hooks(usd_stage, depsgraph); /* Finish up by going back to the keyframe that was current before we started. */ if (scene->r.cfra != orig_frame) { @@ -295,7 +290,19 @@ pxr::UsdStageRefPtr export_to_stage(const USDExportParams ¶ms, return usd_stage; } -static void export_startjob(void *customdata, wmJobWorkerStatus *worker_status) +pxr::UsdStageRefPtr export_to_stage(const USDExportParams ¶ms, + Depsgraph *depsgraph, + const char *filepath) +{ + return export_to_stage(params, depsgraph, filepath, nullptr, nullptr, nullptr); +} + +static void export_startjob(void *customdata, + /* Cannot be const, this function implements wm_jobs_start_callback. + * NOLINTNEXTLINE: readability-non-const-parameter. */ + bool *stop, + bool *do_update, + float *progress) { ExportJobData *data = static_cast(customdata); data->export_ok = false; @@ -307,7 +314,7 @@ static void export_startjob(void *customdata, wmJobWorkerStatus *worker_status) } G.is_break = false; - /* Evaluate the depsgraph for exporting. + /* Evaluate the despgraph for exporting. * * Note that, unlike with its building, this is expected to be safe to perform from worker * thread, since UI is locked during export, so there should not be any more changes in the Main @@ -316,20 +323,18 @@ static void export_startjob(void *customdata, wmJobWorkerStatus *worker_status) * #USD_export. */ BKE_scene_graph_update_tagged(data->depsgraph, data->bmain); - worker_status->progress = 0.0f; - worker_status->do_update = true; - data->params.worker_status = worker_status; + *progress = 0.0f; + *do_update = true; pxr::UsdStageRefPtr usd_stage = export_to_stage( - data->params, data->depsgraph, data->unarchived_filepath); + data->params, data->depsgraph, data->unarchived_filepath, stop, do_update, progress); if (!usd_stage) { /* This happens when the USD JSON files cannot be found. When that happens, * the USD library doesn't know it has the functionality to write USDA and * USDC files, and creating a new UsdStage fails. */ - BKE_reportf(worker_status->reports, - RPT_ERROR, - "USD Export: unable to find suitable USD plugin to write %s", - data->unarchived_filepath); + WM_reportf(RPT_ERROR, + "USD Export: unable to find suitable USD plugin to write %s", + data->unarchived_filepath); return; } @@ -339,15 +344,15 @@ static void export_startjob(void *customdata, wmJobWorkerStatus *worker_status) bool usd_conversion_success = perform_usdz_conversion(data); if (!usd_conversion_success) { data->export_ok = false; - worker_status->progress = 1.0f; - worker_status->do_update = true; + *progress = 1.0f; + *do_update = true; return; } } data->export_ok = true; - worker_status->progress = 1.0f; - worker_status->do_update = true; + *progress = 1.0f; + *do_update = true; } static void export_endjob_usdz_cleanup(const ExportJobData *data) @@ -425,8 +430,7 @@ static void set_job_filepath(blender::io::usd::ExportJobData *job, const char *f bool USD_export(bContext *C, const char *filepath, const USDExportParams *params, - bool as_background_job, - ReportList *reports) + bool as_background_job) { if (!blender::io::usd::export_params_valid(*params)) { return false; @@ -449,7 +453,7 @@ bool USD_export(bContext *C, /* Construct the depsgraph for exporting. * * Has to be done from main thread currently, as it may affect Main original data (e.g. when - * doing deferred update of the view-layers, see #112534 for details). */ + * doing deferred update of the viewlayers, see #112534 for details). */ if (job->params.visible_objects_only) { DEG_graph_build_from_view_layer(job->depsgraph); } @@ -474,11 +478,11 @@ bool USD_export(bContext *C, WM_jobs_start(CTX_wm_manager(C), wm_job); } else { - wmJobWorkerStatus worker_status = {}; - /* Use the operator's reports in non-background case. */ - worker_status.reports = reports; + /* Fake a job context, so that we don't need null pointer checks while exporting. */ + bool stop = false, do_update = false; + float progress = 0.0f; - blender::io::usd::export_startjob(job, &worker_status); + blender::io::usd::export_startjob(job, &stop, &do_update, &progress); blender::io::usd::export_endjob(job); export_ok = job->export_ok; diff --git a/source/blender/io/usd/intern/usd_capi_import.cc b/source/blender/io/usd/intern/usd_capi_import.cc index 0fb0abb1745..f26c83adcb0 100644 --- a/source/blender/io/usd/intern/usd_capi_import.cc +++ b/source/blender/io/usd/intern/usd_capi_import.cc @@ -21,7 +21,6 @@ #include "BKE_main.h" #include "BKE_node.hh" #include "BKE_object.hh" -#include "BKE_report.h" #include "BKE_scene.h" #include "BKE_world.h" @@ -112,52 +111,6 @@ static void convert_to_z_up(pxr::UsdStageRefPtr stage, ImportSettings *r_setting copy_m4_m3(r_settings->conversion_mat, rmat); } -/** - * Find the lowest level of Blender generated roots - * so that round tripping an export can be more invisible - */ -static void find_prefix_to_skip(pxr::UsdStageRefPtr stage, ImportSettings *r_settings) -{ - if (!stage) { - return; - } - - pxr::TfToken generated_key("Blender:generated"); - pxr::SdfPath path("/"); - auto prim = stage->GetPseudoRoot(); - while (true) { - - uint32_t child_count = 0; - for (auto child : prim.GetChildren()) { - if (child_count == 0) { - prim = child.GetPrim(); - } - ++child_count; - } - - if (child_count != 1) { - /* Our blender write out only supports a single root chain, - * so whenever we encounter more than one child, we should - * early exit */ - break; - } - - /* We only care about prims that have the key and the value doesn't matter */ - if (!prim.HasCustomDataKey(generated_key)) { - break; - } - path = path.AppendChild(prim.GetName()); - } - - /* Treat the root as empty */ - auto path_string = path.GetString(); - if (path == pxr::SdfPath("/")) { - path = pxr::SdfPath(); - } - - r_settings->skip_prefix = path; -} - enum { USD_NO_ERROR = 0, USD_ARCHIVE_FAIL, @@ -193,19 +146,17 @@ static void report_job_duration(const ImportJobData *data) std::cout << '\n'; } -static void import_startjob(void *customdata, wmJobWorkerStatus *worker_status) +static void import_startjob(void *customdata, bool *stop, bool *do_update, float *progress) { ImportJobData *data = static_cast(customdata); - data->stop = &worker_status->stop; - data->do_update = &worker_status->do_update; - data->progress = &worker_status->progress; + data->stop = stop; + data->do_update = do_update; + data->progress = progress; data->was_canceled = false; data->archive = nullptr; data->start_time = timeit::Clock::now(); - data->params.worker_status = worker_status; - WM_set_locked_interface(data->wm, true); G.is_break = false; @@ -270,17 +221,13 @@ static void import_startjob(void *customdata, wmJobWorkerStatus *worker_status) pxr::UsdStage::OpenMasked(data->filepath, pop_mask); if (!stage) { - BKE_reportf(worker_status->reports, - RPT_ERROR, - "USD Import: unable to open stage to read %s", - data->filepath); + WM_reportf(RPT_ERROR, "USD Import: unable to open stage to read %s", data->filepath); data->import_ok = false; data->error_code = USD_ARCHIVE_FAIL; return; } convert_to_z_up(stage, &data->settings); - find_prefix_to_skip(stage, &data->settings); data->settings.stage_meters_per_unit = UsdGeomGetStageMetersPerUnit(stage); /* Set up the stage for animated data. */ @@ -362,8 +309,8 @@ static void import_startjob(void *customdata, wmJobWorkerStatus *worker_status) data->import_ok = !data->was_canceled; - worker_status->progress = 1.0f; - worker_status->do_update = true; + *progress = 1.0f; + *do_update = true; } static void import_endjob(void *customdata) @@ -445,9 +392,7 @@ static void import_endjob(void *customdata) data->import_ok = !data->was_canceled; break; case USD_ARCHIVE_FAIL: - BKE_report(data->params.worker_status->reports, - RPT_ERROR, - "Could not open USD archive for reading, see console for detail"); + WM_report(RPT_ERROR, "Could not open USD archive for reading, see console for detail"); break; } @@ -472,8 +417,7 @@ using namespace blender::io::usd; bool USD_import(bContext *C, const char *filepath, const USDImportParams *params, - bool as_background_job, - ReportList *reports) + bool as_background_job) { /* Using new here since `MEM_*` functions do not call constructor to properly initialize data. */ ImportJobData *job = new ImportJobData(); @@ -515,11 +459,11 @@ bool USD_import(bContext *C, WM_jobs_start(CTX_wm_manager(C), wm_job); } else { - wmJobWorkerStatus worker_status = {}; - /* Use the operator's reports in non-background case. */ - worker_status.reports = reports; + /* Fake a job context, so that we don't need null pointer checks while importing. */ + bool stop = false, do_update = false; + float progress = 0.0f; - import_startjob(job, &worker_status); + import_startjob(job, &stop, &do_update, &progress); import_endjob(job); import_ok = job->import_ok; @@ -655,7 +599,7 @@ CacheArchiveHandle *USD_create_handle(Main * /*bmain*/, blender::io::usd::ImportSettings settings{}; convert_to_z_up(stage, &settings); - find_prefix_to_skip(stage, &settings); + USDStageReader *stage_reader = new USDStageReader(stage, params, settings); if (object_paths) { diff --git a/source/blender/io/usd/intern/usd_hook.cc b/source/blender/io/usd/intern/usd_hook.cc index f075bdc7123..a60ecf679ae 100644 --- a/source/blender/io/usd/intern/usd_hook.cc +++ b/source/blender/io/usd/intern/usd_hook.cc @@ -15,8 +15,6 @@ #include "BLI_listbase.h" -#include "BKE_report.h" - #include "RNA_access.hh" #include "RNA_prototypes.h" #include "RNA_types.hh" @@ -154,7 +152,7 @@ void register_export_hook_converters() } /* Retrieve and report the current Python error. */ -static void handle_python_error(USDHook *hook, ReportList *reports) +static void handle_python_error(USDHook *hook) { if (!PyErr_Occurred()) { return; @@ -162,10 +160,9 @@ static void handle_python_error(USDHook *hook, ReportList *reports) PyErr_Print(); - BKE_reportf(reports, - RPT_ERROR, - "An exception occurred invoking USD hook '%s'. Please see the console for details", - hook->name); + WM_reportf(RPT_ERROR, + "An exception occurred invoking USD hook '%s'. Please see the console for details", + hook->name); } /* Abstract base class to facilitate calling a function with a given @@ -210,11 +207,10 @@ class USDHookInvoker { call_hook(hook_obj); } catch (python::error_already_set const &) { - handle_python_error(hook, reports_); + handle_python_error(hook); } catch (...) { - BKE_reportf( - reports_, RPT_ERROR, "An exception occurred invoking USD hook '%s'", hook->name); + WM_reportf(RPT_ERROR, "An exception occurred invoking USD hook '%s'", hook->name); } } @@ -229,9 +225,6 @@ class USDHookInvoker { * * python::call_method(hook_obj, function_name(), arg1, arg2); */ virtual void call_hook(PyObject *hook_obj) const = 0; - - /* Reports list provided when constructing the subclass, used by #call() to store reports. */ - ReportList *reports_; }; class OnExportInvoker : public USDHookInvoker { @@ -239,10 +232,9 @@ class OnExportInvoker : public USDHookInvoker { USDSceneExportContext hook_context_; public: - OnExportInvoker(pxr::UsdStageRefPtr stage, Depsgraph *depsgraph, ReportList *reports) + OnExportInvoker(pxr::UsdStageRefPtr stage, Depsgraph *depsgraph) : hook_context_(stage, depsgraph) { - reports_ = reports; } protected: @@ -266,12 +258,10 @@ class OnMaterialExportInvoker : public USDHookInvoker { public: OnMaterialExportInvoker(pxr::UsdStageRefPtr stage, Material *material, - pxr::UsdShadeMaterial &usd_material, - ReportList *reports) + pxr::UsdShadeMaterial &usd_material) : hook_context_(stage), usd_material_(usd_material) { material_ptr_ = RNA_pointer_create(nullptr, &RNA_Material, material); - reports_ = reports; } protected: @@ -287,26 +277,25 @@ class OnMaterialExportInvoker : public USDHookInvoker { } }; -void call_export_hooks(pxr::UsdStageRefPtr stage, Depsgraph *depsgraph, ReportList *reports) +void call_export_hooks(pxr::UsdStageRefPtr stage, Depsgraph *depsgraph) { if (g_usd_hooks.empty()) { return; } - OnExportInvoker on_export(stage, depsgraph, reports); + OnExportInvoker on_export(stage, depsgraph); on_export.call(); } void call_material_export_hooks(pxr::UsdStageRefPtr stage, Material *material, - pxr::UsdShadeMaterial &usd_material, - ReportList *reports) + pxr::UsdShadeMaterial &usd_material) { if (g_usd_hooks.empty()) { return; } - OnMaterialExportInvoker on_material_export(stage, material, usd_material, reports); + OnMaterialExportInvoker on_material_export(stage, material, usd_material); on_material_export.call(); } diff --git a/source/blender/io/usd/intern/usd_hook.h b/source/blender/io/usd/intern/usd_hook.h index e8c3c3a5b32..8f53fd2c399 100644 --- a/source/blender/io/usd/intern/usd_hook.h +++ b/source/blender/io/usd/intern/usd_hook.h @@ -11,7 +11,6 @@ struct Depsgraph; struct ExportJobData; struct Material; -struct ReportList; struct USDExportParams; namespace blender::io::usd { @@ -20,12 +19,11 @@ namespace blender::io::usd { void register_export_hook_converters(); /** Call the 'on_export' chaser function defined in the registered USDHook classes. */ -void call_export_hooks(pxr::UsdStageRefPtr stage, Depsgraph *depsgraph, ReportList *reports); +void call_export_hooks(pxr::UsdStageRefPtr stage, Depsgraph *depsgraph); /** Call the 'on_material_export' hook functions defined in the registered #USDHook classes. */ void call_material_export_hooks(pxr::UsdStageRefPtr stage, Material *material, - pxr::UsdShadeMaterial &usd_material, - ReportList *reports); + pxr::UsdShadeMaterial &usd_material); } // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_reader_material.cc b/source/blender/io/usd/intern/usd_reader_material.cc index c9534114a74..25b804af9e0 100644 --- a/source/blender/io/usd/intern/usd_reader_material.cc +++ b/source/blender/io/usd/intern/usd_reader_material.cc @@ -760,7 +760,7 @@ void USDMaterialReader::load_tex_image(const pxr::UsdShadeShader &usd_shader, USD_TEX_NAME_COLLISION_OVERWRITE : params_.tex_name_collision_mode; - file_path = import_asset(file_path.c_str(), textures_dir, name_collision_mode, reports()); + file_path = import_asset(file_path.c_str(), textures_dir, name_collision_mode); } /* If this is a UDIM texture, this will store the diff --git a/source/blender/io/usd/intern/usd_reader_material.h b/source/blender/io/usd/intern/usd_reader_material.h index 53fcc4b01d1..0b570bfc735 100644 --- a/source/blender/io/usd/intern/usd_reader_material.h +++ b/source/blender/io/usd/intern/usd_reader_material.h @@ -5,8 +5,6 @@ #include "usd.h" -#include "WM_types.hh" - #include "BLI_map.hh" #include @@ -88,12 +86,6 @@ class USDMaterialReader { Material *add_material(const pxr::UsdShadeMaterial &usd_material) const; - /** Get the wmJobWorkerStatus-provided `reports` list pointer, to use with the BKE_report API. */ - ReportList *reports() const - { - return params_.worker_status->reports; - } - protected: /** Create the Principled BSDF shader node network. */ void import_usd_preview(Material *mtl, const pxr::UsdShadeShader &usd_shader) const; diff --git a/source/blender/io/usd/intern/usd_reader_mesh.cc b/source/blender/io/usd/intern/usd_reader_mesh.cc index 0cbc0722027..d6aa7168165 100644 --- a/source/blender/io/usd/intern/usd_reader_mesh.cc +++ b/source/blender/io/usd/intern/usd_reader_mesh.cc @@ -15,7 +15,6 @@ #include "BKE_material.h" #include "BKE_mesh.hh" #include "BKE_object.hh" -#include "BKE_report.h" #include "BLI_math_color.hh" #include "BLI_math_geom.h" @@ -166,7 +165,7 @@ USDMeshReader::USDMeshReader(const pxr::UsdPrim &prim, } static std::optional convert_usd_type_to_blender( - const pxr::SdfValueTypeName usd_type, ReportList *reports) + const pxr::SdfValueTypeName usd_type) { static const blender::Map type_map = []() { blender::Map map; @@ -203,10 +202,7 @@ static std::optional convert_usd_type_to_blender( const eCustomDataType *value = type_map.lookup_ptr(usd_type); if (value == nullptr) { - BKE_reportf(reports, - RPT_WARNING, - "Unsupported type %s for mesh data", - usd_type.GetAsToken().GetText()); + WM_reportf(RPT_WARNING, "Unsupported type %s for mesh data", usd_type.GetAsToken().GetText()); return std::nullopt; } @@ -214,7 +210,7 @@ static std::optional convert_usd_type_to_blender( } static const std::optional convert_usd_varying_to_blender( - const pxr::TfToken usd_domain, ReportList *reports) + const pxr::TfToken usd_domain) { static const blender::Map domain_map = []() { blender::Map map; @@ -233,8 +229,7 @@ static const std::optional convert_usd_varying_to_blender( const eAttrDomain *value = domain_map.lookup_ptr(usd_domain); if (value == nullptr) { - BKE_reportf( - reports, RPT_WARNING, "Unsupported domain for mesh data type %s", usd_domain.GetText()); + WM_reportf(RPT_WARNING, "Unsupported domain for mesh data type %s", usd_domain.GetText()); return std::nullopt; } @@ -284,11 +279,11 @@ void USDMeshReader::read_object_data(Main *bmain, const double motionSampleTime) } if (import_params_.import_blendshapes) { - import_blendshapes(bmain, object_, prim_, reports()); + import_blendshapes(bmain, object_, prim_); } if (import_params_.import_skeletons) { - import_mesh_skel_bindings(bmain, object_, prim_, reports()); + import_mesh_skel_bindings(bmain, object_, prim_); } USDXformReader::read_object_data(bmain, motionSampleTime); @@ -362,26 +357,22 @@ void USDMeshReader::read_mpolys(Mesh *mesh) template pxr::VtArray get_prim_attribute_array(const pxr::UsdGeomPrimvar &primvar, - const double motionSampleTime, - ReportList *reports) + const double motionSampleTime) { pxr::VtArray array; pxr::VtValue primvar_val; if (!primvar.ComputeFlattened(&primvar_val, motionSampleTime)) { - BKE_reportf(reports, - RPT_WARNING, - "Unable to get array values for primvar %s", - primvar.GetName().GetText()); + WM_reportf( + RPT_WARNING, "Unable to get array values for primvar %s", primvar.GetName().GetText()); return array; } if (!primvar_val.CanCast>()) { - BKE_reportf(reports, - RPT_WARNING, - "USD Import: can't cast attribute '%s' to array", - primvar.GetName().GetText()); + WM_reportf(RPT_WARNING, + "USD Import: can't cast attribute '%s' to array", + primvar.GetName().GetText()); return array; } @@ -397,8 +388,8 @@ void USDMeshReader::read_color_data_primvar(Mesh *mesh, return; } - pxr::VtArray usd_colors = get_prim_attribute_array( - primvar, motionSampleTime, reports()); + pxr::VtArray usd_colors = get_prim_attribute_array(primvar, + motionSampleTime); if (usd_colors.empty()) { return; @@ -412,11 +403,9 @@ void USDMeshReader::read_color_data_primvar(Mesh *mesh, (interp == pxr::UsdGeomTokens->constant && usd_colors.size() != 1) || (interp == pxr::UsdGeomTokens->uniform && usd_colors.size() != mesh->faces_num)) { - BKE_reportf( - reports(), - RPT_WARNING, - "USD Import: color attribute value '%s' count inconsistent with interpolation type", - primvar.GetName().GetText()); + WM_reportf(RPT_WARNING, + "USD Import: color attribute value '%s' count inconsistent with interpolation type", + primvar.GetName().GetText()); return; } @@ -437,10 +426,9 @@ void USDMeshReader::read_color_data_primvar(Mesh *mesh, color_data = attributes.lookup_or_add_for_write_only_span(primvar_name, color_domain); if (!color_data) { - BKE_reportf(reports(), - RPT_WARNING, - "USD Import: couldn't add color attribute '%s'", - primvar.GetBaseName().GetText()); + WM_reportf(RPT_WARNING, + "USD Import: couldn't add color attribute '%s'", + primvar.GetBaseName().GetText()); return; } @@ -514,8 +502,8 @@ void USDMeshReader::read_uv_data_primvar(Mesh *mesh, { const StringRef primvar_name(primvar.StripPrimvarsName(primvar.GetName()).GetString()); - pxr::VtArray usd_uvs = get_prim_attribute_array( - primvar, motionSampleTime, reports()); + pxr::VtArray usd_uvs = get_prim_attribute_array(primvar, + motionSampleTime); if (usd_uvs.empty()) { return; @@ -531,10 +519,9 @@ void USDMeshReader::read_uv_data_primvar(Mesh *mesh, (varying_type == pxr::UsdGeomTokens->vertex && usd_uvs.size() != mesh->totvert) || (varying_type == pxr::UsdGeomTokens->varying && usd_uvs.size() != mesh->totloop)) { - BKE_reportf(reports(), - RPT_WARNING, - "USD Import: UV attribute value '%s' count inconsistent with interpolation type", - primvar.GetName().GetText()); + WM_reportf(RPT_WARNING, + "USD Import: UV attribute value '%s' count inconsistent with interpolation type", + primvar.GetName().GetText()); return; } @@ -543,10 +530,9 @@ void USDMeshReader::read_uv_data_primvar(Mesh *mesh, primvar_name, ATTR_DOMAIN_CORNER); if (!uv_data) { - BKE_reportf(reports(), - RPT_WARNING, - "USD Import: couldn't add UV attribute '%s'", - primvar.GetBaseName().GetText()); + WM_reportf(RPT_WARNING, + "USD Import: couldn't add UV attribute '%s'", + primvar.GetBaseName().GetText()); return; } @@ -610,13 +596,10 @@ void USDMeshReader::copy_prim_array_to_blender_attribute(const Mesh *mesh, MutableSpan attribute) { const pxr::TfToken interp = primvar.GetInterpolation(); - pxr::VtArray primvar_array = get_prim_attribute_array( - primvar, motionSampleTime, reports()); + pxr::VtArray primvar_array = get_prim_attribute_array(primvar, motionSampleTime); if (primvar_array.empty()) { - BKE_reportf(reports(), - RPT_WARNING, - "Unable to get array values for primvar %s", - primvar.GetName().GetText()); + WM_reportf( + RPT_WARNING, "Unable to get array values for primvar %s", primvar.GetName().GetText()); return; } @@ -667,9 +650,8 @@ void USDMeshReader::read_generic_data_primvar(Mesh *mesh, const pxr::TfToken varying_type = primvar.GetInterpolation(); const pxr::TfToken name = pxr::UsdGeomPrimvar::StripPrimvarsName(primvar.GetPrimvarName()); - const std::optional domain = convert_usd_varying_to_blender(varying_type, - reports()); - const std::optional type = convert_usd_type_to_blender(sdf_type, reports()); + const std::optional domain = convert_usd_varying_to_blender(varying_type); + const std::optional type = convert_usd_type_to_blender(sdf_type); if (!domain.has_value() || !type.has_value()) { return; @@ -704,11 +686,10 @@ void USDMeshReader::read_generic_data_primvar(Mesh *mesh, mesh, primvar, motionSampleTime, attribute.span.typed()); break; default: - BKE_reportf(reports(), - RPT_ERROR, - "Generic primvar %s: invalid type %s", - primvar.GetName().GetText(), - sdf_type.GetAsToken().GetText()); + WM_reportf(RPT_ERROR, + "Generic primvar %s: invalid type %s", + primvar.GetName().GetText(), + sdf_type.GetAsToken().GetText()); break; } attribute.finish(); @@ -781,6 +762,8 @@ void USDMeshReader::process_normals_face_varying(Mesh *mesh) return; } + mesh->flag |= ME_AUTOSMOOTH; + long int loop_count = normals_.size(); float(*lnors)[3] = static_cast( @@ -834,6 +817,7 @@ void USDMeshReader::process_normals_uniform(Mesh *mesh) } } + mesh->flag |= ME_AUTOSMOOTH; BKE_mesh_set_custom_normals(mesh, lnors); MEM_freeN(lnors); @@ -902,11 +886,10 @@ void USDMeshReader::read_custom_data(const ImportSettings *settings, /* Convert primvars to custom layer data. */ for (pxr::UsdGeomPrimvar &pv : primvars) { if (!pv.HasValue()) { - BKE_reportf(reports(), - RPT_WARNING, - "Skipping primvar %s, mesh %s -- no value", - pv.GetName().GetText(), - &mesh->id.name[2]); + WM_reportf(RPT_WARNING, + "Skipping primvar %s, mesh %s -- no value", + pv.GetName().GetText(), + &mesh->id.name[2]); continue; } @@ -933,12 +916,12 @@ void USDMeshReader::read_custom_data(const ImportSettings *settings, pxr::SdfValueTypeNames->QuatdArray, pxr::SdfValueTypeNames->QuathArray)) { - /* Skip creating known unsupported types, and avoid noisy error prints. */ + /* Skip creating known unsupported types, and avoid spammy error prints. */ continue; } /* Read Color primvars. */ - if (convert_usd_type_to_blender(type, reports()) == CD_PROP_COLOR) { + if (convert_usd_type_to_blender(type) == CD_PROP_COLOR) { if ((settings->read_flag & MOD_MESHSEQ_READ_COLOR) != 0) { /* Set the active color name to 'displayColor', if a color primvar * with this name exists. Otherwise, use the name of the first @@ -956,7 +939,7 @@ void USDMeshReader::read_custom_data(const ImportSettings *settings, pxr::UsdGeomTokens->vertex, pxr::UsdGeomTokens->faceVarying, pxr::UsdGeomTokens->varying) && - convert_usd_type_to_blender(type, reports()) == CD_PROP_FLOAT2) + convert_usd_type_to_blender(type) == CD_PROP_FLOAT2) { if ((settings->read_flag & MOD_MESHSEQ_READ_UV) != 0) { /* Set the active uv set name to 'st', if a uv set primvar @@ -1179,11 +1162,10 @@ std::optional USDMeshReader::get_local_usd_xform(const float time) return XformResult(pxr::GfMatrix4f(bind_xf), true); } else { - BKE_reportf(reports(), - RPT_WARNING, - "%s: Couldn't compute geom bind transform for %s", - __func__, - prim_.GetPath().GetAsString().c_str()); + WM_reportf(RPT_WARNING, + "%s: Couldn't compute geom bind transform for %s", + __func__, + prim_.GetPath().GetAsString().c_str()); } } } diff --git a/source/blender/io/usd/intern/usd_reader_prim.h b/source/blender/io/usd/intern/usd_reader_prim.h index 49c0cdd3b3e..df896f09dec 100644 --- a/source/blender/io/usd/intern/usd_reader_prim.h +++ b/source/blender/io/usd/intern/usd_reader_prim.h @@ -9,9 +9,6 @@ #include "usd.h" -#include "WM_types.hh" - -#include #include #include @@ -60,8 +57,6 @@ struct ImportSettings { * correct millimeter scale that Blender uses for camera parameters. */ double stage_meters_per_unit; - pxr::SdfPath skip_prefix; - ImportSettings() : do_convert_mat(false), from_up(0), @@ -74,8 +69,7 @@ struct ImportSettings { read_flag(0), validate_meshes(false), cache_file(NULL), - stage_meters_per_unit(1.0), - skip_prefix(pxr::SdfPath{}) + stage_meters_per_unit(1.0) { } }; @@ -119,12 +113,6 @@ class USDPrimReader { parent_reader_ = parent; } - /** Get the wmJobWorkerStatus-provided `reports` list pointer, to use with the BKE_report API. */ - ReportList *reports() const - { - return import_params_.worker_status->reports; - } - /* Since readers might be referenced through handles * maintained by modifiers and constraints, we provide * a reference count to facilitate managing the object diff --git a/source/blender/io/usd/intern/usd_reader_shape.cc b/source/blender/io/usd/intern/usd_reader_shape.cc index e09b47ea9fa..50d1a0b32e0 100644 --- a/source/blender/io/usd/intern/usd_reader_shape.cc +++ b/source/blender/io/usd/intern/usd_reader_shape.cc @@ -6,7 +6,6 @@ #include "BKE_mesh.hh" #include "BKE_modifier.h" #include "BKE_object.hh" -#include "BKE_report.h" #include "DNA_cachefile_types.h" #include "DNA_mesh_types.h" @@ -119,11 +118,10 @@ bool USDShapeReader::read_mesh_values(double motionSampleTime, return true; } - BKE_reportf(reports(), - RPT_ERROR, - "Unhandled Gprim type: %s (%s)", - prim_.GetTypeName().GetText(), - prim_.GetPath().GetText()); + WM_reportf(RPT_ERROR, + "Unhandled Gprim type: %s (%s)", + prim_.GetTypeName().GetText(), + prim_.GetPath().GetText()); return false; } @@ -232,11 +230,10 @@ bool USDShapeReader::is_time_varying() return geom.GetRadiusAttr().ValueMightBeTimeVarying(); } - BKE_reportf(reports(), - RPT_ERROR, - "Unhandled Gprim type: %s (%s)", - prim_.GetTypeName().GetText(), - prim_.GetPath().GetText()); + WM_reportf(RPT_ERROR, + "Unhandled Gprim type: %s (%s)", + prim_.GetTypeName().GetText(), + prim_.GetPath().GetText()); return false; } diff --git a/source/blender/io/usd/intern/usd_reader_skeleton.cc b/source/blender/io/usd/intern/usd_reader_skeleton.cc index 797330a90f5..28f55bc11fa 100644 --- a/source/blender/io/usd/intern/usd_reader_skeleton.cc +++ b/source/blender/io/usd/intern/usd_reader_skeleton.cc @@ -39,7 +39,7 @@ void USDSkeletonReader::read_object_data(Main *bmain, const double motionSampleT return; } - import_skeleton(bmain, object_, skel_, reports()); + import_skeleton(bmain, object_, skel_); USDXformReader::read_object_data(bmain, motionSampleTime); } diff --git a/source/blender/io/usd/intern/usd_reader_stage.cc b/source/blender/io/usd/intern/usd_reader_stage.cc index 893639b9608..a908e5b11d6 100644 --- a/source/blender/io/usd/intern/usd_reader_stage.cc +++ b/source/blender/io/usd/intern/usd_reader_stage.cc @@ -44,7 +44,6 @@ #include "BKE_lib_id.h" #include "BKE_modifier.h" -#include "BKE_report.h" #include "DNA_material_types.h" @@ -283,14 +282,6 @@ USDPrimReader *USDStageReader::collect_readers(Main *bmain, const pxr::UsdPrim & return nullptr; } - /* If we find prims that have been auto generated by Blender, we skip them on import - * so that the imported scene can closely match the exported scene */ - if (!settings_.skip_prefix.IsEmpty()) { - if (settings_.skip_prefix.HasPrefix(prim.GetPath())) { - return nullptr; - } - } - /* Check if we can merge an Xform with its child prim. */ if (child_readers.size() == 1) { @@ -375,11 +366,10 @@ void USDStageReader::process_armature_modifiers() const std::string skel_path = mesh_reader->get_skeleton_path(); std::map::const_iterator it = usd_path_to_armature.find(skel_path); if (it == usd_path_to_armature.end()) { - BKE_reportf(reports(), - RPT_WARNING, - "%s: Couldn't find armature object corresponding to USD skeleton %s", - __func__, - skel_path.c_str()); + WM_reportf(RPT_WARNING, + "%s: Couldn't find armature object corresponding to USD skeleton %s", + __func__, + skel_path.c_str()); } amd->object = it->second; } diff --git a/source/blender/io/usd/intern/usd_reader_stage.h b/source/blender/io/usd/intern/usd_reader_stage.h index 8e4cb05eac5..b0d50f0902a 100644 --- a/source/blender/io/usd/intern/usd_reader_stage.h +++ b/source/blender/io/usd/intern/usd_reader_stage.h @@ -5,8 +5,6 @@ struct Main; -#include "WM_types.hh" - #include "usd.h" #include "usd_reader_prim.h" @@ -81,12 +79,6 @@ class USDStageReader { return settings_; } - /** Get the wmJobWorkerStatus-provided `reports` list pointer, to use with the BKE_report API. */ - ReportList *reports() const - { - return params_.worker_status->reports; - } - void clear_readers(); const std::vector &readers() const @@ -117,7 +109,7 @@ class USDStageReader { */ bool include_by_purpose(const pxr::UsdGeomImageable &imageable) const; - /** + /* * Returns true if the specified UsdPrim is a UsdGeom primitive, * procedural shape, such as UsdGeomCube. */ diff --git a/source/blender/io/usd/intern/usd_skel_convert.cc b/source/blender/io/usd/intern/usd_skel_convert.cc index de60076a648..d0da2a68606 100644 --- a/source/blender/io/usd/intern/usd_skel_convert.cc +++ b/source/blender/io/usd/intern/usd_skel_convert.cc @@ -32,7 +32,6 @@ #include "BKE_modifier.h" #include "BKE_object.hh" #include "BKE_object_deform.h" -#include "BKE_report.h" #include "BLI_math_vector.h" #include "BLI_string.h" @@ -104,14 +103,11 @@ void add_bezt(FCurve *fcu, * \param arm_obj: Armature object to which the action will be added * \param skel_query: The USD skeleton query for reading the animation * \param joint_to_bone_map: Map a USD skeleton joint name to a bone name - * \param reports: the storage for potential warning or error reports (generated using BKE_report - * API). */ void import_skeleton_curves(Main *bmain, Object *arm_obj, const pxr::UsdSkelSkeletonQuery &skel_query, - const std::map &joint_to_bone_map, - ReportList *reports) + const std::map &joint_to_bone_map) { if (!(bmain && arm_obj && skel_query)) { @@ -222,20 +218,18 @@ void import_skeleton_curves(Main *bmain, /* Get the world space joint transforms at bind time. */ pxr::VtMatrix4dArray bind_xforms; if (!skel_query.GetJointWorldBindTransforms(&bind_xforms)) { - BKE_reportf(reports, - RPT_WARNING, - "%s: Couldn't get world bind transforms for skeleton %s", - __func__, - skel_query.GetSkeleton().GetPrim().GetPath().GetAsString().c_str()); + WM_reportf(RPT_WARNING, + "%s: Couldn't get world bind transforms for skeleton %s", + __func__, + skel_query.GetSkeleton().GetPrim().GetPath().GetAsString().c_str()); return; } if (bind_xforms.size() != joint_order.size()) { - BKE_reportf(reports, - RPT_WARNING, - "%s: Number of bind transforms doesn't match the number of joints for skeleton %s", - __func__, - skel_query.GetSkeleton().GetPrim().GetPath().GetAsString().c_str()); + WM_reportf(RPT_WARNING, + "%s: Number of bind transforms doesn't match the number of joints for skeleton %s", + __func__, + skel_query.GetSkeleton().GetPrim().GetPath().GetAsString().c_str()); return; } @@ -340,7 +334,6 @@ namespace blender::io::usd { void import_blendshapes(Main *bmain, Object *mesh_obj, const pxr::UsdPrim &prim, - ReportList *reports, const bool import_anim) { if (!(mesh_obj && mesh_obj->data && mesh_obj->type == OB_MESH && prim)) { @@ -370,11 +363,10 @@ void import_blendshapes(Main *bmain, pxr::SdfPathVector targets; if (!skel_api.GetBlendShapeTargetsRel().GetTargets(&targets)) { - BKE_reportf(reports, - RPT_WARNING, - "%s: Couldn't get blendshape targets for prim %s", - __func__, - prim.GetPath().GetAsString().c_str()); + WM_reportf(RPT_WARNING, + "%s: Couldn't get blendshape targets for prim %s", + __func__, + prim.GetPath().GetAsString().c_str()); return; } @@ -398,22 +390,20 @@ void import_blendshapes(Main *bmain, /* Sanity check. */ if (targets.size() != blendshapes.size()) { - BKE_reportf(reports, - RPT_WARNING, - "%s: Number of blendshapes doesn't match number of blendshape targets for prim %s", - __func__, - prim.GetPath().GetAsString().c_str()); + WM_reportf(RPT_WARNING, + "%s: Number of blendshapes doesn't match number of blendshape targets for prim %s", + __func__, + prim.GetPath().GetAsString().c_str()); return; } pxr::UsdStageRefPtr stage = prim.GetStage(); if (!stage) { - BKE_reportf(reports, - RPT_WARNING, - "%s: Couldn't get stage for prim %s", - __func__, - prim.GetPath().GetAsString().c_str()); + WM_reportf(RPT_WARNING, + "%s: Couldn't get stage for prim %s", + __func__, + prim.GetPath().GetAsString().c_str()); return; } @@ -450,20 +440,16 @@ void import_blendshapes(Main *bmain, pxr::VtVec3fArray offsets; if (!blendshape.GetOffsetsAttr().Get(&offsets)) { - BKE_reportf(reports, - RPT_WARNING, - "%s: Couldn't get offsets for blend shape %s", - __func__, - path.GetAsString().c_str()); + WM_reportf(RPT_WARNING, + "%s: Couldn't get offsets for blend shape %s", + __func__, + path.GetAsString().c_str()); continue; } if (offsets.empty()) { - BKE_reportf(reports, - RPT_WARNING, - "%s: No offsets for blend shape %s", - __func__, - path.GetAsString().c_str()); + WM_reportf( + RPT_WARNING, "%s: No offsets for blend shape %s", __func__, path.GetAsString().c_str()); continue; } @@ -487,8 +473,7 @@ void import_blendshapes(Main *bmain, * offset to the key block point. */ for (int a = 0; a < kb->totelem; ++a, fp += 3) { if (a >= offsets.size()) { - BKE_reportf( - reports, + WM_reportf( RPT_WARNING, "%s: Number of offsets greater than number of mesh vertices for blend shape %s", __func__, @@ -510,8 +495,7 @@ void import_blendshapes(Main *bmain, continue; } if (a >= offsets.size()) { - BKE_reportf( - reports, + WM_reportf( RPT_WARNING, "%s: Number of offsets greater than number of mesh vertices for blend shape %s", __func__, @@ -637,7 +621,6 @@ void import_blendshapes(Main *bmain, void import_skeleton(Main *bmain, Object *arm_obj, const pxr::UsdSkelSkeleton &skel, - ReportList *reports, const bool import_anim) { if (!(arm_obj && arm_obj->data && arm_obj->type == OB_ARMATURE)) { @@ -648,11 +631,10 @@ void import_skeleton(Main *bmain, pxr::UsdSkelSkeletonQuery skel_query = skel_cache.GetSkelQuery(skel); if (!skel_query.IsValid()) { - BKE_reportf(reports, - RPT_WARNING, - "%s: Couldn't query skeleton %s", - __func__, - skel.GetPath().GetAsString().c_str()); + WM_reportf(RPT_WARNING, + "%s: Couldn't query skeleton %s", + __func__, + skel.GetPath().GetAsString().c_str()); return; } @@ -661,11 +643,10 @@ void import_skeleton(Main *bmain, pxr::VtTokenArray joint_order = skel_query.GetJointOrder(); if (joint_order.size() != skel_topology.size()) { - BKE_reportf(reports, - RPT_WARNING, - "%s: Topology and joint order size mismatch for skeleton %s", - __func__, - skel.GetPath().GetAsString().c_str()); + WM_reportf(RPT_WARNING, + "%s: Topology and joint order size mismatch for skeleton %s", + __func__, + skel.GetPath().GetAsString().c_str()); return; } @@ -687,11 +668,8 @@ void import_skeleton(Main *bmain, std::string name = pxr::SdfPath(joint).GetName(); EditBone *bone = ED_armature_ebone_add(arm, name.c_str()); if (!bone) { - BKE_reportf(reports, - RPT_WARNING, - "%s: Couldn't add bone for joint %s", - __func__, - joint.GetString().c_str()); + WM_reportf( + RPT_WARNING, "%s: Couldn't add bone for joint %s", __func__, joint.GetString().c_str()); edit_bones.push_back(nullptr); continue; } @@ -702,31 +680,28 @@ void import_skeleton(Main *bmain, /* Sanity check: we should have created a bone for each joint. */ const size_t num_joints = skel_topology.GetNumJoints(); if (edit_bones.size() != num_joints) { - BKE_reportf(reports, - RPT_WARNING, - "%s: Mismatch in bone and joint counts for skeleton %s", - __func__, - skel.GetPath().GetAsString().c_str()); + WM_reportf(RPT_WARNING, + "%s: Mismatch in bone and joint counts for skeleton %s", + __func__, + skel.GetPath().GetAsString().c_str()); return; } /* Get the world space joint transforms at bind time. */ pxr::VtMatrix4dArray bind_xforms; if (!skel_query.GetJointWorldBindTransforms(&bind_xforms)) { - BKE_reportf(reports, - RPT_WARNING, - "%s: Couldn't get world bind transforms for skeleton %s", - __func__, - skel.GetPath().GetAsString().c_str()); + WM_reportf(RPT_WARNING, + "%s: Couldn't get world bind transforms for skeleton %s", + __func__, + skel.GetPath().GetAsString().c_str()); return; } if (bind_xforms.size() != num_joints) { - BKE_reportf(reports, - RPT_WARNING, - "%s: Mismatch in bind xforms and joint counts for skeleton %s", - __func__, - skel.GetPath().GetAsString().c_str()); + WM_reportf(RPT_WARNING, + "%s: Mismatch in bind xforms and joint counts for skeleton %s", + __func__, + skel.GetPath().GetAsString().c_str()); return; } @@ -771,8 +746,7 @@ void import_skeleton(Main *bmain, bool valid_skeleton = true; if (negative_determinant) { valid_skeleton = false; - BKE_reportf( - reports, + WM_reportf( RPT_WARNING, "USD Skeleton Import: bone matrices with negative determinants detected in prim %s. " "Such matrices may indicate negative scales, possibly due to mirroring operations, " @@ -876,14 +850,11 @@ void import_skeleton(Main *bmain, ED_armature_edit_free(arm); if (import_anim && valid_skeleton) { - import_skeleton_curves(bmain, arm_obj, skel_query, joint_to_bone_map, reports); + import_skeleton_curves(bmain, arm_obj, skel_query, joint_to_bone_map); } } -void import_mesh_skel_bindings(Main *bmain, - Object *mesh_obj, - const pxr::UsdPrim &prim, - ReportList *reports) +void import_mesh_skel_bindings(Main *bmain, Object *mesh_obj, const pxr::UsdPrim &prim) { if (!(bmain && mesh_obj && mesh_obj->type == OB_MESH && prim)) { return; @@ -941,11 +912,10 @@ void import_mesh_skel_bindings(Main *bmain, /* We expect the element counts to match. */ if (joint_indices_elem_size != joint_weights_elem_size) { - BKE_reportf(reports, - RPT_WARNING, - "%s: Joint weights and joint indices element size mismatch for prim %s", - __func__, - prim.GetPath().GetAsString().c_str()); + WM_reportf(RPT_WARNING, + "%s: Joint weights and joint indices element size mismatch for prim %s", + __func__, + prim.GetPath().GetAsString().c_str()); return; } @@ -961,11 +931,10 @@ void import_mesh_skel_bindings(Main *bmain, } if (joint_indices.size() != joint_weights.size()) { - BKE_reportf(reports, - RPT_WARNING, - "%s: Joint weights and joint indices size mismatch for prim %s", - __func__, - prim.GetPath().GetAsString().c_str()); + WM_reportf(RPT_WARNING, + "%s: Joint weights and joint indices size mismatch for prim %s", + __func__, + prim.GetPath().GetAsString().c_str()); return; } @@ -975,12 +944,11 @@ void import_mesh_skel_bindings(Main *bmain, /* Sanity check: we expect only vertex or constant interpolation. */ if (!ELEM(interp, pxr::UsdGeomTokens->vertex, pxr::UsdGeomTokens->constant)) { - BKE_reportf(reports, - RPT_WARNING, - "%s: Unexpected joint weights interpolation type %s for prim %s", - __func__, - interp.GetString().c_str(), - prim.GetPath().GetAsString().c_str()); + WM_reportf(RPT_WARNING, + "%s: Unexpected joint weights interpolation type %s for prim %s", + __func__, + interp.GetString().c_str(), + prim.GetPath().GetAsString().c_str()); return; } @@ -988,20 +956,18 @@ void import_mesh_skel_bindings(Main *bmain, if (interp == pxr::UsdGeomTokens->vertex && joint_weights.size() != mesh->totvert * joint_weights_elem_size) { - BKE_reportf(reports, - RPT_WARNING, - "%s: Joint weights of unexpected size for vertex interpolation for prim %s", - __func__, - prim.GetPath().GetAsString().c_str()); + WM_reportf(RPT_WARNING, + "%s: Joint weights of unexpected size for vertex interpolation for prim %s", + __func__, + prim.GetPath().GetAsString().c_str()); return; } if (interp == pxr::UsdGeomTokens->constant && joint_weights.size() != joint_weights_elem_size) { - BKE_reportf(reports, - RPT_WARNING, - "%s: Joint weights of unexpected size for constant interpolation for prim %s", - __func__, - prim.GetPath().GetAsString().c_str()); + WM_reportf(RPT_WARNING, + "%s: Joint weights of unexpected size for constant interpolation for prim %s", + __func__, + prim.GetPath().GetAsString().c_str()); return; } @@ -1023,11 +989,10 @@ void import_mesh_skel_bindings(Main *bmain, } if (BKE_object_defgroup_data_create(static_cast(mesh_obj->data)) == nullptr) { - BKE_reportf(reports, - RPT_WARNING, - "%s: Error creating deform group data for mesh %s", - __func__, - mesh_obj->id.name + 2); + WM_reportf(RPT_WARNING, + "%s: Error creating deform group data for mesh %s", + __func__, + mesh_obj->id.name + 2); return; } diff --git a/source/blender/io/usd/intern/usd_skel_convert.h b/source/blender/io/usd/intern/usd_skel_convert.h index a2fd237a877..aa130980b15 100644 --- a/source/blender/io/usd/intern/usd_skel_convert.h +++ b/source/blender/io/usd/intern/usd_skel_convert.h @@ -3,7 +3,6 @@ * SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once -#include "DNA_windowmanager_types.h" #include #include #include @@ -36,15 +35,12 @@ struct ImportSettings; * \param bmain: Main pointer * \param mesh_obj: Mesh object to which imported shape keys will be added * \param prim: The USD primitive from which blend-shapes will be imported - * \param reports: the storage for potential warning or error reports (generated using BKE_report - * API). * \param import_anim: Whether to import time-sampled weights as shape key * animation curves */ void import_blendshapes(Main *bmain, Object *mesh_obj, const pxr::UsdPrim &prim, - ReportList *reports, bool import_anim = true); /** @@ -55,15 +51,12 @@ void import_blendshapes(Main *bmain, * \param bmain: Main pointer * \param arm_obj: Armature object to which the bone hierarchy will be added * \param skel: The USD skeleton from which bones and animation will be imported - * \param reports: the storage for potential warning or error reports (generated using BKE_report - * API). * \param import_anim: Whether to import time-sampled joint transforms as bone * animation curves */ void import_skeleton(Main *bmain, Object *arm_obj, const pxr::UsdSkelSkeleton &skel, - ReportList *reports, bool import_anim = true); /** * Import skinning data from a source USD prim as deform groups and an armature @@ -73,12 +66,7 @@ void import_skeleton(Main *bmain, * \param bmain: Main pointer * \param obj: Mesh object to which an armature modifier will be added * \param prim: The USD primitive from which skinning data will be imported - * \param reports: the storage for potential warning or error reports (generated using BKE_report - * API). */ -void import_mesh_skel_bindings(Main *bmain, - Object *mesh_obj, - const pxr::UsdPrim &prim, - ReportList *reports); +void import_mesh_skel_bindings(Main *bmain, Object *mesh_obj, const pxr::UsdPrim &prim); } // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_writer_abstract.cc b/source/blender/io/usd/intern/usd_writer_abstract.cc index 7140f51ef3b..4c41f8f67a0 100644 --- a/source/blender/io/usd/intern/usd_writer_abstract.cc +++ b/source/blender/io/usd/intern/usd_writer_abstract.cc @@ -9,8 +9,6 @@ #include #include "BKE_customdata.h" -#include "BKE_report.h" - #include "BLI_assert.h" #include "DNA_mesh_types.h" @@ -169,10 +167,9 @@ void USDAbstractWriter::author_extent(const pxr::UsdTimeCode timecode, pxr::UsdG pxr::GfBBox3d bounds = bboxCache.ComputeLocalBound(prim.GetPrim()); if (pxr::GfBBox3d() == bounds) { /* This will occur, for example, if a mesh does not have any vertices. */ - BKE_reportf(reports(), - RPT_WARNING, - "USD Export: no bounds could be computed for %s", - prim.GetPrim().GetName().GetText()); + WM_reportf(RPT_WARNING, + "USD Export: no bounds could be computed for %s", + prim.GetPrim().GetName().GetText()); return; } diff --git a/source/blender/io/usd/intern/usd_writer_abstract.h b/source/blender/io/usd/intern/usd_writer_abstract.h index 94f5c2224c7..b1f8a0e2fb4 100644 --- a/source/blender/io/usd/intern/usd_writer_abstract.h +++ b/source/blender/io/usd/intern/usd_writer_abstract.h @@ -16,10 +16,7 @@ #include "DEG_depsgraph_query.hh" -#include "WM_types.hh" - #include "DNA_material_types.h" -#include "DNA_windowmanager_types.h" struct Material; @@ -54,12 +51,6 @@ class USDAbstractWriter : public AbstractHierarchyWriter { const pxr::SdfPath &usd_path() const; - /** Get the wmJobWorkerStatus-provided `reports` list pointer, to use with the BKE_report API. */ - ReportList *reports() const - { - return usd_export_context_.export_params.worker_status->reports; - } - protected: virtual void do_write(HierarchyContext &context) = 0; std::string get_export_file_path() const; diff --git a/source/blender/io/usd/intern/usd_writer_curves.cc b/source/blender/io/usd/intern/usd_writer_curves.cc index 1c2d0119b11..77f5e07ba08 100644 --- a/source/blender/io/usd/intern/usd_writer_curves.cc +++ b/source/blender/io/usd/intern/usd_writer_curves.cc @@ -18,7 +18,6 @@ #include "BKE_curves.hh" #include "BKE_lib_id.h" #include "BKE_material.h" -#include "BKE_report.h" #include "BLI_math_geom.h" #include "BLT_translation.h" @@ -27,6 +26,7 @@ #include "RNA_enum_types.hh" #include "WM_api.hh" +#include "WM_types.hh" namespace blender::io::usd { @@ -85,8 +85,7 @@ static void populate_curve_widths(const bke::CurvesGeometry &geometry, pxr::VtAr static pxr::TfToken get_curve_width_interpolation(const pxr::VtArray &widths, const pxr::VtArray &segments, const pxr::VtIntArray &control_point_counts, - const bool is_cyclic, - ReportList *reports) + const bool is_cyclic) { if (widths.empty()) { return pxr::TfToken(); @@ -111,7 +110,7 @@ static pxr::TfToken get_curve_width_interpolation(const pxr::VtArray &wid return pxr::UsdGeomTokens->varying; } - BKE_report(reports, RPT_WARNING, "Curve width size not supported for USD interpolation"); + WM_report(RPT_WARNING, "Curve width size not supported for USD interpolation"); return pxr::TfToken(); } @@ -159,8 +158,7 @@ static void populate_curve_props(const bke::CurvesGeometry &geometry, pxr::VtArray &widths, pxr::TfToken &interpolation, const bool is_cyclic, - const bool is_cubic, - ReportList *reports) + const bool is_cubic) { const int num_curves = geometry.curve_num; const Span positions = geometry.positions(); @@ -171,8 +169,7 @@ static void populate_curve_props(const bke::CurvesGeometry &geometry, geometry, positions, verts, control_point_counts, segments, is_cyclic, is_cubic); populate_curve_widths(geometry, widths); - interpolation = get_curve_width_interpolation( - widths, segments, control_point_counts, is_cyclic, reports); + interpolation = get_curve_width_interpolation(widths, segments, control_point_counts, is_cyclic); } static void populate_curve_verts_for_bezier(const bke::CurvesGeometry &geometry, @@ -249,8 +246,7 @@ static void populate_curve_props_for_bezier(const bke::CurvesGeometry &geometry, pxr::VtIntArray &control_point_counts, pxr::VtArray &widths, pxr::TfToken &interpolation, - const bool is_cyclic, - ReportList *reports) + const bool is_cyclic) { const int num_curves = geometry.curve_num; @@ -266,8 +262,7 @@ static void populate_curve_props_for_bezier(const bke::CurvesGeometry &geometry, geometry, positions, handles_l, handles_r, verts, control_point_counts, segments, is_cyclic); populate_curve_widths(geometry, widths); - interpolation = get_curve_width_interpolation( - widths, segments, control_point_counts, is_cyclic, reports); + interpolation = get_curve_width_interpolation(widths, segments, control_point_counts, is_cyclic); } static void populate_curve_props_for_nurbs(const bke::CurvesGeometry &geometry, @@ -402,8 +397,7 @@ void USDCurvesWriter::do_write(HierarchyContext &context) }); if (number_of_curve_types > 1) { - BKE_report( - reports(), RPT_WARNING, "Cannot export mixed curve types in the same Curves object"); + WM_report(RPT_WARNING, "Cannot export mixed curve types in the same Curves object"); return; } @@ -419,9 +413,8 @@ void USDCurvesWriter::do_write(HierarchyContext &context) } if (!all_same_cyclic_type) { - BKE_report(reports(), - RPT_WARNING, - "Cannot export mixed cyclic and non-cyclic curves in the same Curves object"); + WM_report(RPT_WARNING, + "Cannot export mixed cyclic and non-cyclic curves in the same Curves object"); return; } @@ -448,13 +441,12 @@ void USDCurvesWriter::do_write(HierarchyContext &context) RNA_enum_name_from_value( rna_enum_curves_type_items, int(curve_type), ¤t_curve_type_name); - BKE_reportf(reports(), - RPT_WARNING, - "USD does not support animating curve types. The curve type changes from %s to " - "%s on frame %f", - IFACE_(first_frame_curve_type_name), - IFACE_(current_curve_type_name), - timecode.GetValue()); + WM_reportf(RPT_WARNING, + "USD does not support animating curve types. The curve type changes from %s to " + "%s on frame %f", + IFACE_(first_frame_curve_type_name), + IFACE_(current_curve_type_name), + timecode.GetValue()); return; } @@ -462,34 +454,22 @@ void USDCurvesWriter::do_write(HierarchyContext &context) case CURVE_TYPE_POLY: usd_curves = DefineUsdGeomBasisCurves(pxr::VtValue(), is_cyclic, false); - populate_curve_props(geometry, - verts, - control_point_counts, - widths, - interpolation, - is_cyclic, - false, - reports()); + populate_curve_props( + geometry, verts, control_point_counts, widths, interpolation, is_cyclic, false); break; case CURVE_TYPE_CATMULL_ROM: usd_curves = DefineUsdGeomBasisCurves( pxr::VtValue(pxr::UsdGeomTokens->catmullRom), is_cyclic, true); - populate_curve_props(geometry, - verts, - control_point_counts, - widths, - interpolation, - is_cyclic, - true, - reports()); + populate_curve_props( + geometry, verts, control_point_counts, widths, interpolation, is_cyclic, true); break; case CURVE_TYPE_BEZIER: usd_curves = DefineUsdGeomBasisCurves( pxr::VtValue(pxr::UsdGeomTokens->bezier), is_cyclic, true); populate_curve_props_for_bezier( - geometry, verts, control_point_counts, widths, interpolation, is_cyclic, reports()); + geometry, verts, control_point_counts, widths, interpolation, is_cyclic); break; case CURVE_TYPE_NURBS: { pxr::VtArray knots; diff --git a/source/blender/io/usd/intern/usd_writer_material.cc b/source/blender/io/usd/intern/usd_writer_material.cc index dbff0be8fc4..e683a24ca12 100644 --- a/source/blender/io/usd/intern/usd_writer_material.cc +++ b/source/blender/io/usd/intern/usd_writer_material.cc @@ -13,7 +13,6 @@ #include "BKE_main.h" #include "BKE_node.hh" #include "BKE_node_runtime.hh" -#include "BKE_report.h" #include "IMB_colormanagement.h" @@ -23,7 +22,7 @@ #include "BLI_memory_utils.hh" #include "BLI_path_util.h" #include "BLI_string.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "DNA_material_types.h" @@ -429,8 +428,7 @@ static std::string get_in_memory_texture_filename(Image *ima) static void export_in_memory_texture(Image *ima, const std::string &export_dir, - const bool allow_overwrite, - ReportList *reports) + const bool allow_overwrite) { char image_abs_path[FILE_MAX]; @@ -474,8 +472,7 @@ static void export_in_memory_texture(Image *ima, std::cout << "Exporting in-memory texture to " << export_path << std::endl; if (BKE_imbuf_write_as(imbuf, export_path, &imageFormat, true) == 0) { - BKE_reportf( - reports, RPT_WARNING, "USD export: couldn't export in-memory texture to %s", export_path); + WM_reportf(RPT_WARNING, "USD export: couldn't export in-memory texture to %s", export_path); } } @@ -705,8 +702,7 @@ static std::string get_tex_image_asset_filepath(const USDExporterContext &usd_ex * destination directory. */ static void copy_tiled_textures(Image *ima, const std::string &dest_dir, - const bool allow_overwrite, - ReportList *reports) + const bool allow_overwrite) { char src_path[FILE_MAX]; get_absolute_path(ima, src_path); @@ -747,21 +743,17 @@ static void copy_tiled_textures(Image *ima, /* Copy the file. */ if (BLI_copy(src_tile_path, dest_tile_path) != 0) { - BKE_reportf(reports, - RPT_WARNING, - "USD export: could not copy texture tile from %s to %s", - src_tile_path, - dest_tile_path); + WM_reportf(RPT_WARNING, + "USD export: could not copy texture tile from %s to %s", + src_tile_path, + dest_tile_path); } } MEM_SAFE_FREE(udim_pattern); } /* Copy the given image to the destination directory. */ -static void copy_single_file(Image *ima, - const std::string &dest_dir, - const bool allow_overwrite, - ReportList *reports) +static void copy_single_file(Image *ima, const std::string &dest_dir, const bool allow_overwrite) { char source_path[FILE_MAX]; get_absolute_path(ima, source_path); @@ -785,11 +777,8 @@ static void copy_single_file(Image *ima, /* Copy the file. */ if (BLI_copy(source_path, dest_path) != 0) { - BKE_reportf(reports, - RPT_WARNING, - "USD export: could not copy texture from %s to %s", - source_path, - dest_path); + WM_reportf( + RPT_WARNING, "USD export: could not copy texture from %s to %s", source_path, dest_path); } } @@ -827,16 +816,13 @@ static void export_texture(const USDExporterContext &usd_export_context, bNode * std::string dest_dir(tex_dir_path); if (is_generated || is_dirty || is_packed) { - export_in_memory_texture( - ima, dest_dir, allow_overwrite, usd_export_context.export_params.worker_status->reports); + export_in_memory_texture(ima, dest_dir, allow_overwrite); } else if (ima->source == IMA_SRC_TILED) { - copy_tiled_textures( - ima, dest_dir, allow_overwrite, usd_export_context.export_params.worker_status->reports); + copy_tiled_textures(ima, dest_dir, allow_overwrite); } else { - copy_single_file( - ima, dest_dir, allow_overwrite, usd_export_context.export_params.worker_status->reports); + copy_single_file(ima, dest_dir, allow_overwrite); } } @@ -867,10 +853,7 @@ pxr::UsdShadeMaterial create_usd_material(const USDExporterContext &usd_export_c create_usd_viewport_material(usd_export_context, material, usd_material); } - call_material_export_hooks(usd_export_context.stage, - material, - usd_material, - usd_export_context.export_params.worker_status->reports); + call_material_export_hooks(usd_export_context.stage, material, usd_material); return usd_material; } diff --git a/source/blender/io/usd/intern/usd_writer_mesh.cc b/source/blender/io/usd/intern/usd_writer_mesh.cc index 62f1530f3db..2b30a68a3bd 100644 --- a/source/blender/io/usd/intern/usd_writer_mesh.cc +++ b/source/blender/io/usd/intern/usd_writer_mesh.cc @@ -10,7 +10,6 @@ #include #include -#include "BLI_array_utils.hh" #include "BLI_assert.h" #include "BLI_math_color.hh" #include "BLI_math_quaternion_types.hh" @@ -25,7 +24,6 @@ #include "BKE_mesh_wrapper.hh" #include "BKE_modifier.h" #include "BKE_object.hh" -#include "BKE_report.h" #include "DEG_depsgraph.hh" @@ -130,7 +128,7 @@ void USDGenericMeshWriter::write_custom_data(const Mesh *mesh, pxr::UsdGeomMesh } static std::optional convert_blender_type_to_usd( - const eCustomDataType blender_type, ReportList *reports) + const eCustomDataType blender_type) { switch (blender_type) { case CD_PROP_FLOAT: @@ -149,13 +147,13 @@ static std::optional convert_blender_type_to_usd( case CD_PROP_QUATERNION: return pxr::SdfValueTypeNames->QuatfArray; default: - BKE_reportf(reports, RPT_WARNING, "Unsupported type for mesh data"); + WM_reportf(RPT_WARNING, "Unsupported type for mesh data"); return std::nullopt; } } static const std::optional convert_blender_domain_to_usd( - const eAttrDomain blender_domain, ReportList *reports) + const eAttrDomain blender_domain) { switch (blender_domain) { case ATTR_DOMAIN_CORNER: @@ -167,7 +165,7 @@ static const std::optional convert_blender_domain_to_usd( /* Notice: Edge types are not supported in USD! */ default: - BKE_reportf(reports, RPT_WARNING, "Unsupported type for mesh data"); + WM_reportf(RPT_WARNING, "Unsupported type for mesh data"); return std::nullopt; } } @@ -233,10 +231,9 @@ void USDGenericMeshWriter::write_generic_data(const Mesh *mesh, const pxr::UsdGeomPrimvarsAPI pvApi = pxr::UsdGeomPrimvarsAPI(usd_mesh); /* Varying type depends on original domain. */ - const std::optional prim_varying = convert_blender_domain_to_usd(meta_data.domain, - reports()); + const std::optional prim_varying = convert_blender_domain_to_usd(meta_data.domain); const std::optional prim_attr_type = convert_blender_type_to_usd( - meta_data.data_type, reports()); + meta_data.data_type); const GVArraySpan attribute = *mesh->attributes().lookup( attribute_id, meta_data.domain, meta_data.data_type); @@ -245,11 +242,10 @@ void USDGenericMeshWriter::write_generic_data(const Mesh *mesh, } if (!prim_varying || !prim_attr_type) { - BKE_reportf(reports(), - RPT_WARNING, - "Mesh %s, Attribute %s cannot be converted to USD", - &mesh->id.name[2], - attribute_id.name().data()); + WM_reportf(RPT_WARNING, + "Mesh %s, Attribute %s cannot be converted to USD", + &mesh->id.name[2], + attribute_id.name().data()); return; } @@ -649,28 +645,42 @@ void USDGenericMeshWriter::assign_materials(const HierarchyContext &context, void USDGenericMeshWriter::write_normals(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh) { pxr::UsdTimeCode timecode = get_export_time_code(); + const float(*lnors)[3] = static_cast( + CustomData_get_layer(&mesh->loop_data, CD_NORMAL)); + const OffsetIndices faces = mesh->faces(); + const Span corner_verts = mesh->corner_verts(); pxr::VtVec3fArray loop_normals; - loop_normals.resize(mesh->totloop); + loop_normals.reserve(mesh->totloop); - MutableSpan dst_normals(reinterpret_cast(loop_normals.data()), loop_normals.size()); - - switch (mesh->normals_domain()) { - case bke::MeshNormalDomain::Point: { - array_utils::gather(mesh->vert_normals(), mesh->corner_verts(), dst_normals); - break; + if (lnors != nullptr) { + /* Export custom loop normals. */ + for (int loop_idx = 0, totloop = mesh->totloop; loop_idx < totloop; ++loop_idx) { + loop_normals.push_back(pxr::GfVec3f(lnors[loop_idx])); } - case bke::MeshNormalDomain::Face: { - const OffsetIndices faces = mesh->faces(); - const Span face_normals = mesh->face_normals(); - for (const int i : faces.index_range()) { - dst_normals.slice(faces[i]).fill(face_normals[i]); + } + else { + /* Compute the loop normals based on the 'smooth' flag. */ + bke::AttributeAccessor attributes = mesh->attributes(); + const Span vert_normals = mesh->vert_normals(); + const Span face_normals = mesh->face_normals(); + const VArray sharp_faces = *attributes.lookup_or_default( + "sharp_face", ATTR_DOMAIN_FACE, false); + for (const int i : faces.index_range()) { + const IndexRange face = faces[i]; + if (sharp_faces[i]) { + /* Flat shaded, use common normal for all verts. */ + pxr::GfVec3f pxr_normal(&face_normals[i].x); + for (int loop_idx = 0; loop_idx < face.size(); ++loop_idx) { + loop_normals.push_back(pxr_normal); + } + } + else { + /* Smooth shaded, use individual vert normals. */ + for (const int vert : corner_verts.slice(face)) { + loop_normals.push_back(pxr::GfVec3f(&vert_normals[vert].x)); + } } - break; - } - case bke::MeshNormalDomain::Corner: { - array_utils::copy(mesh->corner_normals(), dst_normals); - break; } } diff --git a/source/blender/io/usd/intern/usd_writer_volume.cc b/source/blender/io/usd/intern/usd_writer_volume.cc index 8d85f924735..784f61e81e5 100644 --- a/source/blender/io/usd/intern/usd_writer_volume.cc +++ b/source/blender/io/usd/intern/usd_writer_volume.cc @@ -11,7 +11,6 @@ #include "DNA_volume_types.h" #include "DNA_windowmanager_types.h" -#include "BKE_report.h" #include "BKE_volume.h" #include "BLI_fileops.h" @@ -48,10 +47,9 @@ void USDVolumeWriter::do_write(HierarchyContext &context) auto vdb_file_path = resolve_vdb_file(volume); if (!vdb_file_path.has_value()) { - BKE_reportf(reports(), - RPT_WARNING, - "USD Export: failed to resolve .vdb file for object: %s", - volume->id.name + 2); + WM_reportf(RPT_WARNING, + "USD Export: failed to resolve .vdb file for object: %s", + volume->id.name + 2); return; } @@ -60,10 +58,9 @@ void USDVolumeWriter::do_write(HierarchyContext &context) vdb_file_path = relative_vdb_file_path; } else { - BKE_reportf(reports(), - RPT_WARNING, - "USD Export: couldn't construct relative file path for .vdb file, absolute path " - "will be used instead"); + WM_reportf(RPT_WARNING, + "USD Export: couldn't construct relative file path for .vdb file, absolute path " + "will be used instead"); } } diff --git a/source/blender/io/usd/tests/usd_curves_test.cc b/source/blender/io/usd/tests/usd_curves_test.cc index 1a1aab8cdd6..ccdcc97681f 100644 --- a/source/blender/io/usd/tests/usd_curves_test.cc +++ b/source/blender/io/usd/tests/usd_curves_test.cc @@ -110,7 +110,7 @@ TEST_F(UsdCurvesTest, usd_export_curves) USDExportParams params; - const bool result = USD_export(context, output_filename.c_str(), ¶ms, false, nullptr); + const bool result = USD_export(context, output_filename.c_str(), ¶ms, false); EXPECT_TRUE(result) << "USD export should succed."; pxr::UsdStageRefPtr stage = pxr::UsdStage::Open(output_filename); diff --git a/source/blender/io/usd/tests/usd_export_test.cc b/source/blender/io/usd/tests/usd_export_test.cc index dd93ae2a4b5..90fa3b366f4 100644 --- a/source/blender/io/usd/tests/usd_export_test.cc +++ b/source/blender/io/usd/tests/usd_export_test.cc @@ -214,7 +214,7 @@ TEST_F(UsdExportTest, usd_export_rain_mesh) params.export_uvmaps = false; params.visible_objects_only = true; - bool result = USD_export(context, output_filename.c_str(), ¶ms, false, nullptr); + bool result = USD_export(context, output_filename.c_str(), ¶ms, false); ASSERT_TRUE(result) << "Writing to " << output_filename << " failed!"; pxr::UsdStageRefPtr stage = pxr::UsdStage::Open(output_filename); @@ -280,7 +280,7 @@ TEST_F(UsdExportTest, usd_export_material) params.generate_preview_surface = true; params.relative_paths = false; - const bool result = USD_export(context, output_filename.c_str(), ¶ms, false, nullptr); + const bool result = USD_export(context, output_filename.c_str(), ¶ms, false); ASSERT_TRUE(result) << "Unable to export stage to " << output_filename; pxr::UsdStageRefPtr stage = pxr::UsdStage::Open(output_filename); diff --git a/source/blender/io/usd/tests/usd_usdz_export_test.cc b/source/blender/io/usd/tests/usd_usdz_export_test.cc index d36ecf9c5f3..d04eaae046f 100644 --- a/source/blender/io/usd/tests/usd_usdz_export_test.cc +++ b/source/blender/io/usd/tests/usd_usdz_export_test.cc @@ -98,7 +98,7 @@ TEST_F(UsdUsdzExportTest, usdz_export) params.export_materials = false; params.visible_objects_only = false; - bool result = USD_export(context, output_filepath, ¶ms, false, nullptr); + bool result = USD_export(context, output_filepath, ¶ms, false); ASSERT_TRUE(result) << "usd export to " << output_filepath << " failed."; pxr::UsdStageRefPtr stage = pxr::UsdStage::Open(output_filepath); diff --git a/source/blender/io/usd/usd.h b/source/blender/io/usd/usd.h index 3b40d86cfb7..5908bdfae26 100644 --- a/source/blender/io/usd/usd.h +++ b/source/blender/io/usd/usd.h @@ -16,7 +16,6 @@ struct CacheArchiveHandle; struct CacheReader; struct Object; struct bContext; -struct wmJobWorkerStatus; /* Behavior when the name of an imported material * conflicts with an existing material. */ @@ -56,10 +55,6 @@ struct USDExportParams { bool overwrite_textures = true; bool relative_paths = true; char root_prim_path[1024] = ""; /* FILE_MAX */ - - /** Communication structure between the wmJob management code and the worker code. Currently used - * to generate safely reports from the worker thread. */ - wmJobWorkerStatus *worker_status; }; struct USDImportParams { @@ -96,10 +91,6 @@ struct USDImportParams { char import_textures_dir[768]; /* FILE_MAXDIR */ eUSDTexNameCollisionMode tex_name_collision_mode; bool import_all_materials; - - /** Communication structure between the wmJob management code and the worker code. Currently used - * to generate safely reports from the worker thread. */ - wmJobWorkerStatus *worker_status; }; /* This struct is in place to store the mesh sequence parameters needed when reading a data from a @@ -124,14 +115,12 @@ USDMeshReadParams create_mesh_read_params(double motion_sample_time, int read_fl bool USD_export(struct bContext *C, const char *filepath, const struct USDExportParams *params, - bool as_background_job, - ReportList *reports); + bool as_background_job); bool USD_import(struct bContext *C, const char *filepath, const struct USDImportParams *params, - bool as_background_job, - ReportList *reports); + bool as_background_job); int USD_get_version(void); diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc index 8a0b3a97139..c4cd13beec2 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc +++ b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc @@ -192,6 +192,12 @@ int OBJMesh::ith_smooth_group(const int face_index) const return poly_smooth_groups_[face_index]; } +void OBJMesh::ensure_mesh_normals() const +{ + /* Constant cast can be removed when calculating face corner normals lazily is possible. */ + BKE_mesh_calc_normals_split(const_cast(export_mesh_)); +} + void OBJMesh::calc_smooth_groups(const bool use_bitflags) { const bool *sharp_edges = static_cast( @@ -366,23 +372,16 @@ void OBJMesh::store_normal_coords_and_indices() normal_to_index.reserve(export_mesh_->faces_num); loop_to_normal_index_.resize(export_mesh_->totloop); loop_to_normal_index_.fill(-1); - - Span corner_normals; - if (ELEM(export_mesh_->normals_domain(), - bke::MeshNormalDomain::Point, - bke::MeshNormalDomain::Corner)) - { - corner_normals = export_mesh_->corner_normals(); - } - + const float(*lnors)[3] = static_cast( + CustomData_get_layer(&export_mesh_->loop_data, CD_NORMAL)); for (int face_index = 0; face_index < export_mesh_->faces_num; ++face_index) { const IndexRange face = mesh_faces_[face_index]; - bool need_per_loop_normals = !corner_normals.is_empty() || !(sharp_faces_[face_index]); + bool need_per_loop_normals = lnors != nullptr || !(sharp_faces_[face_index]); if (need_per_loop_normals) { for (const int corner : face) { float3 loop_normal; BLI_assert(corner < export_mesh_->totloop); - copy_v3_v3(loop_normal, corner_normals[corner]); + copy_v3_v3(loop_normal, lnors[corner]); mul_m3_v3(world_and_axes_normal_transform_, loop_normal); normalize_v3(loop_normal); float3 rounded_loop_normal = round_float3_to_n_digits(loop_normal, round_digits); diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh index 919ecce61b3..a47b2679956 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh +++ b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh @@ -119,6 +119,8 @@ class OBJMesh : NonCopyable { */ const Material *get_object_material(int16_t mat_nr) const; + void ensure_mesh_normals() const; + /** * Calculate smooth groups of a smooth-shaded object. * \return A polygon aligned array of smooth group numbers. diff --git a/source/blender/io/wavefront_obj/exporter/obj_exporter.cc b/source/blender/io/wavefront_obj/exporter/obj_exporter.cc index 35701c0b452..b049addbf04 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_exporter.cc +++ b/source/blender/io/wavefront_obj/exporter/obj_exporter.cc @@ -160,6 +160,9 @@ static void write_mesh_objects(Vector> exportable_as_me if (mtl_writer) { mtlindices.append(mtl_writer->add_materials(obj)); } + if (export_params.export_normals) { + obj.ensure_mesh_normals(); + } } /* Parallel over meshes: store normal coords & indices, uv coords and indices. */ diff --git a/source/blender/io/wavefront_obj/importer/importer_mesh_utils.cc b/source/blender/io/wavefront_obj/importer/importer_mesh_utils.cc index 633a3a53825..5666893edc3 100644 --- a/source/blender/io/wavefront_obj/importer/importer_mesh_utils.cc +++ b/source/blender/io/wavefront_obj/importer/importer_mesh_utils.cc @@ -116,8 +116,8 @@ void transform_object(Object *object, const OBJImportParams &import_params) if (import_params.clamp_size != 0.0f) { float3 max_coord(-INT_MAX); float3 min_coord(INT_MAX); - const BoundBox bb = BKE_mesh_boundbox_get(object); - for (const float(&vertex)[3] : bb.vec) { + BoundBox *bb = BKE_mesh_boundbox_get(object); + for (const float(&vertex)[3] : bb->vec) { for (int axis = 0; axis < 3; axis++) { max_coord[axis] = max_ff(max_coord[axis], vertex[axis]); min_coord[axis] = min_ff(min_coord[axis], vertex[axis]); diff --git a/source/blender/io/wavefront_obj/importer/obj_import_mesh.cc b/source/blender/io/wavefront_obj/importer/obj_import_mesh.cc index e31ce580e39..5a602cd3521 100644 --- a/source/blender/io/wavefront_obj/importer/obj_import_mesh.cc +++ b/source/blender/io/wavefront_obj/importer/obj_import_mesh.cc @@ -384,6 +384,7 @@ void MeshFromGeometry::create_normals(Mesh *mesh) tot_loop_idx++; } } + mesh->flag |= ME_AUTOSMOOTH; BKE_mesh_set_custom_normals(mesh, loop_normals); MEM_freeN(loop_normals); } diff --git a/source/blender/io/wavefront_obj/tests/obj_importer_tests.cc b/source/blender/io/wavefront_obj/tests/obj_importer_tests.cc index 869a23ca3a3..164dd4f22da 100644 --- a/source/blender/io/wavefront_obj/tests/obj_importer_tests.cc +++ b/source/blender/io/wavefront_obj/tests/obj_importer_tests.cc @@ -137,9 +137,7 @@ class obj_importer_test : public BlendfileLoadingBaseTest { const Span positions = mesh->vert_positions(); EXPECT_V3_NEAR(positions.first(), exp.vert_first, 0.0001f); EXPECT_V3_NEAR(positions.last(), exp.vert_last, 0.0001f); - const float3 *lnors = mesh->normals_domain() == bke::MeshNormalDomain::Corner ? - mesh->corner_normals().data() : - nullptr; + const float3 *lnors = (const float3 *)CustomData_get_layer(&mesh->loop_data, CD_NORMAL); float3 normal_first = lnors != nullptr ? lnors[0] : float3(0, 0, 0); EXPECT_V3_NEAR(normal_first, exp.normal_first, 0.0001f); const float2 *mloopuv = static_cast( diff --git a/source/blender/makesdna/DNA_curve_types.h b/source/blender/makesdna/DNA_curve_types.h index ca5eca27073..6b60b6ebf28 100644 --- a/source/blender/makesdna/DNA_curve_types.h +++ b/source/blender/makesdna/DNA_curve_types.h @@ -280,7 +280,7 @@ typedef struct Curve { struct CharInfo curinfo; /* font part end */ - /** Current evaluation-time, for use by Objects parented to curves. */ + /** Current evaltime - for use by Objects parented to curves. */ float ctime; float bevfac1, bevfac2; char bevfac1_mapping, bevfac2_mapping; diff --git a/source/blender/makesdna/DNA_grease_pencil_types.h b/source/blender/makesdna/DNA_grease_pencil_types.h index 11b656e40e1..1ee7b306a77 100644 --- a/source/blender/makesdna/DNA_grease_pencil_types.h +++ b/source/blender/makesdna/DNA_grease_pencil_types.h @@ -16,24 +16,21 @@ #ifdef __cplusplus # include "BLI_bounds_types.hh" # include "BLI_function_ref.hh" -# include "BLI_generic_virtual_array.hh" # include "BLI_map.hh" # include "BLI_math_vector_types.hh" # include "BLI_span.hh" namespace blender::bke { -class AttributeAccessor; -class MutableAttributeAccessor; class GreasePencilRuntime; class GreasePencilDrawingRuntime; namespace greasepencil { class DrawingRuntime; class Drawing; -class DrawingReference; class TreeNode; class Layer; class LayerRuntime; class LayerGroup; class LayerGroupRuntime; +struct StrokePoint; } // namespace greasepencil } // namespace blender::bke using GreasePencilRuntimeHandle = blender::bke::GreasePencilRuntime; @@ -121,10 +118,6 @@ typedef struct GreasePencilDrawingReference { * See the note in `GreasePencilLayer->frames()` for a detailed explanation of this. */ struct GreasePencil *id_reference; -#ifdef __cplusplus - blender::bke::greasepencil::DrawingReference &wrap(); - const blender::bke::greasepencil::DrawingReference &wrap() const; -#endif } GreasePencilDrawingReference; /** @@ -407,16 +400,6 @@ typedef struct GreasePencil { /* Root group of the layer tree. */ GreasePencilLayerTreeGroup *root_group_ptr; - /** - * All attributes stored on the grease pencil layers (#ATTR_DOMAIN_LAYER). - */ - CustomData layers_data; - /** - * The index of the active attribute in the UI. - */ - int attributes_active_index; - char _pad2[4]; - /** * Pointer to the active layer. Can be NULL. * This pointer does not own the data. @@ -428,7 +411,7 @@ typedef struct GreasePencil { */ struct Material **material_array; short material_array_num; - char _pad3[2]; + char _pad2[2]; /** * Global flag on the data-block. */ @@ -470,18 +453,8 @@ typedef struct GreasePencil { bool is_layer_active(const blender::bke::greasepencil::Layer *layer) const; /* Adding layers and layer groups. */ - /** Adds a new layer with the given name to the top of root group. */ - blender::bke::greasepencil::Layer &add_layer(blender::StringRefNull name); - /** Adds a new layer with the given name to the top of the given group. */ blender::bke::greasepencil::Layer &add_layer( blender::bke::greasepencil::LayerGroup &parent_group, blender::StringRefNull name); - /** Duplicates the given layer to the top of the root group. */ - blender::bke::greasepencil::Layer &add_layer( - const blender::bke::greasepencil::Layer &duplicate_layer); - /** Duplicates the given layer to the top of the given group. */ - blender::bke::greasepencil::Layer &add_layer( - blender::bke::greasepencil::LayerGroup &parent_group, - const blender::bke::greasepencil::Layer &duplicate_layer); blender::bke::greasepencil::LayerGroup &add_layer_group( blender::bke::greasepencil::LayerGroup &parent_group, blender::StringRefNull name); @@ -499,10 +472,11 @@ typedef struct GreasePencil { blender::bke::greasepencil::LayerGroup &parent_group); /* Search functions. */ - const blender::bke::greasepencil::TreeNode *find_node_by_name(blender::StringRefNull name) const; - blender::bke::greasepencil::TreeNode *find_node_by_name(blender::StringRefNull name); - blender::IndexMask layer_selection_by_name(const blender::StringRefNull name, - blender::IndexMaskMemory &memory) const; + const blender::bke::greasepencil::Layer *find_layer_by_name(blender::StringRefNull name) const; + blender::bke::greasepencil::Layer *find_layer_by_name(blender::StringRefNull name); + const blender::bke::greasepencil::LayerGroup *find_layer_group_by_name( + blender::StringRefNull name) const; + blender::bke::greasepencil::LayerGroup *find_layer_group_by_name(blender::StringRefNull name); void rename_node(blender::bke::greasepencil::TreeNode &node, blender::StringRefNull new_name); @@ -559,10 +533,6 @@ typedef struct GreasePencil { * drawings array. */ void remove_drawings_with_no_users(); - /** - * Makes sure all the drawings that the layer points to have a user. - */ - void update_drawing_users_for_layer(const blender::bke::greasepencil::Layer &layer); /** * Returns a drawing on \a layer at frame \a frame_number or `nullptr` if no such @@ -579,23 +549,16 @@ typedef struct GreasePencil { void foreach_visible_drawing( const int frame, - blender::FunctionRef function); + blender::FunctionRef function); void foreach_visible_drawing( const int frame, - blender::FunctionRef function) - const; + blender::FunctionRef function) const; void foreach_editable_drawing( const int frame, - blender::FunctionRef function); + blender::FunctionRef function); std::optional> bounds_min_max() const; - blender::bke::AttributeAccessor attributes() const; - blender::bke::MutableAttributeAccessor attributes_for_write(); - /* For debugging purposes. */ void print_layer_tree(); #endif diff --git a/source/blender/makesdna/DNA_ipo_types.h b/source/blender/makesdna/DNA_ipo_types.h index 8a133f8c796..320369b3f7d 100644 --- a/source/blender/makesdna/DNA_ipo_types.h +++ b/source/blender/makesdna/DNA_ipo_types.h @@ -472,7 +472,7 @@ typedef struct Ipo { #define IPO_SHORT_BIT 17 #define IPO_INT_BIT 18 -/* icu->ipo: the type of curve. */ +/* icu->ipo: the type of curve */ #define IPO_CONST 0 #define IPO_LIN 1 #define IPO_BEZ 2 diff --git a/source/blender/makesdna/DNA_lightprobe_defaults.h b/source/blender/makesdna/DNA_lightprobe_defaults.h index 26069455e48..a89135e3fa2 100644 --- a/source/blender/makesdna/DNA_lightprobe_defaults.h +++ b/source/blender/makesdna/DNA_lightprobe_defaults.h @@ -42,6 +42,7 @@ .intensity = 1.0f, \ .flag = LIGHTPROBE_FLAG_SHOW_INFLUENCE, \ .grid_flag = LIGHTPROBE_GRID_CAPTURE_INDIRECT | LIGHTPROBE_GRID_CAPTURE_EMISSION, \ + .resolution = LIGHT_PROBE_RESOLUTION_1024, \ } /** \} */ diff --git a/source/blender/makesdna/DNA_lightprobe_types.h b/source/blender/makesdna/DNA_lightprobe_types.h index 6858cc33d60..58f9734a518 100644 --- a/source/blender/makesdna/DNA_lightprobe_types.h +++ b/source/blender/makesdna/DNA_lightprobe_types.h @@ -67,7 +67,7 @@ typedef struct LightProbe { /** Irradiance grid: Dilation. */ float grid_dilation_threshold; float grid_dilation_radius; - + char _pad1[4]; /** Light intensity clamp. */ float grid_clamp_direct; float grid_clamp_indirect; @@ -75,15 +75,30 @@ typedef struct LightProbe { /** Surface element density for scene surface cache. In surfel per unit distance. */ float surfel_density; + /** + * Resolution of the light probe when baked to a texture. Contains `eLightProbeResolution`. + */ + int resolution; + /** Object visibility group, inclusive or exclusive. */ struct Collection *visibility_grp; } LightProbe; +/* LightProbe->resolution, World->probe_resolution. */ +typedef enum eLightProbeResolution { + LIGHT_PROBE_RESOLUTION_64 = 6, + LIGHT_PROBE_RESOLUTION_128 = 7, + LIGHT_PROBE_RESOLUTION_256 = 8, + LIGHT_PROBE_RESOLUTION_512 = 9, + LIGHT_PROBE_RESOLUTION_1024 = 10, + LIGHT_PROBE_RESOLUTION_2048 = 11, +} eLightProbeResolution; + /* Probe->type */ enum { - LIGHTPROBE_TYPE_SPHERE = 0, - LIGHTPROBE_TYPE_PLANE = 1, - LIGHTPROBE_TYPE_VOLUME = 2, + LIGHTPROBE_TYPE_CUBE = 0, + LIGHTPROBE_TYPE_PLANAR = 1, + LIGHTPROBE_TYPE_GRID = 2, }; /* Probe->flag */ diff --git a/source/blender/makesdna/DNA_mask_types.h b/source/blender/makesdna/DNA_mask_types.h index 13cf68800ca..a80de7898cd 100644 --- a/source/blender/makesdna/DNA_mask_types.h +++ b/source/blender/makesdna/DNA_mask_types.h @@ -19,10 +19,7 @@ typedef struct Mask { ID id; struct AnimData *adt; - /** - * Engines draw data, must be immediately after AnimData. See IdDdtTemplate and - * DRW_drawdatalist_from_id to understand this requirement. - */ + /* runtime (must be immediately after id for utilities to use it). */ DrawDataList drawdata; /** Mask layers. */ ListBase masklayers; diff --git a/source/blender/makesdna/DNA_material_types.h b/source/blender/makesdna/DNA_material_types.h index 57b3c5e0324..546d4f79174 100644 --- a/source/blender/makesdna/DNA_material_types.h +++ b/source/blender/makesdna/DNA_material_types.h @@ -169,9 +169,7 @@ typedef struct Material { struct AnimData *adt; short flag; - /** Rendering modes for EEVEE. */ - char surface_render_method; - char _pad1[1]; + char _pad1[2]; /* Colors from Blender Internal that we are still using. */ float r, g, b, a; @@ -214,12 +212,10 @@ typedef struct Material { /* Transparency. */ float alpha_threshold; float refract_depth; - char blend_method; /* TODO(fclem): Deprecate once we remove legacy EEVEE. */ - char blend_shadow; /* TODO(fclem): Deprecate once we remove legacy EEVEE. */ + char blend_method; + char blend_shadow; char blend_flag; - - /* Volume. */ - char volume_intersection_method; + char _pad3[1]; /** * Cached slots for texture painting, must be refreshed in @@ -330,18 +326,6 @@ enum { MA_PREVIEW_WORLD = 1 << 0, }; -/** #Material::surface_render_method */ -enum { - MA_SURFACE_METHOD_DEFERRED = 0, - MA_SURFACE_METHOD_FORWARD = 1, -}; - -/** #Material::volume_intersection_method */ -enum { - MA_VOLUME_ISECT_FAST = 0, - MA_VOLUME_ISECT_ACCURATE = 1, -}; - /** #Material::blend_method */ enum { MA_BM_SOLID = 0, @@ -358,8 +342,6 @@ enum { MA_BL_SS_REFRACTION = (1 << 1), MA_BL_CULL_BACKFACE = (1 << 2), MA_BL_TRANSLUCENCY = (1 << 3), - MA_BL_LIGHTPROBE_VOLUME_DOUBLE_SIDED = (1 << 4), - MA_BL_CULL_BACKFACE_SHADOW = (1 << 5), }; /** #Material::blend_shadow */ diff --git a/source/blender/makesdna/DNA_mesh_defaults.h b/source/blender/makesdna/DNA_mesh_defaults.h index 15bf7cb4a83..50db3c88c4c 100644 --- a/source/blender/makesdna/DNA_mesh_defaults.h +++ b/source/blender/makesdna/DNA_mesh_defaults.h @@ -18,6 +18,7 @@ #define _DNA_DEFAULT_Mesh \ { \ .texspace_size = {1.0f, 1.0f, 1.0f}, \ + .smoothresh = DEG2RADF(30), \ .texspace_flag = ME_TEXSPACE_FLAG_AUTO, \ .remesh_voxel_size = 0.1f, \ .remesh_voxel_adaptivity = 0.0f, \ diff --git a/source/blender/makesdna/DNA_mesh_types.h b/source/blender/makesdna/DNA_mesh_types.h index 6d08fafcf44..4032138c025 100644 --- a/source/blender/makesdna/DNA_mesh_types.h +++ b/source/blender/makesdna/DNA_mesh_types.h @@ -32,7 +32,6 @@ class AttributeAccessor; class MutableAttributeAccessor; struct LooseVertCache; struct LooseEdgeCache; -enum class MeshNormalDomain : int8_t; } // namespace bke } // namespace blender using MeshRuntimeHandle = blender::bke::MeshRuntime; @@ -150,7 +149,10 @@ typedef struct Mesh { /** Mostly more flags used when editing or displaying the mesh. */ uint16_t flag; - float smoothresh_legacy DNA_DEPRECATED; + /** + * The angle for auto smooth in radians. `M_PI` (180 degrees) causes all edges to be smooth. + */ + float smoothresh; /** Per-mesh settings for voxel remesh. */ float remesh_voxel_size; @@ -357,17 +359,6 @@ typedef struct Mesh { */ void tag_loose_verts_none() const; - /** - * Returns the least complex attribute domain needed to store normals encoding all relevant mesh - * data. When all edges or faces are sharp, face normals are enough. When all are smooth, vertex - * normals are enough. With a combination of sharp and smooth, normals may be "split", - * requiring face corner storage. - * - * When possible, it's preferred to use face normals over vertex normals and vertex normals over - * face corner normals, since there is a 2-4x performance cost increase for each more complex - * domain. - */ - blender::bke::MeshNormalDomain normals_domain() const; /** * Normal direction of polygons, defined by positions and the winding direction of face corners. */ @@ -377,14 +368,6 @@ typedef struct Mesh { * surrounding each vertex and the normalized position for loose vertices. */ blender::Span vert_normals() const; - /** - * Normal direction at each face corner. Defined by a combination of face normals, vertex - * normals, the `sharp_edge` and `sharp_face` attributes, and potentially by custom normals. - * - * \note Because of the large memory requirements of storing normals per face corner, prefer - * using #face_normals() or #vert_normals() when possible (see #normals_domain()). - */ - blender::Span corner_normals() const; #endif } Mesh; @@ -439,9 +422,9 @@ enum { ME_FLAG_DEPRECATED_2 = 1 << 2, /* deprecated */ ME_FLAG_UNUSED_3 = 1 << 3, /* cleared */ ME_FLAG_UNUSED_4 = 1 << 4, /* cleared */ - ME_AUTOSMOOTH_LEGACY = 1 << 5, /* deprecated */ - ME_FLAG_UNUSED_6 = 1 << 6, /* cleared */ - ME_FLAG_UNUSED_7 = 1 << 7, /* cleared */ + ME_AUTOSMOOTH = 1 << 5, + ME_FLAG_UNUSED_6 = 1 << 6, /* cleared */ + ME_FLAG_UNUSED_7 = 1 << 7, /* cleared */ ME_REMESH_REPROJECT_VERTEX_COLORS = 1 << 8, ME_DS_EXPAND = 1 << 9, ME_SCULPT_DYNAMIC_TOPOLOGY = 1 << 10, diff --git a/source/blender/makesdna/DNA_movieclip_types.h b/source/blender/makesdna/DNA_movieclip_types.h index 0ab3d100a25..4ab2ee77891 100644 --- a/source/blender/makesdna/DNA_movieclip_types.h +++ b/source/blender/makesdna/DNA_movieclip_types.h @@ -56,11 +56,6 @@ typedef struct MovieClip { ID id; /** Animation data (must be immediately after id for utilities to use it). */ struct AnimData *adt; - /** - * Engines draw data, must be immediately after AnimData. See IdDdtTemplate and - * DRW_drawdatalist_from_id to understand this requirement. - */ - DrawDataList drawdata; /** File path, 1024 = FILE_MAX. */ char filepath[1024]; diff --git a/source/blender/makesdna/DNA_node_tree_interface_types.h b/source/blender/makesdna/DNA_node_tree_interface_types.h index 20e4d8a4a96..03489cb4020 100644 --- a/source/blender/makesdna/DNA_node_tree_interface_types.h +++ b/source/blender/makesdna/DNA_node_tree_interface_types.h @@ -64,9 +64,8 @@ typedef enum NodeTreeInterfaceSocketFlag { NODE_INTERFACE_SOCKET_HIDE_IN_MODIFIER = 1 << 3, NODE_INTERFACE_SOCKET_COMPACT = 1 << 4, NODE_INTERFACE_SOCKET_SINGLE_VALUE_ONLY = 1 << 5, - NODE_INTERFACE_SOCKET_LAYER_SELECTION = 1 << 6, } NodeTreeInterfaceSocketFlag; -ENUM_OPERATORS(NodeTreeInterfaceSocketFlag, NODE_INTERFACE_SOCKET_LAYER_SELECTION); +ENUM_OPERATORS(NodeTreeInterfaceSocketFlag, NODE_INTERFACE_SOCKET_SINGLE_VALUE_ONLY); typedef struct bNodeTreeInterfaceSocket { bNodeTreeInterfaceItem item; @@ -80,9 +79,7 @@ typedef struct bNodeTreeInterfaceSocket { int flag; /* eAttrDomain */ - int16_t attribute_domain; - /** GeometryNodeDefaultInputType. */ - int16_t default_input; + int attribute_domain; char *default_attribute_name; /* Unique identifier for generated sockets. */ @@ -121,17 +118,6 @@ typedef enum NodeTreeInterfacePanelFlag { } NodeTreeInterfacePanelFlag; ENUM_OPERATORS(NodeTreeInterfacePanelFlag, NODE_INTERFACE_PANEL_DEFAULT_CLOSED); -/** Use the same default for different node systems. */ -#define NODE_INPUT_DEFAULT_VALUE 0 - -typedef enum GeometryNodeDefaultInputType { - GEO_NODE_DEFAULT_INPUT_VALUE = NODE_INPUT_DEFAULT_VALUE, - GEO_NODE_DEFAULT_FIELD_INPUT_INDEX_FIELD = 1, - GEO_NODE_DEFAULT_FIELD_INPUT_ID_INDEX_FIELD = 2, - GEO_NODE_DEFAULT_FIELD_INPUT_NORMAL_FIELD = 3, - GEO_NODE_DEFAULT_FIELD_INPUT_POSITION_FIELD = 4, -} GeometryNodeDefaultInputType; - typedef struct bNodeTreeInterfacePanel { bNodeTreeInterfaceItem item; diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index 3df53a58dd0..4c224057f0a 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -370,11 +370,7 @@ typedef struct bNode { */ int16_t type; - /** - * Depth of the node in the node editor, used to keep recently selected nodes at the front, and - * to order frame nodes properly. - */ - int16_t ui_order; + char _pad1[2]; /** Used for some builtin nodes that store properties but don't have a storage struct. */ int16_t custom1, custom2; @@ -788,12 +784,9 @@ typedef struct bNodeTree { void ensure_interface_cache() const; /* Cached interface item lists. */ - blender::Span interface_inputs(); - blender::Span interface_inputs() const; - blender::Span interface_outputs(); - blender::Span interface_outputs() const; - blender::Span interface_items(); - blender::Span interface_items() const; + blender::Span interface_inputs() const; + blender::Span interface_outputs() const; + blender::Span interface_items() const; #endif } bNodeTree; @@ -1051,7 +1044,7 @@ typedef struct NodeBilateralBlurData { } NodeBilateralBlurData; typedef struct NodeKuwaharaData { - short size DNA_DEPRECATED; + short size; short variation; int uniformity; float sharpness; @@ -1353,7 +1346,6 @@ typedef struct TexNodeOutput { typedef struct NodeKeyingScreenData { char tracking_object[64]; - float smoothness; } NodeKeyingScreenData; typedef struct NodeKeyingData { @@ -1810,6 +1802,7 @@ typedef struct NodeGeometrySimulationOutput { #ifdef __cplusplus blender::Span items_span() const; blender::MutableSpan items_span(); + blender::IndexRange items_range() const; #endif } NodeGeometrySimulationOutput; @@ -1823,6 +1816,11 @@ typedef struct NodeRepeatItem { * names change. */ int identifier; + +#ifdef __cplusplus + static bool supports_type(eNodeSocketDatatype type); + std::string identifier_str() const; +#endif } NodeRepeatItem; typedef struct NodeGeometryRepeatInput { @@ -1841,6 +1839,8 @@ typedef struct NodeGeometryRepeatOutput { #ifdef __cplusplus blender::Span items_span() const; blender::MutableSpan items_span(); + NodeRepeatItem *add_item(const char *name, eNodeSocketDatatype type); + void set_item_name(NodeRepeatItem &item, const char *name); #endif } NodeGeometryRepeatOutput; diff --git a/source/blender/makesdna/DNA_object_types.h b/source/blender/makesdna/DNA_object_types.h index 27e71ac4ab4..7296e9509a9 100644 --- a/source/blender/makesdna/DNA_object_types.h +++ b/source/blender/makesdna/DNA_object_types.h @@ -317,10 +317,7 @@ typedef struct Object { ID id; /** Animation data (must be immediately after id for utilities to use it). */ struct AnimData *adt; - /** - * Engines draw data, must be immediately after AnimData. See IdDdtTemplate and - * DRW_drawdatalist_from_id to understand this requirement. - */ + /** Runtime (must be immediately after id for utilities to use it). */ struct DrawDataList drawdata; struct SculptSession *sculpt; @@ -814,7 +811,6 @@ enum { OB_SHADOW_CATCHER = 1 << 10, OB_HIDE_PROBE_VOLUME = 1 << 11, OB_HIDE_PROBE_CUBEMAP = 1 << 12, - OB_HIDE_PROBE_PLANAR = 1 << 13, }; /** #Object.shapeflag */ diff --git a/source/blender/makesdna/DNA_scene_defaults.h b/source/blender/makesdna/DNA_scene_defaults.h index 3e8d53b5f66..f411f40add5 100644 --- a/source/blender/makesdna/DNA_scene_defaults.h +++ b/source/blender/makesdna/DNA_scene_defaults.h @@ -202,7 +202,6 @@ .volumetric_tile_size = 8, \ .volumetric_samples = 64, \ .volumetric_sample_distribution = 0.8f, \ - .volumetric_ray_depth = 16, \ .volumetric_light_clamp = 0.0f, \ .volumetric_shadow_samples = 16, \ \ diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h index 922220eb069..94a4ada6c75 100644 --- a/source/blender/makesdna/DNA_scene_types.h +++ b/source/blender/makesdna/DNA_scene_types.h @@ -1855,7 +1855,6 @@ typedef struct SceneEEVEE { float volumetric_sample_distribution; float volumetric_light_clamp; int volumetric_shadow_samples; - int volumetric_ray_depth; float gtao_distance; float gtao_factor; @@ -1888,6 +1887,7 @@ typedef struct SceneEEVEE { int shadow_ray_count; int shadow_step_count; float shadow_normal_bias; + char _pad[4]; int ray_split_settings; int ray_tracing_method; @@ -1946,10 +1946,7 @@ typedef struct Scene { ID id; /** Animation data (must be immediately after id for utilities to use it). */ struct AnimData *adt; - /** - * Engines draw data, must be immediately after AnimData. See IdDdtTemplate and - * DRW_drawdatalist_from_id to understand this requirement. - */ + /** Runtime (must be immediately after id for utilities to use it). */ DrawDataList drawdata; struct Object *camera; @@ -2246,7 +2243,6 @@ enum { /** #RenderData::engine (scene.cc) */ extern const char *RE_engine_id_BLENDER_EEVEE; -extern const char *RE_engine_id_BLENDER_EEVEE_NEXT; extern const char *RE_engine_id_BLENDER_WORKBENCH; extern const char *RE_engine_id_CYCLES; diff --git a/source/blender/makesdna/DNA_screen_types.h b/source/blender/makesdna/DNA_screen_types.h index ce35081c048..5ce238e4f70 100644 --- a/source/blender/makesdna/DNA_screen_types.h +++ b/source/blender/makesdna/DNA_screen_types.h @@ -31,7 +31,6 @@ struct uiList; struct wmDrawBuffer; struct wmTimer; struct wmTooltipState; -struct Panel_Runtime; /* TODO: Doing this is quite ugly :) * Once the top-bar is merged bScreen should be refactored to use ScrAreaMap. */ @@ -121,6 +120,29 @@ typedef struct ScrAreaMap { ListBase areabase; } ScrAreaMap; +typedef struct Panel_Runtime { + /* Applied to Panel.ofsx, but saved separately so we can track changes between redraws. */ + int region_ofsx; + + char _pad[4]; + + /** + * Pointer for storing which data the panel corresponds to. + * Useful when there can be multiple instances of the same panel type. + * + * \note A panel and its sub-panels share the same custom data pointer. + * This avoids freeing the same pointer twice when panels are removed. + */ + struct PointerRNA *custom_data_ptr; + + /* Pointer to the panel's block. Useful when changes to panel #uiBlocks + * need some context from traversal of the panel "tree". */ + struct uiBlock *block; + + /* Non-owning pointer. The context is stored in the block. */ + struct bContextStore *context; +} Panel_Runtime; + /** The part from uiBlock that needs saved in file. */ typedef struct Panel { struct Panel *next, *prev; @@ -150,7 +172,7 @@ typedef struct Panel { /** Sub panels. */ ListBase children; - struct Panel_Runtime *runtime; + Panel_Runtime runtime; } Panel; /** diff --git a/source/blender/makesdna/DNA_space_types.h b/source/blender/makesdna/DNA_space_types.h index 9eced9bae75..5c8c055ca50 100644 --- a/source/blender/makesdna/DNA_space_types.h +++ b/source/blender/makesdna/DNA_space_types.h @@ -1985,12 +1985,9 @@ typedef struct SpaceSpreadsheet { uint8_t attribute_domain; /* eSpaceSpreadsheet_ObjectEvalState. */ uint8_t object_eval_state; - /* Active grease pencil layer index for grease pencil component. */ - int active_layer_index; /* eSpaceSpreadsheet_Flag. */ uint32_t flag; - char _pad1[4]; SpaceSpreadsheet_Runtime *runtime; } SpaceSpreadsheet; diff --git a/source/blender/makesdna/DNA_texture_types.h b/source/blender/makesdna/DNA_texture_types.h index 01f60963e01..bf0da4d3872 100644 --- a/source/blender/makesdna/DNA_texture_types.h +++ b/source/blender/makesdna/DNA_texture_types.h @@ -162,10 +162,7 @@ typedef struct Tex { ID id; /** Animation data (must be immediately after id for utilities to use it). */ struct AnimData *adt; - /** - * Engines draw data, must be immediately after AnimData. See IdDdtTemplate and - * DRW_drawdatalist_from_id to understand this requirement. - */ + /* runtime (must be immediately after id for utilities to use it). */ DrawDataList drawdata; float noisesize, turbul; diff --git a/source/blender/makesdna/DNA_userdef_types.h b/source/blender/makesdna/DNA_userdef_types.h index 1479bcaaeae..416e6168132 100644 --- a/source/blender/makesdna/DNA_userdef_types.h +++ b/source/blender/makesdna/DNA_userdef_types.h @@ -81,8 +81,7 @@ typedef struct uiFontStyle { float shadowalpha; /** 1 value, typically white or black anyway. */ float shadowcolor; - /** Weight class 100-900, 400 is normal. */ - int character_weight; + char _pad2[4]; } uiFontStyle; /* this is fed to the layout engine and widget code */ @@ -705,6 +704,7 @@ typedef struct UserDef_Experimental { char use_sculpt_tools_tilt; char use_extended_asset_browser; char use_override_templates; + char enable_eevee_next; char use_sculpt_texture_paint; char use_grease_pencil_version3; char enable_overlay_next; @@ -712,7 +712,7 @@ typedef struct UserDef_Experimental { char use_shader_node_previews; char use_extension_repos; - char _pad[3]; + char _pad[2]; /** `makesdna` does not allow empty structs. */ } UserDef_Experimental; @@ -1281,6 +1281,7 @@ typedef enum eZoomFrame_Mode { * Auto-Keying flag * #UserDef.autokey_flag (not strictly used when autokeying only - * is also used when keyframing these days). + * \note #eAutokey_Flag is used with a macro, search for lines like IS_AUTOKEY_FLAG(INSERTAVAIL). */ typedef enum eAutokey_Flag { AUTOKEY_FLAG_INSERTAVAIL = (1 << 0), diff --git a/source/blender/makesdna/DNA_windowmanager_types.h b/source/blender/makesdna/DNA_windowmanager_types.h index 6439ccb884e..e5f17026601 100644 --- a/source/blender/makesdna/DNA_windowmanager_types.h +++ b/source/blender/makesdna/DNA_windowmanager_types.h @@ -14,13 +14,6 @@ #include "DNA_ID.h" -#ifdef __cplusplus -# include -using std_mutex_type = std::mutex; -#else -# define std_mutex_type void -#endif - /* Defined here: */ struct wmWindow; @@ -105,9 +98,6 @@ typedef struct ReportList { int flag; char _pad[4]; struct wmTimer *reporttimer; - - /** Mutex for thread-safety, runtime only. */ - std_mutex_type *lock; } ReportList; /* Timer custom-data to control reports display. */ @@ -172,7 +162,6 @@ typedef struct wmWindowManager { * \note keep in sync with `notifier_queue` adding/removing elements must also update this set. */ struct GSet *notifier_queue_set; - void *_pad1; /** Information and error reports. */ struct ReportList reports; @@ -369,9 +358,7 @@ typedef struct wmWindow { * Input Method Editor data - complex character input (especially for Asian character input) * Currently WIN32 and APPLE, runtime-only data. */ - const struct wmIMEData *ime_data; - char ime_data_is_composing; - char _pad1[7]; + struct wmIMEData *ime_data; /** All events #wmEvent (ghost level events were handled). */ ListBase event_queue; @@ -391,13 +378,6 @@ typedef struct wmWindow { /** Private runtime info to show text in the status bar. */ void *cursor_keymap_status; - - /** - * The time when the key is pressed in milliseconds (see #GHOST_GetEventTime). - * Used to detect double-click events. - */ - uint64_t eventstate_prev_press_time_ms; - } wmWindow; #ifdef ime_data diff --git a/source/blender/makesdna/DNA_world_types.h b/source/blender/makesdna/DNA_world_types.h index 0f0bc1bf7f6..03a864e4b1d 100644 --- a/source/blender/makesdna/DNA_world_types.h +++ b/source/blender/makesdna/DNA_world_types.h @@ -29,10 +29,7 @@ typedef struct World { ID id; /** Animation data (must be immediately after id for utilities to use it). */ struct AnimData *adt; - /** - * Engines draw data, must be immediately after AnimData. See IdDdtTemplate and - * DRW_drawdatalist_from_id to understand this requirement. - */ + /* runtime (must be immediately after id for utilities to use it). */ DrawDataList drawdata; char _pad0[4]; @@ -116,13 +113,3 @@ enum { */ WO_DS_SHOW_TEXS = 1 << 2, }; - -/** #World::probe_resolution. */ -typedef enum eLightProbeResolution { - LIGHT_PROBE_RESOLUTION_64 = 6, - LIGHT_PROBE_RESOLUTION_128 = 7, - LIGHT_PROBE_RESOLUTION_256 = 8, - LIGHT_PROBE_RESOLUTION_512 = 9, - LIGHT_PROBE_RESOLUTION_1024 = 10, - LIGHT_PROBE_RESOLUTION_2048 = 11, -} eLightProbeResolution; diff --git a/source/blender/makesdna/intern/dna_rename_defs.h b/source/blender/makesdna/intern/dna_rename_defs.h index b214e986760..7535aff9ed6 100644 --- a/source/blender/makesdna/intern/dna_rename_defs.h +++ b/source/blender/makesdna/intern/dna_rename_defs.h @@ -116,7 +116,6 @@ DNA_STRUCT_RENAME_ELEM(Mesh, loc, texspace_location) DNA_STRUCT_RENAME_ELEM(Mesh, pdata, face_data) DNA_STRUCT_RENAME_ELEM(Mesh, poly_offset_indices, face_offset_indices) DNA_STRUCT_RENAME_ELEM(Mesh, size, texspace_size) -DNA_STRUCT_RENAME_ELEM(Mesh, smoothresh, smoothresh_legacy) DNA_STRUCT_RENAME_ELEM(Mesh, texflag, texspace_flag) DNA_STRUCT_RENAME_ELEM(Mesh, totface, totface_legacy) DNA_STRUCT_RENAME_ELEM(Mesh, totpoly, faces_num) diff --git a/source/blender/makesrna/RNA_enum_items.hh b/source/blender/makesrna/RNA_enum_items.hh index 243bda78e56..5cc1fefc547 100644 --- a/source/blender/makesrna/RNA_enum_items.hh +++ b/source/blender/makesrna/RNA_enum_items.hh @@ -225,8 +225,6 @@ DEF_ENUM(rna_enum_attribute_type_with_auto_items) DEF_ENUM(rna_enum_attribute_domain_items) DEF_ENUM(rna_enum_attribute_domain_edge_face_items) DEF_ENUM(rna_enum_attribute_domain_only_mesh_items) -DEF_ENUM(rna_enum_attribute_domain_only_mesh_no_edge_items) -DEF_ENUM(rna_enum_attribute_domain_point_face_curve_items) DEF_ENUM(rna_enum_attribute_domain_point_edge_face_curve_items) DEF_ENUM(rna_enum_attribute_curves_domain_items) DEF_ENUM(rna_enum_color_attribute_domain_items) diff --git a/source/blender/makesrna/RNA_types.hh b/source/blender/makesrna/RNA_types.hh index d5457ee183f..a7426b51d80 100644 --- a/source/blender/makesrna/RNA_types.hh +++ b/source/blender/makesrna/RNA_types.hh @@ -560,7 +560,7 @@ typedef enum eStringPropertySearchFlag { * if search is supported. */ PROP_STRING_SEARCH_SUPPORTED = (1 << 0), - /** Items resulting from the search must be sorted. */ + /** Items resulting from the search must be sorted. */ PROP_STRING_SEARCH_SORT = (1 << 1), /** * Allow members besides the ones listed to be entered. diff --git a/source/blender/makesrna/intern/makesrna.cc b/source/blender/makesrna/intern/makesrna.cc index d58653feb34..983b267a08c 100644 --- a/source/blender/makesrna/intern/makesrna.cc +++ b/source/blender/makesrna/intern/makesrna.cc @@ -14,7 +14,6 @@ #include #include #include -#include #include "MEM_guardedalloc.h" @@ -647,12 +646,6 @@ static void rna_float_print(FILE *f, float num) else if ((fabsf(num) < float(INT64_MAX)) && (int64_t(num) == num)) { fprintf(f, "%.1ff", num); } - else if (num == std::numeric_limits::infinity()) { - fprintf(f, "std::numeric_limits::infinity()"); - } - else if (num == -std::numeric_limits::infinity()) { - fprintf(f, "-std::numeric_limits::infinity()"); - } else { fprintf(f, "%.10ff", num); } @@ -3961,12 +3954,6 @@ static void rna_generate_static_function_prototypes(BlenderRNA * /*brna*/, dfunc = rna_find_function_def(func); if (dfunc->call) { - if (strstr(dfunc->call, "<")) { - /* Can't generate the declaration for templates. We'll still get compile errors when trying - * to call it with a wrong signature. */ - continue; - } - if (first) { fprintf(f, "/* Repeated prototypes to detect errors */\n\n"); first = 0; @@ -4230,7 +4217,7 @@ static void rna_generate_property(FILE *f, StructRNA *srna, const char *nest, Pr StructRNA *type = rna_find_struct((const char *)pprop->type); if (type && (type->flag & STRUCT_ID) && !(prop->flag_internal & PROP_INTERN_PTR_OWNERSHIP_FORCED)) { - RNA_def_property_flag(prop, PROP_PTR_NO_OWNERSHIP); + prop->flag |= PROP_PTR_NO_OWNERSHIP; } break; } @@ -4242,7 +4229,7 @@ static void rna_generate_property(FILE *f, StructRNA *srna, const char *nest, Pr StructRNA *type = rna_find_struct((const char *)cprop->item_type); if (type && (type->flag & STRUCT_ID) && !(prop->flag_internal & PROP_INTERN_PTR_OWNERSHIP_FORCED)) { - RNA_def_property_flag(prop, PROP_PTR_NO_OWNERSHIP); + prop->flag |= PROP_PTR_NO_OWNERSHIP; } break; } @@ -4821,7 +4808,6 @@ static void rna_generate(BlenderRNA *brna, FILE *f, const char *filename, const fprintf(f, "#include \n"); fprintf(f, "#include \n"); fprintf(f, "#include \n"); - fprintf(f, "#include \n"); fprintf(f, "#include \n\n"); fprintf(f, "#include \n\n"); diff --git a/source/blender/makesrna/intern/rna_ID.cc b/source/blender/makesrna/intern/rna_ID.cc index 068f9c5bd3f..7e1f0720093 100644 --- a/source/blender/makesrna/intern/rna_ID.cc +++ b/source/blender/makesrna/intern/rna_ID.cc @@ -49,7 +49,7 @@ const EnumPropertyItem rna_enum_id_type_items[] = { {ID_LT, "LATTICE", ICON_LATTICE_DATA, "Lattice", ""}, {ID_LI, "LIBRARY", ICON_LIBRARY_DATA_DIRECT, "Library", ""}, {ID_LA, "LIGHT", ICON_LIGHT_DATA, "Light", ""}, - {ID_LP, "LIGHT_PROBE", ICON_LIGHTPROBE_SPHERE, "Light Probe", ""}, + {ID_LP, "LIGHT_PROBE", ICON_LIGHTPROBE_CUBEMAP, "Light Probe", ""}, {ID_LS, "LINESTYLE", ICON_LINE_DATA, "Line Style", ""}, {ID_MSK, "MASK", ICON_MOD_MASK, "Mask", ""}, {ID_MA, "MATERIAL", ICON_MATERIAL_DATA, "Material", ""}, diff --git a/source/blender/makesrna/intern/rna_animation_api.cc b/source/blender/makesrna/intern/rna_animation_api.cc index be63783bcfa..c57851f93cf 100644 --- a/source/blender/makesrna/intern/rna_animation_api.cc +++ b/source/blender/makesrna/intern/rna_animation_api.cc @@ -31,22 +31,17 @@ static void rna_KeyingSet_context_refresh(KeyingSet *ks, bContext *C, ReportList /* TODO: enable access to providing a list of overrides (dsources)? */ const eModifyKey_Returns error = ANIM_validate_keyingset(C, nullptr, ks); - if (error == MODIFYKEY_SUCCESS) { - return; - } + if (error != 0) { + switch (error) { + case MODIFYKEY_INVALID_CONTEXT: + BKE_report(reports, RPT_ERROR, "Invalid context for keying set"); + break; - switch (error) { - case MODIFYKEY_INVALID_CONTEXT: - BKE_report(reports, RPT_ERROR, "Invalid context for keying set"); - break; - - case MODIFYKEY_MISSING_TYPEINFO: - BKE_report( - reports, RPT_ERROR, "Incomplete built-in keying set, appears to be missing type info"); - break; - - default: - break; + case MODIFYKEY_MISSING_TYPEINFO: + BKE_report( + reports, RPT_ERROR, "Incomplete built-in keying set, appears to be missing type info"); + break; + } } } diff --git a/source/blender/makesrna/intern/rna_armature.cc b/source/blender/makesrna/intern/rna_armature.cc index 7f9278ab391..60dfeeaf5d8 100644 --- a/source/blender/makesrna/intern/rna_armature.cc +++ b/source/blender/makesrna/intern/rna_armature.cc @@ -1311,7 +1311,7 @@ static void rna_def_bone_common(StructRNA *srna, int editbone) prop, "B-Bone Segments", "Number of subdivisions of bone (for B-Bones only)"); prop = RNA_def_property(srna, "bbone_mapping_mode", PROP_ENUM, PROP_NONE); - RNA_def_property_enum_sdna(prop, nullptr, "bbone_mapping_mode"); + RNA_def_property_enum_sdna(prop, NULL, "bbone_mapping_mode"); RNA_def_property_enum_items(prop, prop_bbone_mapping_mode); RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); RNA_def_property_ui_text( diff --git a/source/blender/makesrna/intern/rna_attribute.cc b/source/blender/makesrna/intern/rna_attribute.cc index dbd846e1851..0ab50d3a799 100644 --- a/source/blender/makesrna/intern/rna_attribute.cc +++ b/source/blender/makesrna/intern/rna_attribute.cc @@ -88,7 +88,6 @@ const EnumPropertyItem rna_enum_attribute_domain_items[] = { // {ATTR_DOMAIN_GRIDS, "GRIDS", 0, "Grids", "Attribute on mesh multires grids"}, {ATTR_DOMAIN_CURVE, "CURVE", 0, "Spline", "Attribute on spline"}, {ATTR_DOMAIN_INSTANCE, "INSTANCE", 0, "Instance", "Attribute on instance"}, - {ATTR_DOMAIN_LAYER, "LAYER", 0, "Layer", "Attribute on Grease Pencil layer"}, {0, nullptr, 0, nullptr, nullptr}, }; @@ -100,20 +99,6 @@ const EnumPropertyItem rna_enum_attribute_domain_only_mesh_items[] = { {0, nullptr, 0, nullptr, nullptr}, }; -const EnumPropertyItem rna_enum_attribute_domain_only_mesh_no_edge_items[] = { - {ATTR_DOMAIN_POINT, "POINT", 0, "Point", "Attribute on point"}, - {ATTR_DOMAIN_FACE, "FACE", 0, "Face", "Attribute on mesh faces"}, - {ATTR_DOMAIN_CORNER, "CORNER", 0, "Face Corner", "Attribute on mesh face corner"}, - {0, nullptr, 0, nullptr, nullptr}, -}; - -const EnumPropertyItem rna_enum_attribute_domain_point_face_curve_items[] = { - {ATTR_DOMAIN_POINT, "POINT", 0, "Point", "Attribute on point"}, - {ATTR_DOMAIN_FACE, "FACE", 0, "Face", "Attribute on mesh faces"}, - {ATTR_DOMAIN_CURVE, "CURVE", 0, "Spline", "Attribute on spline"}, - {0, nullptr, 0, nullptr, nullptr}, -}; - const EnumPropertyItem rna_enum_attribute_domain_point_edge_face_curve_items[] = { {ATTR_DOMAIN_POINT, "POINT", 0, "Point", "Attribute on point"}, {ATTR_DOMAIN_EDGE, "EDGE", 0, "Edge", "Attribute on mesh edge"}, @@ -134,7 +119,6 @@ const EnumPropertyItem rna_enum_attribute_domain_without_corner_items[] = { {ATTR_DOMAIN_FACE, "FACE", 0, "Face", "Attribute on mesh faces"}, {ATTR_DOMAIN_CURVE, "CURVE", 0, "Spline", "Attribute on spline"}, {ATTR_DOMAIN_INSTANCE, "INSTANCE", 0, "Instance", "Attribute on instance"}, - {ATTR_DOMAIN_LAYER, "LAYER", 0, "Layer", "Attribute on Grease Pencil layer"}, {0, nullptr, 0, nullptr, nullptr}, }; @@ -146,7 +130,6 @@ const EnumPropertyItem rna_enum_attribute_domain_with_auto_items[] = { {ATTR_DOMAIN_CORNER, "CORNER", 0, "Face Corner", "Attribute on mesh face corner"}, {ATTR_DOMAIN_CURVE, "CURVE", 0, "Spline", "Attribute on spline"}, {ATTR_DOMAIN_INSTANCE, "INSTANCE", 0, "Instance", "Attribute on instance"}, - {ATTR_DOMAIN_LAYER, "LAYER", 0, "Layer", "Attribute on Grease Pencil layer"}, {0, nullptr, 0, nullptr, nullptr}, }; @@ -264,9 +247,6 @@ const EnumPropertyItem *rna_enum_attribute_domain_itemf(ID *id, if (!include_instances && domain_item->value == ATTR_DOMAIN_INSTANCE) { continue; } - if (!U.experimental.use_grease_pencil_version3 && domain_item->value == ATTR_DOMAIN_LAYER) { - continue; - } if (domain_item->value == ATTR_DOMAIN_POINT && id_type == ID_ME) { RNA_enum_item_add(&item, &totitem, &mesh_vertex_domain_item); diff --git a/source/blender/makesrna/intern/rna_define.cc b/source/blender/makesrna/intern/rna_define.cc index 4acf47801c7..f5d39828ea0 100644 --- a/source/blender/makesrna/intern/rna_define.cc +++ b/source/blender/makesrna/intern/rna_define.cc @@ -967,10 +967,10 @@ StructRNA *RNA_def_struct_ptr(BlenderRNA *brna, const char *identifier, StructRN } else { if (srnafrom->flag & STRUCT_PUBLIC_NAMESPACE_INHERIT) { - RNA_def_struct_flag(srna, STRUCT_PUBLIC_NAMESPACE | STRUCT_PUBLIC_NAMESPACE_INHERIT); + srna->flag |= STRUCT_PUBLIC_NAMESPACE | STRUCT_PUBLIC_NAMESPACE_INHERIT; } else { - RNA_def_struct_clear_flag(srna, STRUCT_PUBLIC_NAMESPACE | STRUCT_PUBLIC_NAMESPACE_INHERIT); + srna->flag &= ~(STRUCT_PUBLIC_NAMESPACE | STRUCT_PUBLIC_NAMESPACE_INHERIT); } } } @@ -982,11 +982,11 @@ StructRNA *RNA_def_struct_ptr(BlenderRNA *brna, const char *identifier, StructRN srna->translation_context = BLT_I18NCONTEXT_DEFAULT_BPYRNA; if (!srnafrom) { srna->icon = ICON_DOT; - RNA_def_struct_flag(srna, STRUCT_UNDO); + srna->flag |= STRUCT_UNDO; } if (DefRNA.preprocess) { - RNA_def_struct_flag(srna, STRUCT_PUBLIC_NAMESPACE); + srna->flag |= STRUCT_PUBLIC_NAMESPACE; } rna_brna_structs_add(brna, srna); @@ -1006,7 +1006,7 @@ StructRNA *RNA_def_struct_ptr(BlenderRNA *brna, const char *identifier, StructRN RNA_def_struct_sdna(srna, srna->identifier); } else { - RNA_def_struct_flag(srna, STRUCT_RUNTIME); + srna->flag |= STRUCT_RUNTIME; } if (srnafrom) { @@ -1395,16 +1395,13 @@ PropertyRNA *RNA_def_property(StructOrFunctionRNA *cont_, } case PROP_STRING: { StringPropertyRNA *sprop = (StringPropertyRNA *)prop; - /* By default don't allow nullptr string args, callers may clear. - * Used so generated 'get/length/set' functions skip a nullptr check - * in some cases we want it */ + /* By default don't allow nullptr string args, callers may clear. */ RNA_def_property_flag(prop, PROP_NEVER_NULL); sprop->defaultvalue = ""; break; } case PROP_POINTER: - /* needed for default behavior when PARM_RNAPTR is set */ - RNA_def_property_flag(prop, PROP_THICK_WRAP); + prop->flag |= PROP_THICK_WRAP; /* needed for default behavior when PARM_RNAPTR is set */ break; case PROP_ENUM: case PROP_COLLECTION: @@ -1431,14 +1428,14 @@ PropertyRNA *RNA_def_property(StructOrFunctionRNA *cont_, prop->rawtype = RawPropertyType(-1); if (!ELEM(type, PROP_COLLECTION, PROP_POINTER)) { - RNA_def_property_flag(prop, PROP_EDITABLE); + prop->flag = PROP_EDITABLE; if (type != PROP_STRING) { #ifdef RNA_RUNTIME - RNA_def_property_flag(prop, PROP_ANIMATABLE); + prop->flag |= PROP_ANIMATABLE; #else if (DefRNA.animate) { - RNA_def_property_flag(prop, PROP_ANIMATABLE); + prop->flag |= PROP_ANIMATABLE; } #endif } @@ -1446,10 +1443,16 @@ PropertyRNA *RNA_def_property(StructOrFunctionRNA *cont_, #ifndef RNA_RUNTIME if (DefRNA.make_overridable) { - RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); + prop->flag_override |= PROPOVERRIDE_OVERRIDABLE_LIBRARY; } #endif + if (type == PROP_STRING) { + /* used so generated 'get/length/set' functions skip a nullptr check + * in some cases we want it */ + RNA_def_property_flag(prop, PROP_NEVER_NULL); + } + if (DefRNA.preprocess) { switch (type) { case PROP_BOOLEAN: @@ -1493,7 +1496,7 @@ PropertyRNA *RNA_def_property(StructOrFunctionRNA *cont_, } } else { - RNA_def_property_flag(prop, PROP_IDPROPERTY); + prop->flag |= PROP_IDPROPERTY; prop->flag_internal |= PROP_INTERN_RUNTIME; #ifdef RNA_RUNTIME if (cont->prophash) { @@ -1688,10 +1691,10 @@ void RNA_def_property_ui_icon(PropertyRNA *prop, int icon, int consecutive) { prop->icon = icon; if (consecutive != 0) { - RNA_def_property_flag(prop, PROP_ICONS_CONSECUTIVE); + prop->flag |= PROP_ICONS_CONSECUTIVE; } if (consecutive < 0) { - RNA_def_property_flag(prop, PROP_ICONS_REVERSE); + prop->flag |= PROP_ICONS_REVERSE; } } @@ -1862,7 +1865,7 @@ void RNA_def_property_struct_runtime(StructOrFunctionRNA *cont, PropertyRNA *pro } if (type && (type->flag & STRUCT_ID_REFCOUNT)) { - RNA_def_property_flag(prop, PROP_ID_REFCOUNT); + prop->flag |= PROP_ID_REFCOUNT; } break; @@ -1882,7 +1885,7 @@ void RNA_def_property_struct_runtime(StructOrFunctionRNA *cont, PropertyRNA *pro } if (is_id_type) { - RNA_def_property_flag(prop, PROP_PTR_NO_OWNERSHIP); + prop->flag |= PROP_PTR_NO_OWNERSHIP; } } @@ -3114,10 +3117,10 @@ void RNA_def_property_boolean_funcs_runtime(PropertyRNA *prop, if (getfunc || setfunc) { /* don't save in id properties */ - RNA_def_property_clear_flag(prop, PROP_IDPROPERTY); + prop->flag &= ~PROP_IDPROPERTY; if (!setfunc) { - RNA_def_property_clear_flag(prop, PROP_EDITABLE); + prop->flag &= ~PROP_EDITABLE; } } } @@ -3137,10 +3140,10 @@ void RNA_def_property_boolean_array_funcs_runtime(PropertyRNA *prop, if (getfunc || setfunc) { /* don't save in id properties */ - RNA_def_property_clear_flag(prop, PROP_IDPROPERTY); + prop->flag &= ~PROP_IDPROPERTY; if (!setfunc) { - RNA_def_property_clear_flag(prop, PROP_EDITABLE); + prop->flag &= ~PROP_EDITABLE; } } } @@ -3208,10 +3211,10 @@ void RNA_def_property_int_funcs_runtime(PropertyRNA *prop, if (getfunc || setfunc) { /* don't save in id properties */ - RNA_def_property_clear_flag(prop, PROP_IDPROPERTY); + prop->flag &= ~PROP_IDPROPERTY; if (!setfunc) { - RNA_def_property_clear_flag(prop, PROP_EDITABLE); + prop->flag &= ~PROP_EDITABLE; } } } @@ -3235,10 +3238,10 @@ void RNA_def_property_int_array_funcs_runtime(PropertyRNA *prop, if (getfunc || setfunc) { /* don't save in id properties */ - RNA_def_property_clear_flag(prop, PROP_IDPROPERTY); + prop->flag &= ~PROP_IDPROPERTY; if (!setfunc) { - RNA_def_property_clear_flag(prop, PROP_EDITABLE); + prop->flag &= ~PROP_EDITABLE; } } } @@ -3306,10 +3309,10 @@ void RNA_def_property_float_funcs_runtime(PropertyRNA *prop, if (getfunc || setfunc) { /* don't save in id properties */ - RNA_def_property_clear_flag(prop, PROP_IDPROPERTY); + prop->flag &= ~PROP_IDPROPERTY; if (!setfunc) { - RNA_def_property_clear_flag(prop, PROP_EDITABLE); + prop->flag &= ~PROP_EDITABLE; } } } @@ -3333,10 +3336,10 @@ void RNA_def_property_float_array_funcs_runtime(PropertyRNA *prop, if (getfunc || setfunc) { /* don't save in id properties */ - RNA_def_property_clear_flag(prop, PROP_IDPROPERTY); + prop->flag &= ~PROP_IDPROPERTY; if (!setfunc) { - RNA_def_property_clear_flag(prop, PROP_EDITABLE); + prop->flag &= ~PROP_EDITABLE; } } } @@ -3394,10 +3397,10 @@ void RNA_def_property_enum_funcs_runtime(PropertyRNA *prop, if (getfunc || setfunc) { /* don't save in id properties */ - RNA_def_property_clear_flag(prop, PROP_IDPROPERTY); + prop->flag &= ~PROP_IDPROPERTY; if (!setfunc) { - RNA_def_property_clear_flag(prop, PROP_EDITABLE); + prop->flag &= ~PROP_EDITABLE; } } } @@ -3482,10 +3485,10 @@ void RNA_def_property_string_funcs_runtime(PropertyRNA *prop, if (getfunc || setfunc) { /* don't save in id properties */ - RNA_def_property_clear_flag(prop, PROP_IDPROPERTY); + prop->flag &= ~PROP_IDPROPERTY; if (!setfunc) { - RNA_def_property_clear_flag(prop, PROP_EDITABLE); + prop->flag &= ~PROP_EDITABLE; } } } @@ -4315,7 +4318,7 @@ PropertyRNA *RNA_def_pointer_runtime(StructOrFunctionRNA *cont_, prop = RNA_def_property(cont, identifier, PROP_POINTER, PROP_NONE); RNA_def_property_struct_runtime(cont, prop, type); if ((type->flag & STRUCT_ID) != 0) { - RNA_def_property_flag(prop, PROP_EDITABLE); + prop->flag |= PROP_EDITABLE; } RNA_def_property_ui_text(prop, ui_name, ui_description); @@ -4383,7 +4386,7 @@ static FunctionRNA *rna_def_function(StructRNA *srna, const char *identifier) dfunc->func = func; } else { - RNA_def_function_flag(func, FUNC_RUNTIME); + func->flag |= FUNC_RUNTIME; } return func; @@ -4628,7 +4631,7 @@ void RNA_def_struct_duplicate_pointers(BlenderRNA *brna, StructRNA *srna) srna->description = BLI_strdup(srna->description); } - RNA_def_struct_flag(srna, STRUCT_FREE_POINTERS); + srna->flag |= STRUCT_FREE_POINTERS; } void RNA_def_struct_free_pointers(BlenderRNA *brna, StructRNA *srna) @@ -4660,7 +4663,7 @@ void RNA_def_func_duplicate_pointers(FunctionRNA *func) func->description = BLI_strdup(func->description); } - RNA_def_function_flag(func, FUNC_FREE_POINTERS); + func->flag |= FUNC_FREE_POINTERS; } void RNA_def_func_free_pointers(FunctionRNA *func) diff --git a/source/blender/makesrna/intern/rna_gpencil_legacy.cc b/source/blender/makesrna/intern/rna_gpencil_legacy.cc index 1cb9e0f2686..15d8e6b6100 100644 --- a/source/blender/makesrna/intern/rna_gpencil_legacy.cc +++ b/source/blender/makesrna/intern/rna_gpencil_legacy.cc @@ -151,7 +151,7 @@ static const EnumPropertyItem rna_enum_gpencil_caps_modes_items[] = { # include "BLI_ghash.h" # include "BLI_listbase.h" -# include "BLI_string_utils.hh" +# include "BLI_string_utils.h" # include "WM_api.hh" diff --git a/source/blender/makesrna/intern/rna_gpencil_legacy_modifier.cc b/source/blender/makesrna/intern/rna_gpencil_legacy_modifier.cc index 8f378af34ba..eeaf0bd7d62 100644 --- a/source/blender/makesrna/intern/rna_gpencil_legacy_modifier.cc +++ b/source/blender/makesrna/intern/rna_gpencil_legacy_modifier.cc @@ -22,7 +22,7 @@ #include "BLI_math_base.h" #include "BLI_math_rotation.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLT_translation.h" diff --git a/source/blender/makesrna/intern/rna_grease_pencil.cc b/source/blender/makesrna/intern/rna_grease_pencil.cc index 79b5621cf1e..d5e26c9d4f0 100644 --- a/source/blender/makesrna/intern/rna_grease_pencil.cc +++ b/source/blender/makesrna/intern/rna_grease_pencil.cc @@ -297,9 +297,6 @@ static void rna_def_grease_pencil_data(BlenderRNA *brna) RNA_def_struct_ui_text(srna, "Grease Pencil", "Grease Pencil data-block"); RNA_def_struct_ui_icon(srna, ICON_OUTLINER_DATA_GREASEPENCIL); - /* attributes */ - rna_def_attributes_common(srna); - /* Animation Data */ rna_def_animdata_common(srna); diff --git a/source/blender/makesrna/intern/rna_key.cc b/source/blender/makesrna/intern/rna_key.cc index e663267a6d1..e9c30aa2503 100644 --- a/source/blender/makesrna/intern/rna_key.cc +++ b/source/blender/makesrna/intern/rna_key.cc @@ -43,7 +43,7 @@ const EnumPropertyItem rna_enum_keyblock_type_items[] = { # include "DNA_object_types.h" # include "BLI_listbase.h" -# include "BLI_string_utils.hh" +# include "BLI_string_utils.h" # include "BKE_animsys.h" # include "BKE_key.h" diff --git a/source/blender/makesrna/intern/rna_layer.cc b/source/blender/makesrna/intern/rna_layer.cc index 3fae31ae4e4..3e58ff83cf9 100644 --- a/source/blender/makesrna/intern/rna_layer.cc +++ b/source/blender/makesrna/intern/rna_layer.cc @@ -41,7 +41,7 @@ # include "BKE_node.h" # include "BKE_scene.h" -# include "NOD_composite.hh" +# include "NOD_composite.h" # include "BLI_listbase.h" diff --git a/source/blender/makesrna/intern/rna_lightprobe.cc b/source/blender/makesrna/intern/rna_lightprobe.cc index d2a566d0685..1cc1a45fd93 100644 --- a/source/blender/makesrna/intern/rna_lightprobe.cc +++ b/source/blender/makesrna/intern/rna_lightprobe.cc @@ -43,21 +43,27 @@ static EnumPropertyItem parallax_type_items[] = { }; static EnumPropertyItem lightprobe_type_items[] = { - {LIGHTPROBE_TYPE_SPHERE, - "SPHERE", - ICON_LIGHTPROBE_SPHERE, - "Sphere", - "Light probe that captures precise lighting from all directions at a single point in space"}, - {LIGHTPROBE_TYPE_PLANE, - "PLANE", - ICON_LIGHTPROBE_PLANE, - "Plane", - "Light probe that captures incoming light from a single direction on a plane"}, - {LIGHTPROBE_TYPE_VOLUME, - "VOLUME", - ICON_LIGHTPROBE_VOLUME, - "Volume", - "Light probe that captures low frequency lighting inside a volume"}, + {LIGHTPROBE_TYPE_CUBE, + "CUBEMAP", + ICON_LIGHTPROBE_CUBEMAP, + "Reflection Cubemap", + "Capture reflections"}, + {LIGHTPROBE_TYPE_PLANAR, "PLANAR", ICON_LIGHTPROBE_PLANAR, "Reflection Plane", ""}, + {LIGHTPROBE_TYPE_GRID, + "GRID", + ICON_LIGHTPROBE_GRID, + "Irradiance Volume", + "Volume used for precomputing indirect lighting"}, + {0, nullptr, 0, nullptr, nullptr}, +}; + +static EnumPropertyItem lightprobe_resolution_items[] = { + {LIGHT_PROBE_RESOLUTION_64, "64", 0, "64", ""}, + {LIGHT_PROBE_RESOLUTION_128, "128", 0, "128", ""}, + {LIGHT_PROBE_RESOLUTION_256, "256", 0, "256", ""}, + {LIGHT_PROBE_RESOLUTION_512, "512", 0, "512", ""}, + {LIGHT_PROBE_RESOLUTION_1024, "1024", 0, "1024", ""}, + {LIGHT_PROBE_RESOLUTION_2048, "2048", 0, "2048", ""}, {0, nullptr, 0, nullptr, nullptr}, }; @@ -290,6 +296,12 @@ static void rna_def_lightprobe(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Visibility Blur", "Filter size of the visibility blur"); RNA_def_property_update(prop, NC_MATERIAL | ND_SHADING, "rna_LightProbe_recalc"); + prop = RNA_def_property(srna, "resolution", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, nullptr, "resolution"); + RNA_def_property_enum_items(prop, lightprobe_resolution_items); + RNA_def_property_ui_text(prop, "Resolution", "Resolution when baked to a texture"); + RNA_def_property_update(prop, NC_MATERIAL | ND_SHADING, "rna_LightProbe_recalc"); + prop = RNA_def_property(srna, "intensity", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, nullptr, "intensity"); RNA_def_property_range(prop, 0.0f, FLT_MAX); diff --git a/source/blender/makesrna/intern/rna_linestyle.cc b/source/blender/makesrna/intern/rna_linestyle.cc index ccb030477b5..2a96d730483 100644 --- a/source/blender/makesrna/intern/rna_linestyle.cc +++ b/source/blender/makesrna/intern/rna_linestyle.cc @@ -115,7 +115,7 @@ const EnumPropertyItem rna_enum_linestyle_geometry_modifier_type_items[] = { #ifdef RNA_RUNTIME -# include "BLI_string_utils.hh" +# include "BLI_string_utils.h" # include "BKE_linestyle.h" # include "BKE_texture.h" diff --git a/source/blender/makesrna/intern/rna_material.cc b/source/blender/makesrna/intern/rna_material.cc index a41401cabfc..60f6a207e13 100644 --- a/source/blender/makesrna/intern/rna_material.cc +++ b/source/blender/makesrna/intern/rna_material.cc @@ -781,24 +781,6 @@ void RNA_def_material(BlenderRNA *brna) {0, nullptr, 0, nullptr, nullptr}, }; - static EnumPropertyItem prop_eevee_volume_isect_method_items[] = { - {MA_VOLUME_ISECT_FAST, - "FAST", - 0, - "Fast", - "Each face is considered as a medium interface. Gives correct results for manifold " - "geometry that contains no inner parts"}, - {MA_VOLUME_ISECT_ACCURATE, - "ACCURATE", - 0, - "Accurate", - "Faces are considered as medium interface only when they have different consecutive " - "facing. Gives correct results as long as the max ray depth is not exceeded. Have " - "significant memory overhead compared to the fast method"}, - {0, nullptr, 0, nullptr, nullptr}, - }; - -# if 1 /* Delete this section once we remove old eevee. */ static EnumPropertyItem prop_eevee_blend_items[] = { {MA_BM_SOLID, "OPAQUE", 0, "Opaque", "Render surface without transparency"}, {MA_BM_CLIP, @@ -834,23 +816,6 @@ void RNA_def_material(BlenderRNA *brna) "Use noise to dither the binary visibility and use filtering to reduce the noise"}, {0, nullptr, 0, nullptr, nullptr}, }; -# endif - - static EnumPropertyItem prop_eevee_surface_render_method_items[] = { - {MA_SURFACE_METHOD_DEFERRED, - "DITHERED", - 0, - "Dithered", - "Allows for grayscale hashed transparency, and compatible with render passes and " - "raytracing. Also known as deferred rendering"}, - {MA_SURFACE_METHOD_FORWARD, - "BLENDED", - 0, - "Blended", - "Allows for colored transparency, but incompatible with render passes and raytracing. Also " - "known as forward rendering"}, - {0, nullptr, 0, nullptr, nullptr}, - }; srna = RNA_def_struct(brna, "Material", "ID"); RNA_def_struct_ui_text( @@ -859,14 +824,6 @@ void RNA_def_material(BlenderRNA *brna) "Material data-block to define the appearance of geometric objects for rendering"); RNA_def_struct_ui_icon(srna, ICON_MATERIAL_DATA); - prop = RNA_def_property(srna, "surface_render_method", PROP_ENUM, PROP_NONE); - RNA_def_property_enum_items(prop, prop_eevee_surface_render_method_items); - RNA_def_property_ui_text(prop, - "Surface Render Method", - "Controls the blending and the compatibility with certain features"); - RNA_def_property_update(prop, 0, "rna_Material_draw_update"); - -# if 1 /* Delete this section once we remove old eevee. */ /* Blending (only Eevee for now) */ prop = RNA_def_property(srna, "blend_method", PROP_ENUM, PROP_NONE); RNA_def_property_enum_items(prop, prop_eevee_blend_items); @@ -886,9 +843,7 @@ void RNA_def_material(BlenderRNA *brna) "Clip Threshold", "A pixel is rendered only if its alpha value is above this threshold"); RNA_def_property_update(prop, 0, "rna_Material_draw_update"); -# endif - /* TODO(fclem): Should be renamed to use_transparency_overlap. */ prop = RNA_def_property(srna, "show_transparent_back", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_negative_sdna(prop, nullptr, "blend_flag", MA_BL_HIDE_BACKFACE); RNA_def_property_ui_text(prop, @@ -903,34 +858,12 @@ void RNA_def_material(BlenderRNA *brna) prop, "Backface Culling", "Use back face culling to hide the back side of faces"); RNA_def_property_update(prop, 0, "rna_Material_draw_update"); - prop = RNA_def_property(srna, "use_backface_culling_shadow", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, nullptr, "blend_flag", MA_BL_CULL_BACKFACE_SHADOW); - RNA_def_property_ui_text( - prop, "Shadow Backface Culling", "Use back face culling when casting shadows"); - RNA_def_property_update(prop, 0, "rna_Material_draw_update"); - - prop = RNA_def_property(srna, "lightprobe_volume_single_sided", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_negative_sdna( - prop, nullptr, "blend_flag", MA_BL_LIGHTPROBE_VOLUME_DOUBLE_SIDED); - RNA_def_property_ui_text( - prop, - "Light Probe Volume Single Sided", - "Consider material single sided for light probe volume capture. " - "Additionnaly helps rejecting probes inside the object to avoid light leaks"); - RNA_def_property_update(prop, 0, "rna_Material_draw_update"); - - /* TODO(fclem): Should be renamed to use_raytraced_refraction. */ prop = RNA_def_property(srna, "use_screen_refraction", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, nullptr, "blend_flag", MA_BL_SS_REFRACTION); RNA_def_property_ui_text( - prop, - "Raytrace Refraction", - "Use raytracing to determine refracted color instead of using only light probes. " - "This prevents the surface from contributing to the lighting of surfaces not using this " - "setting"); + prop, "Screen Space Refraction", "Use raytraced screen space refractions"); RNA_def_property_update(prop, 0, "rna_Material_draw_update"); -# if 1 /* Delete this section once we remove old eevee. */ prop = RNA_def_property(srna, "use_sss_translucency", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, nullptr, "blend_flag", MA_BL_TRANSLUCENCY); RNA_def_property_ui_text( @@ -945,15 +878,6 @@ void RNA_def_material(BlenderRNA *brna) "Approximate the thickness of the object to compute two refraction " "events (0 is disabled)"); RNA_def_property_update(prop, 0, "rna_Material_draw_update"); -# endif - - prop = RNA_def_property(srna, "volume_intersection_method", PROP_ENUM, PROP_NONE); - RNA_def_property_enum_items(prop, prop_eevee_volume_isect_method_items); - RNA_def_property_ui_text( - prop, - "Volume Intersection Method", - "Determines which inner part of the mesh will produce volumetric effect"); - RNA_def_property_update(prop, 0, "rna_Material_draw_update"); /* For Preview Render */ prop = RNA_def_property(srna, "preview_render_type", PROP_ENUM, PROP_NONE); diff --git a/source/blender/makesrna/intern/rna_mesh.cc b/source/blender/makesrna/intern/rna_mesh.cc index 6624570d2ff..c0a5b2918dd 100644 --- a/source/blender/makesrna/intern/rna_mesh.cc +++ b/source/blender/makesrna/intern/rna_mesh.cc @@ -23,7 +23,6 @@ #include "BKE_attribute.h" #include "BKE_editmesh.h" -#include "BKE_mesh_types.hh" #include "RNA_access.hh" #include "RNA_define.hh" @@ -479,7 +478,27 @@ static void rna_MeshLoop_normal_get(PointerRNA *ptr, float *values) { Mesh *me = rna_mesh(ptr); const int index = rna_MeshLoop_index_get(ptr); - copy_v3_v3(values, me->corner_normals()[index]); + const float(*layer)[3] = static_cast( + CustomData_get_layer(&me->loop_data, CD_NORMAL)); + + if (!layer) { + zero_v3(values); + } + else { + copy_v3_v3(values, layer[index]); + } +} + +static void rna_MeshLoop_normal_set(PointerRNA *ptr, const float *values) +{ + Mesh *me = rna_mesh(ptr); + const int index = rna_MeshLoop_index_get(ptr); + float(*layer)[3] = static_cast( + CustomData_get_layer_for_write(&me->loop_data, CD_NORMAL, me->totloop)); + + if (layer) { + normalize_v3_v3(layer[index], values); + } } static void rna_MeshLoop_tangent_get(PointerRNA *ptr, float *values) @@ -511,11 +530,13 @@ static void rna_MeshLoop_bitangent_get(PointerRNA *ptr, float *values) { Mesh *me = rna_mesh(ptr); const int index = rna_MeshLoop_index_get(ptr); + const float(*nor)[3] = static_cast( + CustomData_get_layer(&me->loop_data, CD_NORMAL)); const float(*vec)[4] = static_cast( CustomData_get_layer(&me->loop_data, CD_MLOOPTANGENT)); - if (vec) { - cross_v3_v3v3(values, me->corner_normals()[index], vec[index]); + if (nor && vec) { + cross_v3_v3v3(values, nor[index], vec[index]); mul_v3_fl(values, vec[index][3]); } else { @@ -583,10 +604,7 @@ static void rna_MeshPolygon_use_smooth_set(PointerRNA *ptr, bool value) &mesh->face_data, CD_PROP_BOOL, CD_SET_DEFAULT, mesh->faces_num, "sharp_face")); } const int index = rna_MeshPolygon_index_get(ptr); - if (value == sharp_faces[index]) { - sharp_faces[index] = !value; - BKE_mesh_tag_sharpness_changed(mesh); - } + sharp_faces[index] = !value; } static bool rna_MeshPolygon_select_get(PointerRNA *ptr) @@ -688,11 +706,20 @@ static void rna_MeshLoopTriangle_normal_get(PointerRNA *ptr, float *values) static void rna_MeshLoopTriangle_split_normals_get(PointerRNA *ptr, float *values) { Mesh *me = rna_mesh(ptr); - const blender::Span loop_normals = me->corner_normals(); - const MLoopTri *lt = (const MLoopTri *)ptr->data; - copy_v3_v3(values + 0, loop_normals[lt->tri[0]]); - copy_v3_v3(values + 3, loop_normals[lt->tri[1]]); - copy_v3_v3(values + 6, loop_normals[lt->tri[2]]); + const float(*lnors)[3] = static_cast( + CustomData_get_layer(&me->loop_data, CD_NORMAL)); + + if (!lnors) { + zero_v3(values + 0); + zero_v3(values + 3); + zero_v3(values + 6); + } + else { + MLoopTri *lt = (MLoopTri *)ptr->data; + copy_v3_v3(values + 0, lnors[lt->tri[0]]); + copy_v3_v3(values + 3, lnors[lt->tri[1]]); + copy_v3_v3(values + 6, lnors[lt->tri[2]]); + } } static float rna_MeshLoopTriangle_area_get(PointerRNA *ptr) @@ -1372,10 +1399,7 @@ static void rna_MeshEdge_use_edge_sharp_set(PointerRNA *ptr, bool value) &mesh->edge_data, CD_PROP_BOOL, CD_SET_DEFAULT, mesh->totedge, "sharp_edge")); } const int index = rna_MeshEdge_index_get(ptr); - if (value != sharp_edge[index]) { - sharp_edge[index] = value; - BKE_mesh_tag_sharpness_changed(mesh); - } + sharp_edge[index] = value; } static bool rna_MeshEdge_use_seam_get(PointerRNA *ptr) @@ -1618,11 +1642,6 @@ int rna_Mesh_loops_lookup_int(PointerRNA *ptr, int index, PointerRNA *r_ptr) return true; } -static int rna_Mesh_normals_domain_get(PointerRNA *ptr) -{ - return int(rna_mesh(ptr)->normals_domain()); -} - static void rna_Mesh_vertex_normals_begin(CollectionPropertyIterator *iter, PointerRNA *ptr) { const Mesh *mesh = rna_mesh(ptr); @@ -1688,32 +1707,41 @@ int rna_Mesh_poly_normals_lookup_int(PointerRNA *ptr, int index, PointerRNA *r_p static void rna_Mesh_corner_normals_begin(CollectionPropertyIterator *iter, PointerRNA *ptr) { const Mesh *mesh = rna_mesh(ptr); - const blender::Span normals = mesh->corner_normals(); - if (normals.is_empty()) { + const blender::float3 *normals = static_cast( + CustomData_get_layer(&mesh->loop_data, CD_NORMAL)); + if (!normals) { iter->valid = false; return; } - rna_iterator_array_begin( - iter, (void *)normals.data(), sizeof(float[3]), mesh->totloop, false, nullptr); + rna_iterator_array_begin(iter, + const_cast(normals), + sizeof(float[3]), + mesh->totloop, + false, + nullptr); } static int rna_Mesh_corner_normals_length(PointerRNA *ptr) { const Mesh *mesh = rna_mesh(ptr); + if (!CustomData_has_layer(&mesh->loop_data, CD_NORMAL)) { + return 0; + } return mesh->totloop; } int rna_Mesh_corner_normals_lookup_int(PointerRNA *ptr, int index, PointerRNA *r_ptr) { const Mesh *mesh = rna_mesh(ptr); - const blender::Span normals = mesh->corner_normals(); - if (index < 0 || index >= mesh->totloop || normals.is_empty()) { + const blender::float3 *normals = static_cast( + CustomData_get_layer(&mesh->loop_data, CD_NORMAL)); + if (index < 0 || index >= mesh->totloop || !normals) { return false; } /* Casting away const is okay because this RNA type doesn't allow changing the value. */ r_ptr->owner_id = (ID *)&mesh->id; r_ptr->type = &RNA_MeshNormalValue; - r_ptr->data = (float *)&normals[index]; + r_ptr->data = const_cast(&normals[index]); return true; } @@ -2165,7 +2193,8 @@ static void rna_def_mlooptri(BlenderRNA *brna) RNA_def_property_ui_text( prop, "Split Normals", - "Local space unit length split normal vectors of the face corners of this triangle"); + "Local space unit length split normals vectors of the vertices of this triangle " + "(must be computed beforehand using calc_normals_split or calc_tangents)"); prop = RNA_def_property(srna, "area", PROP_FLOAT, PROP_UNSIGNED); RNA_def_property_clear_flag(prop, PROP_EDITABLE); @@ -2214,14 +2243,15 @@ static void rna_def_mloop(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Index", "Index of this loop"); prop = RNA_def_property(srna, "normal", PROP_FLOAT, PROP_DIRECTION); - RNA_def_property_clear_flag(prop, PROP_EDITABLE); RNA_def_property_array(prop, 3); RNA_def_property_range(prop, -1.0f, 1.0f); - RNA_def_property_float_funcs(prop, "rna_MeshLoop_normal_get", nullptr, nullptr); - RNA_def_property_ui_text(prop, - "Normal", - "The normal direction of the face corner, taking into account sharp " - "faces, sharp edges, and custom normal data"); + RNA_def_property_float_funcs( + prop, "rna_MeshLoop_normal_get", "rna_MeshLoop_normal_set", nullptr); + RNA_def_property_ui_text( + prop, + "Normal", + "Local space unit length split normal vector of this vertex for this face " + "(must be computed beforehand using calc_normals_split or calc_tangents)"); prop = RNA_def_property(srna, "tangent", PROP_FLOAT, PROP_DIRECTION); RNA_def_property_array(prop, 3); @@ -3000,22 +3030,6 @@ static void rna_def_mesh(BlenderRNA *brna) rna_def_normal_layer_value(brna); - static const EnumPropertyItem normal_domain_items[] = { - {int(blender::bke::MeshNormalDomain::Point), "POINT", 0, "Point", ""}, - {int(blender::bke::MeshNormalDomain::Face), "FACE", 0, "Face", ""}, - {int(blender::bke::MeshNormalDomain::Corner), "CORNER", 0, "Corner", ""}, - {0, nullptr, 0, nullptr, nullptr}, - }; - - prop = RNA_def_property(srna, "normals_domain", PROP_ENUM, PROP_NONE); - RNA_def_property_enum_items(prop, normal_domain_items); - RNA_def_property_ui_text( - prop, - "Normal Domain", - "The attribute domain that gives enough information to represent the mesh's normals"); - RNA_def_property_clear_flag(prop, PROP_EDITABLE); - RNA_def_property_enum_funcs(prop, "rna_Mesh_normals_domain_get", nullptr, nullptr); - prop = RNA_def_property(srna, "vertex_normals", PROP_COLLECTION, PROP_NONE); RNA_def_property_struct_type(prop, "MeshNormalValue"); RNA_def_property_override_flag(prop, PROPOVERRIDE_IGNORE); @@ -3311,6 +3325,24 @@ static void rna_def_mesh(BlenderRNA *brna) RNA_def_property_update(prop, 0, "rna_Mesh_update_draw"); /* End Symmetry */ + prop = RNA_def_property(srna, "use_auto_smooth", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, nullptr, "flag", ME_AUTOSMOOTH); + RNA_def_property_ui_text( + prop, + "Auto Smooth", + "Auto smooth (based on smooth/sharp faces/edges and angle between faces), " + "or use custom split normals data if available"); + RNA_def_property_update(prop, 0, "rna_Mesh_update_geom_and_params"); + + prop = RNA_def_property(srna, "auto_smooth_angle", PROP_FLOAT, PROP_ANGLE); + RNA_def_property_float_sdna(prop, nullptr, "smoothresh"); + RNA_def_property_range(prop, 0.0f, DEG2RADF(180.0f)); + RNA_def_property_ui_text(prop, + "Auto Smooth Angle", + "Maximum angle between face normals that will be considered as smooth " + "(unused if custom split normals data are available)"); + RNA_def_property_update(prop, 0, "rna_Mesh_update_geom_and_params"); + RNA_define_verify_sdna(false); prop = RNA_def_property(srna, "has_custom_normals", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, nullptr, "", 0); diff --git a/source/blender/makesrna/intern/rna_mesh_api.cc b/source/blender/makesrna/intern/rna_mesh_api.cc index e1436bda500..41123183024 100644 --- a/source/blender/makesrna/intern/rna_mesh_api.cc +++ b/source/blender/makesrna/intern/rna_mesh_api.cc @@ -13,7 +13,6 @@ #include "DNA_customdata_types.h" -#include "BLI_math_base.h" #include "BLI_sys_types.h" #include "BLI_utildefines.h" @@ -24,8 +23,6 @@ # include "DNA_mesh_types.h" # include "BKE_anim_data.h" -# include "BKE_attribute.hh" -# include "BKE_mesh.h" # include "BKE_mesh.hh" # include "BKE_mesh_mapping.hh" # include "BKE_mesh_runtime.hh" @@ -49,12 +46,17 @@ static const char *rna_Mesh_unit_test_compare(Mesh *mesh, Mesh *mesh2, float thr return ret; } -static void rna_Mesh_sharp_from_angle_set(Mesh *mesh, const float angle) +static void rna_Mesh_create_normals_split(Mesh *mesh) { - mesh->attributes_for_write().remove("sharp_edge"); - mesh->attributes_for_write().remove("sharp_face"); - BKE_mesh_sharp_edges_set_from_angle(mesh, angle); - DEG_id_tag_update(&mesh->id, ID_RECALC_GEOMETRY); + if (!CustomData_has_layer(&mesh->loop_data, CD_NORMAL)) { + CustomData_add_layer(&mesh->loop_data, CD_NORMAL, CD_SET_DEFAULT, mesh->totloop); + CustomData_set_layer_flag(&mesh->loop_data, CD_NORMAL, CD_FLAG_TEMPORARY); + } +} + +static void rna_Mesh_free_normals_split(Mesh *mesh) +{ + CustomData_free_layers(&mesh->loop_data, CD_NORMAL, mesh->totloop); } static void rna_Mesh_calc_tangents(Mesh *mesh, ReportList *reports, const char *uvmap) @@ -72,6 +74,11 @@ static void rna_Mesh_calc_tangents(Mesh *mesh, ReportList *reports, const char * CustomData_set_layer_flag(&mesh->loop_data, CD_MLOOPTANGENT, CD_FLAG_TEMPORARY); } + /* Compute loop normals if needed. */ + if (!CustomData_has_layer(&mesh->loop_data, CD_NORMAL)) { + BKE_mesh_calc_normals_split(mesh); + } + BKE_mesh_calc_loop_tangent_single(mesh, uvmap, r_looptangents, reports); } @@ -178,7 +185,6 @@ static void rna_Mesh_update(Mesh *mesh, mesh->runtime->vert_normals_cache.tag_dirty(); mesh->runtime->face_normals_cache.tag_dirty(); - mesh->runtime->corner_normals_cache.tag_dirty(); DEG_id_tag_update(&mesh->id, 0); WM_event_add_notifier(C, NC_GEOM | ND_DATA, mesh); @@ -224,19 +230,15 @@ void RNA_api_mesh(StructRNA *srna) "Invert winding of all polygons " "(clears tessellation, does not handle custom normals)"); - func = RNA_def_function(srna, "set_sharp_from_angle", "rna_Mesh_sharp_from_angle_set"); + func = RNA_def_function(srna, "create_normals_split", "rna_Mesh_create_normals_split"); + RNA_def_function_ui_description(func, "Empty split vertex normals"); + + func = RNA_def_function(srna, "calc_normals_split", "BKE_mesh_calc_normals_split"); RNA_def_function_ui_description(func, - "Reset and fill the \"sharp_edge\" attribute based on the angle " - "of faces neighboring manifold edges"); - RNA_def_float(func, - "angle", - M_PI, - 0.0f, - M_PI, - "Angle", - "Angle between faces beyond which edges are marked sharp", - 0.0f, - M_PI); + "Calculate split vertex normals, which preserve sharp edges"); + + func = RNA_def_function(srna, "free_normals_split", "rna_Mesh_free_normals_split"); + RNA_def_function_ui_description(func, "Free split vertex normals"); func = RNA_def_function(srna, "split_faces", "ED_mesh_split_faces"); RNA_def_function_ui_description(func, "Split faces based on the edge angle"); diff --git a/source/blender/makesrna/intern/rna_node_socket.cc b/source/blender/makesrna/intern/rna_node_socket.cc index fbc3dfc5c5d..53a74e22a4f 100644 --- a/source/blender/makesrna/intern/rna_node_socket.cc +++ b/source/blender/makesrna/intern/rna_node_socket.cc @@ -406,9 +406,6 @@ static void rna_NodeSocketStandard_value_and_relation_update(bContext *C, Pointe bool rna_NodeSocketMaterial_default_value_poll(PointerRNA * /*ptr*/, PointerRNA value) { - if (U.experimental.use_grease_pencil_version3) { - return true; - } /* Do not show grease pencil materials for now. */ Material *ma = static_cast(value.data); return ma->gp_style == nullptr; diff --git a/source/blender/makesrna/intern/rna_node_tree_interface.cc b/source/blender/makesrna/intern/rna_node_tree_interface.cc index a124f475292..767492b6577 100644 --- a/source/blender/makesrna/intern/rna_node_tree_interface.cc +++ b/source/blender/makesrna/intern/rna_node_tree_interface.cc @@ -28,16 +28,11 @@ static const EnumPropertyItem node_tree_interface_socket_in_out_items[] = { #ifdef RNA_RUNTIME -# include "BKE_attribute.h" # include "BKE_node.h" # include "BKE_node_runtime.hh" # include "BKE_node_tree_interface.hh" # include "BKE_node_tree_update.h" - # include "BLI_set.hh" - -# include "BLT_translation.h" - # include "DNA_material_types.h" # include "ED_node.hh" # include "WM_api.hh" @@ -390,84 +385,6 @@ static const EnumPropertyItem *rna_NodeTreeInterfaceSocket_socket_type_itemf( ntree->typeinfo, rna_NodeTreeInterfaceSocket_socket_type_poll, r_free); } -static const EnumPropertyItem *rna_NodeTreeInterfaceSocket_default_input_itemf( - bContext * /*C*/, PointerRNA *ptr, PropertyRNA * /*prop*/, bool *r_free) -{ - const bNodeTree *ntree = reinterpret_cast(ptr->owner_id); - const bNodeTreeInterfaceSocket *socket = static_cast( - ptr->data); - if (!ntree) { - return rna_enum_dummy_NULL_items; - } - - *r_free = true; - EnumPropertyItem *items = nullptr; - int items_count = 0; - - const EnumPropertyItem none{GEO_NODE_DEFAULT_INPUT_VALUE, - "VALUE", - 0, - N_("Default Value"), - N_("The node socket's default value")}; - RNA_enum_item_add(&items, &items_count, &none); - - if (ntree->type == NTREE_GEOMETRY) { - const bNodeSocketType *type = socket->socket_typeinfo(); - if (type->type == SOCK_INT) { - const EnumPropertyItem index{GEO_NODE_DEFAULT_FIELD_INPUT_INDEX_FIELD, - "INDEX", - 0, - N_("Index"), - N_("The index from the context")}; - RNA_enum_item_add(&items, &items_count, &index); - const EnumPropertyItem index_or_id{ - GEO_NODE_DEFAULT_FIELD_INPUT_ID_INDEX_FIELD, - "ID_OR_INDEX", - 0, - N_("ID or Index"), - N_("The \"id\" attribute if available, otherwise the index")}; - RNA_enum_item_add(&items, &items_count, &index_or_id); - } - else if (type->type == SOCK_VECTOR) { - const EnumPropertyItem normal{GEO_NODE_DEFAULT_FIELD_INPUT_NORMAL_FIELD, - "NORMAL", - 0, - N_("Normal"), - N_("The geometry's normal direction")}; - RNA_enum_item_add(&items, &items_count, &normal); - const EnumPropertyItem position{GEO_NODE_DEFAULT_FIELD_INPUT_POSITION_FIELD, - "POSITION", - 0, - N_("Position"), - N_("The position from the context")}; - RNA_enum_item_add(&items, &items_count, &position); - } - } - - RNA_enum_item_end(&items, &items_count); - return items; -} - -static const EnumPropertyItem *rna_NodeTreeInterfaceSocket_attribute_domain_itemf( - bContext * /*C*/, PointerRNA * /*ptr*/, PropertyRNA * /*prop*/, bool *r_free) -{ - EnumPropertyItem *item_array = nullptr; - int items_len = 0; - - for (const EnumPropertyItem *item = rna_enum_attribute_domain_items; item->identifier != nullptr; - item++) - { - if (!U.experimental.use_grease_pencil_version3 && item->value == ATTR_DOMAIN_LAYER) { - continue; - } - RNA_enum_item_add(&item_array, &items_len, item); - } - RNA_enum_item_end(&item_array, &items_len); - - *r_free = true; - return item_array; -} - static PointerRNA rna_NodeTreeInterfaceItems_active_get(PointerRNA *ptr) { bNodeTreeInterface *interface = static_cast(ptr->data); @@ -1000,17 +917,8 @@ static void rna_def_node_interface_socket(BlenderRNA *brna) prop, "Single Value", "Only allow single value inputs rather than fields"); RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceItem_update"); - prop = RNA_def_property(srna, "layer_selection_field", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, nullptr, "flag", NODE_INTERFACE_SOCKET_LAYER_SELECTION); - RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); - RNA_def_property_ui_text( - prop, "Layer Selection", "Take Grease Pencil Layer or Layer Group as selection field"); - RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceItem_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_enum_funcs( - prop, nullptr, nullptr, "rna_NodeTreeInterfaceSocket_attribute_domain_itemf"); RNA_def_property_ui_text( prop, "Attribute Domain", @@ -1025,15 +933,6 @@ static void rna_def_node_interface_socket(BlenderRNA *brna) "geometry nodes modifier"); RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceItem_update"); - prop = RNA_def_property(srna, "default_input", PROP_ENUM, PROP_NONE); - RNA_def_property_enum_items(prop, rna_enum_dummy_NULL_items); - RNA_def_property_ui_text(prop, - "Default Input", - "Input to use when the socket is unconnected. Requires \"Hide Value\""); - RNA_def_property_enum_funcs( - prop, nullptr, nullptr, "rna_NodeTreeInterfaceSocket_default_input_itemf"); - RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceItem_update"); - /* Registered properties and functions for custom socket types. */ prop = RNA_def_property(srna, "bl_socket_idname", PROP_STRING, PROP_NONE); RNA_def_property_string_sdna(prop, nullptr, "socket_type"); diff --git a/source/blender/makesrna/intern/rna_nodetree.cc b/source/blender/makesrna/intern/rna_nodetree.cc index 1b7a22dbcee..f670f9e8c20 100644 --- a/source/blender/makesrna/intern/rna_nodetree.cc +++ b/source/blender/makesrna/intern/rna_nodetree.cc @@ -11,9 +11,7 @@ #include #include "BLI_function_ref.hh" -#include "BLI_linear_allocator.hh" #include "BLI_math_rotation.h" -#include "BLI_string.h" #include "BLI_string_utf8_symbols.h" #include "BLI_utildefines.h" @@ -58,14 +56,14 @@ #include "RE_texture.h" -#include "NOD_composite.hh" +#include "NOD_composite.h" #include "NOD_geometry.hh" #include "NOD_socket.hh" #include "DEG_depsgraph.hh" #include "DEG_depsgraph_query.hh" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" const EnumPropertyItem rna_enum_node_socket_in_out_items[] = {{SOCK_IN, "IN", 0, "Input", ""}, {SOCK_OUT, "OUT", 0, "Output", ""}, @@ -594,13 +592,11 @@ static const EnumPropertyItem node_cryptomatte_layer_name_items[] = { # include "GPU_material.h" # include "NOD_common.h" -# include "NOD_composite.hh" +# include "NOD_composite.h" # include "NOD_geometry.hh" # include "NOD_shader.h" # include "NOD_socket.hh" -# include "NOD_socket_items.hh" # include "NOD_texture.h" -# include "NOD_zone_socket_items.hh" # include "RE_engine.h" # include "RE_pipeline.h" @@ -608,9 +604,6 @@ static const EnumPropertyItem node_cryptomatte_layer_name_items[] = { # include "DNA_scene_types.h" # include "WM_api.hh" -using blender::nodes::RepeatItemsAccessor; -using blender::nodes::SimulationItemsAccessor; - extern FunctionRNA rna_NodeTree_poll_func; extern FunctionRNA rna_NodeTree_update_func; extern FunctionRNA rna_NodeTree_get_from_context_func; @@ -1877,26 +1870,6 @@ static const EnumPropertyItem *rna_GeometryNodeAttributeType_type_with_socket_it generic_attribute_type_supported_with_socket); } -static const EnumPropertyItem *rna_GeometryNodeAttributeDomain_attribute_domain_itemf( - bContext * /*C*/, PointerRNA * /*ptr*/, PropertyRNA * /*prop*/, bool *r_free) -{ - EnumPropertyItem *item_array = nullptr; - int items_len = 0; - - for (const EnumPropertyItem *item = rna_enum_attribute_domain_items; item->identifier != nullptr; - item++) - { - if (!U.experimental.use_grease_pencil_version3 && item->value == ATTR_DOMAIN_LAYER) { - continue; - } - RNA_enum_item_add(&item_array, &items_len, item); - } - RNA_enum_item_end(&item_array, &items_len); - - *r_free = true; - return item_array; -} - static StructRNA *rna_ShaderNode_register(Main *bmain, ReportList *reports, void *data, @@ -3225,6 +3198,105 @@ static void rna_NodeCryptomatte_update_remove(Main *bmain, Scene *scene, Pointer rna_Node_update(bmain, scene, ptr); } +static void rna_SimulationStateItem_update(Main *bmain, Scene * /*scene*/, PointerRNA *ptr) +{ + bNodeTree *ntree = reinterpret_cast(ptr->owner_id); + NodeSimulationItem *item = static_cast(ptr->data); + bNode *node = NOD_geometry_simulation_output_find_node_by_item(ntree, item); + + BKE_ntree_update_tag_node_property(ntree, node); + ED_node_tree_propagate_change(nullptr, bmain, ntree); +} + +static bNode *find_node_by_repeat_item(PointerRNA *ptr) +{ + const NodeRepeatItem *item = static_cast(ptr->data); + bNodeTree *ntree = reinterpret_cast(ptr->owner_id); + ntree->ensure_topology_cache(); + for (bNode *node : ntree->nodes_by_type("GeometryNodeRepeatOutput")) { + NodeGeometryRepeatOutput *storage = static_cast(node->storage); + if (storage->items_span().contains_ptr(item)) { + return node; + } + } + return nullptr; +} + +static void rna_RepeatItem_update(Main *bmain, Scene * /*scene*/, PointerRNA *ptr) +{ + bNodeTree *ntree = reinterpret_cast(ptr->owner_id); + bNode *node = find_node_by_repeat_item(ptr); + + BKE_ntree_update_tag_node_property(ntree, node); + ED_node_tree_propagate_change(nullptr, bmain, ntree); +} + +static bool rna_SimulationStateItem_socket_type_supported(const EnumPropertyItem *item) +{ + return NOD_geometry_simulation_output_item_socket_type_supported( + (eNodeSocketDatatype)item->value); +} + +static const EnumPropertyItem *rna_SimulationStateItem_socket_type_itemf(bContext * /*C*/, + PointerRNA * /*ptr*/, + PropertyRNA * /*prop*/, + bool *r_free) +{ + *r_free = true; + return itemf_function_check(rna_enum_node_socket_data_type_items, + rna_SimulationStateItem_socket_type_supported); +} + +static bool rna_RepeatItem_socket_type_supported(const EnumPropertyItem *item) +{ + return NodeRepeatItem::supports_type(eNodeSocketDatatype(item->value)); +} + +static const EnumPropertyItem *rna_RepeatItem_socket_type_itemf(bContext * /*C*/, + PointerRNA * /*ptr*/, + PropertyRNA * /*prop*/, + bool *r_free) +{ + *r_free = true; + return itemf_function_check(rna_enum_node_socket_data_type_items, + rna_RepeatItem_socket_type_supported); +} + +static void rna_SimulationStateItem_name_set(PointerRNA *ptr, const char *value) +{ + bNodeTree *ntree = reinterpret_cast(ptr->owner_id); + NodeSimulationItem *item = static_cast(ptr->data); + bNode *node = NOD_geometry_simulation_output_find_node_by_item(ntree, item); + NodeGeometrySimulationOutput *sim = static_cast(node->storage); + + const char *defname = nodeStaticSocketLabel(item->socket_type, 0); + NOD_geometry_simulation_output_item_set_unique_name(sim, item, value, defname); +} + +static void rna_RepeatItem_name_set(PointerRNA *ptr, const char *value) +{ + bNode *node = find_node_by_repeat_item(ptr); + NodeRepeatItem *item = static_cast(ptr->data); + NodeGeometryRepeatOutput *storage = static_cast(node->storage); + storage->set_item_name(*item, value); +} + +static void rna_SimulationStateItem_color_get(PointerRNA *ptr, float *values) +{ + NodeSimulationItem *item = static_cast(ptr->data); + + const char *socket_type_idname = nodeStaticSocketType(item->socket_type, 0); + ED_node_type_draw_color(socket_type_idname, values); +} + +static void rna_RepeatItem_color_get(PointerRNA *ptr, float *values) +{ + NodeRepeatItem *item = static_cast(ptr->data); + + const char *socket_type_idname = nodeStaticSocketType(item->socket_type, 0); + ED_node_type_draw_color(socket_type_idname, values); +} + static PointerRNA rna_Node_paired_output_get(PointerRNA *ptr) { bNodeTree *ntree = reinterpret_cast(ptr->owner_id); @@ -3270,148 +3342,200 @@ static bool rna_Node_pair_with_output( return true; } -template -static void rna_Node_ItemArray_remove(ID *id, - bNode *node, - Main *bmain, - ReportList *reports, - typename Accessor::ItemT *item_to_remove) -{ - blender::nodes::socket_items::SocketItemsRef ref = Accessor::get_items_from_node(*node); - if (item_to_remove < *ref.items || item_to_remove >= *ref.items + *ref.items_num) { - BKE_reportf(reports, RPT_ERROR, "Unable to locate item '%s' in node", item_to_remove->name); - return; - } - const int remove_index = item_to_remove - *ref.items; - blender::nodes::socket_items::remove_item( - ref.items, ref.items_num, ref.active_index, remove_index, Accessor::destruct_item); - - bNodeTree *ntree = reinterpret_cast(id); - BKE_ntree_update_tag_node_property(ntree, node); - ED_node_tree_propagate_change(nullptr, bmain, ntree); - WM_main_add_notifier(NC_NODE | NA_EDITED, ntree); -} - -template static void rna_Node_ItemArray_clear(ID *id, bNode *node, Main *bmain) -{ - blender::nodes::socket_items::SocketItemsRef ref = Accessor::get_items_from_node(*node); - blender::nodes::socket_items::clear_items( - ref.items, ref.items_num, ref.active_index, Accessor::destruct_item); - - bNodeTree *ntree = reinterpret_cast(id); - BKE_ntree_update_tag_node_property(ntree, node); - ED_node_tree_propagate_change(nullptr, bmain, ntree); - WM_main_add_notifier(NC_NODE | NA_EDITED, ntree); -} - -template -static void rna_Node_ItemArray_move( - ID *id, bNode *node, Main *bmain, const int from_index, const int to_index) -{ - blender::nodes::socket_items::SocketItemsRef ref = Accessor::get_items_from_node(*node); - const int items_num = *ref.items_num; - if (from_index < 0 || to_index < 0 || from_index >= items_num || to_index >= items_num) { - return; - } - blender::nodes::socket_items::move_item(*ref.items, items_num, from_index, to_index); - - bNodeTree *ntree = reinterpret_cast(id); - BKE_ntree_update_tag_node_property(ntree, node); - ED_node_tree_propagate_change(nullptr, bmain, ntree); - WM_main_add_notifier(NC_NODE | NA_EDITED, ntree); -} - -template static PointerRNA rna_Node_ItemArray_active_get(PointerRNA *ptr) -{ - bNode *node = static_cast(ptr->data); - blender::nodes::socket_items::SocketItemsRef ref = Accessor::get_items_from_node(*node); - typename Accessor::ItemT *active_item = nullptr; - const int active_index = *ref.active_index; - const int items_num = *ref.items_num; - if (active_index >= 0 && active_index < items_num) { - active_item = &(*ref.items)[active_index]; - } - return RNA_pointer_create(ptr->owner_id, Accessor::item_srna, active_item); -} -template -static void rna_Node_ItemArray_active_set(PointerRNA *ptr, - PointerRNA value, - ReportList * /*reports*/) -{ - using ItemT = typename Accessor::ItemT; - bNode *node = static_cast(ptr->data); - ItemT *item = static_cast(value.data); - - blender::nodes::socket_items::SocketItemsRef ref = Accessor::get_items_from_node(*node); - if (item >= *ref.items && item < *ref.items + *ref.items_num) { - *ref.active_index = item - *ref.items; - } -} - -template -static void rna_Node_ItemArray_item_update(Main *bmain, Scene * /*scene*/, PointerRNA *ptr) -{ - using ItemT = typename Accessor::ItemT; - bNodeTree &ntree = *reinterpret_cast(ptr->owner_id); - ItemT &item = *static_cast(ptr->data); - bNode *node = blender::nodes::socket_items::find_node_by_item(ntree, item); - BLI_assert(node != nullptr); - - BKE_ntree_update_tag_node_property(&ntree, node); - ED_node_tree_propagate_change(nullptr, bmain, &ntree); -} - -template -static const EnumPropertyItem *rna_Node_ItemArray_socket_type_itemf(bContext * /*C*/, - PointerRNA * /*ptr*/, - PropertyRNA * /*prop*/, - bool *r_free) -{ - *r_free = true; - return itemf_function_check( - rna_enum_node_socket_data_type_items, [](const EnumPropertyItem *item) { - return Accessor::supports_socket_type(eNodeSocketDatatype(item->value)); - }); -} - -template -static void rna_Node_ItemArray_item_name_set(PointerRNA *ptr, const char *value) -{ - using ItemT = typename Accessor::ItemT; - bNodeTree &ntree = *reinterpret_cast(ptr->owner_id); - ItemT &item = *static_cast(ptr->data); - bNode *node = blender::nodes::socket_items::find_node_by_item(ntree, item); - BLI_assert(node != nullptr); - blender::nodes::socket_items::set_item_name_and_make_unique(*node, item, value); -} - -template -static void rna_Node_ItemArray_item_color_get(PointerRNA *ptr, float *values) -{ - using ItemT = typename Accessors::ItemT; - ItemT &item = *static_cast(ptr->data); - const char *socket_type_idname = nodeStaticSocketType(*Accessors::get_socket_type(item), 0); - ED_node_type_draw_color(socket_type_idname, values); -} - -template -typename Accessor::ItemT *rna_Node_ItemArray_new_with_socket_and_name( +static NodeSimulationItem *rna_NodeGeometrySimulationOutput_items_new( ID *id, bNode *node, Main *bmain, ReportList *reports, int socket_type, const char *name) { - using ItemT = typename Accessor::ItemT; - if (!Accessor::supports_socket_type(eNodeSocketDatatype(socket_type))) { - BKE_report(reports, RPT_ERROR, "Unable to create item with this socket type"); - return nullptr; + NodeGeometrySimulationOutput *sim = static_cast(node->storage); + NodeSimulationItem *item = NOD_geometry_simulation_output_add_item( + sim, short(socket_type), name); + + if (item == nullptr) { + BKE_report(reports, RPT_ERROR, "Unable to create socket"); } - ItemT *new_item = blender::nodes::socket_items::add_item_with_socket_and_name( - *node, eNodeSocketDatatype(socket_type), name); + else { + bNodeTree *ntree = reinterpret_cast(id); + BKE_ntree_update_tag_node_property(ntree, node); + ED_node_tree_propagate_change(nullptr, bmain, ntree); + WM_main_add_notifier(NC_NODE | NA_EDITED, ntree); + } + + return item; +} + +static NodeRepeatItem *rna_NodeGeometryRepeatOutput_items_new( + ID *id, bNode *node, Main *bmain, ReportList *reports, int socket_type, const char *name) +{ + NodeGeometryRepeatOutput *storage = static_cast(node->storage); + NodeRepeatItem *item = storage->add_item(name, eNodeSocketDatatype(socket_type)); + if (item == nullptr) { + BKE_report(reports, RPT_ERROR, "Unable to create socket"); + } + else { + bNodeTree *ntree = reinterpret_cast(id); + BKE_ntree_update_tag_node_property(ntree, node); + ED_node_tree_propagate_change(nullptr, bmain, ntree); + WM_main_add_notifier(NC_NODE | NA_EDITED, ntree); + } + + return item; +} + +static void rna_NodeGeometrySimulationOutput_items_remove( + ID *id, bNode *node, Main *bmain, ReportList *reports, NodeSimulationItem *item) +{ + NodeGeometrySimulationOutput *sim = static_cast(node->storage); + if (!NOD_geometry_simulation_output_contains_item(sim, item)) { + BKE_reportf(reports, RPT_ERROR, "Unable to locate item '%s' in node", item->name); + } + else { + NOD_geometry_simulation_output_remove_item(sim, item); + + bNodeTree *ntree = reinterpret_cast(id); + BKE_ntree_update_tag_node_property(ntree, node); + ED_node_tree_propagate_change(nullptr, bmain, ntree); + WM_main_add_notifier(NC_NODE | NA_EDITED, ntree); + } +} + +static void rna_NodeGeometryRepeatOutput_items_remove( + ID *id, bNode *node, Main *bmain, ReportList *reports, NodeRepeatItem *item) +{ + NodeGeometryRepeatOutput *storage = static_cast(node->storage); + if (!storage->items_span().contains_ptr(item)) { + BKE_reportf(reports, RPT_ERROR, "Unable to locate item '%s' in node", item->name); + return; + } + + const int remove_index = item - storage->items; + NodeRepeatItem *old_items = storage->items; + storage->items = MEM_cnew_array(storage->items_num - 1, __func__); + std::copy_n(old_items, remove_index, storage->items); + std::copy_n(old_items + remove_index + 1, + storage->items_num - remove_index - 1, + storage->items + remove_index); + + MEM_SAFE_FREE(old_items[remove_index].name); + storage->items_num--; + MEM_SAFE_FREE(old_items); bNodeTree *ntree = reinterpret_cast(id); BKE_ntree_update_tag_node_property(ntree, node); ED_node_tree_propagate_change(nullptr, bmain, ntree); WM_main_add_notifier(NC_NODE | NA_EDITED, ntree); +} - return new_item; +static void rna_NodeGeometrySimulationOutput_items_clear(ID *id, bNode *node, Main *bmain) +{ + NodeGeometrySimulationOutput *sim = static_cast(node->storage); + NOD_geometry_simulation_output_clear_items(sim); + + bNodeTree *ntree = reinterpret_cast(id); + BKE_ntree_update_tag_node_property(ntree, node); + ED_node_tree_propagate_change(nullptr, bmain, ntree); + WM_main_add_notifier(NC_NODE | NA_EDITED, ntree); +} + +static void rna_NodeGeometryRepeatOutput_items_clear(ID * /*id*/, bNode *node, Main * /*bmain*/) +{ + NodeGeometryRepeatOutput *storage = static_cast(node->storage); + for (NodeRepeatItem &item : storage->items_span()) { + MEM_SAFE_FREE(item.name); + } + MEM_SAFE_FREE(storage->items); + storage->items_num = 0; + storage->active_index = 0; +} + +static void rna_NodeGeometrySimulationOutput_items_move( + ID *id, bNode *node, Main *bmain, int from_index, int to_index) +{ + NodeGeometrySimulationOutput *sim = static_cast(node->storage); + + if (from_index < 0 || from_index >= sim->items_num || to_index < 0 || to_index >= sim->items_num) + { + return; + } + + NOD_geometry_simulation_output_move_item(sim, from_index, to_index); + + bNodeTree *ntree = reinterpret_cast(id); + BKE_ntree_update_tag_node_property(ntree, node); + ED_node_tree_propagate_change(nullptr, bmain, ntree); + WM_main_add_notifier(NC_NODE | NA_EDITED, ntree); +} + +static void rna_NodeGeometryRepeatOutput_items_move( + ID *id, bNode *node, Main *bmain, int from_index, int to_index) +{ + NodeGeometryRepeatOutput *storage = static_cast(node->storage); + if (from_index < 0 || from_index >= storage->items_num || to_index < 0 || + to_index >= storage->items_num) + { + return; + } + + if (from_index < to_index) { + const NodeRepeatItem tmp = storage->items[from_index]; + for (int i = from_index; i < to_index; i++) { + storage->items[i] = storage->items[i + 1]; + } + storage->items[to_index] = tmp; + } + else if (from_index > to_index) { + const NodeRepeatItem tmp = storage->items[from_index]; + for (int i = from_index; i > to_index; i--) { + storage->items[i] = storage->items[i - 1]; + } + storage->items[to_index] = tmp; + } + + bNodeTree *ntree = reinterpret_cast(id); + BKE_ntree_update_tag_node_property(ntree, node); + ED_node_tree_propagate_change(nullptr, bmain, ntree); + WM_main_add_notifier(NC_NODE | NA_EDITED, ntree); +} + +static PointerRNA rna_NodeGeometrySimulationOutput_active_item_get(PointerRNA *ptr) +{ + bNode *node = static_cast(ptr->data); + NodeGeometrySimulationOutput *sim = static_cast(node->storage); + NodeSimulationItem *item = NOD_geometry_simulation_output_get_active_item(sim); + PointerRNA r_ptr = RNA_pointer_create(ptr->owner_id, &RNA_SimulationStateItem, item); + return r_ptr; +} + +static PointerRNA rna_NodeGeometryRepeatOutput_active_item_get(PointerRNA *ptr) +{ + bNode *node = static_cast(ptr->data); + NodeGeometryRepeatOutput *storage = static_cast(node->storage); + blender::MutableSpan items = storage->items_span(); + PointerRNA r_ptr{}; + if (items.index_range().contains(storage->active_index)) { + r_ptr = RNA_pointer_create(ptr->owner_id, &RNA_RepeatItem, &items[storage->active_index]); + } + return r_ptr; +} + +static void rna_NodeGeometrySimulationOutput_active_item_set(PointerRNA *ptr, + PointerRNA value, + ReportList * /*reports*/) +{ + bNode *node = static_cast(ptr->data); + NodeGeometrySimulationOutput *sim = static_cast(node->storage); + NOD_geometry_simulation_output_set_active_item(sim, + static_cast(value.data)); +} + +static void rna_NodeGeometryRepeatOutput_active_item_set(PointerRNA *ptr, + PointerRNA value, + ReportList * /*reports*/) +{ + bNode *node = static_cast(ptr->data); + NodeGeometryRepeatOutput *storage = static_cast(node->storage); + NodeRepeatItem *item = static_cast(value.data); + if (storage->items_span().contains_ptr(item)) { + storage->active_index = item - storage->items; + } } /* ******** Node Socket Types ******** */ @@ -8098,12 +8222,6 @@ static void def_cmp_keyingscreen(StructRNA *srna) RNA_def_property_string_sdna(prop, nullptr, "tracking_object"); RNA_def_property_ui_text(prop, "Tracking Object", ""); RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); - - prop = RNA_def_property(srna, "smoothness", PROP_FLOAT, PROP_FACTOR); - RNA_def_property_float_sdna(prop, nullptr, "smoothness"); - RNA_def_property_range(prop, 0.0f, 1.0f); - RNA_def_property_ui_text(prop, "Smoothness", ""); - RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); } static void def_cmp_keying(StructRNA *srna) @@ -8528,6 +8646,14 @@ static void def_cmp_kuwahara(StructRNA *srna) {0, nullptr, 0, nullptr, nullptr}, }; + prop = RNA_def_property(srna, "size", PROP_INT, PROP_NONE); + RNA_def_property_int_sdna(prop, nullptr, "size"); + RNA_def_property_range(prop, 1.0, 100.0); + RNA_def_property_ui_range(prop, 1, 100, 1, -1); + RNA_def_property_ui_text( + prop, "Size", "Size of filter. Larger values give stronger stylized effect"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); + prop = RNA_def_property(srna, "variation", PROP_ENUM, PROP_NONE); RNA_def_property_enum_sdna(prop, nullptr, "variation"); RNA_def_property_enum_items(prop, variation_items); @@ -8814,97 +8940,56 @@ static void def_geo_repeat_input(StructRNA *srna) def_common_zone_input(srna); } -static void rna_def_node_item_array_socket_item_common(StructRNA *srna, const char *accessor) +static void rna_def_simulation_state_item(BlenderRNA *brna) { - static blender::LinearAllocator<> allocator; PropertyRNA *prop; - char name_set_func[64]; - SNPRINTF(name_set_func, "rna_Node_ItemArray_item_name_set<%s>", accessor); - - char item_update_func[64]; - SNPRINTF(item_update_func, "rna_Node_ItemArray_item_update<%s>", accessor); - const char *item_update_func_ptr = allocator.copy_string(item_update_func).c_str(); - - char socket_type_itemf[64]; - SNPRINTF(socket_type_itemf, "rna_Node_ItemArray_socket_type_itemf<%s>", accessor); - - char color_get_func[64]; - SNPRINTF(color_get_func, "rna_Node_ItemArray_item_color_get<%s>", accessor); + StructRNA *srna = RNA_def_struct(brna, "SimulationStateItem", nullptr); + RNA_def_struct_ui_text(srna, "Simulation Item", ""); + RNA_def_struct_sdna(srna, "NodeSimulationItem"); prop = RNA_def_property(srna, "name", PROP_STRING, PROP_NONE); - RNA_def_property_string_funcs( - prop, nullptr, nullptr, allocator.copy_string(name_set_func).c_str()); + RNA_def_property_string_funcs(prop, nullptr, nullptr, "rna_SimulationStateItem_name_set"); RNA_def_property_ui_text(prop, "Name", ""); RNA_def_struct_name_property(srna, prop); - RNA_def_property_update(prop, NC_NODE | NA_EDITED, item_update_func_ptr); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_SimulationStateItem_update"); prop = RNA_def_property(srna, "socket_type", PROP_ENUM, PROP_NONE); RNA_def_property_enum_items(prop, rna_enum_node_socket_data_type_items); - RNA_def_property_enum_funcs( - prop, nullptr, nullptr, allocator.copy_string(socket_type_itemf).c_str()); + RNA_def_property_enum_funcs(prop, nullptr, nullptr, "rna_SimulationStateItem_socket_type_itemf"); RNA_def_property_ui_text(prop, "Socket Type", ""); RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); - RNA_def_property_update(prop, NC_NODE | NA_EDITED, item_update_func_ptr); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_SimulationStateItem_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 where the attribute is stored in the simulation state"); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_SimulationStateItem_update"); prop = RNA_def_property(srna, "color", PROP_FLOAT, PROP_COLOR_GAMMA); RNA_def_property_array(prop, 4); - RNA_def_property_float_funcs( - prop, allocator.copy_string(color_get_func).c_str(), nullptr, nullptr); + RNA_def_property_float_funcs(prop, "rna_SimulationStateItem_color_get", nullptr, nullptr); RNA_def_property_clear_flag(prop, PROP_EDITABLE); RNA_def_property_ui_text( prop, "Color", "Color of the corresponding socket type in the node editor"); } -static void rna_def_node_item_array_common_functions(StructRNA *srna, - const char *item_name, - const char *accessor_name) +static void rna_def_geo_simulation_output_items(BlenderRNA *brna) { - static blender::LinearAllocator<> allocator; + StructRNA *srna; PropertyRNA *parm; FunctionRNA *func; - char remove_call[64]; - SNPRINTF(remove_call, "rna_Node_ItemArray_remove<%s>", accessor_name); - char clear_call[64]; - SNPRINTF(clear_call, "rna_Node_ItemArray_clear<%s>", accessor_name); - char move_call[64]; - SNPRINTF(move_call, "rna_Node_ItemArray_move<%s>", accessor_name); + srna = RNA_def_struct(brna, "NodeGeometrySimulationOutputItems", nullptr); + RNA_def_struct_sdna(srna, "bNode"); + RNA_def_struct_ui_text(srna, "Items", "Collection of simulation items"); - func = RNA_def_function(srna, "remove", allocator.copy_string(remove_call).c_str()); - RNA_def_function_ui_description(func, "Remove an item"); - RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN | FUNC_USE_REPORTS); - parm = RNA_def_pointer(func, "item", item_name, "Item", "The item to remove"); - RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED); - - func = RNA_def_function(srna, "clear", allocator.copy_string(clear_call).c_str()); - RNA_def_function_ui_description(func, "Remove all items"); - RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN); - - func = RNA_def_function(srna, "move", allocator.copy_string(move_call).c_str()); - RNA_def_function_ui_description(func, "Move an item to another position"); - RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN); - parm = RNA_def_int( - func, "from_index", -1, 0, INT_MAX, "From Index", "Index of the item to move", 0, 10000); - RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED); - parm = RNA_def_int( - func, "to_index", -1, 0, INT_MAX, "To Index", "Target index for the item", 0, 10000); - RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED); -} - -static void rna_def_node_item_array_new_with_socket_and_name(StructRNA *srna, - const char *item_name, - const char *accessor_name) -{ - static blender::LinearAllocator<> allocator; - PropertyRNA *parm; - FunctionRNA *func; - - char name[128]; - SNPRINTF(name, "rna_Node_ItemArray_new_with_socket_and_name<%s>", accessor_name); - - func = RNA_def_function(srna, "new", allocator.copy_string(name).c_str()); - RNA_def_function_ui_description(func, "Add an item at the end"); + func = RNA_def_function(srna, "new", "rna_NodeGeometrySimulationOutput_items_new"); + RNA_def_function_ui_description(func, "Add a item to this simulation zone"); RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN | FUNC_USE_REPORTS); parm = RNA_def_enum(func, "socket_type", @@ -8916,44 +9001,28 @@ static void rna_def_node_item_array_new_with_socket_and_name(StructRNA *srna, parm = RNA_def_string(func, "name", nullptr, MAX_NAME, "Name", ""); RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED); /* return value */ - parm = RNA_def_pointer(func, "item", item_name, "Item", "New item"); + parm = RNA_def_pointer(func, "item", "SimulationStateItem", "Item", "New item"); RNA_def_function_return(func, parm); -} -static void rna_def_simulation_state_item(BlenderRNA *brna) -{ - PropertyRNA *prop; + func = RNA_def_function(srna, "remove", "rna_NodeGeometrySimulationOutput_items_remove"); + RNA_def_function_ui_description(func, "Remove an item from this simulation zone"); + RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN | FUNC_USE_REPORTS); + parm = RNA_def_pointer(func, "item", "SimulationStateItem", "Item", "The item to remove"); + RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED); - StructRNA *srna = RNA_def_struct(brna, "SimulationStateItem", nullptr); - RNA_def_struct_ui_text(srna, "Simulation Item", ""); - RNA_def_struct_sdna(srna, "NodeSimulationItem"); + func = RNA_def_function(srna, "clear", "rna_NodeGeometrySimulationOutput_items_clear"); + RNA_def_function_ui_description(func, "Remove all items from this simulation zone"); + RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN); - rna_def_node_item_array_socket_item_common(srna, "SimulationItemsAccessor"); - - 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_enum_funcs( - prop, nullptr, nullptr, "rna_GeometryNodeAttributeDomain_attribute_domain_itemf"); - RNA_def_property_ui_text( - prop, - "Attribute Domain", - "Attribute domain where the attribute is stored in the simulation state"); - RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); - RNA_def_property_update( - prop, NC_NODE | NA_EDITED, "rna_Node_ItemArray_item_update"); -} - -static void rna_def_geo_simulation_output_items(BlenderRNA *brna) -{ - StructRNA *srna; - - srna = RNA_def_struct(brna, "NodeGeometrySimulationOutputItems", nullptr); - RNA_def_struct_sdna(srna, "bNode"); - RNA_def_struct_ui_text(srna, "Items", "Collection of simulation items"); - - rna_def_node_item_array_new_with_socket_and_name( - srna, "SimulationStateItem", "SimulationItemsAccessor"); - rna_def_node_item_array_common_functions(srna, "SimulationStateItem", "SimulationItemsAccessor"); + func = RNA_def_function(srna, "move", "rna_NodeGeometrySimulationOutput_items_move"); + RNA_def_function_ui_description(func, "Move an item to another position"); + RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN); + parm = RNA_def_int( + func, "from_index", -1, 0, INT_MAX, "From Index", "Index of the item to move", 0, 10000); + RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED); + parm = RNA_def_int( + func, "to_index", -1, 0, INT_MAX, "To Index", "Target index for the item", 0, 10000); + RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED); } static void def_geo_simulation_output(StructRNA *srna) @@ -8978,8 +9047,8 @@ static void def_geo_simulation_output(StructRNA *srna) prop = RNA_def_property(srna, "active_item", PROP_POINTER, PROP_NONE); RNA_def_property_struct_type(prop, "SimulationStateItem"); RNA_def_property_pointer_funcs(prop, - "rna_Node_ItemArray_active_get", - "rna_Node_ItemArray_active_set", + "rna_NodeGeometrySimulationOutput_active_item_get", + "rna_NodeGeometrySimulationOutput_active_item_set", nullptr, nullptr); RNA_def_property_flag(prop, PROP_EDITABLE | PROP_NO_DEG_UPDATE); @@ -8989,23 +9058,78 @@ static void def_geo_simulation_output(StructRNA *srna) static void rna_def_repeat_item(BlenderRNA *brna) { + PropertyRNA *prop; + StructRNA *srna = RNA_def_struct(brna, "RepeatItem", nullptr); RNA_def_struct_ui_text(srna, "Repeat Item", ""); RNA_def_struct_sdna(srna, "NodeRepeatItem"); - rna_def_node_item_array_socket_item_common(srna, "RepeatItemsAccessor"); + prop = RNA_def_property(srna, "name", PROP_STRING, PROP_NONE); + RNA_def_property_string_funcs(prop, nullptr, nullptr, "rna_RepeatItem_name_set"); + RNA_def_property_ui_text(prop, "Name", ""); + RNA_def_struct_name_property(srna, prop); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_RepeatItem_update"); + + prop = RNA_def_property(srna, "socket_type", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, rna_enum_node_socket_data_type_items); + RNA_def_property_enum_funcs(prop, nullptr, nullptr, "rna_RepeatItem_socket_type_itemf"); + RNA_def_property_ui_text(prop, "Socket Type", ""); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_RepeatItem_update"); + + prop = RNA_def_property(srna, "color", PROP_FLOAT, PROP_COLOR_GAMMA); + RNA_def_property_array(prop, 4); + RNA_def_property_float_funcs(prop, "rna_RepeatItem_color_get", nullptr, nullptr); + RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_ui_text( + prop, "Color", "Color of the corresponding socket type in the node editor"); } static void rna_def_geo_repeat_output_items(BlenderRNA *brna) { StructRNA *srna; + PropertyRNA *parm; + FunctionRNA *func; srna = RNA_def_struct(brna, "NodeGeometryRepeatOutputItems", nullptr); RNA_def_struct_sdna(srna, "bNode"); RNA_def_struct_ui_text(srna, "Items", "Collection of repeat items"); - rna_def_node_item_array_new_with_socket_and_name(srna, "RepeatItem", "RepeatItemsAccessor"); - rna_def_node_item_array_common_functions(srna, "RepeatItem", "RepeatItemsAccessor"); + func = RNA_def_function(srna, "new", "rna_NodeGeometryRepeatOutput_items_new"); + RNA_def_function_ui_description(func, "Add a item to this repeat zone"); + RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN | FUNC_USE_REPORTS); + parm = RNA_def_enum(func, + "socket_type", + rna_enum_node_socket_data_type_items, + SOCK_GEOMETRY, + "Socket Type", + "Socket type of the item"); + RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED); + parm = RNA_def_string(func, "name", nullptr, MAX_NAME, "Name", ""); + RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED); + /* return value */ + parm = RNA_def_pointer(func, "item", "RepeatItem", "Item", "New item"); + RNA_def_function_return(func, parm); + + func = RNA_def_function(srna, "remove", "rna_NodeGeometryRepeatOutput_items_remove"); + RNA_def_function_ui_description(func, "Remove an item from this repeat zone"); + RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN | FUNC_USE_REPORTS); + parm = RNA_def_pointer(func, "item", "RepeatItem", "Item", "The item to remove"); + RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED); + + func = RNA_def_function(srna, "clear", "rna_NodeGeometryRepeatOutput_items_clear"); + RNA_def_function_ui_description(func, "Remove all items from this repeat zone"); + RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN); + + func = RNA_def_function(srna, "move", "rna_NodeGeometryRepeatOutput_items_move"); + RNA_def_function_ui_description(func, "Move an item to another position"); + RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_MAIN); + parm = RNA_def_int( + func, "from_index", -1, 0, INT_MAX, "From Index", "Index of the item to move", 0, 10000); + RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED); + parm = RNA_def_int( + func, "to_index", -1, 0, INT_MAX, "To Index", "Target index for the item", 0, 10000); + RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED); } static void def_geo_repeat_output(StructRNA *srna) @@ -9030,8 +9154,8 @@ static void def_geo_repeat_output(StructRNA *srna) prop = RNA_def_property(srna, "active_item", PROP_POINTER, PROP_NONE); RNA_def_property_struct_type(prop, "RepeatItem"); RNA_def_property_pointer_funcs(prop, - "rna_Node_ItemArray_active_get", - "rna_Node_ItemArray_active_set", + "rna_NodeGeometryRepeatOutput_active_item_get", + "rna_NodeGeometryRepeatOutput_active_item_set", nullptr, nullptr); RNA_def_property_flag(prop, PROP_EDITABLE | PROP_NO_DEG_UPDATE); @@ -9127,8 +9251,6 @@ static void def_geo_sample_index(StructRNA *srna) prop = RNA_def_property(srna, "domain", PROP_ENUM, PROP_NONE); RNA_def_property_enum_items(prop, rna_enum_attribute_domain_items); - RNA_def_property_enum_funcs( - prop, nullptr, nullptr, "rna_GeometryNodeAttributeDomain_attribute_domain_itemf"); RNA_def_property_enum_default(prop, ATTR_DOMAIN_POINT); RNA_def_property_ui_text(prop, "Domain", ""); RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); diff --git a/source/blender/makesrna/intern/rna_object.cc b/source/blender/makesrna/intern/rna_object.cc index 5461f73f3c1..fcc2bae398d 100644 --- a/source/blender/makesrna/intern/rna_object.cc +++ b/source/blender/makesrna/intern/rna_object.cc @@ -233,9 +233,9 @@ const EnumPropertyItem rna_enum_metaelem_type_items[] = { }; const EnumPropertyItem rna_enum_lightprobes_type_items[] = { - {LIGHTPROBE_TYPE_SPHERE, "SPHERE", ICON_LIGHTPROBE_SPHERE, "Sphere", ""}, - {LIGHTPROBE_TYPE_PLANE, "PLANE", ICON_LIGHTPROBE_PLANE, "Plane", ""}, - {LIGHTPROBE_TYPE_VOLUME, "VOLUME", ICON_LIGHTPROBE_VOLUME, "Volume", ""}, + {LIGHTPROBE_TYPE_CUBE, "CUBE", ICON_LIGHTPROBE_CUBEMAP, "Cube", ""}, + {LIGHTPROBE_TYPE_PLANAR, "PLANAR", ICON_LIGHTPROBE_PLANAR, "Planar", ""}, + {LIGHTPROBE_TYPE_GRID, "GRID", ICON_LIGHTPROBE_GRID, "Grid", ""}, {0, nullptr, 0, nullptr, nullptr}, }; @@ -1383,7 +1383,7 @@ static bool rna_MaterialSlot_material_poll(PointerRNA *ptr, PointerRNA value) Object *ob = reinterpret_cast(ptr->owner_id); Material *ma = static_cast(value.data); - if (ELEM(ob->type, OB_GPENCIL_LEGACY, OB_GREASE_PENCIL)) { + if (ob->type == OB_GPENCIL_LEGACY) { /* GP Materials only */ return (ma->gp_style != nullptr); } @@ -1990,7 +1990,8 @@ static void rna_Object_shaderfx_clear(Object *object, bContext *C) static void rna_Object_boundbox_get(PointerRNA *ptr, float *values) { Object *ob = reinterpret_cast(ptr->owner_id); - if (const std::optional bb = BKE_object_boundbox_get(ob)) { + const BoundBox *bb = BKE_object_boundbox_get(ob); + if (bb) { memcpy(values, bb->vec, sizeof(bb->vec)); } else { @@ -2920,11 +2921,6 @@ static void rna_def_object_visibility(StructRNA *srna) prop, "Disable in Cubemap Probes", "Globally disable in cubemap probes"); RNA_def_property_update(prop, NC_OBJECT | ND_DRAW, "rna_Object_internal_update_draw"); - prop = RNA_def_property(srna, "hide_probe_planar", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, nullptr, "visibility_flag", OB_HIDE_PROBE_PLANAR); - RNA_def_property_ui_text(prop, "Disable in Planar Probes", "Globally disable in planar probes"); - RNA_def_property_update(prop, NC_OBJECT | ND_DRAW, "rna_Object_internal_update_draw"); - /* Instancer options. */ prop = RNA_def_property(srna, "show_instancer_for_render", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, nullptr, "duplicator_visibility_flag", OB_DUPLI_FLAG_RENDER); diff --git a/source/blender/makesrna/intern/rna_object_api.cc b/source/blender/makesrna/intern/rna_object_api.cc index eefbadbab2d..73a8171fab5 100644 --- a/source/blender/makesrna/intern/rna_object_api.cc +++ b/source/blender/makesrna/intern/rna_object_api.cc @@ -608,7 +608,7 @@ static void rna_Object_ray_cast(Object *ob, } /* Test BoundBox first (efficiency) */ - const std::optional bb = BKE_object_boundbox_get(ob); + const BoundBox *bb = BKE_object_boundbox_get(ob); float distmin; /* Needed for valid distance check from #isect_ray_aabb_v3_simple() call. */ diff --git a/source/blender/makesrna/intern/rna_particle.cc b/source/blender/makesrna/intern/rna_particle.cc index 0aa3b84e739..9f5881f07b4 100644 --- a/source/blender/makesrna/intern/rna_particle.cc +++ b/source/blender/makesrna/intern/rna_particle.cc @@ -136,7 +136,7 @@ static const EnumPropertyItem part_fluid_type_items[] = { #ifdef RNA_RUNTIME -# include "BLI_string_utils.hh" +# include "BLI_string_utils.h" # include "BKE_boids.h" # include "BKE_cloth.hh" diff --git a/source/blender/makesrna/intern/rna_path.cc b/source/blender/makesrna/intern/rna_path.cc index eefda8e3c5c..4b3da0764c1 100644 --- a/source/blender/makesrna/intern/rna_path.cc +++ b/source/blender/makesrna/intern/rna_path.cc @@ -1098,13 +1098,7 @@ static char *rna_path_from_ptr_to_property_index_ex( } else { if (is_rna) { - if (index_dim == 0) { - /* Use direct duplication instead of #BLI_sprintfN because it's faster. */ - path = BLI_strdup(propname); - } - else { - path = BLI_sprintfN("%s%s", propname, index_str); - } + path = BLI_sprintfN("%s%s", propname, index_str); } else { char propname_esc[MAX_IDPROP_NAME * 2]; diff --git a/source/blender/makesrna/intern/rna_pose.cc b/source/blender/makesrna/intern/rna_pose.cc index 2bb6bb49a82..1716da4c2ab 100644 --- a/source/blender/makesrna/intern/rna_pose.cc +++ b/source/blender/makesrna/intern/rna_pose.cc @@ -60,7 +60,7 @@ const EnumPropertyItem rna_enum_color_sets_items[] = { #ifdef RNA_RUNTIME # include "BLI_ghash.h" -# include "BLI_string_utils.hh" +# include "BLI_string_utils.h" # include "BIK_api.h" # include "BKE_action.h" diff --git a/source/blender/makesrna/intern/rna_scene.cc b/source/blender/makesrna/intern/rna_scene.cc index 1f0e9474386..7f3b7eb5a80 100644 --- a/source/blender/makesrna/intern/rna_scene.cc +++ b/source/blender/makesrna/intern/rna_scene.cc @@ -705,7 +705,7 @@ const EnumPropertyItem rna_enum_grease_pencil_selectmode_items[] = { #ifdef RNA_RUNTIME -# include "BLI_string_utils.hh" +# include "BLI_string_utils.h" # include "DNA_anim_types.h" # include "DNA_cachefile_types.h" @@ -741,7 +741,7 @@ const EnumPropertyItem rna_enum_grease_pencil_selectmode_items[] = { # include "BKE_screen.hh" # include "BKE_unit.h" -# include "NOD_composite.hh" +# include "NOD_composite.h" # include "ED_image.hh" # include "ED_info.hh" @@ -7785,15 +7785,6 @@ static void rna_def_scene_eevee(BlenderRNA *brna) RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, nullptr); - prop = RNA_def_property(srna, "volumetric_ray_depth", PROP_INT, PROP_NONE); - RNA_def_property_ui_text(prop, - "Volume Max Ray Depth", - "Maximum surface intersection count used by the accurate volume " - "intersection method. Will create artifact if it is exceeded"); - RNA_def_property_range(prop, 1, 16); - RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); - RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, nullptr); - prop = RNA_def_property(srna, "use_volumetric_lights", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, nullptr, "flag", SCE_EEVEE_VOLUMETRIC_LIGHTS); RNA_def_property_ui_text( @@ -8074,7 +8065,7 @@ static void rna_def_scene_eevee(BlenderRNA *brna) prop = RNA_def_property(srna, "shadow_normal_bias", PROP_FLOAT, PROP_FACTOR); RNA_def_property_range(prop, 0.0f, FLT_MAX); RNA_def_property_ui_range(prop, 0.001f, 0.1f, 0.001, 3); - RNA_def_property_ui_text(prop, "Shadow Normal Bias", "Move along their normal"); + RNA_def_property_ui_text(prop, "Shadow Normal Bias", "Move along their normal"); RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, nullptr); diff --git a/source/blender/makesrna/intern/rna_sequencer.cc b/source/blender/makesrna/intern/rna_sequencer.cc index 7f2bca241a5..e4030e1ac68 100644 --- a/source/blender/makesrna/intern/rna_sequencer.cc +++ b/source/blender/makesrna/intern/rna_sequencer.cc @@ -20,7 +20,7 @@ #include "BLI_listbase.h" #include "BLI_math_rotation.h" #include "BLI_string_utf8_symbols.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLT_translation.h" @@ -2449,7 +2449,7 @@ static void rna_def_editor(BlenderRNA *brna) prop = RNA_def_property(srna, "selected_retiming_keys", PROP_BOOLEAN, PROP_NONE); RNA_def_property_ui_text(prop, "Retiming Key Selection Status", ""); - RNA_def_property_boolean_funcs(prop, "rna_SequenceEditor_selected_retiming_key_get", nullptr); + RNA_def_property_boolean_funcs(prop, "rna_SequenceEditor_selected_retiming_key_get", NULL); RNA_def_property_clear_flag(prop, PROP_EDITABLE); prop = RNA_def_property(srna, "show_overlay_frame", PROP_BOOLEAN, PROP_NONE); diff --git a/source/blender/makesrna/intern/rna_space.cc b/source/blender/makesrna/intern/rna_space.cc index df41b98f906..cb22f9bcbfc 100644 --- a/source/blender/makesrna/intern/rna_space.cc +++ b/source/blender/makesrna/intern/rna_space.cc @@ -85,11 +85,6 @@ const EnumPropertyItem rna_enum_geometry_component_type_items[] = { ICON_EMPTY_AXIS, "Instances", "Instances of objects or collections"}, - {int(blender::bke::GeometryComponent::Type::GreasePencil), - "GREASEPENCIL", - ICON_GREASEPENCIL, - "Grease Pencil", - "Grease Pencil component containing layers and curves data"}, {0, nullptr, 0, nullptr, nullptr}, }; @@ -3322,9 +3317,6 @@ const EnumPropertyItem *rna_SpaceSpreadsheet_attribute_domain_itemf(bContext * / continue; } } - if (!U.experimental.use_grease_pencil_version3 && item->value == ATTR_DOMAIN_LAYER) { - continue; - } if (item->value == ATTR_DOMAIN_POINT && component_type == blender::bke::GeometryComponent::Type::Mesh) { @@ -5470,7 +5462,7 @@ static void rna_def_space_properties(BlenderRNA *brna) prop, "Tab Search Results", "Whether or not each visible tab has a search result"); prop = RNA_def_property(srna, "search_filter", PROP_STRING, PROP_NONE); - /* The search filter is stored in the property editor's runtime which + /* The search filter is stored in the property editor's runtime which * is only defined in an internal header, so use the getter / setter here. */ RNA_def_property_string_funcs(prop, "rna_SpaceProperties_search_filter_get", diff --git a/source/blender/makesrna/intern/rna_ui_api.cc b/source/blender/makesrna/intern/rna_ui_api.cc index b04e1ba5c3b..c0ace2e8fbb 100644 --- a/source/blender/makesrna/intern/rna_ui_api.cc +++ b/source/blender/makesrna/intern/rna_ui_api.cc @@ -84,7 +84,6 @@ static void rna_uiItemR(uiLayout *layout, const char *text_ctxt, bool translate, int icon, - const char *placeholder, bool expand, bool slider, int toggle, @@ -110,7 +109,6 @@ static void rna_uiItemR(uiLayout *layout, /* Get translated name (label). */ name = rna_translate_ui_text(name, text_ctxt, nullptr, prop, translate); - placeholder = rna_translate_ui_text(placeholder, text_ctxt, nullptr, prop, translate); if (slider) { flag |= UI_ITEM_R_SLIDER; @@ -142,7 +140,7 @@ static void rna_uiItemR(uiLayout *layout, flag |= UI_ITEM_R_CHECKBOX_INVERT; } - uiItemFullR(layout, ptr, prop, index, 0, flag, name, icon, placeholder); + uiItemFullR(layout, ptr, prop, index, 0, flag, name, icon); } static void rna_uiItemR_with_popover(uiLayout *layout, @@ -1149,8 +1147,6 @@ void RNA_api_ui_layout(StructRNA *srna) RNA_def_function_ui_description(func, "Item. Exposes an RNA item and places it into the layout"); api_ui_item_rna_common(func); api_ui_item_common(func); - RNA_def_string( - func, "placeholder", nullptr, 0, "", "Hint describing the expected value when empty"); RNA_def_boolean(func, "expand", false, "", "Expand button to show more detail"); RNA_def_boolean(func, "slider", false, "", "Use slider widget for numeric values"); RNA_def_int(func, diff --git a/source/blender/makesrna/intern/rna_userdef.cc b/source/blender/makesrna/intern/rna_userdef.cc index 7f6e35a4e29..73bf041c43b 100644 --- a/source/blender/makesrna/intern/rna_userdef.cc +++ b/source/blender/makesrna/intern/rna_userdef.cc @@ -56,6 +56,7 @@ const EnumPropertyItem rna_enum_preference_section_items[] = { {USER_SECTION_EDITING, "EDITING", 0, "Editing", ""}, {USER_SECTION_ANIMATION, "ANIMATION", 0, "Animation", ""}, RNA_ENUM_ITEM_SEPR, + {USER_SECTION_EXTENSIONS, "EXTENSIONS", 0, "Extensions", ""}, {USER_SECTION_ADDONS, "ADDONS", 0, "Add-ons", ""}, #if 0 /* def WITH_USERDEF_WORKSPACES */ RNA_ENUM_ITEM_SEPR, @@ -71,7 +72,6 @@ const EnumPropertyItem rna_enum_preference_section_items[] = { {USER_SECTION_SYSTEM, "SYSTEM", 0, "System", ""}, {USER_SECTION_SAVE_LOAD, "SAVE_LOAD", 0, "Save & Load", ""}, {USER_SECTION_FILE_PATHS, "FILE_PATHS", 0, "File Paths", ""}, - {USER_SECTION_EXTENSIONS, "EXTENSIONS", 0, "Extensions", ""}, RNA_ENUM_ITEM_SEPR, {USER_SECTION_EXPERIMENTAL, "EXPERIMENTAL", 0, "Experimental", ""}, {0, nullptr, 0, nullptr, nullptr}, @@ -159,7 +159,7 @@ static const EnumPropertyItem rna_enum_preference_gpu_backend_items[] = { #ifdef RNA_RUNTIME # include "BLI_math_vector.h" -# include "BLI_string_utils.hh" +# include "BLI_string_utils.h" # include "DNA_object_types.h" # include "DNA_screen_types.h" @@ -1294,14 +1294,6 @@ static void rna_def_userdef_theme_ui_font_style(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Points", "Font size in points"); RNA_def_property_update(prop, 0, "rna_userdef_gpu_update"); - prop = RNA_def_property(srna, "character_weight", PROP_INT, PROP_NONE); - RNA_def_property_int_default(prop, 400); - RNA_def_property_range(prop, 100.0f, 900.0f); - RNA_def_property_ui_range(prop, 100.0f, 900.0f, 50, 0); - RNA_def_property_ui_text( - prop, "Character Weight", "Weight of the characters. 100-900, 400 is normal"); - RNA_def_property_update(prop, 0, "rna_userdef_text_update"); - prop = RNA_def_property(srna, "shadow", PROP_INT, PROP_PIXEL); RNA_def_property_range(prop, 0, 5); RNA_def_property_ui_text(prop, "Shadow Size", "Shadow size (0, 3 and 5 supported)"); @@ -7052,6 +7044,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, "enable_eevee_next", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, nullptr, "enable_eevee_next", 1); + RNA_def_property_ui_text(prop, "EEVEE Next", "Enable the new EEVEE codebase, requires restart"); + prop = RNA_def_property(srna, "use_grease_pencil_version3", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, nullptr, "use_grease_pencil_version3", 1); RNA_def_property_ui_text(prop, "Grease Pencil 3.0", "Enable the new grease pencil 3.0 codebase"); diff --git a/source/blender/makesrna/intern/rna_wm.cc b/source/blender/makesrna/intern/rna_wm.cc index f890379382a..14aa836107a 100644 --- a/source/blender/makesrna/intern/rna_wm.cc +++ b/source/blender/makesrna/intern/rna_wm.cc @@ -543,7 +543,7 @@ const EnumPropertyItem rna_enum_wm_report_items[] = { #ifdef RNA_RUNTIME -# include "BLI_string_utils.hh" +# include "BLI_string_utils.h" # include "WM_api.hh" diff --git a/source/blender/makesrna/intern/rna_wm_gizmo.cc b/source/blender/makesrna/intern/rna_wm_gizmo.cc index 18c61fa8519..aebee38ca7c 100644 --- a/source/blender/makesrna/intern/rna_wm_gizmo.cc +++ b/source/blender/makesrna/intern/rna_wm_gizmo.cc @@ -33,7 +33,7 @@ #ifdef RNA_RUNTIME -# include "BLI_string_utils.hh" +# include "BLI_string_utils.h" # include "WM_api.hh" diff --git a/source/blender/modifiers/intern/MOD_bevel.cc b/source/blender/modifiers/intern/MOD_bevel.cc index 624fd7b79d0..a777c10b44e 100644 --- a/source/blender/modifiers/intern/MOD_bevel.cc +++ b/source/blender/modifiers/intern/MOD_bevel.cc @@ -193,6 +193,13 @@ static Mesh *modify_mesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh } } + Object *ob = ctx->object; + + if (harden_normals && (ob->type == OB_MESH) && !(((Mesh *)ob->data)->flag & ME_AUTOSMOOTH)) { + BKE_modifier_set_error(ob, md, "Enable 'Auto Smooth' in Object Data Properties"); + harden_normals = false; + } + BM_mesh_bevel(bm, value, offset_type, @@ -213,6 +220,7 @@ static Mesh *modify_mesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh miter_outer, miter_inner, spread, + mesh->smoothresh, bmd->custom_profile, bmd->vmesh_method); diff --git a/source/blender/modifiers/intern/MOD_datatransfer.cc b/source/blender/modifiers/intern/MOD_datatransfer.cc index 269af31f03a..ee331b821c5 100644 --- a/source/blender/modifiers/intern/MOD_datatransfer.cc +++ b/source/blender/modifiers/intern/MOD_datatransfer.cc @@ -117,6 +117,8 @@ static void update_depsgraph(ModifierData *md, const ModifierUpdateDepsgraphCont if (dtmd->ob_source != nullptr) { CustomData_MeshMasks cddata_masks = {0}; BKE_object_data_transfer_dttypes_to_cdmask(dtmd->data_types, &cddata_masks); + BKE_mesh_remap_calc_source_cddata_masks_from_map_modes( + dtmd->vmap_mode, dtmd->emap_mode, dtmd->lmap_mode, dtmd->pmap_mode, &cddata_masks); DEG_add_object_relation( ctx->node, dtmd->ob_source, DEG_OB_COMP_GEOMETRY, "DataTransfer Modifier"); @@ -220,8 +222,10 @@ static Mesh *modify_mesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh BKE_modifier_set_error(ctx->object, md, "%s", report_str); MEM_freeN((void *)report_str); } - - BKE_reports_free(&reports); + else if ((dtmd->data_types & DT_TYPE_LNOR) && !(me->flag & ME_AUTOSMOOTH)) { + BKE_modifier_set_error( + ctx->object, (ModifierData *)dtmd, "Enable 'Auto Smooth' in Object Data Properties"); + } return result; } diff --git a/source/blender/modifiers/intern/MOD_displace.cc b/source/blender/modifiers/intern/MOD_displace.cc index 23f34695547..def32e3bb30 100644 --- a/source/blender/modifiers/intern/MOD_displace.cc +++ b/source/blender/modifiers/intern/MOD_displace.cc @@ -302,15 +302,22 @@ static void displaceModifier_do(DisplaceModifierData *dmd, } if (direction == MOD_DISP_DIR_CLNOR) { - if (CustomData_has_layer(&mesh->loop_data, CD_CUSTOMLOOPNORMAL)) { + CustomData *ldata = &mesh->loop_data; + + if (CustomData_has_layer(ldata, CD_CUSTOMLOOPNORMAL)) { + if (!CustomData_has_layer(ldata, CD_NORMAL)) { + BKE_mesh_calc_normals_split(mesh); + } + + float(*clnors)[3] = static_cast( + CustomData_get_layer_for_write(ldata, CD_NORMAL, mesh->totloop)); vert_clnors = static_cast( MEM_malloc_arrayN(verts_num, sizeof(*vert_clnors), __func__)); - BKE_mesh_normals_loop_to_vertex( - verts_num, - mesh->corner_verts().data(), - mesh->totloop, - reinterpret_cast(mesh->corner_normals().data()), - vert_clnors); + BKE_mesh_normals_loop_to_vertex(verts_num, + mesh->corner_verts().data(), + mesh->totloop, + (const float(*)[3])clnors, + vert_clnors); } else { direction = MOD_DISP_DIR_NOR; @@ -407,7 +414,7 @@ static void panel_draw(const bContext *C, Panel *panel) } } else if (texture_coords == MOD_DISP_MAP_UV && RNA_enum_get(&ob_ptr, "type") == OB_MESH) { - uiItemPointerR(col, ptr, "uv_layer", &obj_data_ptr, "uv_layers", nullptr, ICON_GROUP_UVS); + uiItemPointerR(col, ptr, "uv_layer", &obj_data_ptr, "uv_layers", nullptr, ICON_NONE); } uiItemS(layout); diff --git a/source/blender/modifiers/intern/MOD_explode.cc b/source/blender/modifiers/intern/MOD_explode.cc index 18a5fe8cbd6..ff391f3e09c 100644 --- a/source/blender/modifiers/intern/MOD_explode.cc +++ b/source/blender/modifiers/intern/MOD_explode.cc @@ -1179,7 +1179,7 @@ static void panel_draw(const bContext * /*C*/, Panel *panel) uiLayoutSetPropSep(layout, true); - uiItemPointerR(layout, ptr, "particle_uv", &obj_data_ptr, "uv_layers", nullptr, ICON_GROUP_UVS); + uiItemPointerR(layout, ptr, "particle_uv", &obj_data_ptr, "uv_layers", nullptr, ICON_NONE); row = uiLayoutRowWithHeading(layout, true, IFACE_("Show")); uiItemR(row, ptr, "show_alive", toggles_flag, nullptr, ICON_NONE); diff --git a/source/blender/modifiers/intern/MOD_multires.cc b/source/blender/modifiers/intern/MOD_multires.cc index 1280c34abfc..88cb63f1b12 100644 --- a/source/blender/modifiers/intern/MOD_multires.cc +++ b/source/blender/modifiers/intern/MOD_multires.cc @@ -67,6 +67,7 @@ static void required_data_mask(ModifierData *md, CustomData_MeshMasks *r_cddata_ { MultiresModifierData *mmd = (MultiresModifierData *)md; if (mmd->flags & eMultiresModifierFlag_UseCustomNormals) { + r_cddata_masks->lmask |= CD_MASK_NORMAL; r_cddata_masks->lmask |= CD_MASK_CUSTOMLOOPNORMAL; } } @@ -216,7 +217,8 @@ static Mesh *modify_mesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh return result; } const bool use_clnors = mmd->flags & eMultiresModifierFlag_UseCustomNormals && - mesh->normals_domain() == blender::bke::MeshNormalDomain::Corner; + mesh->flag & ME_AUTOSMOOTH && + CustomData_has_layer(&mesh->loop_data, CD_CUSTOMLOOPNORMAL); /* NOTE: Orco needs final coordinates on CPU side, which are expected to be * accessible via mesh vertices. For this reason we do not evaluate multires to * grids when orco is requested. */ @@ -251,8 +253,10 @@ static Mesh *modify_mesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh } else { if (use_clnors) { - void *data = CustomData_add_layer(&mesh->loop_data, CD_NORMAL, CD_CONSTRUCT, mesh->totloop); - memcpy(data, mesh->corner_normals().data(), mesh->corner_normals().size_in_bytes()); + /* If custom normals are present and the option is turned on calculate the split + * normals and clear flag so the normals get interpolated to the result mesh. */ + BKE_mesh_calc_normals_split(mesh); + CustomData_clear_layer_flag(&mesh->loop_data, CD_NORMAL, CD_FLAG_TEMPORARY); } result = multires_as_mesh(mmd, ctx, mesh, subdiv); @@ -260,8 +264,10 @@ static Mesh *modify_mesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh if (use_clnors) { float(*lnors)[3] = static_cast( CustomData_get_layer_for_write(&result->loop_data, CD_NORMAL, result->totloop)); + BLI_assert(lnors != nullptr); BKE_mesh_set_custom_normals(result, lnors); - CustomData_free_layers(&result->loop_data, CD_NORMAL, result->totloop); + CustomData_set_layer_flag(&mesh->loop_data, CD_NORMAL, CD_FLAG_TEMPORARY); + CustomData_set_layer_flag(&result->loop_data, CD_NORMAL, CD_FLAG_TEMPORARY); } // BKE_subdiv_stats_print(&subdiv->stats); if (subdiv != runtime_data->subdiv) { diff --git a/source/blender/modifiers/intern/MOD_nodes.cc b/source/blender/modifiers/intern/MOD_nodes.cc index 842059f6120..28403304dc0 100644 --- a/source/blender/modifiers/intern/MOD_nodes.cc +++ b/source/blender/modifiers/intern/MOD_nodes.cc @@ -517,155 +517,6 @@ const NodesModifierBake *NodesModifierData::find_bake(const int id) const namespace blender { -/** - * Setup side effects nodes so that the given node in the given compute context will be executed. - * To make sure that it is executed, all parent group nodes and zones have to be set to have side - * effects as well. - */ -static void try_add_side_effect_node(const ComputeContext &final_compute_context, - const int final_node_id, - const NodesModifierData &nmd, - nodes::GeoNodesSideEffectNodes &r_side_effect_nodes) -{ - if (nmd.node_group == nullptr) { - return; - } - - Vector compute_context_vec; - for (const ComputeContext *c = &final_compute_context; c; c = c->parent()) { - compute_context_vec.append(c); - } - std::reverse(compute_context_vec.begin(), compute_context_vec.end()); - - const auto *modifier_compute_context = dynamic_cast( - compute_context_vec[0]); - if (modifier_compute_context == nullptr) { - return; - } - if (modifier_compute_context->modifier_name() != nmd.modifier.name) { - return; - } - - const bNodeTree *current_tree = nmd.node_group; - const bke::bNodeTreeZone *current_zone = nullptr; - - /* Write side effect nodes to a new map and only if everything succeeds, move the nodes to the - * caller. This is easier than changing r_side_effect_nodes directly and then undoing changes in - * case of errors. */ - nodes::GeoNodesSideEffectNodes local_side_effect_nodes; - for (const ComputeContext *compute_context_generic : compute_context_vec.as_span().drop_front(1)) - { - const bke::bNodeTreeZones *current_zones = current_tree->zones(); - if (current_zones == nullptr) { - return; - } - const auto *lf_graph_info = nodes::ensure_geometry_nodes_lazy_function_graph(*current_tree); - if (lf_graph_info == nullptr) { - return; - } - const ComputeContextHash &parent_compute_context_hash = - compute_context_generic->parent()->hash(); - if (const auto *compute_context = dynamic_cast( - compute_context_generic)) - { - const bke::bNodeTreeZone *simulation_zone = current_zones->get_zone_by_node( - compute_context->output_node_id()); - if (simulation_zone == nullptr) { - return; - } - if (simulation_zone->parent_zone != current_zone) { - return; - } - const lf::FunctionNode *lf_zone_node = lf_graph_info->mapping.zone_node_map.lookup_default( - simulation_zone, nullptr); - if (lf_zone_node == nullptr) { - return; - } - local_side_effect_nodes.nodes_by_context.add(parent_compute_context_hash, lf_zone_node); - current_zone = simulation_zone; - } - else if (const auto *compute_context = dynamic_cast( - compute_context_generic)) - { - const bke::bNodeTreeZone *repeat_zone = current_zones->get_zone_by_node( - compute_context->output_node_id()); - if (repeat_zone == nullptr) { - return; - } - if (repeat_zone->parent_zone != current_zone) { - return; - } - const lf::FunctionNode *lf_zone_node = lf_graph_info->mapping.zone_node_map.lookup_default( - repeat_zone, nullptr); - if (lf_zone_node == nullptr) { - return; - } - local_side_effect_nodes.nodes_by_context.add(parent_compute_context_hash, lf_zone_node); - local_side_effect_nodes.iterations_by_repeat_zone.add( - {parent_compute_context_hash, compute_context->output_node_id()}, - compute_context->iteration()); - current_zone = repeat_zone; - } - else if (const auto *compute_context = dynamic_cast( - compute_context_generic)) - { - const bNode *group_node = current_tree->node_by_id(compute_context->node_id()); - if (group_node == nullptr) { - return; - } - if (group_node->id == nullptr) { - return; - } - if (group_node->is_muted()) { - return; - } - if (current_zone != current_zones->get_zone_by_node(group_node->identifier)) { - return; - } - const lf::FunctionNode *lf_group_node = lf_graph_info->mapping.group_node_map.lookup_default( - group_node, nullptr); - if (lf_group_node == nullptr) { - return; - } - local_side_effect_nodes.nodes_by_context.add(parent_compute_context_hash, lf_group_node); - current_tree = reinterpret_cast(group_node->id); - current_zone = nullptr; - } - else { - return; - } - } - const bNode *final_node = current_tree->node_by_id(final_node_id); - if (final_node == nullptr) { - return; - } - const auto *lf_graph_info = nodes::ensure_geometry_nodes_lazy_function_graph(*current_tree); - if (lf_graph_info == nullptr) { - return; - } - const bke::bNodeTreeZones *tree_zones = current_tree->zones(); - if (tree_zones == nullptr) { - return; - } - if (tree_zones->get_zone_by_node(final_node_id) != current_zone) { - return; - } - const lf::FunctionNode *lf_node = - lf_graph_info->mapping.possible_side_effect_node_map.lookup_default(final_node, nullptr); - if (lf_node == nullptr) { - return; - } - local_side_effect_nodes.nodes_by_context.add(final_compute_context.hash(), lf_node); - - /* Successfully found all side effect nodes for the viewer path. */ - for (const auto item : local_side_effect_nodes.nodes_by_context.items()) { - r_side_effect_nodes.nodes_by_context.add_multiple(item.key, item.value); - } - for (const auto item : local_side_effect_nodes.iterations_by_repeat_zone.items()) { - r_side_effect_nodes.iterations_by_repeat_zone.add_multiple(item.key, item.value); - } -} - static void find_side_effect_nodes_for_viewer_path( const ViewerPath &viewer_path, const NodesModifierData &nmd, @@ -687,15 +538,130 @@ static void find_side_effect_nodes_for_viewer_path( ComputeContextBuilder compute_context_builder; compute_context_builder.push(parsed_path->modifier_name); + /* Write side effect nodes to a new map and only if everything succeeds, move the nodes to the + * caller. This is easier than changing r_side_effect_nodes directly and then undoing changes in + * case of errors. */ + nodes::GeoNodesSideEffectNodes local_side_effect_nodes; + + const bNodeTree *group = nmd.node_group; + const bke::bNodeTreeZone *zone = nullptr; for (const ViewerPathElem *elem : parsed_path->node_path) { - if (!ed::viewer_path::add_compute_context_for_viewer_path_elem(*elem, compute_context_builder)) - { + const bke::bNodeTreeZones *tree_zones = group->zones(); + if (tree_zones == nullptr) { return; } + const auto *lf_graph_info = nodes::ensure_geometry_nodes_lazy_function_graph(*group); + if (lf_graph_info == nullptr) { + return; + } + switch (elem->type) { + case VIEWER_PATH_ELEM_TYPE_SIMULATION_ZONE: { + const auto &typed_elem = *reinterpret_cast(elem); + const bke::bNodeTreeZone *next_zone = tree_zones->get_zone_by_node( + typed_elem.sim_output_node_id); + if (next_zone == nullptr) { + return; + } + if (next_zone->parent_zone != zone) { + return; + } + const lf::FunctionNode *lf_zone_node = lf_graph_info->mapping.zone_node_map.lookup_default( + next_zone, nullptr); + if (lf_zone_node == nullptr) { + return; + } + local_side_effect_nodes.nodes_by_context.add(compute_context_builder.hash(), lf_zone_node); + compute_context_builder.push(*next_zone->output_node); + zone = next_zone; + break; + } + case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE: { + const auto &typed_elem = *reinterpret_cast(elem); + const bke::bNodeTreeZone *next_zone = tree_zones->get_zone_by_node( + typed_elem.repeat_output_node_id); + if (next_zone == nullptr) { + return; + } + if (next_zone->parent_zone != zone) { + return; + } + const lf::FunctionNode *lf_zone_node = lf_graph_info->mapping.zone_node_map.lookup_default( + next_zone, nullptr); + if (lf_zone_node == nullptr) { + return; + } + local_side_effect_nodes.nodes_by_context.add(compute_context_builder.hash(), lf_zone_node); + local_side_effect_nodes.iterations_by_repeat_zone.add( + {compute_context_builder.hash(), typed_elem.repeat_output_node_id}, + typed_elem.iteration); + compute_context_builder.push(*next_zone->output_node, + typed_elem.iteration); + zone = next_zone; + break; + } + case VIEWER_PATH_ELEM_TYPE_GROUP_NODE: { + const auto &typed_elem = *reinterpret_cast(elem); + const bNode *node = group->node_by_id(typed_elem.node_id); + if (node == nullptr) { + return; + } + if (node->id == nullptr) { + return; + } + if (node->is_muted()) { + return; + } + if (zone != tree_zones->get_zone_by_node(node->identifier)) { + return; + } + const lf::FunctionNode *lf_group_node = + lf_graph_info->mapping.group_node_map.lookup_default(node, nullptr); + if (lf_group_node == nullptr) { + return; + } + local_side_effect_nodes.nodes_by_context.add(compute_context_builder.hash(), + lf_group_node); + compute_context_builder.push(*node); + group = reinterpret_cast(node->id); + zone = nullptr; + break; + } + default: { + BLI_assert_unreachable(); + return; + } + } } - try_add_side_effect_node( - *compute_context_builder.current(), parsed_path->viewer_node_id, nmd, r_side_effect_nodes); + const bNode *found_viewer_node = group->node_by_id(parsed_path->viewer_node_id); + if (found_viewer_node == nullptr) { + return; + } + const auto *lf_graph_info = nodes::ensure_geometry_nodes_lazy_function_graph(*group); + if (lf_graph_info == nullptr) { + return; + } + const bke::bNodeTreeZones *tree_zones = group->zones(); + if (tree_zones == nullptr) { + return; + } + if (tree_zones->get_zone_by_node(found_viewer_node->identifier) != zone) { + return; + } + const lf::FunctionNode *lf_viewer_node = lf_graph_info->mapping.viewer_node_map.lookup_default( + found_viewer_node, nullptr); + if (lf_viewer_node == nullptr) { + return; + } + local_side_effect_nodes.nodes_by_context.add(compute_context_builder.hash(), lf_viewer_node); + + /* Successfully found all side effect nodes for the viewer path. */ + for (const auto item : local_side_effect_nodes.nodes_by_context.items()) { + r_side_effect_nodes.nodes_by_context.add_multiple(item.key, item.value); + } + for (const auto item : local_side_effect_nodes.iterations_by_repeat_zone.items()) { + r_side_effect_nodes.iterations_by_repeat_zone.add_multiple(item.key, item.value); + } } static void find_side_effect_nodes(const NodesModifierData &nmd, @@ -1621,13 +1587,6 @@ static void draw_property_for_socket(const bContext &C, uiItemPointerR(row, md_ptr, rna_path, bmain_ptr, "images", name, ICON_IMAGE); break; } - case SOCK_BOOLEAN: { - if (is_layer_selection_field(socket)) { - uiItemR(row, md_ptr, rna_path, UI_ITEM_NONE, name, ICON_NONE); - break; - } - ATTR_FALLTHROUGH; - } default: { if (nodes::input_has_attribute_toggle(*nmd->node_group, socket_index)) { add_attribute_search_or_value_buttons(C, row, *nmd, md_ptr, socket); @@ -1696,8 +1655,7 @@ static void panel_draw(const bContext *C, Panel *panel) for (const int socket_index : nmd->node_group->interface_inputs().index_range()) { const bNodeTreeInterfaceSocket *socket = nmd->node_group->interface_inputs()[socket_index]; - if (is_layer_selection_field(*socket) || - !(socket->flag & NODE_INTERFACE_SOCKET_HIDE_IN_MODIFIER)) { + if (!(socket->flag & NODE_INTERFACE_SOCKET_HIDE_IN_MODIFIER)) { draw_property_for_socket(*C, layout, nmd, &bmain_ptr, ptr, *socket, socket_index); } } @@ -1981,10 +1939,10 @@ ModifierTypeInfo modifierType_Nodes = { /*srna*/ &RNA_NodesModifier, /*type*/ eModifierTypeType_Constructive, /*flags*/ - static_cast( - eModifierTypeFlag_AcceptsMesh | eModifierTypeFlag_AcceptsCVs | - eModifierTypeFlag_SupportsEditmode | eModifierTypeFlag_EnableInEditmode | - eModifierTypeFlag_SupportsMapping | eModifierTypeFlag_AcceptsGreasePencil), + static_cast(eModifierTypeFlag_AcceptsMesh | eModifierTypeFlag_AcceptsCVs | + eModifierTypeFlag_SupportsEditmode | + eModifierTypeFlag_EnableInEditmode | + eModifierTypeFlag_SupportsMapping), /*icon*/ ICON_GEOMETRY_NODES, /*copy_data*/ blender::copy_data, diff --git a/source/blender/modifiers/intern/MOD_normal_edit.cc b/source/blender/modifiers/intern/MOD_normal_edit.cc index bd996c3d250..2960b35c7f1 100644 --- a/source/blender/modifiers/intern/MOD_normal_edit.cc +++ b/source/blender/modifiers/intern/MOD_normal_edit.cc @@ -477,6 +477,22 @@ static Mesh *normalEditModifier_do(NormalEditModifierData *enmd, return mesh; } + /* XXX TODO(Rohan Rathi): + * Once we fully switch to Mesh evaluation of modifiers, + * we can expect to get that flag from the COW copy. + * But for now, it is lost in the DM intermediate step, + * so we need to directly check orig object's data. */ +#if 0 + if (!(mesh->flag & ME_AUTOSMOOTH)) +#else + if (!(((Mesh *)ob->data)->flag & ME_AUTOSMOOTH)) +#endif + { + BKE_modifier_set_error( + ob, (ModifierData *)enmd, "Enable 'Auto Smooth' in Object Data Properties"); + return mesh; + } + Mesh *result; if (mesh->edges().data() == ((Mesh *)ob->data)->edges().data()) { /* We need to duplicate data here, otherwise setting custom normals @@ -524,6 +540,8 @@ static Mesh *normalEditModifier_do(NormalEditModifierData *enmd, sharp_edges.span.data(), sharp_faces, clnors, + true, + result->smoothresh, nullptr, loop_normals); } diff --git a/source/blender/modifiers/intern/MOD_shrinkwrap.cc b/source/blender/modifiers/intern/MOD_shrinkwrap.cc index 2a30b96843c..f6e39f4a6d4 100644 --- a/source/blender/modifiers/intern/MOD_shrinkwrap.cc +++ b/source/blender/modifiers/intern/MOD_shrinkwrap.cc @@ -108,7 +108,7 @@ static void update_depsgraph(ModifierData *md, const ModifierUpdateDepsgraphCont CustomData_MeshMasks mask = {0}; if (BKE_shrinkwrap_needs_normals(smd->shrinkType, smd->shrinkMode)) { - mask.lmask |= CD_MASK_CUSTOMLOOPNORMAL; + mask.lmask |= CD_MASK_NORMAL | CD_MASK_CUSTOMLOOPNORMAL; } if (smd->target != nullptr) { diff --git a/source/blender/modifiers/intern/MOD_simpledeform.cc b/source/blender/modifiers/intern/MOD_simpledeform.cc index 5a33b603418..134cb859413 100644 --- a/source/blender/modifiers/intern/MOD_simpledeform.cc +++ b/source/blender/modifiers/intern/MOD_simpledeform.cc @@ -80,10 +80,8 @@ BLI_INLINE void copy_v3_v3_unmap(float a[3], const float b[3], const uint map[3] a[map[2]] = b[2]; } -/** - * Clamps/Limits the given coordinate to: limits[0] <= co[axis] <= limits[1] - * The amount of clamp is saved on `dcut`. - */ +/* Clamps/Limits the given coordinate to: limits[0] <= co[axis] <= limits[1] + * The amount of clamp is saved on dcut */ static void axis_limit(const int axis, const float limits[2], float co[3], float dcut[3]) { float val = co[axis]; diff --git a/source/blender/modifiers/intern/MOD_subsurf.cc b/source/blender/modifiers/intern/MOD_subsurf.cc index 0e99ea09560..16b3212b082 100644 --- a/source/blender/modifiers/intern/MOD_subsurf.cc +++ b/source/blender/modifiers/intern/MOD_subsurf.cc @@ -66,6 +66,7 @@ static void required_data_mask(ModifierData *md, CustomData_MeshMasks *r_cddata_ { SubsurfModifierData *smd = (SubsurfModifierData *)md; if (smd->flags & eSubsurfModifierFlag_UseCustomNormals) { + r_cddata_masks->lmask |= CD_MASK_NORMAL; r_cddata_masks->lmask |= CD_MASK_CUSTOMLOOPNORMAL; } } @@ -205,6 +206,7 @@ static void subdiv_cache_mesh_wrapper_settings(const ModifierEvalContext *ctx, runtime_data->has_gpu_subdiv = true; runtime_data->resolution = mesh_settings.resolution; runtime_data->use_optimal_display = mesh_settings.use_optimal_display; + runtime_data->calc_loop_normals = false; /* Set at the end of modifier stack evaluation. */ runtime_data->use_loop_normals = (smd->flags & eSubsurfModifierFlag_UseCustomNormals); mesh->runtime->subsurf_runtime_data = runtime_data; @@ -258,8 +260,10 @@ static Mesh *modify_mesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh } const bool use_clnors = BKE_subsurf_modifier_use_custom_loop_normals(smd, mesh); if (use_clnors) { - void *data = CustomData_add_layer(&mesh->loop_data, CD_NORMAL, CD_CONSTRUCT, mesh->totloop); - memcpy(data, mesh->corner_normals().data(), mesh->corner_normals().size_in_bytes()); + /* If custom normals are present and the option is turned on calculate the split + * normals and clear flag so the normals get interpolated to the result mesh. */ + BKE_mesh_calc_normals_split(mesh); + CustomData_clear_layer_flag(&mesh->loop_data, CD_NORMAL, CD_FLAG_TEMPORARY); } /* TODO(sergey): Decide whether we ever want to use CCG for subsurf, * maybe when it is a last modifier in the stack? */ @@ -271,10 +275,12 @@ static Mesh *modify_mesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh } if (use_clnors) { - BKE_mesh_set_custom_normals(result, - static_cast(CustomData_get_layer_for_write( - &result->loop_data, CD_NORMAL, result->totloop))); - CustomData_free_layers(&result->loop_data, CD_NORMAL, result->totloop); + float(*lnors)[3] = static_cast( + CustomData_get_layer_for_write(&result->loop_data, CD_NORMAL, result->totloop)); + BLI_assert(lnors != nullptr); + BKE_mesh_set_custom_normals(result, lnors); + CustomData_set_layer_flag(&mesh->loop_data, CD_NORMAL, CD_FLAG_TEMPORARY); + CustomData_set_layer_flag(&result->loop_data, CD_NORMAL, CD_FLAG_TEMPORARY); } // BKE_subdiv_stats_print(&subdiv->stats); if (!ELEM(subdiv, runtime_data->subdiv_cpu, runtime_data->subdiv_gpu)) { diff --git a/source/blender/modifiers/intern/MOD_triangulate.cc b/source/blender/modifiers/intern/MOD_triangulate.cc index 5f6ee851e9b..a9d29d02ebb 100644 --- a/source/blender/modifiers/intern/MOD_triangulate.cc +++ b/source/blender/modifiers/intern/MOD_triangulate.cc @@ -53,8 +53,9 @@ static Mesh *triangulate_mesh(Mesh *mesh, bool keep_clnors = (flag & MOD_TRIANGULATE_KEEP_CUSTOMLOOP_NORMALS) != 0; if (keep_clnors) { - void *data = CustomData_add_layer(&mesh->loop_data, CD_NORMAL, CD_CONSTRUCT, mesh->totloop); - memcpy(data, mesh->corner_normals().data(), mesh->corner_normals().size_in_bytes()); + BKE_mesh_calc_normals_split(mesh); + /* We need that one to 'survive' to/from BMesh conversions. */ + CustomData_clear_layer_flag(&mesh->loop_data, CD_NORMAL, CD_FLAG_TEMPORARY); cd_mask_extra.lmask |= CD_MASK_NORMAL; } @@ -75,8 +76,13 @@ static Mesh *triangulate_mesh(Mesh *mesh, if (keep_clnors) { float(*lnors)[3] = static_cast( CustomData_get_layer_for_write(&result->loop_data, CD_NORMAL, result->totloop)); + BLI_assert(lnors != nullptr); + BKE_mesh_set_custom_normals(result, lnors); - CustomData_free_layers(&result->loop_data, CD_NORMAL, result->totloop); + + /* Do some cleanup, we do not want those temp data to stay around. */ + CustomData_set_layer_flag(&mesh->loop_data, CD_NORMAL, CD_FLAG_TEMPORARY); + CustomData_set_layer_flag(&result->loop_data, CD_NORMAL, CD_FLAG_TEMPORARY); } return result; diff --git a/source/blender/modifiers/intern/MOD_ui_common.cc b/source/blender/modifiers/intern/MOD_ui_common.cc index af7e9f33d7b..8778099e56c 100644 --- a/source/blender/modifiers/intern/MOD_ui_common.cc +++ b/source/blender/modifiers/intern/MOD_ui_common.cc @@ -134,7 +134,7 @@ void modifier_vgroup_ui(uiLayout *layout, bool has_vertex_group = RNA_string_length(ptr, vgroup_prop) != 0; uiLayout *row = uiLayoutRow(layout, true); - uiItemPointerR(row, ptr, vgroup_prop, ob_ptr, "vertex_groups", text, ICON_GROUP_VERTEX); + uiItemPointerR(row, ptr, vgroup_prop, ob_ptr, "vertex_groups", text, ICON_NONE); if (invert_vgroup_prop != nullptr) { uiLayout *sub = uiLayoutRow(row, true); uiLayoutSetActive(sub, has_vertex_group); diff --git a/source/blender/modifiers/intern/MOD_uvproject.cc b/source/blender/modifiers/intern/MOD_uvproject.cc index aea82b04142..ccf23741e5b 100644 --- a/source/blender/modifiers/intern/MOD_uvproject.cc +++ b/source/blender/modifiers/intern/MOD_uvproject.cc @@ -296,7 +296,7 @@ static void panel_draw(const bContext * /*C*/, Panel *panel) uiLayoutSetPropSep(layout, true); - uiItemPointerR(layout, ptr, "uv_layer", &obj_data_ptr, "uv_layers", nullptr, ICON_GROUP_UVS); + uiItemPointerR(layout, ptr, "uv_layer", &obj_data_ptr, "uv_layers", nullptr, ICON_NONE); /* Aspect and Scale are only used for camera projectors. */ bool has_camera = false; diff --git a/source/blender/modifiers/intern/MOD_uvwarp.cc b/source/blender/modifiers/intern/MOD_uvwarp.cc index 3c3bca5239c..5cb57d0b956 100644 --- a/source/blender/modifiers/intern/MOD_uvwarp.cc +++ b/source/blender/modifiers/intern/MOD_uvwarp.cc @@ -254,7 +254,7 @@ static void panel_draw(const bContext * /*C*/, Panel *panel) uiLayoutSetPropSep(layout, true); - uiItemPointerR(layout, ptr, "uv_layer", &obj_data_ptr, "uv_layers", nullptr, ICON_GROUP_UVS); + uiItemPointerR(layout, ptr, "uv_layer", &obj_data_ptr, "uv_layers", nullptr, ICON_NONE); col = uiLayoutColumn(layout, false); uiItemR(col, ptr, "center", UI_ITEM_NONE, nullptr, ICON_NONE); @@ -268,14 +268,14 @@ static void panel_draw(const bContext * /*C*/, Panel *panel) warp_obj_ptr = RNA_pointer_get(ptr, "object_from"); if (!RNA_pointer_is_null(&warp_obj_ptr) && RNA_enum_get(&warp_obj_ptr, "type") == OB_ARMATURE) { PointerRNA warp_obj_data_ptr = RNA_pointer_get(&warp_obj_ptr, "data"); - uiItemPointerR(col, ptr, "bone_from", &warp_obj_data_ptr, "bones", nullptr, ICON_BONE_DATA); + uiItemPointerR(col, ptr, "bone_from", &warp_obj_data_ptr, "bones", nullptr, ICON_NONE); } uiItemR(col, ptr, "object_to", UI_ITEM_NONE, IFACE_("To"), ICON_NONE); warp_obj_ptr = RNA_pointer_get(ptr, "object_to"); if (!RNA_pointer_is_null(&warp_obj_ptr) && RNA_enum_get(&warp_obj_ptr, "type") == OB_ARMATURE) { PointerRNA warp_obj_data_ptr = RNA_pointer_get(&warp_obj_ptr, "data"); - uiItemPointerR(col, ptr, "bone_to", &warp_obj_data_ptr, "bones", nullptr, ICON_BONE_DATA); + uiItemPointerR(col, ptr, "bone_to", &warp_obj_data_ptr, "bones", nullptr, ICON_NONE); } modifier_vgroup_ui(layout, ptr, &ob_ptr, "vertex_group", "invert_vertex_group", nullptr); diff --git a/source/blender/modifiers/intern/MOD_warp.cc b/source/blender/modifiers/intern/MOD_warp.cc index 34ab55d1ab7..1d6053f3fd6 100644 --- a/source/blender/modifiers/intern/MOD_warp.cc +++ b/source/blender/modifiers/intern/MOD_warp.cc @@ -361,8 +361,7 @@ static void panel_draw(const bContext * /*C*/, Panel *panel) if (!RNA_pointer_is_null(&from_obj_ptr) && RNA_enum_get(&from_obj_ptr, "type") == OB_ARMATURE) { PointerRNA from_obj_data_ptr = RNA_pointer_get(&from_obj_ptr, "data"); - uiItemPointerR( - col, ptr, "bone_from", &from_obj_data_ptr, "bones", IFACE_("Bone"), ICON_BONE_DATA); + uiItemPointerR(col, ptr, "bone_from", &from_obj_data_ptr, "bones", IFACE_("Bone"), ICON_NONE); } col = uiLayoutColumn(layout, true); @@ -370,7 +369,7 @@ static void panel_draw(const bContext * /*C*/, Panel *panel) PointerRNA to_obj_ptr = RNA_pointer_get(ptr, "object_to"); if (!RNA_pointer_is_null(&to_obj_ptr) && RNA_enum_get(&to_obj_ptr, "type") == OB_ARMATURE) { PointerRNA to_obj_data_ptr = RNA_pointer_get(&to_obj_ptr, "data"); - uiItemPointerR(col, ptr, "bone_to", &to_obj_data_ptr, "bones", IFACE_("Bone"), ICON_BONE_DATA); + uiItemPointerR(col, ptr, "bone_to", &to_obj_data_ptr, "bones", IFACE_("Bone"), ICON_NONE); } uiItemR(layout, ptr, "use_volume_preserve", UI_ITEM_NONE, nullptr, ICON_NONE); @@ -437,7 +436,7 @@ static void texture_panel_draw(const bContext *C, Panel *panel) } else if (texture_coords == MOD_DISP_MAP_UV && RNA_enum_get(&ob_ptr, "type") == OB_MESH) { PointerRNA obj_data_ptr = RNA_pointer_get(&ob_ptr, "data"); - uiItemPointerR(col, ptr, "uv_layer", &obj_data_ptr, "uv_layers", nullptr, ICON_GROUP_UVS); + uiItemPointerR(col, ptr, "uv_layer", &obj_data_ptr, "uv_layers", nullptr, ICON_NONE); } } diff --git a/source/blender/modifiers/intern/MOD_wave.cc b/source/blender/modifiers/intern/MOD_wave.cc index f9afabb90f1..093c211d545 100644 --- a/source/blender/modifiers/intern/MOD_wave.cc +++ b/source/blender/modifiers/intern/MOD_wave.cc @@ -401,7 +401,7 @@ static void texture_panel_draw(const bContext *C, Panel *panel) } else if (texture_coords == MOD_DISP_MAP_UV && RNA_enum_get(&ob_ptr, "type") == OB_MESH) { PointerRNA obj_data_ptr = RNA_pointer_get(&ob_ptr, "data"); - uiItemPointerR(col, ptr, "uv_layer", &obj_data_ptr, "uv_layers", nullptr, ICON_GROUP_UVS); + uiItemPointerR(col, ptr, "uv_layer", &obj_data_ptr, "uv_layers", nullptr, ICON_NONE); } } diff --git a/source/blender/modifiers/intern/MOD_weighted_normal.cc b/source/blender/modifiers/intern/MOD_weighted_normal.cc index f376b1f4577..b462c986c77 100644 --- a/source/blender/modifiers/intern/MOD_weighted_normal.cc +++ b/source/blender/modifiers/intern/MOD_weighted_normal.cc @@ -8,7 +8,6 @@ #include "MEM_guardedalloc.h" -#include "BLI_array_utils.hh" #include "BLI_bitmap.h" #include "BLI_linklist.h" #include "BLI_math_vector.h" @@ -83,6 +82,7 @@ struct WeightedNormalData { blender::Span loop_to_face; blender::MutableSpan clnors; bool has_clnors; /* True if clnors already existed, false if we had to create them. */ + float split_angle; blender::OffsetIndices faces; blender::Span face_normals; @@ -206,6 +206,7 @@ static void apply_weights_vertex_normal(WeightedNormalModifierData *wnmd, ModePair *mode_pair = wn_data->mode_pair; const bool has_clnors = wn_data->has_clnors; + const float split_angle = wn_data->split_angle; bke::mesh::CornerNormalSpaceArray lnors_spacearr; const bool keep_sharp = (wnmd->flag & MOD_WEIGHTEDNORMAL_KEEP_SHARP) != 0; @@ -231,6 +232,8 @@ static void apply_weights_vertex_normal(WeightedNormalModifierData *wnmd, wn_data->sharp_edges.data(), wn_data->sharp_faces, has_clnors ? clnors.data() : nullptr, + true, + split_angle, &lnors_spacearr, loop_normals); @@ -243,7 +246,8 @@ static void apply_weights_vertex_normal(WeightedNormalModifierData *wnmd, start_item.curr_strength = FACE_STRENGTH_WEAK; items_data = Array(verts_num, start_item); lnors_spacearr.corner_space_indices.reinitialize(corner_verts.size()); - array_utils::fill_index_range(lnors_spacearr.corner_space_indices); + std::iota( + lnors_spacearr.corner_space_indices.begin(), lnors_spacearr.corner_space_indices.end(), 0); } wn_data->items_data = items_data; @@ -359,6 +363,8 @@ static void apply_weights_vertex_normal(WeightedNormalModifierData *wnmd, wn_data->sharp_edges.data(), wn_data->sharp_faces, has_clnors ? clnors.data() : nullptr, + true, + split_angle, nullptr, loop_normals); @@ -476,6 +482,23 @@ static Mesh *modify_mesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh { using namespace blender; WeightedNormalModifierData *wnmd = (WeightedNormalModifierData *)md; + Object *ob = ctx->object; + + /* XXX TODO(Rohan Rathi): + * Once we fully switch to Mesh evaluation of modifiers, + * we can expect to get that flag from the COW copy. + * But for now, it is lost in the DM intermediate step, + * so we need to directly check orig object's data. */ +#if 0 + if (!(mesh->flag & ME_AUTOSMOOTH)) +#else + if (!(((Mesh *)ob->data)->flag & ME_AUTOSMOOTH)) +#endif + { + BKE_modifier_set_error( + ctx->object, (ModifierData *)wnmd, "Enable 'Auto Smooth' in Object Data Properties"); + return mesh; + } Mesh *result; result = (Mesh *)BKE_id_copy_ex(nullptr, &mesh->id, nullptr, LIB_ID_COPY_LOCALIZE); @@ -504,6 +527,7 @@ static Mesh *modify_mesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh weight = (weight - 1) * 25; } + const float split_angle = mesh->smoothresh; blender::short2 *clnors = static_cast( CustomData_get_layer_for_write(&result->loop_data, CD_CUSTOMLOOPNORMAL, mesh->totloop)); @@ -538,6 +562,7 @@ static Mesh *modify_mesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh wn_data.loop_to_face = loop_to_face_map; wn_data.clnors = {clnors, mesh->totloop}; wn_data.has_clnors = has_clnors; + wn_data.split_angle = split_angle; wn_data.faces = faces; wn_data.face_normals = mesh->face_normals(); diff --git a/source/blender/modifiers/intern/MOD_weightvgedit.cc b/source/blender/modifiers/intern/MOD_weightvgedit.cc index c96bda0bb55..5ddae895aeb 100644 --- a/source/blender/modifiers/intern/MOD_weightvgedit.cc +++ b/source/blender/modifiers/intern/MOD_weightvgedit.cc @@ -296,7 +296,7 @@ static void panel_draw(const bContext * /*C*/, Panel *panel) uiLayoutSetPropSep(layout, true); col = uiLayoutColumn(layout, true); - uiItemPointerR(col, ptr, "vertex_group", &ob_ptr, "vertex_groups", nullptr, ICON_GROUP_VERTEX); + uiItemPointerR(col, ptr, "vertex_group", &ob_ptr, "vertex_groups", nullptr, ICON_NONE); uiItemR(layout, ptr, "default_weight", UI_ITEM_R_SLIDER, nullptr, ICON_NONE); diff --git a/source/blender/modifiers/intern/MOD_weightvgproximity.cc b/source/blender/modifiers/intern/MOD_weightvgproximity.cc index 91661fcd4ff..fc22a548068 100644 --- a/source/blender/modifiers/intern/MOD_weightvgproximity.cc +++ b/source/blender/modifiers/intern/MOD_weightvgproximity.cc @@ -652,8 +652,7 @@ static void panel_draw(const bContext * /*C*/, Panel *panel) uiLayoutSetPropSep(layout, true); - uiItemPointerR( - layout, ptr, "vertex_group", &ob_ptr, "vertex_groups", nullptr, ICON_GROUP_VERTEX); + uiItemPointerR(layout, ptr, "vertex_group", &ob_ptr, "vertex_groups", nullptr, ICON_NONE); uiItemR(layout, ptr, "target", UI_ITEM_NONE, nullptr, ICON_NONE); diff --git a/source/blender/nodes/CMakeLists.txt b/source/blender/nodes/CMakeLists.txt index 951bb68e68c..7cd01e716a6 100644 --- a/source/blender/nodes/CMakeLists.txt +++ b/source/blender/nodes/CMakeLists.txt @@ -55,7 +55,6 @@ set(INC texture ../editors/include ../blenkernel - ../blenloader ../blentranslation ../bmesh ../depsgraph @@ -92,11 +91,10 @@ set(SRC intern/node_socket.cc intern/node_socket_declarations.cc intern/node_util.cc - intern/node_zone_socket_items.cc intern/socket_search_link.cc NOD_common.h - NOD_composite.hh + NOD_composite.h NOD_derived_node_tree.hh NOD_geometry.hh NOD_geometry_exec.hh @@ -112,11 +110,9 @@ set(SRC NOD_socket.hh NOD_socket_declarations.hh NOD_socket_declarations_geometry.hh - NOD_socket_items.hh NOD_socket_search_link.hh NOD_static_types.h NOD_texture.h - NOD_zone_socket_items.hh intern/node_common.h intern/node_exec.hh intern/node_util.hh diff --git a/source/blender/nodes/NOD_common.h b/source/blender/nodes/NOD_common.h index 69cb7f60eb4..f2517799324 100644 --- a/source/blender/nodes/NOD_common.h +++ b/source/blender/nodes/NOD_common.h @@ -30,7 +30,9 @@ struct bNodeSocket *node_group_output_find_socket(struct bNode *node, const char namespace blender::nodes { -void node_group_declare(NodeDeclarationBuilder &b); +void node_group_declare_dynamic(const bNodeTree &node_tree, + const bNode &node, + NodeDeclaration &r_declaration); } // namespace blender::nodes diff --git a/source/blender/nodes/NOD_composite.hh b/source/blender/nodes/NOD_composite.h similarity index 98% rename from source/blender/nodes/NOD_composite.hh rename to source/blender/nodes/NOD_composite.h index 1ada2935654..ac054e90239 100644 --- a/source/blender/nodes/NOD_composite.hh +++ b/source/blender/nodes/NOD_composite.h @@ -10,6 +10,10 @@ #include "BKE_node.h" +#ifdef __cplusplus +extern "C" { +#endif + struct Scene; struct RenderData; struct Render; @@ -97,3 +101,7 @@ void ntreeCompositCryptomatteLayerPrefix(const Scene *scene, */ void ntreeCompositCryptomatteUpdateLayerNames(const Scene *scene, bNode *node); struct CryptomatteSession *ntreeCompositCryptomatteSession(const Scene *scene, bNode *node); + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/nodes/NOD_geometry.hh b/source/blender/nodes/NOD_geometry.hh index 390592a5435..b49ba956cde 100644 --- a/source/blender/nodes/NOD_geometry.hh +++ b/source/blender/nodes/NOD_geometry.hh @@ -11,7 +11,54 @@ extern bNodeTreeType *ntreeType_Geometry; void register_node_tree_type_geo(); void register_node_type_geo_custom_group(bNodeType *ntype); +/* -------------------------------------------------------------------- */ +/** \name Simulation Output Node + * \{ */ + +bool NOD_geometry_simulation_output_item_socket_type_supported(eNodeSocketDatatype socket_type); + /** - * Returns true if the socket is a Named Layer Selection field. + * Set a unique item name. + * \return True if the unique name differs from the original name. */ -bool is_layer_selection_field(const bNodeTreeInterfaceSocket &socket); +bool NOD_geometry_simulation_output_item_set_unique_name(NodeGeometrySimulationOutput *sim, + NodeSimulationItem *item, + const char *name, + const char *defname); + +/** + * Find the node owning this simulation state item. + */ +bNode *NOD_geometry_simulation_output_find_node_by_item(bNodeTree *ntree, + const NodeSimulationItem *item); + +bool NOD_geometry_simulation_output_contains_item(NodeGeometrySimulationOutput *sim, + const NodeSimulationItem *item); +NodeSimulationItem *NOD_geometry_simulation_output_get_active_item( + NodeGeometrySimulationOutput *sim); +void NOD_geometry_simulation_output_set_active_item(NodeGeometrySimulationOutput *sim, + NodeSimulationItem *item); +NodeSimulationItem *NOD_geometry_simulation_output_find_item(NodeGeometrySimulationOutput *sim, + const char *name); +NodeSimulationItem *NOD_geometry_simulation_output_add_item(NodeGeometrySimulationOutput *sim, + short socket_type, + const char *name); +NodeSimulationItem *NOD_geometry_simulation_output_insert_item(NodeGeometrySimulationOutput *sim, + short socket_type, + const char *name, + int index); +NodeSimulationItem *NOD_geometry_simulation_output_add_item_from_socket( + NodeGeometrySimulationOutput *sim, const bNode *from_node, const bNodeSocket *from_sock); +NodeSimulationItem *NOD_geometry_simulation_output_insert_item_from_socket( + NodeGeometrySimulationOutput *sim, + const bNode *from_node, + const bNodeSocket *from_sock, + int index); +void NOD_geometry_simulation_output_remove_item(NodeGeometrySimulationOutput *sim, + NodeSimulationItem *item); +void NOD_geometry_simulation_output_clear_items(NodeGeometrySimulationOutput *sim); +void NOD_geometry_simulation_output_move_item(NodeGeometrySimulationOutput *sim, + int from_index, + int to_index); + +/** \} */ diff --git a/source/blender/nodes/NOD_geometry_exec.hh b/source/blender/nodes/NOD_geometry_exec.hh index 8e52a81a24a..f5513e81675 100644 --- a/source/blender/nodes/NOD_geometry_exec.hh +++ b/source/blender/nodes/NOD_geometry_exec.hh @@ -8,7 +8,6 @@ #include "BLI_math_quaternion_types.hh" #include "FN_field.hh" -#include "FN_field_cpp_type.hh" #include "FN_lazy_function.hh" #include "FN_multi_function_builder.hh" @@ -40,7 +39,6 @@ using bke::GAttributeWriter; using bke::GeometryComponent; using bke::GeometryComponentEditData; using bke::GeometrySet; -using bke::GreasePencilComponent; using bke::GSpanAttributeWriter; using bke::InstancesComponent; using bke::MeshComponent; @@ -105,14 +103,6 @@ class GeoNodeExecParams { identifier); return value_or_field.as_field(); } - else if constexpr (std::is_same_v, GField>) { - const int index = this->get_input_index(identifier); - const bNodeSocket &input_socket = node_.input_by_identifier(identifier); - const CPPType &value_type = *input_socket.typeinfo->geometry_nodes_cpp_type; - const fn::ValueOrFieldCPPType &value_or_field_type = *fn::ValueOrFieldCPPType::get_from_self( - value_type); - return value_or_field_type.as_field(params_.try_get_input_data_ptr(index)); - } else { #ifdef DEBUG this->check_input_access(identifier, &CPPType::get()); diff --git a/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh b/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh index 05a08df3c60..ef468717b7c 100644 --- a/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh +++ b/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh @@ -270,7 +270,7 @@ struct GeometryNodeLazyFunctionGraphMapping { * types, so better have more specialized mappings for now. */ Map group_node_map; - Map possible_side_effect_node_map; + Map viewer_node_map; Map zone_node_map; /* Indexed by #bNodeSocket::index_in_all_outputs. */ diff --git a/source/blender/nodes/NOD_geometry_nodes_log.hh b/source/blender/nodes/NOD_geometry_nodes_log.hh index f33a403d948..95b0356b7d2 100644 --- a/source/blender/nodes/NOD_geometry_nodes_log.hh +++ b/source/blender/nodes/NOD_geometry_nodes_log.hh @@ -135,9 +135,6 @@ class GeometryInfoLog : public ValueLog { struct PointCloudInfo { int points_num; }; - struct GreasePencilInfo { - int layers_num; - }; struct InstancesInfo { int instances_num; }; @@ -149,7 +146,6 @@ class GeometryInfoLog : public ValueLog { std::optional mesh_info; std::optional curve_info; std::optional pointcloud_info; - std::optional grease_pencil_info; std::optional instances_info; std::optional edit_data_info; diff --git a/source/blender/nodes/NOD_node_declaration.hh b/source/blender/nodes/NOD_node_declaration.hh index 37de7a70587..2f337a5a59d 100644 --- a/source/blender/nodes/NOD_node_declaration.hh +++ b/source/blender/nodes/NOD_node_declaration.hh @@ -52,9 +52,10 @@ enum class OutputSocketFieldType { enum class CompositorInputRealizationOptions : uint8_t { None = 0, RealizeOnOperationDomain = (1 << 0), + RealizeRotation = (1 << 1), + RealizeScale = (1 << 2), }; -ENUM_OPERATORS(CompositorInputRealizationOptions, - CompositorInputRealizationOptions::RealizeOnOperationDomain) +ENUM_OPERATORS(CompositorInputRealizationOptions, CompositorInputRealizationOptions::RealizeScale) /** * Contains information about how a node output's field state depends on inputs of the same node. @@ -74,7 +75,6 @@ class OutputFieldDependency { Span linked_input_indices() const; friend bool operator==(const OutputFieldDependency &a, const OutputFieldDependency &b); - friend bool operator!=(const OutputFieldDependency &a, const OutputFieldDependency &b); }; /** @@ -83,9 +83,6 @@ class OutputFieldDependency { struct FieldInferencingInterface { Vector inputs; Vector outputs; - - friend bool operator==(const FieldInferencingInterface &a, const FieldInferencingInterface &b); - friend bool operator!=(const FieldInferencingInterface &a, const FieldInferencingInterface &b); }; namespace anonymous_attribute_lifetime { @@ -208,13 +205,11 @@ class SocketDeclaration : public ItemDeclaration { /** Utility method to make the socket available if there is a straightforward way to do so. */ std::function make_available_fn_; - public: /** Some input sockets can have non-trivial values in the case when they are unlinked. This * callback computes the default input of a values in geometry nodes when nothing is linked. */ - std::unique_ptr implicit_input_fn; + std::unique_ptr implicit_input_fn_; friend NodeDeclarationBuilder; - friend class BaseSocketDeclarationBuilder; template friend class SocketDeclarationBuilder; public: @@ -241,6 +236,11 @@ class SocketDeclaration : public ItemDeclaration { int compositor_domain_priority() const; bool compositor_expects_single_value() const; + const ImplicitInputValueFn *implicit_input_fn() const + { + return implicit_input_fn_.get(); + } + protected: void set_common_flags(bNodeSocket &socket) const; bool matches_common_data(const bNodeSocket &socket) const; @@ -258,125 +258,15 @@ class BaseSocketDeclarationBuilder { bool field_on_all_ = false; bool propagate_from_all_ = false; NodeDeclarationBuilder *node_decl_builder_ = nullptr; - SocketDeclaration *decl_in_base_ = nullptr; - SocketDeclaration *decl_out_base_ = nullptr; friend class NodeDeclarationBuilder; public: virtual ~BaseSocketDeclarationBuilder() = default; - BaseSocketDeclarationBuilder &hide_label(bool value = true); - - BaseSocketDeclarationBuilder &hide_value(bool value = true); - - BaseSocketDeclarationBuilder &multi_input(bool value = true); - - BaseSocketDeclarationBuilder &short_label(std::string value = ""); - - BaseSocketDeclarationBuilder &description(std::string value = ""); - - BaseSocketDeclarationBuilder &translation_context(std::string value = BLT_I18NCONTEXT_DEFAULT); - - BaseSocketDeclarationBuilder &no_muted_links(bool value = true); - - /** - * Used for sockets that are always unavailable and should not be seen by the user. - * Ideally, no new calls to this method should be added over time. - */ - BaseSocketDeclarationBuilder &unavailable(bool value = true); - - BaseSocketDeclarationBuilder &is_attribute_name(bool value = true); - - BaseSocketDeclarationBuilder &is_default_link_socket(bool value = true); - - /** The input socket allows passing in a field. */ - BaseSocketDeclarationBuilder &supports_field(); - - /** - * For inputs this means that the input field is evaluated on all geometry inputs. For outputs - * it means that this contains an anonymous attribute reference that is available on all geometry - * outputs. This sockets value does not have to be output manually in the node. It's done - * automatically by #LazyFunctionForGeometryNode. This allows outputting this field even if the - * geometry output does not have to be computed. - */ - BaseSocketDeclarationBuilder &field_on_all(); - - /** The output is always a field, regardless of any inputs. */ - BaseSocketDeclarationBuilder &field_source(); - - /** The input supports a field and is a field by default when nothing is connected. */ - BaseSocketDeclarationBuilder &implicit_field(ImplicitInputValueFn fn); - - /** The input is an implicit field that is evaluated on all geometry inputs. */ - BaseSocketDeclarationBuilder &implicit_field_on_all(ImplicitInputValueFn fn); - - /** The input is evaluated on a subset of the geometry inputs. */ - BaseSocketDeclarationBuilder &implicit_field_on(ImplicitInputValueFn fn, - const Span input_indices); - - /** For inputs that are evaluated or available on a subset of the geometry sockets. */ - BaseSocketDeclarationBuilder &field_on(Span indices); - - /** The output is a field if any of the inputs are a field. */ - BaseSocketDeclarationBuilder &dependent_field(); - - /** The output is a field if any of the inputs with indices in the given list is a field. */ - BaseSocketDeclarationBuilder &dependent_field(Vector input_dependencies); - - /** - * For outputs that combine all input fields into a new field. The output is a field even if none - * of the inputs is a field. - */ - BaseSocketDeclarationBuilder &field_source_reference_all(); - - /** - * For outputs that combine a subset of input fields into a new field. - */ - BaseSocketDeclarationBuilder &reference_pass(Span input_indices); - - /** - * For outputs that combine all input fields into a new field. - */ - BaseSocketDeclarationBuilder &reference_pass_all(); - - /** Attributes from the all geometry inputs can be propagated. */ - BaseSocketDeclarationBuilder &propagate_all(); - - BaseSocketDeclarationBuilder &compositor_realization_options( - CompositorInputRealizationOptions value); - - /** - * The priority of the input for determining the domain of the node. See - * realtime_compositor::InputDescriptor for more information. - */ - BaseSocketDeclarationBuilder &compositor_domain_priority(int priority); - - /** - * This input expects a single value and can't operate on non-single values. See - * realtime_compositor::InputDescriptor for more information. - */ - BaseSocketDeclarationBuilder &compositor_expects_single_value(bool value = true); - - /** - * Pass a function that sets properties on the node required to make the corresponding socket - * available, if it is not available on the default state of the node. The function is allowed to - * make other sockets unavailable, since it is meant to be called when the node is first added. - * The node type's update function is called afterwards. - */ - BaseSocketDeclarationBuilder &make_available(std::function fn); - - int input_index() const - { - BLI_assert(decl_in_base_ != nullptr); - return index_in_; - } - - int output_index() const - { - BLI_assert(decl_out_base_ != nullptr); - return index_out_; - } + protected: + virtual SocketDeclaration *input_declaration() = 0; + virtual SocketDeclaration *output_declaration() = 0; }; /** @@ -393,6 +283,302 @@ class SocketDeclarationBuilder : public BaseSocketDeclarationBuilder { SocketDecl *decl_out_; friend class NodeDeclarationBuilder; + + public: + Self &hide_label(bool value = true) + { + if (decl_in_) { + decl_in_->hide_label = value; + } + if (decl_out_) { + decl_out_->hide_label = value; + } + return *(Self *)this; + } + + Self &hide_value(bool value = true) + { + if (decl_in_) { + decl_in_->hide_value = value; + } + if (decl_out_) { + decl_out_->hide_value = value; + } + return *(Self *)this; + } + + Self &multi_input(bool value = true) + { + if (decl_in_) { + decl_in_->is_multi_input = value; + } + return *(Self *)this; + } + + Self &short_label(std::string value = "") + { + if (decl_in_) { + decl_in_->short_label = std::move(value); + } + if (decl_out_) { + decl_out_->short_label = std::move(value); + } + return *(Self *)this; + } + + Self &description(std::string value = "") + { + if (decl_in_) { + decl_in_->description = std::move(value); + } + if (decl_out_) { + decl_out_->description = std::move(value); + } + return *(Self *)this; + } + + Self &translation_context(std::string value = BLT_I18NCONTEXT_DEFAULT) + { + if (decl_in_) { + decl_in_->translation_context = std::move(value); + } + if (decl_out_) { + decl_out_->translation_context = std::move(value); + } + return *(Self *)this; + } + + Self &no_muted_links(bool value = true) + { + if (decl_in_) { + decl_in_->no_mute_links = value; + } + if (decl_out_) { + decl_out_->no_mute_links = value; + } + return *(Self *)this; + } + + /** + * Used for sockets that are always unavailable and should not be seen by the user. + * Ideally, no new calls to this method should be added over time. + */ + Self &unavailable(bool value = true) + { + if (decl_in_) { + decl_in_->is_unavailable = value; + } + if (decl_out_) { + decl_out_->is_unavailable = value; + } + return *(Self *)this; + } + + Self &is_attribute_name(bool value = true) + { + if (decl_in_) { + decl_in_->is_attribute_name = value; + } + if (decl_out_) { + decl_out_->is_attribute_name = value; + } + return *(Self *)this; + } + + Self &is_default_link_socket(bool value = true) + { + if (decl_in_) { + decl_in_->is_default_link_socket = value; + } + if (decl_out_) { + decl_out_->is_default_link_socket = value; + } + return *(Self *)this; + } + + /** The input socket allows passing in a field. */ + Self &supports_field() + { + if (decl_in_) { + decl_in_->input_field_type = InputSocketFieldType::IsSupported; + } + return *(Self *)this; + } + + /** + * For inputs this means that the input field is evaluated on all geometry inputs. For outputs + * it means that this contains an anonymous attribute reference that is available on all geometry + * outputs. This sockets value does not have to be output manually in the node. It's done + * automatically by #LazyFunctionForGeometryNode. This allows outputting this field even if the + * geometry output does not have to be computed. + */ + Self &field_on_all() + { + if (decl_in_) { + this->supports_field(); + } + if (decl_out_) { + this->field_source(); + } + field_on_all_ = true; + return *(Self *)this; + } + + /** For inputs that are evaluated or available on a subset of the geometry sockets. */ + Self &field_on(Span indices); + + /** The input supports a field and is a field by default when nothing is connected. */ + Self &implicit_field(ImplicitInputValueFn fn) + { + this->hide_value(); + if (decl_in_) { + decl_in_->input_field_type = InputSocketFieldType::Implicit; + decl_in_->implicit_input_fn_ = std::make_unique(std::move(fn)); + } + return *(Self *)this; + } + + /** The input is an implicit field that is evaluated on all geometry inputs. */ + Self &implicit_field_on_all(ImplicitInputValueFn fn) + { + this->implicit_field(fn); + field_on_all_ = true; + return *(Self *)this; + } + + /** The input is evaluated on a subset of the geometry inputs. */ + Self &implicit_field_on(ImplicitInputValueFn fn, const Span input_indices) + { + this->field_on(input_indices); + this->implicit_field(fn); + return *(Self *)this; + } + + /** The output is always a field, regardless of any inputs. */ + Self &field_source() + { + if (decl_out_) { + decl_out_->output_field_dependency = OutputFieldDependency::ForFieldSource(); + } + return *(Self *)this; + } + + /** The output is a field if any of the inputs are a field. */ + Self &dependent_field() + { + if (decl_out_) { + decl_out_->output_field_dependency = OutputFieldDependency::ForDependentField(); + } + this->reference_pass_all(); + 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 input_dependencies) + { + this->reference_pass(input_dependencies); + if (decl_out_) { + decl_out_->output_field_dependency = OutputFieldDependency::ForPartiallyDependentField( + std::move(input_dependencies)); + } + return *(Self *)this; + } + + /** + * For outputs that combine all input fields into a new field. The output is a field even if none + * of the inputs is a field. + */ + Self &field_source_reference_all() + { + this->field_source(); + this->reference_pass_all(); + return *(Self *)this; + } + + /** + * For outputs that combine a subset of input fields into a new field. + */ + Self &reference_pass(Span input_indices); + + /** + * For outputs that combine all input fields into a new field. + */ + Self &reference_pass_all() + { + reference_pass_all_ = true; + return *(Self *)this; + } + + /** Attributes from the all geometry inputs can be propagated. */ + Self &propagate_all() + { + propagate_from_all_ = true; + return *(Self *)this; + } + + Self &compositor_realization_options(CompositorInputRealizationOptions value) + { + if (decl_in_) { + decl_in_->compositor_realization_options_ = value; + } + if (decl_out_) { + decl_out_->compositor_realization_options_ = value; + } + return *(Self *)this; + } + + /** The priority of the input for determining the domain of the node. See + * realtime_compositor::InputDescriptor for more information. */ + Self &compositor_domain_priority(int priority) + { + if (decl_in_) { + decl_in_->compositor_domain_priority_ = priority; + } + if (decl_out_) { + decl_out_->compositor_domain_priority_ = priority; + } + return *(Self *)this; + } + + /** This input expects a single value and can't operate on non-single values. See + * realtime_compositor::InputDescriptor for more information. */ + Self &compositor_expects_single_value(bool value = true) + { + if (decl_in_) { + decl_in_->compositor_expects_single_value_ = value; + } + if (decl_out_) { + decl_out_->compositor_expects_single_value_ = value; + } + return *(Self *)this; + } + + /** + * Pass a function that sets properties on the node required to make the corresponding socket + * available, if it is not available on the default state of the node. The function is allowed to + * make other sockets unavailable, since it is meant to be called when the node is first added. + * The node type's update function is called afterwards. + */ + Self &make_available(std::function fn) + { + if (decl_in_) { + decl_in_->make_available_fn_ = std::move(fn); + } + if (decl_out_) { + decl_out_->make_available_fn_ = std::move(fn); + } + return *(Self *)this; + } + + protected: + SocketDeclaration *input_declaration() override + { + return decl_in_; + } + SocketDeclaration *output_declaration() override + { + return decl_out_; + } }; using SocketDeclarationPtr = std::unique_ptr; @@ -438,9 +624,23 @@ class PanelDeclarationBuilder { friend class NodeDeclarationBuilder; public: - Self &description(std::string value = ""); - Self &default_closed(bool closed); - Self &draw_buttons(PanelDrawButtonsFunction func); + Self &description(std::string value = "") + { + decl_->description = std::move(value); + return *this; + } + + Self &default_closed(bool closed) + { + decl_->default_collapsed = closed; + return *this; + } + + Self &draw_buttons(PanelDrawButtonsFunction func) + { + decl_->draw_buttons = func; + return *this; + } template typename DeclType::Builder &add_input(StringRef name, StringRef identifier = ""); @@ -469,11 +669,6 @@ class NodeDeclaration { * outputs | buttons | inputs order. Panels are only supported when using custom socket order. */ bool use_custom_socket_order = false; - /** - * True if any context was used to build this declaration. - */ - bool is_context_dependent = false; - friend NodeDeclarationBuilder; /** Returns true if the declaration is considered valid. */ @@ -493,8 +688,6 @@ class NodeDeclaration { class NodeDeclarationBuilder { private: NodeDeclaration &declaration_; - const bNodeTree *ntree_ = nullptr; - const bNode *node_ = nullptr; Vector> socket_builders_; Vector> panel_builders_; bool is_function_node_ = false; @@ -503,21 +696,7 @@ class NodeDeclarationBuilder { friend PanelDeclarationBuilder; public: - NodeDeclarationBuilder(NodeDeclaration &declaration, - const bNodeTree *ntree = nullptr, - const bNode *node = nullptr); - - const bNode *node_or_null() const - { - declaration_.is_context_dependent = true; - return node_; - } - - const bNodeTree *tree_or_null() const - { - declaration_.is_context_dependent = true; - return ntree_; - } + NodeDeclarationBuilder(NodeDeclaration &declaration); /** * All inputs support fields, and all outputs are fields if any of the inputs is a field. @@ -538,13 +717,6 @@ class NodeDeclarationBuilder { typename DeclType::Builder &add_output(StringRef name, StringRef identifier = ""); PanelDeclarationBuilder &add_panel(StringRef name, int identifier = -1); - BaseSocketDeclarationBuilder &add_input(eNodeSocketDatatype socket_type, - StringRef name, - StringRef identifier = ""); - BaseSocketDeclarationBuilder &add_output(eNodeSocketDatatype socket_type, - StringRef name, - StringRef identifier = ""); - aal::RelationsInNode &get_anonymous_attribute_relations() { if (!declaration_.anonymous_attribute_relations_) { @@ -553,11 +725,6 @@ class NodeDeclarationBuilder { return *declaration_.anonymous_attribute_relations_; } - NodeDeclaration &declaration() - { - return declaration_; - } - private: /* Note: in_out can be a combination of SOCK_IN and SOCK_OUT. * The generated socket declarations only have a single flag set. */ @@ -579,13 +746,155 @@ void index(const bNode &node, void *r_value); void id_or_index(const bNode &node, void *r_value); } // namespace implicit_field_inputs -void build_node_declaration(const bNodeType &typeinfo, - NodeDeclaration &r_declaration, - const bNodeTree *ntree, - const bNode *node); +void build_node_declaration(const bNodeType &typeinfo, NodeDeclaration &r_declaration); +void build_node_declaration_dynamic(const bNodeTree &node_tree, + const bNode &node, + NodeDeclaration &r_declaration); -std::unique_ptr make_declaration_for_socket_type( - eNodeSocketDatatype socket_type); +template +typename SocketDeclarationBuilder::Self &SocketDeclarationBuilder< + SocketDecl>::reference_pass(const Span input_indices) +{ + aal::RelationsInNode &relations = node_decl_builder_->get_anonymous_attribute_relations(); + for (const int from_input : input_indices) { + aal::ReferenceRelation relation; + relation.from_field_input = from_input; + relation.to_field_output = index_out_; + relations.reference_relations.append(relation); + } + return *(Self *)this; +} + +template +typename SocketDeclarationBuilder::Self &SocketDeclarationBuilder< + SocketDecl>::field_on(const Span indices) +{ + aal::RelationsInNode &relations = node_decl_builder_->get_anonymous_attribute_relations(); + if (decl_in_) { + this->supports_field(); + for (const int input_index : indices) { + aal::EvalRelation relation; + relation.field_input = index_in_; + relation.geometry_input = input_index; + relations.eval_relations.append(relation); + } + } + if (decl_out_) { + this->field_source(); + for (const int output_index : indices) { + aal::AvailableRelation relation; + relation.field_output = index_out_; + relation.geometry_output = output_index; + relations.available_relations.append(relation); + } + } + return *(Self *)this; +} + +/* -------------------------------------------------------------------- */ +/** \name #OutputFieldDependency Inline Methods + * \{ */ + +inline OutputFieldDependency OutputFieldDependency::ForFieldSource() +{ + OutputFieldDependency field_dependency; + field_dependency.type_ = OutputSocketFieldType::FieldSource; + return field_dependency; +} + +inline OutputFieldDependency OutputFieldDependency::ForDataSource() +{ + OutputFieldDependency field_dependency; + field_dependency.type_ = OutputSocketFieldType::None; + return field_dependency; +} + +inline OutputFieldDependency OutputFieldDependency::ForDependentField() +{ + OutputFieldDependency field_dependency; + field_dependency.type_ = OutputSocketFieldType::DependentField; + return field_dependency; +} + +inline OutputFieldDependency OutputFieldDependency::ForPartiallyDependentField(Vector 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; +} + +inline OutputSocketFieldType OutputFieldDependency::field_type() const +{ + return type_; +} + +inline Span OutputFieldDependency::linked_input_indices() const +{ + return linked_input_indices_; +} + +inline bool operator==(const OutputFieldDependency &a, const OutputFieldDependency &b) +{ + return a.type_ == b.type_ && a.linked_input_indices_ == b.linked_input_indices_; +} + +inline bool operator!=(const OutputFieldDependency &a, const OutputFieldDependency &b) +{ + return !(a == b); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name #FieldInferencingInterface Inline Methods + * \{ */ + +inline bool operator==(const FieldInferencingInterface &a, const FieldInferencingInterface &b) +{ + return a.inputs == b.inputs && a.outputs == b.outputs; +} + +inline bool operator!=(const FieldInferencingInterface &a, const FieldInferencingInterface &b) +{ + return !(a == b); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name #SocketDeclaration Inline Methods + * \{ */ + +inline const CompositorInputRealizationOptions &SocketDeclaration::compositor_realization_options() + const +{ + return compositor_realization_options_; +} + +inline int SocketDeclaration::compositor_domain_priority() const +{ + return compositor_domain_priority_; +} + +inline bool SocketDeclaration::compositor_expects_single_value() const +{ + return compositor_expects_single_value_; +} + +inline void SocketDeclaration::make_available(bNode &node) const +{ + if (make_available_fn_) { + make_available_fn_(node); + } +} + +/** \} */ /* -------------------------------------------------------------------- */ /** \name #PanelDeclarationBuilder Inline Methods @@ -623,6 +932,16 @@ typename DeclType::Builder &PanelDeclarationBuilder::add_output(StringRef name, /** \name #NodeDeclarationBuilder Inline Methods * \{ */ +inline NodeDeclarationBuilder::NodeDeclarationBuilder(NodeDeclaration &declaration) + : declaration_(declaration) +{ +} + +inline void NodeDeclarationBuilder::use_custom_socket_order(bool enable) +{ + declaration_.use_custom_socket_order = enable; +} + template inline typename DeclType::Builder &NodeDeclarationBuilder::add_input(StringRef name, StringRef identifier) @@ -654,7 +973,6 @@ inline typename DeclType::Builder &NodeDeclarationBuilder::add_socket(StringRef if (in_out & SOCK_IN) { std::unique_ptr socket_decl = std::make_unique(); socket_decl_builder->decl_in_ = &*socket_decl; - socket_decl_builder->decl_in_base_ = &*socket_decl; socket_decl->name = name; socket_decl->identifier = identifier_in.is_empty() ? name : identifier_in; socket_decl->in_out = SOCK_IN; @@ -664,7 +982,6 @@ inline typename DeclType::Builder &NodeDeclarationBuilder::add_socket(StringRef if (in_out & SOCK_OUT) { std::unique_ptr socket_decl = std::make_unique(); socket_decl_builder->decl_out_ = &*socket_decl; - socket_decl_builder->decl_out_base_ = &*socket_decl; socket_decl->name = name; socket_decl->identifier = identifier_out.is_empty() ? name : identifier_out; socket_decl->in_out = SOCK_OUT; @@ -680,4 +997,18 @@ inline typename DeclType::Builder &NodeDeclarationBuilder::add_socket(StringRef /** \} */ +/* -------------------------------------------------------------------- */ +/** \name #NodeDeclaration Inline Methods + * \{ */ + +inline Span NodeDeclaration::sockets(eNodeSocketInOut in_out) const +{ + if (in_out == SOCK_IN) { + return inputs; + } + return outputs; +} + +/** \} */ + } // namespace blender::nodes diff --git a/source/blender/nodes/NOD_socket.hh b/source/blender/nodes/NOD_socket.hh index c77a1db05b5..99219f917f9 100644 --- a/source/blender/nodes/NOD_socket.hh +++ b/source/blender/nodes/NOD_socket.hh @@ -29,6 +29,5 @@ void register_standard_node_socket_types(); namespace blender::nodes { void update_node_declaration_and_sockets(bNodeTree &ntree, bNode &node); -bool socket_type_supports_fields(eNodeSocketDatatype socket_type); } // namespace blender::nodes diff --git a/source/blender/nodes/NOD_socket_declarations.hh b/source/blender/nodes/NOD_socket_declarations.hh index d618f8534d1..b6ffb0c87ac 100644 --- a/source/blender/nodes/NOD_socket_declarations.hh +++ b/source/blender/nodes/NOD_socket_declarations.hh @@ -120,7 +120,7 @@ class ColorBuilder; class Color : public SocketDeclaration { public: - ColorGeometry4f default_value{0.8f, 0.8f, 0.8f, 1.0f}; + ColorGeometry4f default_value; friend ColorBuilder; diff --git a/source/blender/nodes/NOD_socket_items.hh b/source/blender/nodes/NOD_socket_items.hh deleted file mode 100644 index 200d25dc37d..00000000000 --- a/source/blender/nodes/NOD_socket_items.hh +++ /dev/null @@ -1,314 +0,0 @@ -/* SPDX-FileCopyrightText: 2023 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -#pragma once - -/** - * Some nodes have a dynamic number of sockets (e.g. simulation input/output). These nodes store an - * array of items in their `bNode->storage` (e.g. `NodeSimulationItem`). Different nodes have - * slightly different storage requirements, but a lot of the logic is still the same between nodes. - * This file implements various shared functionality that can be used by different nodes to deal - * with these item arrays. - * - * In order to use the functions, one has to implement an "accessor" which tells the shared code - * how to deal with specific item arrays. Different functions have different requirements for the - * accessor. It's easiest to just look at existing accessors like #SimulationItemsAccessor and - * #RepeatItemsAccessor and to implement the same methods. - */ - -#include "BLI_string.h" -#include "BLI_string_utils.hh" - -#include "BKE_node.h" -#include "BKE_node_runtime.hh" - -#include "NOD_socket.hh" - -namespace blender::nodes::socket_items { - -/** - * References a "C-Array" that is stored elsewhere. This is different from a MutableSpan, because - * one can even resize the array through this reference. - */ -template struct SocketItemsRef { - T **items; - int *items_num; - int *active_index; -}; - -/** - * Iterates over the node tree to find the node that this item belongs to. - */ -template -inline bNode *find_node_by_item(bNodeTree &ntree, const typename Accessor::ItemT &item) -{ - ntree.ensure_topology_cache(); - for (bNode *node : ntree.nodes_by_type(Accessor::node_idname)) { - SocketItemsRef array = Accessor::get_items_from_node(*node); - if (&item >= *array.items && &item < *array.items + *array.items_num) { - return node; - } - } - return nullptr; -} - -/** - * Low level utility to remove an item from the array and to shift the elements after it. - */ -template -inline void remove_item(T **items, - int *items_num, - int *active_index, - const int remove_index, - void (*destruct_item)(T *)) -{ - static_assert(std::is_trivial_v); - BLI_assert(remove_index >= 0); - BLI_assert(remove_index < *items_num); - - const int old_items_num = *items_num; - const int new_items_num = old_items_num - 1; - const int old_active_index = *active_index; - - T *old_items = *items; - T *new_items = MEM_cnew_array(new_items_num, __func__); - - std::copy_n(old_items, remove_index, new_items); - std::copy_n( - old_items + remove_index + 1, old_items_num - remove_index - 1, new_items + remove_index); - - destruct_item(&old_items[remove_index]); - MEM_SAFE_FREE(old_items); - - const int new_active_index = std::max( - 0, old_active_index == new_items_num ? new_items_num - 1 : old_active_index); - - *items = new_items; - *items_num = new_items_num; - *active_index = new_active_index; -} - -/** - * Low level utility to remove all elements from an items array. - */ -template -inline void clear_items(T **items, int *items_num, int *active_index, void (*destruct_item)(T *)) -{ - static_assert(std::is_trivial_v); - for (const int i : blender::IndexRange(*items_num)) { - destruct_item(&(*items)[i]); - } - MEM_SAFE_FREE(*items); - *items_num = 0; - *active_index = 0; -} - -/** - * Low level utility to move one item from one index to another. - */ -template -inline void move_item(T *items, const int items_num, const int from_index, const int to_index) -{ - static_assert(std::is_trivial_v); - BLI_assert(from_index >= 0); - BLI_assert(from_index < items_num); - BLI_assert(to_index >= 0); - BLI_assert(to_index < items_num); - UNUSED_VARS_NDEBUG(items_num); - - if (from_index == to_index) { - return; - } - - if (from_index < to_index) { - const T tmp = items[from_index]; - for (int i = from_index; i < to_index; i++) { - items[i] = items[i + 1]; - } - items[to_index] = tmp; - } - else if (from_index > to_index) { - const T tmp = items[from_index]; - for (int i = from_index; i > to_index; i--) { - items[i] = items[i - 1]; - } - items[to_index] = tmp; - } -} - -/** - * Destruct all the items and the free the array itself. - */ -template inline void destruct_array(bNode &node) -{ - using ItemT = typename Accessor::ItemT; - SocketItemsRef ref = Accessor::get_items_from_node(node); - for (const int i : IndexRange(*ref.items_num)) { - ItemT &item = (*ref.items)[i]; - Accessor::destruct_item(&item); - } - MEM_SAFE_FREE(*ref.items); -} - -/** - * Copy the items from the storage of the source node to the storage of the destination node. - */ -template inline void copy_array(const bNode &src_node, bNode &dst_node) -{ - using ItemT = typename Accessor::ItemT; - SocketItemsRef src_ref = Accessor::get_items_from_node(const_cast(src_node)); - SocketItemsRef dst_ref = Accessor::get_items_from_node(dst_node); - const int items_num = *src_ref.items_num; - *dst_ref.items = MEM_cnew_array(items_num, __func__); - for (const int i : IndexRange(items_num)) { - Accessor::copy_item((*src_ref.items)[i], (*dst_ref.items)[i]); - } -} - -/** - * Changes the name of an existing item and makes sure that the name is unique among other the - * other items in the same array. - */ -template -inline void set_item_name_and_make_unique(bNode &node, - typename Accessor::ItemT &item, - const char *value) -{ - using ItemT = typename Accessor::ItemT; - SocketItemsRef array = Accessor::get_items_from_node(node); - const char *default_name = nodeStaticSocketLabel(*Accessor::get_socket_type(item), 0); - - char unique_name[MAX_NAME + 4]; - STRNCPY(unique_name, value); - - struct Args { - SocketItemsRef array; - ItemT *item; - } args = {array, &item}; - BLI_uniquename_cb( - [](void *arg, const char *name) { - const Args &args = *static_cast(arg); - for (ItemT &item : blender::MutableSpan(*args.array.items, *args.array.items_num)) { - if (&item != args.item) { - if (STREQ(*Accessor::get_name(item), name)) { - return true; - } - } - } - return false; - }, - &args, - default_name, - '.', - unique_name, - ARRAY_SIZE(unique_name)); - - char **item_name = Accessor::get_name(item); - MEM_SAFE_FREE(*item_name); - *item_name = BLI_strdup(unique_name); -} - -/** - * Add a new item at the end with the given socket type and name. - */ -template -inline typename Accessor::ItemT *add_item_with_socket_and_name( - bNode &node, const eNodeSocketDatatype socket_type, const char *name) -{ - using ItemT = typename Accessor::ItemT; - BLI_assert(Accessor::supports_socket_type(socket_type)); - - SocketItemsRef array = Accessor::get_items_from_node(node); - - ItemT *old_items = *array.items; - const int old_items_num = *array.items_num; - const int new_items_num = old_items_num + 1; - - ItemT *new_items = MEM_cnew_array(new_items_num, __func__); - std::copy_n(old_items, old_items_num, new_items); - ItemT &new_item = new_items[old_items_num]; - - Accessor::init_with_socket_type_and_name(node, new_item, socket_type, name); - - MEM_SAFE_FREE(old_items); - *array.items = new_items; - *array.items_num = new_items_num; - *array.active_index = old_items_num; - - return &new_item; -} - -/** - * Check if the link connects to the `extend_socket`. If yes, create a new item for the linked - * socket, update the node and then change the link to point to the new socket. - * \return False if the link should be removed. - */ -template -[[nodiscard]] inline bool try_add_item_via_extend_socket(bNodeTree &ntree, - bNode &extend_node, - bNodeSocket &extend_socket, - bNode &storage_node, - bNodeLink &link) -{ - using ItemT = typename Accessor::ItemT; - bNodeSocket *src_socket = nullptr; - if (link.tosock == &extend_socket) { - src_socket = link.fromsock; - } - else if (link.fromsock == &extend_socket) { - src_socket = link.tosock; - } - else { - return false; - } - const eNodeSocketDatatype socket_type = eNodeSocketDatatype(src_socket->type); - if (!Accessor::supports_socket_type(socket_type)) { - return false; - } - const ItemT *item = add_item_with_socket_and_name( - storage_node, socket_type, src_socket->name); - if (item == nullptr) { - return false; - } - update_node_declaration_and_sockets(ntree, extend_node); - const std::string item_identifier = Accessor::socket_identifier_for_item(*item); - if (extend_socket.is_input()) { - bNodeSocket *new_socket = nodeFindSocket(&extend_node, SOCK_IN, item_identifier.c_str()); - link.tosock = new_socket; - } - else { - bNodeSocket *new_socket = nodeFindSocket(&extend_node, SOCK_OUT, item_identifier.c_str()); - link.fromsock = new_socket; - } - return true; -} - -/** - * Allow the item array to be extended from any extend-socket in the node. - * \return False if the link should be removed. - */ -template -[[nodiscard]] inline bool try_add_item_via_any_extend_socket(bNodeTree &ntree, - bNode &extend_node, - bNode &storage_node, - bNodeLink &link) -{ - bNodeSocket *possible_extend_socket = nullptr; - if (link.fromnode == &extend_node) { - possible_extend_socket = link.fromsock; - } - if (link.tonode == &extend_node) { - possible_extend_socket = link.tosock; - } - if (possible_extend_socket == nullptr) { - return true; - } - if (!STREQ(possible_extend_socket->idname, "NodeSocketVirtual")) { - return true; - } - return try_add_item_via_extend_socket( - ntree, extend_node, *possible_extend_socket, storage_node, link); -} - -} // namespace blender::nodes::socket_items diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index 924796a866c..7e9658646c4 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -322,8 +322,8 @@ DefNode(GeometryNode, GEO_NODE_DISTRIBUTE_POINTS_IN_VOLUME, 0, "DISTRIBUTE_POINT DefNode(GeometryNode, GEO_NODE_DISTRIBUTE_POINTS_ON_FACES, def_geo_distribute_points_on_faces, "DISTRIBUTE_POINTS_ON_FACES", DistributePointsOnFaces, "Distribute Points on Faces", "Generate points spread out on the surface of a mesh") DefNode(GeometryNode, GEO_NODE_DUAL_MESH, 0, "DUAL_MESH", DualMesh, "Dual Mesh", "Convert Faces into vertices and vertices into faces") DefNode(GeometryNode, GEO_NODE_DUPLICATE_ELEMENTS, 0, "DUPLICATE_ELEMENTS", DuplicateElements, "Duplicate Elements", "Generate an arbitrary number copies of each selected input element") -DefNode(GeometryNode, GEO_NODE_EDGE_PATHS_TO_CURVES, 0, "EDGE_PATHS_TO_CURVES", EdgePathsToCurves, "Edge Paths to Curves", "Output curves following paths across mesh edges") -DefNode(GeometryNode, GEO_NODE_EDGE_PATHS_TO_SELECTION, 0, "EDGE_PATHS_TO_SELECTION", EdgePathsToSelection, "Edge Paths to Selection", "Output a selection of edges by following paths across mesh edges") +DefNode(GeometryNode, GEO_NODE_EDGE_PATHS_TO_CURVES, 0, "EDGE_PATHS_TO_CURVES", EdgePathsToCurves, "Edge Paths to Curves", "") +DefNode(GeometryNode, GEO_NODE_EDGE_PATHS_TO_SELECTION, 0, "EDGE_PATHS_TO_SELECTION", EdgePathsToSelection, "Edge Paths to Selection", "") DefNode(GeometryNode, GEO_NODE_EDGES_TO_FACE_GROUPS, 0, "EDGES_TO_FACE_GROUPS", EdgesToFaceGroups, "Edges to Face Groups", "Group faces into regions surrounded by the selected boundary edges") DefNode(GeometryNode, GEO_NODE_EVALUATE_AT_INDEX, 0, "FIELD_AT_INDEX", FieldAtIndex, "Evaluate at Index", "Retrieve data of other elements in the context's geometry") DefNode(GeometryNode, GEO_NODE_EVALUATE_ON_DOMAIN, 0, "FIELD_ON_DOMAIN", FieldOnDomain, "Evaluate on Domain", "Retrieve values from a field on a different domain besides the domain from the context") @@ -353,13 +353,12 @@ DefNode(GeometryNode, GEO_NODE_INPUT_MESH_FACE_NEIGHBORS, 0, "MESH_FACE_NEIGHBOR DefNode(GeometryNode, GEO_NODE_INPUT_MESH_ISLAND, 0, "MESH_ISLAND", InputMeshIsland, "Mesh Island", "Retrieve information about separate connected regions in a mesh") DefNode(GeometryNode, GEO_NODE_INPUT_MESH_VERTEX_NEIGHBORS, 0, "MESH_VERTEX_NEIGHBORS", InputMeshVertexNeighbors, "Vertex Neighbors", "Retrieve topology information relating to each vertex of a mesh") DefNode(GeometryNode, GEO_NODE_INPUT_NAMED_ATTRIBUTE, 0, "INPUT_ATTRIBUTE", InputNamedAttribute, "Named Attribute", "Retrieve the data of a specified attribute") -DefNode(GeometryNode, GEO_NODE_INPUT_NAMED_LAYER_SELECTION, 0, "INPUT_NAMED_LAYER_SELECTION", InputNamedLayerSelection, "Named Layer Selection", "Output a selection of a grease pencil layer"); DefNode(GeometryNode, GEO_NODE_INPUT_NORMAL, 0, "INPUT_NORMAL", InputNormal, "Normal", "Retrieve a unit length vector indicating the direction pointing away from the geometry at each element") DefNode(GeometryNode, GEO_NODE_INPUT_POSITION, 0, "POSITION", InputPosition, "Position", "Retrieve a vector indicating the location of each element") DefNode(GeometryNode, GEO_NODE_INPUT_RADIUS, 0, "INPUT_RADIUS", InputRadius, "Radius", "Retrieve the radius at each point on curve or point cloud geometry") DefNode(GeometryNode, GEO_NODE_INPUT_SCENE_TIME, 0, "INPUT_SCENE_TIME", InputSceneTime, "Scene Time", "Retrieve the current time in the scene's animation in units of seconds or frames") DefNode(GeometryNode, GEO_NODE_INPUT_FACE_SMOOTH, 0, "INPUT_SHADE_SMOOTH", InputShadeSmooth, "Is Face Smooth", "Retrieve whether each face is marked for smooth or sharp normals") -DefNode(GeometryNode, GEO_NODE_INPUT_SHORTEST_EDGE_PATHS, 0, "SHORTEST_EDGE_PATHS", InputShortestEdgePaths, "Shortest Edge Paths", "Find the shortest paths along mesh edges to selected end vertices, with customizable cost per edge") +DefNode(GeometryNode, GEO_NODE_INPUT_SHORTEST_EDGE_PATHS, 0, "SHORTEST_EDGE_PATHS", InputShortestEdgePaths, "Shortest Edge Paths", "") DefNode(GeometryNode, GEO_NODE_INPUT_SIGNED_DISTANCE, 0, "SIGNED_DISTANCE", InputSignedDistance, "Signed Distance", "Retrieve the signed distance field grid called 'distance' from a volume") DefNode(GeometryNode, GEO_NODE_INPUT_SPLINE_CYCLIC, 0, "INPUT_SPLINE_CYCLIC",InputSplineCyclic, "Is Spline Cyclic", "Retrieve whether each spline endpoint connects to the beginning") DefNode(GeometryNode, GEO_NODE_INPUT_SPLINE_LENGTH, 0, "SPLINE_LENGTH", SplineLength, "Spline Length", "Retrieve the total length of each spline, as a distance or as a number of points") @@ -440,9 +439,8 @@ DefNode(GeometryNode, GEO_NODE_SET_POSITION, 0, "SET_POSITION", SetPosition, "Se DefNode(GeometryNode, GEO_NODE_SET_SHADE_SMOOTH, 0, "SET_SHADE_SMOOTH", SetShadeSmooth, "Set Shade Smooth", "Control the smoothness of mesh normals around each face by changing the \"shade smooth\" attribute") DefNode(GeometryNode, GEO_NODE_SET_SPLINE_CYCLIC, 0, "SET_SPLINE_CYCLIC", SetSplineCyclic, "Set Spline Cyclic", "Control whether each spline loops back on itself by changing the \"cyclic\" attribute") DefNode(GeometryNode, GEO_NODE_SET_SPLINE_RESOLUTION, 0, "SET_SPLINE_RESOLUTION", SetSplineResolution, "Set Spline Resolution", "Control how many evaluated points should be generated on every curve segment") -DefNode(GeometryNode, GEO_NODE_SIMULATION_INPUT, def_geo_simulation_input, "SIMULATION_INPUT", SimulationInput, "Simulation Input", "Input data for the simulation zone") -DefNode(GeometryNode, GEO_NODE_SIMULATION_OUTPUT, def_geo_simulation_output, "SIMULATION_OUTPUT", SimulationOutput, "Simulation Output", " Output data from the simulation zone") -DefNode(GeometryNode, GEO_NODE_SPLIT_TO_INSTANCES, 0, "Split to Instances", SplitToInstances, "Split to Instances", "Create separate geometries containing the elements from the same group") +DefNode(GeometryNode, GEO_NODE_SIMULATION_INPUT, def_geo_simulation_input, "SIMULATION_INPUT", SimulationInput, "Simulation Input", "") +DefNode(GeometryNode, GEO_NODE_SIMULATION_OUTPUT, def_geo_simulation_output, "SIMULATION_OUTPUT", SimulationOutput, "Simulation Output", "") DefNode(GeometryNode, GEO_NODE_SPLIT_EDGES, 0, "SPLIT_EDGES", SplitEdges, "Split Edges", "Duplicate mesh edges and break connections with the surrounding faces") DefNode(GeometryNode, GEO_NODE_STORE_NAMED_ATTRIBUTE, 0, "STORE_NAMED_ATTRIBUTE", StoreNamedAttribute, "Store Named Attribute", "Store the result of a field on a geometry as an attribute with the specified name") DefNode(GeometryNode, GEO_NODE_STRING_JOIN, 0, "STRING_JOIN", StringJoin, "Join Strings", "Combine any number of input strings") diff --git a/source/blender/nodes/NOD_zone_socket_items.hh b/source/blender/nodes/NOD_zone_socket_items.hh deleted file mode 100644 index 72e4703fe9d..00000000000 --- a/source/blender/nodes/NOD_zone_socket_items.hh +++ /dev/null @@ -1,144 +0,0 @@ -/* SPDX-FileCopyrightText: 2023 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -#pragma once - -#include "DNA_node_types.h" - -#include "NOD_socket_items.hh" - -struct BlendWriter; -struct BlendDataReader; - -namespace blender::nodes { - -/** - * Makes it possible to use various functions (e.g. the ones in `NOD_socket_items.hh`) with - * simulation items. - */ -struct SimulationItemsAccessor { - using ItemT = NodeSimulationItem; - static StructRNA *item_srna; - static int node_type; - static constexpr const char *node_idname = "GeometryNodeSimulationOutput"; - - static socket_items::SocketItemsRef get_items_from_node(bNode &node) - { - auto *storage = static_cast(node.storage); - return {&storage->items, &storage->items_num, &storage->active_index}; - } - static void copy_item(const NodeSimulationItem &src, NodeSimulationItem &dst) - { - dst = src; - dst.name = BLI_strdup_null(dst.name); - } - static void destruct_item(NodeSimulationItem *item) - { - MEM_SAFE_FREE(item->name); - } - static void blend_write(BlendWriter *writer, const bNode &node); - static void blend_read_data(BlendDataReader *reader, bNode &node); - static short *get_socket_type(NodeSimulationItem &item) - { - return &item.socket_type; - } - static char **get_name(NodeSimulationItem &item) - { - return &item.name; - } - static bool supports_socket_type(const eNodeSocketDatatype socket_type) - { - return ELEM(socket_type, - SOCK_FLOAT, - SOCK_VECTOR, - SOCK_RGBA, - SOCK_BOOLEAN, - SOCK_ROTATION, - SOCK_INT, - SOCK_STRING, - SOCK_GEOMETRY); - } - static void init_with_socket_type_and_name(bNode &node, - NodeSimulationItem &item, - const eNodeSocketDatatype socket_type, - const char *name) - { - auto *storage = static_cast(node.storage); - item.socket_type = socket_type; - item.identifier = storage->next_identifier++; - socket_items::set_item_name_and_make_unique(node, item, name); - } - static std::string socket_identifier_for_item(const NodeSimulationItem &item) - { - return "Item_" + std::to_string(item.identifier); - } -}; - -/** - * Makes it possible to use various functions (e.g. the ones in `NOD_socket_items.hh`) with - * repeat items. - */ -struct RepeatItemsAccessor { - using ItemT = NodeRepeatItem; - static StructRNA *item_srna; - static int node_type; - static constexpr const char *node_idname = "GeometryNodeRepeatOutput"; - - static socket_items::SocketItemsRef get_items_from_node(bNode &node) - { - auto *storage = static_cast(node.storage); - return {&storage->items, &storage->items_num, &storage->active_index}; - } - static void copy_item(const NodeRepeatItem &src, NodeRepeatItem &dst) - { - dst = src; - dst.name = BLI_strdup_null(dst.name); - } - static void destruct_item(NodeRepeatItem *item) - { - MEM_SAFE_FREE(item->name); - } - static void blend_write(BlendWriter *writer, const bNode &node); - static void blend_read_data(BlendDataReader *reader, bNode &node); - static short *get_socket_type(NodeRepeatItem &item) - { - return &item.socket_type; - } - static char **get_name(NodeRepeatItem &item) - { - return &item.name; - } - static bool supports_socket_type(const eNodeSocketDatatype socket_type) - { - return ELEM(socket_type, - SOCK_FLOAT, - SOCK_VECTOR, - SOCK_RGBA, - SOCK_BOOLEAN, - SOCK_ROTATION, - SOCK_INT, - SOCK_STRING, - SOCK_GEOMETRY, - SOCK_OBJECT, - SOCK_MATERIAL, - SOCK_IMAGE, - SOCK_COLLECTION); - } - static void init_with_socket_type_and_name(bNode &node, - NodeRepeatItem &item, - const eNodeSocketDatatype socket_type, - const char *name) - { - auto *storage = static_cast(node.storage); - item.socket_type = socket_type; - item.identifier = storage->next_identifier++; - socket_items::set_item_name_and_make_unique(node, item, name); - } - static std::string socket_identifier_for_item(const NodeRepeatItem &item) - { - return "Item_" + std::to_string(item.identifier); - } -}; - -} // namespace blender::nodes diff --git a/source/blender/nodes/composite/node_composite_tree.cc b/source/blender/nodes/composite/node_composite_tree.cc index 10573d7256a..26e4f488c53 100644 --- a/source/blender/nodes/composite/node_composite_tree.cc +++ b/source/blender/nodes/composite/node_composite_tree.cc @@ -31,11 +31,11 @@ #include "RNA_access.hh" #include "RNA_prototypes.h" -#include "NOD_composite.hh" +#include "NOD_composite.h" #include "node_composite_util.hh" #ifdef WITH_COMPOSITOR_CPU -# include "COM_compositor.hh" +# include "COM_compositor.h" #endif static void composite_get_from_context( diff --git a/source/blender/nodes/composite/node_composite_util.hh b/source/blender/nodes/composite/node_composite_util.hh index fa4742605fa..2b54e63d085 100644 --- a/source/blender/nodes/composite/node_composite_util.hh +++ b/source/blender/nodes/composite/node_composite_util.hh @@ -14,7 +14,7 @@ #include "node_composite_register.hh" #include "node_util.hh" -#include "NOD_composite.hh" +#include "NOD_composite.h" #include "NOD_socket.hh" #include "NOD_socket_declarations.hh" diff --git a/source/blender/nodes/composite/nodes/node_composite_bokehblur.cc b/source/blender/nodes/composite/nodes/node_composite_bokehblur.cc index caebfe049d9..a5e46232ec5 100644 --- a/source/blender/nodes/composite/nodes/node_composite_bokehblur.cc +++ b/source/blender/nodes/composite/nodes/node_composite_bokehblur.cc @@ -30,7 +30,7 @@ static void cmp_node_bokehblur_declare(NodeDeclarationBuilder &b) .compositor_domain_priority(0); b.add_input("Bokeh") .default_value({1.0f, 1.0f, 1.0f, 1.0f}) - .compositor_realization_options(CompositorInputRealizationOptions::None); + .compositor_realization_options(CompositorInputRealizationOptions::RealizeRotation); b.add_input("Size") .default_value(1.0f) .min(0.0f) diff --git a/source/blender/nodes/composite/nodes/node_composite_common.cc b/source/blender/nodes/composite/nodes/node_composite_common.cc index d326f115f01..00032703b03 100644 --- a/source/blender/nodes/composite/nodes/node_composite_common.cc +++ b/source/blender/nodes/composite/nodes/node_composite_common.cc @@ -33,7 +33,7 @@ void register_node_type_cmp_group() blender::bke::node_type_size(&ntype, 140, 60, 400); ntype.labelfunc = node_group_label; - ntype.declare = blender::nodes::node_group_declare; + ntype.declare_dynamic = blender::nodes::node_group_declare_dynamic; nodeRegisterType(&ntype); } @@ -47,5 +47,5 @@ void register_node_type_cmp_custom_group(bNodeType *ntype) if (ntype->insert_link == nullptr) { ntype->insert_link = node_insert_link_default; } - ntype->declare = blender::nodes::node_group_declare; + ntype->declare_dynamic = blender::nodes::node_group_declare_dynamic; } diff --git a/source/blender/nodes/composite/nodes/node_composite_composite.cc b/source/blender/nodes/composite/nodes/node_composite_composite.cc index 6ec4cf9b312..3a425eb33e4 100644 --- a/source/blender/nodes/composite/nodes/node_composite_composite.cc +++ b/source/blender/nodes/composite/nodes/node_composite_composite.cc @@ -88,7 +88,7 @@ class CompositeOperation : public NodeOperation { * that compositing region. */ const rcti compositing_region = context().get_compositing_region(); const int2 lower_bound = int2(compositing_region.xmin, compositing_region.ymin); - GPU_shader_uniform_2iv(shader, "lower_bound", lower_bound); + GPU_shader_uniform_2iv(shader, "compositing_region_lower_bound", lower_bound); const Result &image = get_input("Image"); image.bind_as_texture(shader, "input_tx"); @@ -116,7 +116,7 @@ class CompositeOperation : public NodeOperation { * that compositing region. */ const rcti compositing_region = context().get_compositing_region(); const int2 lower_bound = int2(compositing_region.xmin, compositing_region.ymin); - GPU_shader_uniform_2iv(shader, "lower_bound", lower_bound); + GPU_shader_uniform_2iv(shader, "compositing_region_lower_bound", lower_bound); const Result &image = get_input("Image"); image.bind_as_texture(shader, "input_tx"); @@ -143,7 +143,7 @@ class CompositeOperation : public NodeOperation { * that compositing region. */ const rcti compositing_region = context().get_compositing_region(); const int2 lower_bound = int2(compositing_region.xmin, compositing_region.ymin); - GPU_shader_uniform_2iv(shader, "lower_bound", lower_bound); + GPU_shader_uniform_2iv(shader, "compositing_region_lower_bound", lower_bound); const Result &image = get_input("Image"); image.bind_as_texture(shader, "input_tx"); diff --git a/source/blender/nodes/composite/nodes/node_composite_double_edge_mask.cc b/source/blender/nodes/composite/nodes/node_composite_double_edge_mask.cc index 34437d00b4d..4d6a88c38c2 100644 --- a/source/blender/nodes/composite/nodes/node_composite_double_edge_mask.cc +++ b/source/blender/nodes/composite/nodes/node_composite_double_edge_mask.cc @@ -66,14 +66,14 @@ class DoubleEdgeMaskOperation : public NodeOperation { /* Compute an image that marks the boundary pixels of the masks as seed pixels in the format * expected by the jump flooding algorithm. */ - Result inner_boundary = Result::Temporary(ResultType::Int2, texture_pool()); - Result outer_boundary = Result::Temporary(ResultType::Int2, texture_pool()); + Result inner_boundary = Result::Temporary(ResultType::Color, texture_pool()); + Result outer_boundary = Result::Temporary(ResultType::Color, texture_pool()); compute_boundary(inner_boundary, outer_boundary); /* Compute a jump flooding table for each mask boundary to get a distance transform to each of * the boundaries. */ - Result flooded_inner_boundary = Result::Temporary(ResultType::Int2, texture_pool()); - Result flooded_outer_boundary = Result::Temporary(ResultType::Int2, texture_pool()); + Result flooded_inner_boundary = Result::Temporary(ResultType::Color, texture_pool()); + Result flooded_outer_boundary = Result::Temporary(ResultType::Color, texture_pool()); jump_flooding(context(), inner_boundary, flooded_inner_boundary); jump_flooding(context(), outer_boundary, flooded_outer_boundary); inner_boundary.release(); diff --git a/source/blender/nodes/composite/nodes/node_composite_hue_sat_val.cc b/source/blender/nodes/composite/nodes/node_composite_hue_sat_val.cc index 840aa0e20fb..b65c3d0e061 100644 --- a/source/blender/nodes/composite/nodes/node_composite_hue_sat_val.cc +++ b/source/blender/nodes/composite/nodes/node_composite_hue_sat_val.cc @@ -34,11 +34,11 @@ static void cmp_node_huesatval_declare(NodeDeclarationBuilder &b) .subtype(PROP_FACTOR) .compositor_domain_priority(2); b.add_input("Value") + .translation_context(BLT_I18NCONTEXT_COLOR) .default_value(1.0f) .min(0.0f) .max(2.0f) .subtype(PROP_FACTOR) - .translation_context(BLT_I18NCONTEXT_COLOR) .compositor_domain_priority(3); b.add_input("Fac") .default_value(1.0f) diff --git a/source/blender/nodes/composite/nodes/node_composite_image.cc b/source/blender/nodes/composite/nodes/node_composite_image.cc index 73ebe92e1bd..f5a3401c641 100644 --- a/source/blender/nodes/composite/nodes/node_composite_image.cc +++ b/source/blender/nodes/composite/nodes/node_composite_image.cc @@ -516,9 +516,6 @@ class ImageOperation : public NodeOperation { GPUShader *shader = shader_manager().get(get_shader_name(identifier)); GPU_shader_bind(shader); - const int2 lower_bound = int2(0); - GPU_shader_uniform_2iv(shader, "lower_bound", lower_bound); - const int input_unit = GPU_shader_get_sampler_binding(shader, "input_tx"); GPU_texture_bind(image_texture, input_unit); @@ -560,13 +557,13 @@ class ImageOperation : public NodeOperation { const char *get_shader_name(StringRef identifier) { if (identifier == "Alpha") { - return "compositor_read_input_alpha"; + return "compositor_extract_alpha_from_color"; } else if (get_result(identifier).type() == ResultType::Color) { - return "compositor_read_input_color"; + return "compositor_convert_color_to_half_color"; } else { - return "compositor_read_input_float"; + return "compositor_convert_float_to_half_float"; } } @@ -833,10 +830,10 @@ class RenderLayerOperation : public NodeOperation { GPUTexture *combined_texture = context().get_input_texture( scene, view_layer, RE_PASSNAME_COMBINED); if (image_result.should_compute()) { - execute_pass(image_result, combined_texture, "compositor_read_input_color"); + execute_pass(image_result, combined_texture, "compositor_read_pass_color"); } if (alpha_result.should_compute()) { - execute_pass(alpha_result, combined_texture, "compositor_read_input_alpha"); + execute_pass(alpha_result, combined_texture, "compositor_read_pass_alpha"); } } @@ -854,13 +851,13 @@ class RenderLayerOperation : public NodeOperation { GPUTexture *pass_texture = context().get_input_texture( scene, view_layer, output->identifier); if (output->type == SOCK_FLOAT) { - execute_pass(result, pass_texture, "compositor_read_input_float"); + execute_pass(result, pass_texture, "compositor_read_pass_float"); } else if (output->type == SOCK_VECTOR) { - execute_pass(result, pass_texture, "compositor_read_input_vector"); + execute_pass(result, pass_texture, "compositor_read_pass_vector"); } else if (output->type == SOCK_RGBA) { - execute_pass(result, pass_texture, "compositor_read_input_color"); + execute_pass(result, pass_texture, "compositor_read_pass_color"); } else { BLI_assert_unreachable(); @@ -884,7 +881,7 @@ class RenderLayerOperation : public NodeOperation { * compositing region into an appropriately sized texture. */ const rcti compositing_region = context().get_compositing_region(); const int2 lower_bound = int2(compositing_region.xmin, compositing_region.ymin); - GPU_shader_uniform_2iv(shader, "lower_bound", lower_bound); + GPU_shader_uniform_2iv(shader, "compositing_region_lower_bound", lower_bound); const int input_unit = GPU_shader_get_sampler_binding(shader, "input_tx"); GPU_texture_bind(pass_texture, input_unit); diff --git a/source/blender/nodes/composite/nodes/node_composite_inpaint.cc b/source/blender/nodes/composite/nodes/node_composite_inpaint.cc index 766ff02459b..6d858df1601 100644 --- a/source/blender/nodes/composite/nodes/node_composite_inpaint.cc +++ b/source/blender/nodes/composite/nodes/node_composite_inpaint.cc @@ -56,7 +56,7 @@ class InpaintOperation : public NodeOperation { Result inpainting_boundary = compute_inpainting_boundary(); /* Compute a jump flooding table to get the closest boundary pixel to each pixel. */ - Result flooded_boundary = Result::Temporary(ResultType::Int2, texture_pool()); + Result flooded_boundary = Result::Temporary(ResultType::Color, texture_pool()); jump_flooding(context(), inpainting_boundary, flooded_boundary); inpainting_boundary.release(); @@ -73,7 +73,7 @@ class InpaintOperation : public NodeOperation { const Result &input = get_input("Image"); input.bind_as_texture(shader, "input_tx"); - Result inpainting_boundary = Result::Temporary(ResultType::Int2, texture_pool()); + Result inpainting_boundary = Result::Temporary(ResultType::Color, texture_pool()); const Domain domain = compute_domain(); inpainting_boundary.allocate_texture(domain); inpainting_boundary.bind_as_image(shader, "boundary_img"); @@ -92,7 +92,7 @@ class InpaintOperation : public NodeOperation { GPUShader *shader = shader_manager().get("compositor_inpaint_compute_region"); GPU_shader_bind(shader); - GPU_shader_uniform_1i(shader, "max_distance", get_distance()); + GPU_shader_uniform_1i(shader, "distance", get_distance()); const Result &input = get_input("Image"); input.bind_as_texture(shader, "input_tx"); diff --git a/source/blender/nodes/composite/nodes/node_composite_keyingscreen.cc b/source/blender/nodes/composite/nodes/node_composite_keyingscreen.cc index aa1ccf579b4..eaf9c75e8f2 100644 --- a/source/blender/nodes/composite/nodes/node_composite_keyingscreen.cc +++ b/source/blender/nodes/composite/nodes/node_composite_keyingscreen.cc @@ -6,28 +6,23 @@ * \ingroup cmpnodes */ -#include "BLI_math_base.hh" -#include "BLI_math_vector_types.hh" -#include "BLI_string.h" - -#include "DNA_defaults.h" #include "DNA_movieclip_types.h" #include "DNA_tracking_types.h" +#include "BLI_math_base.h" +#include "BLI_math_color.h" +#include "BLI_string.h" + #include "BKE_context.h" #include "BKE_lib_id.h" -#include "BKE_movieclip.h" #include "BKE_tracking.h" -#include "GPU_texture.h" - #include "RNA_access.hh" #include "RNA_prototypes.h" #include "UI_interface.hh" #include "UI_resources.hh" -#include "COM_keying_screen.hh" #include "COM_node_operation.hh" #include "node_composite_util.hh" @@ -36,8 +31,6 @@ namespace blender::nodes::node_composite_keyingscreen_cc { -NODE_STORAGE_FUNCS(NodeKeyingScreenData) - static void cmp_node_keyingscreen_declare(NodeDeclarationBuilder &b) { b.add_output("Screen").translation_context(BLT_I18NCONTEXT_ID_SCREEN); @@ -48,7 +41,6 @@ static void node_composit_init_keyingscreen(const bContext *C, PointerRNA *ptr) bNode *node = (bNode *)ptr->data; NodeKeyingScreenData *data = MEM_cnew(__func__); - data->smoothness = 0.0f; node->storage = data; const Scene *scene = CTX_data_scene(C); @@ -86,8 +78,6 @@ static void node_composit_buts_keyingscreen(uiLayout *layout, bContext *C, Point col = uiLayoutColumn(layout, true); uiItemPointerR(col, ptr, "tracking_object", &tracking_ptr, "objects", "", ICON_OBJECT_DATA); } - - uiItemR(layout, ptr, "smoothness", UI_ITEM_NONE, nullptr, ICON_NONE); } using namespace blender::realtime_compositor; @@ -98,67 +88,8 @@ class KeyingScreenOperation : public NodeOperation { void execute() override { - Result &keying_screen = get_result("Screen"); - MovieTrackingObject *movie_tracking_object = get_movie_tracking_object(); - if (!movie_tracking_object) { - keying_screen.allocate_invalid(); - return; - } - - KeyingScreen &cached_keying_screen = context().cache_manager().keying_screens.get( - context(), get_movie_clip(), movie_tracking_object, get_smoothness()); - - const Domain domain = compute_domain(); - keying_screen.allocate_texture(domain); - GPU_texture_copy(keying_screen.texture(), cached_keying_screen.texture()); - } - - Domain compute_domain() override - { - return Domain(get_size()); - } - - MovieTrackingObject *get_movie_tracking_object() - { - MovieClip *movie_clip = get_movie_clip(); - if (!movie_clip) { - return nullptr; - } - - MovieTracking *movie_tracking = &movie_clip->tracking; - - MovieTrackingObject *movie_tracking_object = BKE_tracking_object_get_named( - movie_tracking, node_storage(bnode()).tracking_object); - if (movie_tracking_object) { - return movie_tracking_object; - } - - return BKE_tracking_object_get_active(movie_tracking); - } - - int2 get_size() - { - MovieClipUser movie_clip_user = *DNA_struct_default_get(MovieClipUser); - const int scene_frame = context().get_frame_number(); - const int clip_frame = BKE_movieclip_remap_scene_to_clip_frame(get_movie_clip(), scene_frame); - BKE_movieclip_user_set_frame(&movie_clip_user, clip_frame); - - int2 size; - BKE_movieclip_get_size(get_movie_clip(), &movie_clip_user, &size.x, &size.y); - return size; - } - - /* The reciprocal of the smoothness is used as a shaping parameter for the radial basis function - * used in the RBF interpolation. The exponential nature of the function can cause numerical - * instability for low smoothness values, so we empirically choose 0.15 as a lower limit. */ - float get_smoothness() - { - return math::interpolate(0.15f, 1.0f, node_storage(bnode()).smoothness); - } - - MovieClip *get_movie_clip() - { - return reinterpret_cast(bnode().id); + get_result("Screen").allocate_invalid(); + context().set_info_message("Viewport compositor setup not fully supported"); } }; @@ -182,6 +113,8 @@ void register_node_type_cmp_keyingscreen() node_type_storage( &ntype, "NodeKeyingScreenData", node_free_standard_storage, node_copy_standard_storage); ntype.get_compositor_operation = file_ns::get_compositor_operation; + ntype.realtime_compositor_unsupported_message = N_( + "Node not supported in the Viewport compositor"); nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_kuwahara.cc b/source/blender/nodes/composite/nodes/node_composite_kuwahara.cc index 71331ea3ea8..eaa4de38e43 100644 --- a/source/blender/nodes/composite/nodes/node_composite_kuwahara.cc +++ b/source/blender/nodes/composite/nodes/node_composite_kuwahara.cc @@ -32,7 +32,6 @@ static void cmp_node_kuwahara_declare(NodeDeclarationBuilder &b) b.add_input("Image") .default_value({1.0f, 1.0f, 1.0f, 1.0f}) .compositor_domain_priority(0); - b.add_input("Size").default_value(6.0f).compositor_domain_priority(1); b.add_output("Image"); } @@ -42,6 +41,7 @@ static void node_composit_init_kuwahara(bNodeTree * /*ntree*/, bNode *node) node->storage = data; /* Set defaults. */ + data->size = 6; data->uniformity = 4; data->eccentricity = 1.0; data->sharpness = 0.5; @@ -54,6 +54,7 @@ static void node_composit_buts_kuwahara(uiLayout *layout, bContext * /*C*/, Poin col = uiLayoutColumn(layout, false); uiItemR(col, ptr, "variation", UI_ITEM_NONE, nullptr, ICON_NONE); + uiItemR(col, ptr, "size", UI_ITEM_NONE, nullptr, ICON_NONE); const int variation = RNA_enum_get(ptr, "variation"); @@ -89,25 +90,19 @@ class ConvertKuwaharaOperation : public NodeOperation { { /* For high radii, we accelerate the filter using a summed area table, making the filter * execute in constant time as opposed to the trivial quadratic complexity. */ - Result &size_input = get_input("Size"); - if (size_input.is_single_value() && size_input.get_float_value() > 5.0f) { + if (node_storage(bnode()).size > 5) { execute_classic_summed_area_table(); return; } - GPUShader *shader = shader_manager().get(get_classic_convolution_shader_name()); + GPUShader *shader = shader_manager().get("compositor_kuwahara_classic"); GPU_shader_bind(shader); + GPU_shader_uniform_1i(shader, "radius", node_storage(bnode()).size); + const Result &input_image = get_input("Image"); input_image.bind_as_texture(shader, "input_tx"); - if (size_input.is_single_value()) { - GPU_shader_uniform_1i(shader, "size", int(size_input.get_float_value())); - } - else { - size_input.bind_as_texture(shader, "size_tx"); - } - const Domain domain = compute_domain(); Result &output_image = get_result("Image"); output_image.allocate_texture(domain); @@ -130,16 +125,10 @@ class ConvertKuwaharaOperation : public NodeOperation { summed_area_table( context(), get_input("Image"), squared_table, SummedAreaTableOperation::Square); - GPUShader *shader = shader_manager().get(get_classic_summed_area_table_shader_name()); + GPUShader *shader = shader_manager().get("compositor_kuwahara_classic_summed_area_table"); GPU_shader_bind(shader); - Result &size_input = get_input("Size"); - if (size_input.is_single_value()) { - GPU_shader_uniform_1i(shader, "size", int(size_input.get_float_value())); - } - else { - size_input.bind_as_texture(shader, "size_tx"); - } + GPU_shader_uniform_1i(shader, "radius", node_storage(bnode()).size); table.bind_as_texture(shader, "table_tx"); squared_table.bind_as_texture(shader, "squared_table_tx"); @@ -175,23 +164,16 @@ class ConvertKuwaharaOperation : public NodeOperation { float2(node_storage(bnode()).uniformity)); structure_tensor.release(); - GPUShader *shader = shader_manager().get(get_anisotropic_shader_name()); + GPUShader *shader = shader_manager().get("compositor_kuwahara_anisotropic"); GPU_shader_bind(shader); + GPU_shader_uniform_1i(shader, "radius", node_storage(bnode()).size); GPU_shader_uniform_1f(shader, "eccentricity", get_eccentricity()); GPU_shader_uniform_1f(shader, "sharpness", get_sharpness()); Result &input = get_input("Image"); input.bind_as_texture(shader, "input_tx"); - Result &size_input = get_input("Size"); - if (size_input.is_single_value()) { - GPU_shader_uniform_1f(shader, "size", size_input.get_float_value()); - } - else { - size_input.bind_as_texture(shader, "size_tx"); - } - smoothed_structure_tensor.bind_as_texture(shader, "structure_tensor_tx"); const Domain domain = compute_domain(); @@ -232,44 +214,15 @@ class ConvertKuwaharaOperation : public NodeOperation { return structure_tensor; } - const char *get_classic_convolution_shader_name() - { - if (is_constant_size()) { - return "compositor_kuwahara_classic_convolution_constant_size"; - } - return "compositor_kuwahara_classic_convolution_variable_size"; - } - - const char *get_classic_summed_area_table_shader_name() - { - if (is_constant_size()) { - return "compositor_kuwahara_classic_summed_area_table_constant_size"; - } - return "compositor_kuwahara_classic_summed_area_table_variable_size"; - } - - const char *get_anisotropic_shader_name() - { - if (is_constant_size()) { - return "compositor_kuwahara_anisotropic_constant_size"; - } - return "compositor_kuwahara_anisotropic_variable_size"; - } - - bool is_constant_size() - { - return get_input("Size").is_single_value(); - } - - /* The sharpness controls the sharpness of the transitions between the kuwahara sectors, which - * is controlled by the weighting function pow(standard_deviation, -sharpness) as can be seen - * in the shader. The transition is completely smooth when the sharpness is zero and completely - * sharp when it is infinity. But realistically, the sharpness doesn't change much beyond the - * value of 16 due to its exponential nature, so we just assume a maximum sharpness of 16. + /* The sharpness controls the sharpness of the transitions between the kuwahara sectors, which is + * controlled by the weighting function pow(standard_deviation, -sharpness) as can be seen in the + * shader. The transition is completely smooth when the sharpness is zero and completely sharp + * when it is infinity. But realistically, the sharpness doesn't change much beyond the value of + * 16 due to its exponential nature, so we just assume a maximum sharpness of 16. * * The stored sharpness is in the range [0, 1], so we multiply by 16 to get it in the range - * [0, 16], however, we also square it before multiplication to slow down the rate of change - * near zero to counter its exponential nature for more intuitive user control. */ + * [0, 16], however, we also square it before multiplication to slow down the rate of change near + * zero to counter its exponential nature for more intuitive user control. */ float get_sharpness() { const float sharpness_factor = node_storage(bnode()).sharpness; @@ -283,12 +236,12 @@ class ConvertKuwaharaOperation : public NodeOperation { * (eccentricity + anisotropy) / eccentricity * * Since the anisotropy is in the [0, 1] range, the factor tends to 1 as the eccentricity tends - * to infinity and tends to infinity when the eccentricity tends to zero. The stored - * eccentricity is in the range [0, 2], we map that to the range [infinity, 0.5] by taking the - * reciprocal, satisfying the aforementioned limits. The upper limit doubles the computed - * default eccentricity, which users can use to enhance the directionality of the filter. - * Instead of actual infinity, we just use an eccentricity of 1 / 0.01 since the result is very - * similar to that of infinity. */ + * to infinity and tends to infinity when the eccentricity tends to zero. The stored eccentricity + * is in the range [0, 2], we map that to the range [infinity, 0.5] by taking the reciprocal, + * satisfying the aforementioned limits. The upper limit doubles the computed default + * eccentricity, which users can use to enhance the directionality of the filter. Instead of + * actual infinity, we just use an eccentricity of 1 / 0.01 since the result is very similar to + * that of infinity. */ float get_eccentricity() { return 1.0f / math::max(0.01f, node_storage(bnode()).eccentricity); diff --git a/source/blender/nodes/composite/nodes/node_composite_movieclip.cc b/source/blender/nodes/composite/nodes/node_composite_movieclip.cc index 2073247aeb7..6ed870593e8 100644 --- a/source/blender/nodes/composite/nodes/node_composite_movieclip.cc +++ b/source/blender/nodes/composite/nodes/node_composite_movieclip.cc @@ -126,12 +126,9 @@ class MovieClipOperation : public NodeOperation { GPU_texture_height(movie_clip_texture)); result.allocate_texture(Domain(size)); - GPUShader *shader = shader_manager().get("compositor_read_input_color"); + GPUShader *shader = shader_manager().get("compositor_convert_color_to_half_color"); GPU_shader_bind(shader); - const int2 lower_bound = int2(0); - GPU_shader_uniform_2iv(shader, "lower_bound", lower_bound); - const int input_unit = GPU_shader_get_sampler_binding(shader, "input_tx"); GPU_texture_bind(movie_clip_texture, input_unit); @@ -163,12 +160,9 @@ class MovieClipOperation : public NodeOperation { GPU_texture_height(movie_clip_texture)); result.allocate_texture(Domain(size)); - GPUShader *shader = shader_manager().get("compositor_read_input_alpha"); + GPUShader *shader = shader_manager().get("compositor_extract_alpha_from_color"); GPU_shader_bind(shader); - const int2 lower_bound = int2(0); - GPU_shader_uniform_2iv(shader, "lower_bound", lower_bound); - const int input_unit = GPU_shader_get_sampler_binding(shader, "input_tx"); GPU_texture_bind(movie_clip_texture, input_unit); diff --git a/source/blender/nodes/composite/nodes/node_composite_output_file.cc b/source/blender/nodes/composite/nodes/node_composite_output_file.cc index 44554ace39b..fc01b006240 100644 --- a/source/blender/nodes/composite/nodes/node_composite_output_file.cc +++ b/source/blender/nodes/composite/nodes/node_composite_output_file.cc @@ -10,7 +10,7 @@ #include "BLI_string.h" #include "BLI_string_utf8.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_utildefines.h" #include "BKE_context.h" diff --git a/source/blender/nodes/composite/nodes/node_composite_rotate.cc b/source/blender/nodes/composite/nodes/node_composite_rotate.cc index 974453fbb5f..d986015648b 100644 --- a/source/blender/nodes/composite/nodes/node_composite_rotate.cc +++ b/source/blender/nodes/composite/nodes/node_composite_rotate.cc @@ -7,13 +7,11 @@ */ #include "BLI_assert.h" -#include "BLI_math_angle_types.hh" #include "BLI_math_matrix.hh" #include "UI_interface.hh" #include "UI_resources.hh" -#include "COM_algorithm_transform.hh" #include "COM_node_operation.hh" #include "node_composite_util.hh" @@ -55,12 +53,15 @@ class RotateOperation : public NodeOperation { void execute() override { Result &input = get_input("Image"); - Result &output = get_result("Image"); + Result &result = get_result("Image"); + input.pass_through(result); const math::AngleRadian rotation = get_input("Degr").get_float_value_default(0.0f); + const float3x3 transformation = math::from_rotation(rotation); - transform(context(), input, output, transformation, get_interpolation()); + result.transform(transformation); + result.get_realization_options().interpolation = get_interpolation(); } Interpolation get_interpolation() diff --git a/source/blender/nodes/composite/nodes/node_composite_scale.cc b/source/blender/nodes/composite/nodes/node_composite_scale.cc index 5d892b9662a..7150c76f299 100644 --- a/source/blender/nodes/composite/nodes/node_composite_scale.cc +++ b/source/blender/nodes/composite/nodes/node_composite_scale.cc @@ -7,10 +7,8 @@ */ #include "BLI_assert.h" -#include "BLI_math_angle_types.hh" #include "BLI_math_base.hh" #include "BLI_math_matrix.hh" -#include "BLI_math_matrix_types.hh" #include "BLI_math_vector_types.hh" #include "BLI_string.h" @@ -19,7 +17,6 @@ #include "UI_interface.hh" #include "UI_resources.hh" -#include "COM_algorithm_transform.hh" #include "COM_node_operation.hh" #include "node_composite_util.hh" @@ -85,16 +82,13 @@ class ScaleOperation : public NodeOperation { void execute() override { Result &input = get_input("Image"); - Result &output = get_result("Image"); + Result &result = get_result("Image"); + input.pass_through(result); - const float2 scale = get_scale(); - const math::AngleRadian rotation = 0.0f; - const float2 translation = get_translation(); const float3x3 transformation = math::from_loc_rot_scale( - translation, rotation, scale); + get_translation(), math::AngleRadian(0.0f), get_scale()); - const Interpolation interpolation = input.get_realization_options().interpolation; - transform(context(), input, output, transformation, interpolation); + result.transform(transformation); } float2 get_scale() diff --git a/source/blender/nodes/composite/nodes/node_composite_split_viewer.cc b/source/blender/nodes/composite/nodes/node_composite_split_viewer.cc index 467a799ebff..2640ff40d08 100644 --- a/source/blender/nodes/composite/nodes/node_composite_split_viewer.cc +++ b/source/blender/nodes/composite/nodes/node_composite_split_viewer.cc @@ -65,7 +65,7 @@ class ViewerOperation : public NodeOperation { * that compositing region. */ const rcti compositing_region = context().get_compositing_region(); const int2 lower_bound = int2(compositing_region.xmin, compositing_region.ymin); - GPU_shader_uniform_2iv(shader, "lower_bound", lower_bound); + GPU_shader_uniform_2iv(shader, "compositing_region_lower_bound", lower_bound); GPU_shader_uniform_1f(shader, "split_ratio", get_split_ratio()); @@ -77,12 +77,11 @@ class ViewerOperation : public NodeOperation { const Result &second_image = get_input("Image_001"); second_image.bind_as_texture(shader, "second_image_tx"); - const int2 viewer_size = compute_domain().size; - GPUTexture *output_texture = context().get_viewer_output_texture(viewer_size); + GPUTexture *output_texture = context().get_viewer_output_texture(); const int image_unit = GPU_shader_get_sampler_binding(shader, "output_img"); GPU_texture_image_bind(output_texture, image_unit); - compute_dispatch_threads_at_least(shader, viewer_size); + compute_dispatch_threads_at_least(shader, compositing_region_size); first_image.unbind_as_texture(); second_image.unbind_as_texture(); @@ -90,17 +89,11 @@ class ViewerOperation : public NodeOperation { GPU_shader_unbind(); } + /* The operation domain has the same size as the compositing region without any transformations + * applied. */ Domain compute_domain() override { - /* The context can use the composite output and thus has a dedicated viewer of an arbitrary - * size, so use the input directly. Otherwise, no dedicated viewer exist so the input should be - * in the domain of the compositing region. */ - if (context().use_composite_output()) { - return NodeOperation::compute_domain(); - } - else { - return Domain(context().get_compositing_region_size()); - } + return Domain(context().get_compositing_region_size()); } GPUShader *get_split_viewer_shader() diff --git a/source/blender/nodes/composite/nodes/node_composite_stabilize2d.cc b/source/blender/nodes/composite/nodes/node_composite_stabilize2d.cc index 960906a94ff..aa6ff1e2e4a 100644 --- a/source/blender/nodes/composite/nodes/node_composite_stabilize2d.cc +++ b/source/blender/nodes/composite/nodes/node_composite_stabilize2d.cc @@ -23,7 +23,6 @@ #include "BKE_movieclip.h" #include "BKE_tracking.h" -#include "COM_algorithm_transform.hh" #include "COM_node_operation.hh" #include "node_composite_util.hh" @@ -83,17 +82,17 @@ class Stabilize2DOperation : public NodeOperation { void execute() override { - Result &input = get_input("Image"); - Result &output = get_result("Image"); + Result &input_image = get_input("Image"); + Result &output_image = get_result("Image"); + input_image.pass_through(output_image); MovieClip *movie_clip = get_movie_clip(); - if (input.is_single_value() || !movie_clip) { - input.pass_through(output); + if (input_image.is_single_value() || !movie_clip) { return; } - const int width = input.domain().size.x; - const int height = input.domain().size.y; + const int width = input_image.domain().size.x; + const int height = input_image.domain().size.y; const int frame_number = BKE_movieclip_remap_scene_to_clip_frame(movie_clip, context().get_frame_number()); @@ -108,7 +107,8 @@ class Stabilize2DOperation : public NodeOperation { transformation = math::invert(transformation); } - transform(context(), input, output, transformation, get_interpolation()); + output_image.transform(transformation); + output_image.get_realization_options().interpolation = get_interpolation(); } Interpolation get_interpolation() diff --git a/source/blender/nodes/composite/nodes/node_composite_switchview.cc b/source/blender/nodes/composite/nodes/node_composite_switchview.cc index 1a047ecb3fe..0b68bac995d 100644 --- a/source/blender/nodes/composite/nodes/node_composite_switchview.cc +++ b/source/blender/nodes/composite/nodes/node_composite_switchview.cc @@ -20,16 +20,13 @@ namespace blender::nodes::node_composite_switchview_cc { -static void node_declare(NodeDeclarationBuilder &b) +static void node_declare_dynamic(const bNodeTree & /*ntree*/, + const bNode &node, + NodeDeclaration &r_declaration) { - b.add_output(N_("Image")); - - const bNode *node = b.node_or_null(); - if (node == nullptr) { - return; - } - - Scene *scene = reinterpret_cast(node->id); + Scene *scene = reinterpret_cast(node.id); + NodeDeclarationBuilder builder(r_declaration); + builder.add_output(N_("Image")); if (scene != nullptr) { /* add the new views */ @@ -37,7 +34,7 @@ static void node_declare(NodeDeclarationBuilder &b) if (srv->viewflag & SCE_VIEW_DISABLE) { continue; } - b.add_input(N_(srv->name)).default_value({0.0f, 0.0f, 0.0f, 1.0f}); + builder.add_input(N_(srv->name)).default_value({0.0f, 0.0f, 0.0f, 1.0f}); } } } @@ -102,7 +99,7 @@ void register_node_type_cmp_switch_view() static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_SWITCH_VIEW, "Switch View", NODE_CLASS_CONVERTER); - ntype.declare = file_ns::node_declare; + ntype.declare_dynamic = file_ns::node_declare_dynamic; ntype.draw_buttons_ex = file_ns::node_composit_buts_switch_view_ex; ntype.initfunc_api = file_ns::init_switch_view; ntype.get_compositor_operation = file_ns::get_compositor_operation; diff --git a/source/blender/nodes/composite/nodes/node_composite_transform.cc b/source/blender/nodes/composite/nodes/node_composite_transform.cc index c767e660dfb..85b3e9f4e73 100644 --- a/source/blender/nodes/composite/nodes/node_composite_transform.cc +++ b/source/blender/nodes/composite/nodes/node_composite_transform.cc @@ -7,14 +7,12 @@ */ #include "BLI_assert.h" -#include "BLI_math_angle_types.hh" #include "BLI_math_matrix.hh" #include "BLI_math_vector.h" #include "UI_interface.hh" #include "UI_resources.hh" -#include "COM_algorithm_transform.hh" #include "COM_node_operation.hh" #include "node_composite_util.hh" @@ -58,6 +56,7 @@ static void node_composit_buts_transform(uiLayout *layout, bContext * /*C*/, Poi } using namespace blender::realtime_compositor; +using namespace blender::math; class TransformOperation : public NodeOperation { public: @@ -66,16 +65,18 @@ class TransformOperation : public NodeOperation { void execute() override { Result &input = get_input("Image"); - Result &output = get_result("Image"); + Result &result = get_result("Image"); + input.pass_through(result); const float2 translation = float2(get_input("X").get_float_value_default(0.0f), get_input("Y").get_float_value_default(0.0f)); - const math::AngleRadian rotation = get_input("Angle").get_float_value_default(0.0f); + const AngleRadian rotation = AngleRadian(get_input("Angle").get_float_value_default(0.0f)); const float2 scale = float2(get_input("Scale").get_float_value_default(1.0f)); - const float3x3 transformation = math::from_loc_rot_scale( - translation, rotation, scale); - transform(context(), input, output, transformation, get_interpolation()); + const float3x3 transformation = from_loc_rot_scale(translation, rotation, scale); + + result.transform(transformation); + result.get_realization_options().interpolation = get_interpolation(); } Interpolation get_interpolation() diff --git a/source/blender/nodes/composite/nodes/node_composite_viewer.cc b/source/blender/nodes/composite/nodes/node_composite_viewer.cc index da10cb691ed..fa5d5ef3ae7 100644 --- a/source/blender/nodes/composite/nodes/node_composite_viewer.cc +++ b/source/blender/nodes/composite/nodes/node_composite_viewer.cc @@ -104,8 +104,7 @@ class ViewerOperation : public NodeOperation { color.w = alpha.get_float_value(); } - const int2 viewer_size = compute_domain().size; - GPU_texture_clear(context().get_viewer_output_texture(viewer_size), GPU_DATA_FLOAT, color); + GPU_texture_clear(context().get_viewer_output_texture(), GPU_DATA_FLOAT, color); } /* Executes when the alpha channel of the image is ignored. */ @@ -118,17 +117,17 @@ class ViewerOperation : public NodeOperation { * write into that compositing region. */ const rcti compositing_region = context().get_compositing_region(); const int2 lower_bound = int2(compositing_region.xmin, compositing_region.ymin); - GPU_shader_uniform_2iv(shader, "lower_bound", lower_bound); + GPU_shader_uniform_2iv(shader, "compositing_region_lower_bound", lower_bound); const Result &image = get_input("Image"); image.bind_as_texture(shader, "input_tx"); - const int2 viewer_size = compute_domain().size; - GPUTexture *output_texture = context().get_viewer_output_texture(viewer_size); + GPUTexture *output_texture = context().get_viewer_output_texture(); const int image_unit = GPU_shader_get_sampler_binding(shader, "output_img"); GPU_texture_image_bind(output_texture, image_unit); - compute_dispatch_threads_at_least(shader, viewer_size); + const int2 compositing_region_size = context().get_compositing_region_size(); + compute_dispatch_threads_at_least(shader, compositing_region_size); image.unbind_as_texture(); GPU_texture_image_unbind(output_texture); @@ -146,17 +145,17 @@ class ViewerOperation : public NodeOperation { * write into that compositing region. */ const rcti compositing_region = context().get_compositing_region(); const int2 lower_bound = int2(compositing_region.xmin, compositing_region.ymin); - GPU_shader_uniform_2iv(shader, "lower_bound", lower_bound); + GPU_shader_uniform_2iv(shader, "compositing_region_lower_bound", lower_bound); const Result &image = get_input("Image"); image.bind_as_texture(shader, "input_tx"); - const int2 viewer_size = compute_domain().size; - GPUTexture *output_texture = context().get_viewer_output_texture(viewer_size); + GPUTexture *output_texture = context().get_viewer_output_texture(); const int image_unit = GPU_shader_get_sampler_binding(shader, "output_img"); GPU_texture_image_bind(output_texture, image_unit); - compute_dispatch_threads_at_least(shader, viewer_size); + const int2 compositing_region_size = context().get_compositing_region_size(); + compute_dispatch_threads_at_least(shader, compositing_region_size); image.unbind_as_texture(); GPU_texture_image_unbind(output_texture); @@ -173,7 +172,7 @@ class ViewerOperation : public NodeOperation { * write into that compositing region. */ const rcti compositing_region = context().get_compositing_region(); const int2 lower_bound = int2(compositing_region.xmin, compositing_region.ymin); - GPU_shader_uniform_2iv(shader, "lower_bound", lower_bound); + GPU_shader_uniform_2iv(shader, "compositing_region_lower_bound", lower_bound); const Result &image = get_input("Image"); image.bind_as_texture(shader, "input_tx"); @@ -181,12 +180,12 @@ class ViewerOperation : public NodeOperation { const Result &alpha = get_input("Alpha"); alpha.bind_as_texture(shader, "alpha_tx"); - const int2 viewer_size = compute_domain().size; - GPUTexture *output_texture = context().get_viewer_output_texture(viewer_size); + GPUTexture *output_texture = context().get_viewer_output_texture(); const int image_unit = GPU_shader_get_sampler_binding(shader, "output_img"); GPU_texture_image_bind(output_texture, image_unit); - compute_dispatch_threads_at_least(shader, viewer_size); + const int2 compositing_region_size = context().get_compositing_region_size(); + compute_dispatch_threads_at_least(shader, compositing_region_size); image.unbind_as_texture(); alpha.unbind_as_texture(); @@ -202,17 +201,11 @@ class ViewerOperation : public NodeOperation { return bnode().custom2 & CMP_NODE_OUTPUT_IGNORE_ALPHA; } + /* The operation domain has the same size as the compositing region without any transformations + * applied. */ Domain compute_domain() override { - /* The context can use the composite output and thus has a dedicated viewer of an arbitrary - * size, so use the input directly. Otherwise, no dedicated viewer exist so the input should be - * in the domain of the compositing region. */ - if (context().use_composite_output()) { - return NodeOperation::compute_domain(); - } - else { - return Domain(context().get_compositing_region_size()); - } + return Domain(context().get_compositing_region_size()); } }; diff --git a/source/blender/nodes/function/nodes/node_fn_random_value.cc b/source/blender/nodes/function/nodes/node_fn_random_value.cc index 69086f01d51..1008cfdf27c 100644 --- a/source/blender/nodes/function/nodes/node_fn_random_value.cc +++ b/source/blender/nodes/function/nodes/node_fn_random_value.cc @@ -107,7 +107,7 @@ static std::optional node_type_from_other_socket(const bNodeSoc static void node_gather_link_search_ops(GatherLinkSearchOpParams ¶ms) { - const NodeDeclaration &declaration = *params.node_type().static_declaration; + const NodeDeclaration &declaration = *params.node_type().fixed_declaration; const std::optional type = node_type_from_other_socket(params.other_socket()); if (!type) { return; diff --git a/source/blender/nodes/function/nodes/node_fn_replace_string.cc b/source/blender/nodes/function/nodes/node_fn_replace_string.cc index 7a6a8d194f0..38f25b8607e 100644 --- a/source/blender/nodes/function/nodes/node_fn_replace_string.cc +++ b/source/blender/nodes/function/nodes/node_fn_replace_string.cc @@ -3,7 +3,7 @@ * SPDX-License-Identifier: GPL-2.0-or-later */ #include "BLI_string_utf8.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "node_function_util.hh" diff --git a/source/blender/nodes/geometry/CMakeLists.txt b/source/blender/nodes/geometry/CMakeLists.txt index e42d89a2c7d..0da46d2dda2 100644 --- a/source/blender/nodes/geometry/CMakeLists.txt +++ b/source/blender/nodes/geometry/CMakeLists.txt @@ -101,7 +101,6 @@ set(SRC nodes/node_geo_input_mesh_island.cc nodes/node_geo_input_mesh_vertex_neighbors.cc nodes/node_geo_input_named_attribute.cc - nodes/node_geo_input_named_layer_selection.cc nodes/node_geo_input_normal.cc nodes/node_geo_input_position.cc nodes/node_geo_input_radius.cc @@ -183,7 +182,6 @@ set(SRC nodes/node_geo_set_spline_resolution.cc nodes/node_geo_simulation_input.cc nodes/node_geo_simulation_output.cc - nodes/node_geo_split_to_instances.cc nodes/node_geo_store_named_attribute.cc nodes/node_geo_string_join.cc nodes/node_geo_string_to_curves.cc diff --git a/source/blender/nodes/geometry/node_geometry_tree.cc b/source/blender/nodes/geometry/node_geometry_tree.cc index 873dea72a18..a1eb0c60a81 100644 --- a/source/blender/nodes/geometry/node_geometry_tree.cc +++ b/source/blender/nodes/geometry/node_geometry_tree.cc @@ -134,17 +134,3 @@ void register_node_tree_type_geo() ntreeTypeAdd(tt); } - -bool is_layer_selection_field(const bNodeTreeInterfaceSocket &socket) -{ - if (!U.experimental.use_grease_pencil_version3) { - return false; - } - const bNodeSocketType *typeinfo = socket.socket_typeinfo(); - BLI_assert(typeinfo != nullptr); - - if (typeinfo->type != SOCK_BOOLEAN) { - return false; - } - return (socket.flag & NODE_INTERFACE_SOCKET_LAYER_SELECTION) != 0; -} diff --git a/source/blender/nodes/geometry/node_geometry_util.cc b/source/blender/nodes/geometry/node_geometry_util.cc index c8670174879..5e59ec6a46a 100644 --- a/source/blender/nodes/geometry/node_geometry_util.cc +++ b/source/blender/nodes/geometry/node_geometry_util.cc @@ -12,7 +12,6 @@ #include "BKE_context.h" #include "BKE_mesh.hh" #include "BKE_mesh_runtime.hh" -#include "BKE_node.hh" #include "BKE_pointcloud.h" #include "NOD_rna_define.hh" @@ -22,6 +21,33 @@ namespace blender::nodes { +std::optional node_data_type_to_custom_data_type(const eNodeSocketDatatype type) +{ + switch (type) { + case SOCK_FLOAT: + return CD_PROP_FLOAT; + case SOCK_VECTOR: + return CD_PROP_FLOAT3; + case SOCK_RGBA: + return CD_PROP_COLOR; + case SOCK_BOOLEAN: + return CD_PROP_BOOL; + case SOCK_ROTATION: + return CD_PROP_QUATERNION; + case SOCK_INT: + return CD_PROP_INT32; + case SOCK_STRING: + return CD_PROP_STRING; + default: + return {}; + } +} + +std::optional node_socket_to_custom_data_type(const bNodeSocket &socket) +{ + return node_data_type_to_custom_data_type(eNodeSocketDatatype(socket.type)); +} + bool check_tool_context_and_error(GeoNodeExecParams ¶ms) { if (!params.user_data()->operator_data) { @@ -67,19 +93,6 @@ bool generic_attribute_type_supported(const EnumPropertyItem &item) CD_PROP_QUATERNION); } -const EnumPropertyItem *domain_experimental_grease_pencil_version3_fn(bContext * /*C*/, - PointerRNA * /*ptr*/, - PropertyRNA * /*prop*/, - bool *r_free) -{ - *r_free = true; - return enum_items_filter( - rna_enum_attribute_domain_items, [](const EnumPropertyItem &item) -> bool { - return (item.value == ATTR_DOMAIN_LAYER) ? U.experimental.use_grease_pencil_version3 : - true; - }); -} - } // namespace enums } // namespace blender::nodes diff --git a/source/blender/nodes/geometry/node_geometry_util.hh b/source/blender/nodes/geometry/node_geometry_util.hh index 53415c20cb9..3269cf71647 100644 --- a/source/blender/nodes/geometry/node_geometry_util.hh +++ b/source/blender/nodes/geometry/node_geometry_util.hh @@ -67,6 +67,9 @@ void get_closest_in_bvhtree(BVHTreeFromMesh &tree_data, int apply_offset_in_cyclic_range(IndexRange range, int start_index, int offset); +std::optional node_data_type_to_custom_data_type(eNodeSocketDatatype type); +std::optional node_socket_to_custom_data_type(const bNodeSocket &socket); + #ifdef WITH_OPENVDB /** * Initializes the VolumeComponent of a GeometrySet with a new Volume from points. @@ -96,6 +99,10 @@ class EvaluateAtIndexInput final : public bke::GeometryFieldInput { } }; +std::string socket_identifier_for_simulation_item(const NodeSimulationItem &item); + +void socket_declarations_for_simulation_items(Span items, + NodeDeclaration &r_declaration); const CPPType &get_simulation_item_cpp_type(eNodeSocketDatatype socket_type); const CPPType &get_simulation_item_cpp_type(const NodeSimulationItem &item); @@ -119,6 +126,9 @@ void copy_with_checked_indices(const GVArray &src, const IndexMask &mask, GMutableSpan dst); +void socket_declarations_for_repeat_items(const Span items, + NodeDeclaration &r_declaration); + namespace enums { const EnumPropertyItem *attribute_type_type_with_socket_fn(bContext * /*C*/, @@ -128,11 +138,6 @@ const EnumPropertyItem *attribute_type_type_with_socket_fn(bContext * /*C*/, bool generic_attribute_type_supported(const EnumPropertyItem &item); -const EnumPropertyItem *domain_experimental_grease_pencil_version3_fn(bContext * /*C*/, - PointerRNA * /*ptr*/, - PropertyRNA * /*prop*/, - bool *r_free); - } // namespace enums } // namespace blender::nodes diff --git a/source/blender/nodes/geometry/nodes/node_geo_accumulate_field.cc b/source/blender/nodes/geometry/nodes/node_geo_accumulate_field.cc index 65007b0364c..ef1ceb89eac 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_accumulate_field.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_accumulate_field.cc @@ -230,7 +230,8 @@ class AccumulateFieldInput final : public bke::GeometryFieldInput { return {}; } - const bke::GeometryFieldContext source_context{context, source_domain_}; + const bke::GeometryFieldContext source_context{ + context.geometry(), context.type(), source_domain_}; fn::FieldEvaluator evaluator{source_context, domain_size}; evaluator.add(input_); evaluator.add(group_index_); @@ -335,7 +336,8 @@ class TotalFieldInput final : public bke::GeometryFieldInput { return {}; } - const bke::GeometryFieldContext source_context{context, source_domain_}; + const bke::GeometryFieldContext source_context{ + context.geometry(), context.type(), source_domain_}; fn::FieldEvaluator evaluator{source_context, domain_size}; evaluator.add(input_); evaluator.add(group_index_); @@ -419,7 +421,7 @@ static void node_geo_exec(GeoNodeExecParams params) using T = decltype(dummy); if constexpr (is_same_any_v) { const std::string suffix = " " + identifier_suffix(); - GField input_field = params.extract_input("Value" + suffix); + Field input_field = params.extract_input>("Value" + suffix); if (params.output_is_required("Leading" + suffix)) { params.set_output( "Leading" + suffix, @@ -464,8 +466,7 @@ static void node_rna(StructRNA *srna) "", rna_enum_attribute_domain_items, NOD_storage_enum_accessors(domain), - ATTR_DOMAIN_POINT, - enums::domain_experimental_grease_pencil_version3_fn); + ATTR_DOMAIN_POINT); } static void node_register() 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 3e6c77b9d7d..d206e1a3155 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc @@ -93,12 +93,12 @@ static void node_update(bNodeTree *ntree, bNode *node) static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) { - const NodeDeclaration &declaration = *params.node_type().static_declaration; + const NodeDeclaration &declaration = *params.node_type().fixed_declaration; search_link_ops_for_declarations(params, declaration.inputs.as_span().take_front(1)); search_link_ops_for_declarations(params, declaration.outputs.as_span().take_front(1)); const bNodeType &node_type = params.node_type(); - const std::optional type = bke::socket_type_to_custom_data_type( + const std::optional type = node_data_type_to_custom_data_type( eNodeSocketDatatype(params.other_socket().type)); if (type && *type != CD_PROP_STRING) { if (params.in_out() == SOCK_OUT) { @@ -199,22 +199,22 @@ static void node_geo_exec(GeoNodeExecParams params) switch (data_type) { case CD_PROP_FLOAT: - field = params.extract_input(input_identifier); + field = params.get_input>(input_identifier); break; case CD_PROP_FLOAT3: - field = params.extract_input(input_identifier); + field = params.get_input>(input_identifier); break; case CD_PROP_COLOR: - field = params.extract_input(input_identifier); + field = params.get_input>(input_identifier); break; case CD_PROP_BOOL: - field = params.extract_input(input_identifier); + field = params.get_input>(input_identifier); break; case CD_PROP_INT32: - field = params.extract_input(input_identifier); + field = params.get_input>(input_identifier); break; case CD_PROP_QUATERNION: - field = params.extract_input(input_identifier); + field = params.get_input>(input_identifier); break; default: break; @@ -237,8 +237,7 @@ static void node_geo_exec(GeoNodeExecParams params) else { static const Array types = {GeometryComponent::Type::Mesh, GeometryComponent::Type::PointCloud, - GeometryComponent::Type::Curve, - GeometryComponent::Type::GreasePencil}; + GeometryComponent::Type::Curve}; geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { for (const GeometryComponent::Type type : types) { @@ -269,8 +268,7 @@ static void node_rna(StructRNA *srna) "Which domain to store the data in", rna_enum_attribute_domain_items, NOD_storage_enum_accessors(domain), - ATTR_DOMAIN_POINT, - enums::domain_experimental_grease_pencil_version3_fn); + ATTR_DOMAIN_POINT); } static void node_register() diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_domain_size.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_domain_size.cc index 75eaefd334f..af9c462f883 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_domain_size.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_domain_size.cc @@ -34,9 +34,6 @@ static void node_declare(NodeDeclarationBuilder &b) b.add_output("Instance Count").make_available([](bNode &node) { node.custom1 = int16_t(GeometryComponent::Type::Instance); }); - b.add_output("Layer Count").make_available([](bNode &node) { - node.custom1 = int16_t(GeometryComponent::Type::GreasePencil); - }); } static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) @@ -57,7 +54,6 @@ static void node_update(bNodeTree *ntree, bNode *node) bNodeSocket *face_corner_socket = face_socket->next; bNodeSocket *spline_socket = face_corner_socket->next; bNodeSocket *instances_socket = spline_socket->next; - bNodeSocket *layers_socket = instances_socket->next; bke::nodeSetSocketAvailability(ntree, point_socket, @@ -75,8 +71,6 @@ static void node_update(bNodeTree *ntree, bNode *node) ntree, spline_socket, node->custom1 == int16_t(GeometryComponent::Type::Curve)); bke::nodeSetSocketAvailability( ntree, instances_socket, node->custom1 == int16_t(GeometryComponent::Type::Instance)); - bke::nodeSetSocketAvailability( - ntree, layers_socket, node->custom1 == int16_t(GeometryComponent::Type::GreasePencil)); } static void node_geo_exec(GeoNodeExecParams params) @@ -130,17 +124,6 @@ static void node_geo_exec(GeoNodeExecParams params) } break; } - case GeometryComponent::Type::GreasePencil: { - if (const GreasePencilComponent *component = - geometry_set.get_component()) { - const AttributeAccessor attributes = *component->attributes(); - params.set_output("Layer Count", attributes.domain_size(ATTR_DOMAIN_LAYER)); - } - else { - params.set_default_remaining_outputs(); - } - break; - } default: BLI_assert_unreachable(); } 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 d77e7acc052..fb322e0edae 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_statistic.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_statistic.cc @@ -125,7 +125,7 @@ static std::optional node_type_from_other_socket(const bNodeSoc static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) { const bNodeType &node_type = params.node_type(); - const NodeDeclaration &declaration = *params.node_type().static_declaration; + const NodeDeclaration &declaration = *params.node_type().fixed_declaration; search_link_ops_for_declarations(params, declaration.inputs.as_span().take_front(2)); const std::optional type = node_type_from_other_socket(params.other_socket()); @@ -407,8 +407,7 @@ static void node_rna(StructRNA *srna) "Which domain to read the data from", rna_enum_attribute_domain_items, NOD_inline_enum_accessors(custom2), - ATTR_DOMAIN_POINT, - enums::domain_experimental_grease_pencil_version3_fn); + ATTR_DOMAIN_POINT); } static void node_register() diff --git a/source/blender/nodes/geometry/nodes/node_geo_blur_attribute.cc b/source/blender/nodes/geometry/nodes/node_geo_blur_attribute.cc index 62e4bc89679..2aa43721e6b 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_blur_attribute.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_blur_attribute.cc @@ -17,7 +17,6 @@ #include "BKE_attribute_math.hh" #include "BKE_curves.hh" #include "BKE_geometry_fields.hh" -#include "BKE_grease_pencil.hh" #include "BKE_mesh.hh" #include "BKE_mesh_mapping.hh" @@ -86,12 +85,12 @@ static void node_init(bNodeTree * /*tree*/, bNode *node) static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) { const bNodeType &node_type = params.node_type(); - const NodeDeclaration &declaration = *node_type.static_declaration; + const NodeDeclaration &declaration = *node_type.fixed_declaration; /* Weight and Iterations inputs don't change based on the data type. */ search_link_ops_for_declarations(params, declaration.inputs.as_span().take_back(2)); - const std::optional new_node_type = bke::socket_type_to_custom_data_type( + const std::optional new_node_type = node_data_type_to_custom_data_type( eNodeSocketDatatype(params.other_socket().type)); if (!new_node_type.has_value()) { return; @@ -442,9 +441,8 @@ class BlurAttributeFieldInput final : public bke::GeometryFieldInput { } break; case GeometryComponent::Type::Curve: - case GeometryComponent::Type::GreasePencil: if (context.domain() == ATTR_DOMAIN_POINT) { - if (const bke::CurvesGeometry *curves = context.curves_or_strokes()) { + if (const bke::CurvesGeometry *curves = context.curves()) { result_buffer = blur_on_curves( *curves, iterations_, neighbor_weights, buffer_a, buffer_b); } @@ -485,12 +483,7 @@ class BlurAttributeFieldInput final : public bke::GeometryFieldInput { std::optional preferred_domain(const GeometryComponent &component) const override { - const std::optional domain = bke::try_detect_field_domain(component, - value_field_); - if (domain.has_value() && *domain == ATTR_DOMAIN_CORNER) { - return ATTR_DOMAIN_POINT; - } - return domain; + return bke::try_detect_field_domain(component, value_field_); } }; diff --git a/source/blender/nodes/geometry/nodes/node_geo_boolean.cc b/source/blender/nodes/geometry/nodes/node_geo_boolean.cc index 121d45036d5..441cd30466c 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_boolean.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_boolean.cc @@ -23,9 +23,8 @@ static void node_declare(NodeDeclarationBuilder &b) { b.add_input("Mesh 1").only_realized_data().supported_type( GeometryComponent::Type::Mesh); - b.add_input("Mesh 2") - .supported_type(GeometryComponent::Type::Mesh) - .multi_input(); + b.add_input("Mesh 2").multi_input().supported_type( + GeometryComponent::Type::Mesh); b.add_input("Self Intersection"); b.add_input("Hole Tolerant"); b.add_output("Mesh").propagate_all(); 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 fd8288b2e59..aab60120cb5 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_collection_info.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_collection_info.cc @@ -89,6 +89,7 @@ static void node_geo_exec(GeoNodeExecParams params) children_objects.append(collection_object->ob); } + instances->reserve(children_collections.size() + children_objects.size()); Vector entries; entries.reserve(children_collections.size() + children_objects.size()); diff --git a/source/blender/nodes/geometry/nodes/node_geo_common.cc b/source/blender/nodes/geometry/nodes/node_geo_common.cc index 84d6c693e4f..528077c0927 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_common.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_common.cc @@ -15,6 +15,31 @@ namespace blender::nodes { +static void node_group_declare(const bNodeTree &node_tree, + const bNode &node, + NodeDeclaration &r_declaration) +{ + const bNodeTree *group = reinterpret_cast(node.id); + if (!group) { + return; + } + node_group_declare_dynamic(node_tree, node, r_declaration); + if (!node.id) { + return; + } + if (ID_IS_LINKED(&group->id) && (group->id.tag & LIB_TAG_MISSING)) { + return; + } + + const FieldInferencingInterface &field_interface = *group->runtime->field_inferencing_interface; + for (const int i : r_declaration.inputs.index_range()) { + r_declaration.inputs[i]->input_field_type = field_interface.inputs[i]; + } + for (const int i : r_declaration.outputs.index_range()) { + r_declaration.outputs[i]->output_field_dependency = field_interface.outputs[i]; + } +} + static void register_node_type_geo_group() { static bNodeType ntype; @@ -28,9 +53,9 @@ static void register_node_type_geo_group() BLI_assert(ntype.rna_ext.srna != nullptr); RNA_struct_blender_type_set(ntype.rna_ext.srna, &ntype); - bke::node_type_size(&ntype, 140, 60, 400); + blender::bke::node_type_size(&ntype, 140, 60, 400); ntype.labelfunc = node_group_label; - ntype.declare = node_group_declare; + ntype.declare_dynamic = node_group_declare; nodeRegisterType(&ntype); } @@ -47,5 +72,5 @@ void register_node_type_geo_custom_group(bNodeType *ntype) if (ntype->insert_link == nullptr) { ntype->insert_link = node_insert_link_default; } - ntype->declare = blender::nodes::node_group_declare; + ntype->declare_dynamic = blender::nodes::node_group_declare_dynamic; } 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 f20f19457cb..d8dd36cfd6b 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_convex_hull.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_convex_hull.cc @@ -7,8 +7,6 @@ #include "DNA_pointcloud_types.h" #include "BKE_curves.hh" -#include "BKE_grease_pencil.hh" -#include "BKE_instances.hh" #include "BKE_material.h" #include "BKE_mesh.hh" @@ -209,53 +207,6 @@ static Mesh *compute_hull(const GeometrySet &geometry_set) return hull_from_bullet(geometry_set.get_mesh(), positions); } -static void convex_hull_grease_pencil(GeometrySet &geometry_set) -{ - using namespace blender::bke::greasepencil; - - const GreasePencil &grease_pencil = *geometry_set.get_grease_pencil(); - Array mesh_by_layer(grease_pencil.layers().size(), nullptr); - - for (const int layer_index : grease_pencil.layers().index_range()) { - const Drawing *drawing = get_eval_grease_pencil_layer_drawing(grease_pencil, layer_index); - if (drawing == nullptr) { - continue; - } - const bke::CurvesGeometry &curves = drawing->strokes(); - const Span positions_span = curves.evaluated_positions(); - if (positions_span.is_empty()) { - continue; - } - mesh_by_layer[layer_index] = hull_from_bullet(nullptr, positions_span); - } - - if (mesh_by_layer.is_empty()) { - return; - } - - InstancesComponent &instances_component = - geometry_set.get_component_for_write(); - bke::Instances *instances = instances_component.get_for_write(); - if (instances == nullptr) { - instances = new bke::Instances(); - instances_component.replace(instances); - } - for (Mesh *mesh : mesh_by_layer) { - if (!mesh) { - /* Add an empty reference so the number of layers and instances match. - * This makes it easy to reconstruct the layers afterwards and keep their attributes. - * Although in this particular case we don't propagate the attributes. */ - const int handle = instances->add_reference(bke::InstanceReference()); - instances->add_instance(handle, float4x4::identity()); - continue; - } - GeometrySet temp_set = GeometrySet::from_mesh(mesh); - const int handle = instances->add_reference(bke::InstanceReference{temp_set}); - instances->add_instance(handle, float4x4::identity()); - } - geometry_set.replace_grease_pencil(nullptr); -} - #endif /* WITH_BULLET */ static void node_geo_exec(GeoNodeExecParams params) @@ -270,9 +221,6 @@ static void node_geo_exec(GeoNodeExecParams params) geometry::debug_randomize_mesh_order(mesh); } geometry_set.replace_mesh(mesh); - if (geometry_set.has_grease_pencil()) { - convex_hull_grease_pencil(geometry_set); - } geometry_set.keep_only_during_modify({GeometryComponent::Type::Mesh}); }); 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 a581723c261..15b21cacc6b 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_fill.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_fill.cc @@ -10,8 +10,6 @@ #include "DNA_meshdata_types.h" #include "BKE_curves.hh" -#include "BKE_grease_pencil.hh" -#include "BKE_instances.hh" #include "BKE_mesh.hh" #include "BLI_task.hh" @@ -29,8 +27,7 @@ NODE_STORAGE_FUNCS(NodeGeometryCurveFill) static void node_declare(NodeDeclarationBuilder &b) { - b.add_input("Curve").supported_type( - {GeometryComponent::Type::Curve, GeometryComponent::Type::GreasePencil}); + b.add_input("Curve").supported_type(GeometryComponent::Type::Curve); b.add_output("Mesh"); } @@ -113,60 +110,26 @@ static Mesh *cdt_to_mesh(const meshintersect::CDT_result &result) static void curve_fill_calculate(GeometrySet &geometry_set, const GeometryNodeCurveFillMode mode) { + if (!geometry_set.has_curves()) { + return; + } + + const Curves &curves_id = *geometry_set.get_curves(); + const bke::CurvesGeometry &curves = curves_id.geometry.wrap(); + if (curves.curves_num() == 0) { + geometry_set.replace_curves(nullptr); + return; + } + const CDT_output_type output_type = (mode == GEO_NODE_CURVE_FILL_MODE_NGONS) ? CDT_CONSTRAINTS_VALID_BMESH_WITH_HOLES : CDT_INSIDE_WITH_HOLES; - if (geometry_set.has_curves()) { - const Curves &curves_id = *geometry_set.get_curves(); - const bke::CurvesGeometry &curves = curves_id.geometry.wrap(); - if (curves.curves_num() > 0) { - const meshintersect::CDT_result results = do_cdt(curves, output_type); - Mesh *mesh = cdt_to_mesh(results); - geometry_set.replace_mesh(mesh); - } - geometry_set.replace_curves(nullptr); - } - if (geometry_set.has_grease_pencil()) { - using namespace blender::bke::greasepencil; - const GreasePencil &grease_pencil = *geometry_set.get_grease_pencil(); - Vector mesh_by_layer(grease_pencil.layers().size(), nullptr); - for (const int layer_index : grease_pencil.layers().index_range()) { - const Drawing *drawing = get_eval_grease_pencil_layer_drawing(grease_pencil, layer_index); - if (drawing == nullptr) { - continue; - } - const bke::CurvesGeometry &src_curves = drawing->strokes(); - if (src_curves.curves_num() == 0) { - continue; - } - const meshintersect::CDT_result results = do_cdt(src_curves, output_type); - mesh_by_layer[layer_index] = cdt_to_mesh(results); - } - if (!mesh_by_layer.is_empty()) { - InstancesComponent &instances_component = - geometry_set.get_component_for_write(); - bke::Instances *instances = instances_component.get_for_write(); - if (instances == nullptr) { - instances = new bke::Instances(); - instances_component.replace(instances); - } - for (Mesh *mesh : mesh_by_layer) { - if (!mesh) { - /* Add an empty reference so the number of layers and instances match. - * This makes it easy to reconstruct the layers afterwards and keep their attributes. - * Although in this particular case we don't propagate the attributes. */ - const int handle = instances->add_reference(bke::InstanceReference()); - instances->add_instance(handle, float4x4::identity()); - continue; - } - GeometrySet temp_set = GeometrySet::from_mesh(mesh); - const int handle = instances->add_reference(bke::InstanceReference{temp_set}); - instances->add_instance(handle, float4x4::identity()); - } - } - geometry_set.replace_grease_pencil(nullptr); - } + const meshintersect::CDT_result results = do_cdt(curves, output_type); + Mesh *mesh = cdt_to_mesh(results); + + geometry_set.replace_mesh(mesh); + geometry_set.replace_curves(nullptr); } static void node_geo_exec(GeoNodeExecParams params) 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 97564546e97..37c609ca465 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc @@ -4,10 +4,6 @@ #include "NOD_rna_define.hh" -#include "DNA_grease_pencil_types.h" - -#include "BKE_grease_pencil.hh" - #include "UI_interface.hh" #include "UI_resources.hh" @@ -21,8 +17,7 @@ NODE_STORAGE_FUNCS(NodeGeometryCurveFillet) static void node_declare(NodeDeclarationBuilder &b) { - b.add_input("Curve").supported_type( - {GeometryComponent::Type::Curve, GeometryComponent::Type::GreasePencil}); + b.add_input("Curve").supported_type(GeometryComponent::Type::Curve); b.add_input("Count").default_value(1).min(1).max(1000).field_on_all().make_available( [](bNode &node) { node_storage(node).mode = GEO_NODE_CURVE_FILLET_POLY; }); b.add_input("Radius") @@ -56,71 +51,6 @@ static void node_update(bNodeTree *ntree, bNode *node) bke::nodeSetSocketAvailability(ntree, poly_socket, mode == GEO_NODE_CURVE_FILLET_POLY); } -static bke::CurvesGeometry fillet_curve(const bke::CurvesGeometry &src_curves, - const GeometryNodeCurveFilletMode mode, - const fn::FieldContext &field_context, - const std::optional> &count_field, - const Field &radius_field, - const bool limit_radius, - const AnonymousAttributePropagationInfo &propagation_info) -{ - fn::FieldEvaluator evaluator{field_context, src_curves.points_num()}; - evaluator.add(radius_field); - - switch (mode) { - case GEO_NODE_CURVE_FILLET_BEZIER: { - evaluator.evaluate(); - return geometry::fillet_curves_bezier(src_curves, - src_curves.curves_range(), - evaluator.get_evaluated(0), - limit_radius, - propagation_info); - } - case GEO_NODE_CURVE_FILLET_POLY: { - evaluator.add(*count_field); - evaluator.evaluate(); - return geometry::fillet_curves_poly(src_curves, - src_curves.curves_range(), - evaluator.get_evaluated(0), - evaluator.get_evaluated(1), - limit_radius, - propagation_info); - } - } - return bke::CurvesGeometry(); -} - -static void fillet_grease_pencil(GreasePencil &grease_pencil, - const GeometryNodeCurveFilletMode mode, - const std::optional> &count_field, - const Field &radius_field, - const bool limit_radius, - const AnonymousAttributePropagationInfo &propagation_info) -{ - using namespace blender::bke::greasepencil; - for (const int layer_index : grease_pencil.layers().index_range()) { - Drawing *drawing = get_eval_grease_pencil_layer_drawing_for_write(grease_pencil, layer_index); - if (drawing == nullptr) { - continue; - } - const bke::CurvesGeometry &src_curves = drawing->strokes(); - if (src_curves.points_num() == 0) { - continue; - } - const bke::GreasePencilLayerFieldContext field_context( - grease_pencil, ATTR_DOMAIN_CURVE, layer_index); - bke::CurvesGeometry dst_curves = fillet_curve(src_curves, - mode, - field_context, - count_field, - radius_field, - limit_radius, - propagation_info); - drawing->strokes_for_write() = std::move(dst_curves); - drawing->tag_topology_changed(); - } -} - static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input("Curve"); @@ -140,25 +70,45 @@ static void node_geo_exec(GeoNodeExecParams params) "Curve"); geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { - if (geometry_set.has_curves()) { - const Curves &curves_id = *geometry_set.get_curves(); - const bke::CurvesGeometry &src_curves = curves_id.geometry.wrap(); - const bke::CurvesFieldContext field_context{src_curves, ATTR_DOMAIN_POINT}; - bke::CurvesGeometry dst_curves = fillet_curve(src_curves, - mode, - field_context, - count_field, - radius_field, - limit_radius, - propagation_info); - Curves *dst_curves_id = bke::curves_new_nomain(std::move(dst_curves)); - bke::curves_copy_parameters(curves_id, *dst_curves_id); - geometry_set.replace_curves(dst_curves_id); + if (!geometry_set.has_curves()) { + return; } - if (geometry_set.has_grease_pencil()) { - GreasePencil &grease_pencil = *geometry_set.get_grease_pencil_for_write(); - fillet_grease_pencil( - grease_pencil, mode, count_field, radius_field, limit_radius, propagation_info); + + const Curves &curves_id = *geometry_set.get_curves(); + const bke::CurvesGeometry &curves = curves_id.geometry.wrap(); + const bke::CurvesFieldContext context{curves, ATTR_DOMAIN_POINT}; + fn::FieldEvaluator evaluator{context, curves.points_num()}; + evaluator.add(radius_field); + + switch (mode) { + case GEO_NODE_CURVE_FILLET_BEZIER: { + evaluator.evaluate(); + bke::CurvesGeometry dst_curves = geometry::fillet_curves_bezier( + curves, + curves.curves_range(), + evaluator.get_evaluated(0), + limit_radius, + propagation_info); + Curves *dst_curves_id = bke::curves_new_nomain(std::move(dst_curves)); + bke::curves_copy_parameters(curves_id, *dst_curves_id); + geometry_set.replace_curves(dst_curves_id); + break; + } + case GEO_NODE_CURVE_FILLET_POLY: { + evaluator.add(*count_field); + evaluator.evaluate(); + bke::CurvesGeometry dst_curves = geometry::fillet_curves_poly( + curves, + curves.curves_range(), + evaluator.get_evaluated(0), + evaluator.get_evaluated(1), + limit_radius, + propagation_info); + Curves *dst_curves_id = bke::curves_new_nomain(std::move(dst_curves)); + bke::curves_copy_parameters(curves_id, *dst_curves_id); + geometry_set.replace_curves(dst_curves_id); + break; + } } }); 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 130a1e3c922..2de1977bb75 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_length.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_length.cc @@ -3,7 +3,6 @@ * SPDX-License-Identifier: GPL-2.0-or-later */ #include "BKE_curves.hh" -#include "BKE_grease_pencil.hh" #include "node_geometry_util.hh" @@ -11,49 +10,29 @@ namespace blender::nodes::node_geo_curve_length_cc { static void node_declare(NodeDeclarationBuilder &b) { - b.add_input("Curve").supported_type( - {GeometryComponent::Type::Curve, GeometryComponent::Type::GreasePencil}); + b.add_input("Curve").supported_type(GeometryComponent::Type::Curve); b.add_output("Length"); } -static float curves_total_length(const bke::CurvesGeometry &curves) -{ - const VArray cyclic = curves.cyclic(); - curves.ensure_evaluated_lengths(); - - float total_length = 0.0f; - for (const int i : curves.curves_range()) { - total_length += curves.evaluated_length_total_for_curve(i, cyclic[i]); - } - return total_length; -} - static void node_geo_exec(GeoNodeExecParams params) { - GeometrySet geometry_set = params.extract_input("Curve"); - float length = 0.0f; - if (geometry_set.has_curves()) { - const Curves &curves_id = *geometry_set.get_curves(); - const bke::CurvesGeometry &curves = curves_id.geometry.wrap(); - length += curves_total_length(curves); - } - else if (geometry_set.has_grease_pencil()) { - using namespace bke::greasepencil; - const GreasePencil &grease_pencil = *geometry_set.get_grease_pencil(); - for (const int layer_index : grease_pencil.layers().index_range()) { - const Drawing *drawing = get_eval_grease_pencil_layer_drawing(grease_pencil, layer_index); - if (drawing == nullptr) { - continue; - } - const bke::CurvesGeometry &curves = drawing->strokes(); - length += curves_total_length(curves); - } - } - else { + GeometrySet curve_set = params.extract_input("Curve"); + if (!curve_set.has_curves()) { params.set_default_remaining_outputs(); return; } + const Curves &curves_id = *curve_set.get_curves(); + const bke::CurvesGeometry &curves = curves_id.geometry.wrap(); + const VArray cyclic = curves.cyclic(); + + curves.ensure_evaluated_lengths(); + + float length = 0.0f; + for (const int i : curves.curves_range()) { + length += curves.evaluated_length_total_for_curve(i, cyclic[i]); + } + params.set_output("Length", length); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_quadrilateral.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_quadrilateral.cc index 2ade299ca63..356531da599 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_quadrilateral.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_quadrilateral.cc @@ -138,7 +138,7 @@ class SocketSearchOp { static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) { - const NodeDeclaration &declaration = *params.node_type().static_declaration; + const NodeDeclaration &declaration = *params.node_type().fixed_declaration; if (params.in_out() == SOCK_OUT) { search_link_ops_for_declarations(params, declaration.outputs); } 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 7806cb5c2d4..35eb8b383cb 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc @@ -5,7 +5,6 @@ #include "GEO_resample_curves.hh" #include "BKE_curves.hh" -#include "BKE_grease_pencil.hh" #include "UI_interface.hh" #include "UI_resources.hh" @@ -20,15 +19,11 @@ NODE_STORAGE_FUNCS(NodeGeometryCurveResample) static void node_declare(NodeDeclarationBuilder &b) { - b.add_input("Curve").supported_type( - {GeometryComponent::Type::Curve, GeometryComponent::Type::GreasePencil}); + b.add_input("Curve").supported_type(GeometryComponent::Type::Curve); b.add_input("Selection").default_value(true).field_on_all().hide_value(); b.add_input("Count").default_value(10).min(1).max(100000).field_on_all(); - b.add_input("Length") - .default_value(0.1f) - .min(0.01f) - .subtype(PROP_DISTANCE) - .field_on_all(); + b.add_input("Length").default_value(0.1f).min(0.01f).field_on_all().subtype( + PROP_DISTANCE); b.add_output("Curve").propagate_all(); } @@ -66,7 +61,7 @@ static void node_geo_exec(GeoNodeExecParams params) const Field selection = params.extract_input>("Selection"); - GeometryComponentEditData::remember_deformed_positions_if_necessary(geometry_set); + GeometryComponentEditData::remember_deformed_curve_positions_if_necessary(geometry_set); switch (mode) { case GEO_NODE_CURVE_RESAMPLE_COUNT: { @@ -74,31 +69,12 @@ static void node_geo_exec(GeoNodeExecParams params) geometry_set.modify_geometry_sets([&](GeometrySet &geometry) { if (const Curves *src_curves_id = geometry.get_curves()) { const bke::CurvesGeometry &src_curves = src_curves_id->geometry.wrap(); - const bke::CurvesFieldContext field_context{src_curves, ATTR_DOMAIN_CURVE}; bke::CurvesGeometry dst_curves = geometry::resample_to_count( - src_curves, field_context, selection, count); + src_curves, selection, count); Curves *dst_curves_id = bke::curves_new_nomain(std::move(dst_curves)); bke::curves_copy_parameters(*src_curves_id, *dst_curves_id); geometry.replace_curves(dst_curves_id); } - if (geometry_set.has_grease_pencil()) { - using namespace blender::bke::greasepencil; - GreasePencil &grease_pencil = *geometry_set.get_grease_pencil_for_write(); - for (const int layer_index : grease_pencil.layers().index_range()) { - Drawing *drawing = get_eval_grease_pencil_layer_drawing_for_write(grease_pencil, - layer_index); - if (drawing == nullptr) { - continue; - } - const bke::CurvesGeometry &src_curves = drawing->strokes(); - const bke::GreasePencilLayerFieldContext field_context( - grease_pencil, ATTR_DOMAIN_CURVE, layer_index); - bke::CurvesGeometry dst_curves = geometry::resample_to_count( - src_curves, field_context, selection, count); - drawing->strokes_for_write() = std::move(dst_curves); - drawing->tag_topology_changed(); - } - } }); break; } @@ -107,31 +83,12 @@ static void node_geo_exec(GeoNodeExecParams params) geometry_set.modify_geometry_sets([&](GeometrySet &geometry) { if (const Curves *src_curves_id = geometry.get_curves()) { const bke::CurvesGeometry &src_curves = src_curves_id->geometry.wrap(); - const bke::CurvesFieldContext field_context{src_curves, ATTR_DOMAIN_CURVE}; bke::CurvesGeometry dst_curves = geometry::resample_to_length( - src_curves, field_context, selection, length); + src_curves, selection, length); Curves *dst_curves_id = bke::curves_new_nomain(std::move(dst_curves)); bke::curves_copy_parameters(*src_curves_id, *dst_curves_id); geometry.replace_curves(dst_curves_id); } - if (geometry_set.has_grease_pencil()) { - using namespace blender::bke::greasepencil; - GreasePencil &grease_pencil = *geometry_set.get_grease_pencil_for_write(); - for (const int layer_index : grease_pencil.layers().index_range()) { - Drawing *drawing = get_eval_grease_pencil_layer_drawing_for_write(grease_pencil, - layer_index); - if (drawing == nullptr) { - continue; - } - const bke::CurvesGeometry &src_curves = drawing->strokes(); - const bke::GreasePencilLayerFieldContext field_context( - grease_pencil, ATTR_DOMAIN_CURVE, layer_index); - bke::CurvesGeometry dst_curves = geometry::resample_to_length( - src_curves, field_context, selection, length); - drawing->strokes_for_write() = std::move(dst_curves); - drawing->tag_topology_changed(); - } - } }); break; } @@ -139,31 +96,11 @@ static void node_geo_exec(GeoNodeExecParams params) geometry_set.modify_geometry_sets([&](GeometrySet &geometry) { if (const Curves *src_curves_id = geometry.get_curves()) { const bke::CurvesGeometry &src_curves = src_curves_id->geometry.wrap(); - const bke::CurvesFieldContext field_context{src_curves, ATTR_DOMAIN_CURVE}; - bke::CurvesGeometry dst_curves = geometry::resample_to_evaluated( - src_curves, field_context, selection); + bke::CurvesGeometry dst_curves = geometry::resample_to_evaluated(src_curves, selection); Curves *dst_curves_id = bke::curves_new_nomain(std::move(dst_curves)); bke::curves_copy_parameters(*src_curves_id, *dst_curves_id); geometry.replace_curves(dst_curves_id); } - if (geometry_set.has_grease_pencil()) { - using namespace blender::bke::greasepencil; - GreasePencil &grease_pencil = *geometry_set.get_grease_pencil_for_write(); - for (const int layer_index : grease_pencil.layers().index_range()) { - Drawing *drawing = get_eval_grease_pencil_layer_drawing_for_write(grease_pencil, - layer_index); - if (drawing == nullptr) { - continue; - } - const bke::CurvesGeometry &src_curves = drawing->strokes(); - const bke::GreasePencilLayerFieldContext field_context( - grease_pencil, ATTR_DOMAIN_CURVE, layer_index); - bke::CurvesGeometry dst_curves = geometry::resample_to_evaluated( - src_curves, field_context, selection); - drawing->strokes_for_write() = std::move(dst_curves); - drawing->tag_topology_changed(); - } - } }); break; } 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 b761c180ade..0d4c0b7e552 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_reverse.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_reverse.cc @@ -5,7 +5,6 @@ #include "BLI_task.hh" #include "BKE_curves.hh" -#include "BKE_grease_pencil.hh" #include "node_geometry_util.hh" @@ -13,58 +12,36 @@ namespace blender::nodes::node_geo_curve_reverse_cc { static void node_declare(NodeDeclarationBuilder &b) { - b.add_input("Curve").supported_type( - {GeometryComponent::Type::Curve, GeometryComponent::Type::GreasePencil}); + b.add_input("Curve").supported_type(GeometryComponent::Type::Curve); b.add_input("Selection").default_value(true).hide_value().field_on_all(); b.add_output("Curve").propagate_all(); } -static void reverse_curve(bke::CurvesGeometry &curves, - const fn::FieldContext &field_context, - const Field &selection_field) -{ - fn::FieldEvaluator selection_evaluator{field_context, curves.curves_num()}; - selection_evaluator.add(selection_field); - selection_evaluator.evaluate(); - const IndexMask selection = selection_evaluator.get_evaluated_as_mask(0); - if (selection.is_empty()) { - return; - } - curves.reverse_curves(selection); -} - -static void reverse_grease_pencil(GreasePencil &grease_pencil, const Field &selection_field) -{ - using namespace blender::bke::greasepencil; - for (const int layer_index : grease_pencil.layers().index_range()) { - Drawing *drawing = get_eval_grease_pencil_layer_drawing_for_write(grease_pencil, layer_index); - if (drawing == nullptr) { - continue; - } - bke::CurvesGeometry &curves = drawing->strokes_for_write(); - const bke::GreasePencilLayerFieldContext field_context( - grease_pencil, ATTR_DOMAIN_CURVE, layer_index); - reverse_curve(curves, field_context, selection_field); - drawing->tag_topology_changed(); - } -} - static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input("Curve"); - Field selection_field = params.extract_input>("Selection"); - GeometryComponentEditData::remember_deformed_positions_if_necessary(geometry_set); + GeometryComponentEditData::remember_deformed_curve_positions_if_necessary(geometry_set); geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { - if (Curves *curves_id = geometry_set.get_curves_for_write()) { - bke::CurvesGeometry &curves = curves_id->geometry.wrap(); - const bke::CurvesFieldContext field_context{curves, ATTR_DOMAIN_CURVE}; - reverse_curve(curves, field_context, selection_field); + if (!geometry_set.has_curves()) { + return; } - if (geometry_set.has_grease_pencil()) { - reverse_grease_pencil(*geometry_set.get_grease_pencil_for_write(), selection_field); + const Curves &src_curves_id = *geometry_set.get_curves(); + const bke::CurvesGeometry &src_curves = src_curves_id.geometry.wrap(); + + const bke::CurvesFieldContext field_context{src_curves, ATTR_DOMAIN_CURVE}; + fn::FieldEvaluator selection_evaluator{field_context, src_curves.curves_num()}; + selection_evaluator.add(params.get_input>("Selection")); + selection_evaluator.evaluate(); + const IndexMask selection = selection_evaluator.get_evaluated_as_mask(0); + if (selection.is_empty()) { + return; } + + Curves &curves_id = *geometry_set.get_curves_for_write(); + bke::CurvesGeometry &curves = curves_id.geometry.wrap(); + curves.reverse_curves(selection); }); params.set_output("Curve", std::move(geometry_set)); 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 92d4f5115f5..400c9348c17 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_sample.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_sample.cc @@ -122,11 +122,11 @@ static void node_update(bNodeTree *ntree, bNode *node) static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) { - const NodeDeclaration &declaration = *params.node_type().static_declaration; + const NodeDeclaration &declaration = *params.node_type().fixed_declaration; search_link_ops_for_declarations(params, declaration.inputs.as_span().take_front(4)); search_link_ops_for_declarations(params, declaration.outputs.as_span().take_front(3)); - const std::optional type = bke::socket_type_to_custom_data_type( + const std::optional type = node_data_type_to_custom_data_type( eNodeSocketDatatype(params.other_socket().type)); if (type && *type != CD_PROP_STRING) { /* The input and output sockets have the same name. */ @@ -471,17 +471,17 @@ static GField get_input_attribute_field(GeoNodeExecParams ¶ms, const eCustom { switch (data_type) { case CD_PROP_FLOAT: - return params.extract_input("Value_Float"); + return params.extract_input>("Value_Float"); case CD_PROP_FLOAT3: - return params.extract_input("Value_Vector"); + return params.extract_input>("Value_Vector"); case CD_PROP_COLOR: - return params.extract_input("Value_Color"); + return params.extract_input>("Value_Color"); case CD_PROP_BOOL: - return params.extract_input("Value_Bool"); + return params.extract_input>("Value_Bool"); case CD_PROP_INT32: - return params.extract_input("Value_Int"); + return params.extract_input>("Value_Int"); case CD_PROP_QUATERNION: - return params.extract_input("Value_Rotation"); + return params.extract_input>("Value_Rotation"); default: BLI_assert_unreachable(); } 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 9911ace9b72..25a20c602c7 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_subdivide.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_subdivide.cc @@ -3,7 +3,6 @@ * SPDX-License-Identifier: GPL-2.0-or-later */ #include "BKE_curves.hh" -#include "BKE_grease_pencil.hh" #include "GEO_subdivide_curves.hh" @@ -16,92 +15,42 @@ namespace blender::nodes::node_geo_curve_subdivide_cc { static void node_declare(NodeDeclarationBuilder &b) { - b.add_input("Curve").supported_type( - {GeometryComponent::Type::Curve, GeometryComponent::Type::GreasePencil}); + b.add_input("Curve").supported_type(GeometryComponent::Type::Curve); b.add_input("Cuts").default_value(1).min(0).max(1000).field_on_all().description( "The number of control points to create on the segment following each point"); b.add_output("Curve").propagate_all(); } -static Curves *subdivide_curves(const Curves &src_curves_id, - Field &cuts_field, - const AnonymousAttributePropagationInfo &propagation_info) -{ - const bke::CurvesGeometry &src_curves = src_curves_id.geometry.wrap(); - - const bke::CurvesFieldContext field_context{src_curves, ATTR_DOMAIN_POINT}; - fn::FieldEvaluator evaluator{field_context, src_curves.points_num()}; - evaluator.add(cuts_field); - evaluator.evaluate(); - const VArray cuts = evaluator.get_evaluated(0); - - if (cuts.is_single() && cuts.get_internal_single() < 1) { - return nullptr; - } - - bke::CurvesGeometry dst_curves = geometry::subdivide_curves( - src_curves, src_curves.curves_range(), cuts, propagation_info); - - Curves *dst_curves_id = bke::curves_new_nomain(std::move(dst_curves)); - bke::curves_copy_parameters(src_curves_id, *dst_curves_id); - return dst_curves_id; -} - -static void subdivide_grease_pencil_curves( - GreasePencil &grease_pencil, - Field &cuts_field, - const AnonymousAttributePropagationInfo &propagation_info) -{ - using namespace bke::greasepencil; - for (const int layer_index : grease_pencil.layers().index_range()) { - Drawing *drawing = get_eval_grease_pencil_layer_drawing_for_write(grease_pencil, layer_index); - - if (drawing == nullptr) { - continue; - } - - const bke::CurvesGeometry &src_curves = drawing->strokes(); - const bke::GreasePencilLayerFieldContext field_context{ - grease_pencil, ATTR_DOMAIN_POINT, layer_index}; - - fn::FieldEvaluator evaluator{field_context, src_curves.points_num()}; - evaluator.add(cuts_field); - evaluator.evaluate(); - const VArray cuts = evaluator.get_evaluated(0); - - if (cuts.is_single() && cuts.get_internal_single() < 1) { - continue; - } - - bke::CurvesGeometry dst_curves = geometry::subdivide_curves( - src_curves, src_curves.curves_range(), cuts, propagation_info); - - drawing->strokes_for_write() = std::move(dst_curves); - drawing->tag_topology_changed(); - } -} - static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input("Curve"); Field cuts_field = params.extract_input>("Cuts"); - GeometryComponentEditData::remember_deformed_positions_if_necessary(geometry_set); - const AnonymousAttributePropagationInfo &propagation_info = params.get_output_propagation_info( - "Curve"); + GeometryComponentEditData::remember_deformed_curve_positions_if_necessary(geometry_set); geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { - if (geometry_set.has_curves()) { - const Curves &src_curves_id = *geometry_set.get_curves(); - Curves *dst_curves_id = subdivide_curves(src_curves_id, cuts_field, propagation_info); - if (dst_curves_id) { - geometry_set.replace_curves(dst_curves_id); - } + if (!geometry_set.has_curves()) { + return; } - if (geometry_set.has_grease_pencil()) { - GreasePencil &grease_pencil = *geometry_set.get_grease_pencil_for_write(); - subdivide_grease_pencil_curves(grease_pencil, cuts_field, propagation_info); + + const Curves &src_curves_id = *geometry_set.get_curves(); + const bke::CurvesGeometry &src_curves = src_curves_id.geometry.wrap(); + + const bke::CurvesFieldContext field_context{src_curves, ATTR_DOMAIN_POINT}; + fn::FieldEvaluator evaluator{field_context, src_curves.points_num()}; + evaluator.add(cuts_field); + evaluator.evaluate(); + const VArray cuts = evaluator.get_evaluated(0); + if (cuts.is_single() && cuts.get_internal_single() < 1) { + return; } + + bke::CurvesGeometry dst_curves = geometry::subdivide_curves( + src_curves, src_curves.curves_range(), cuts, params.get_output_propagation_info("Curve")); + + Curves *dst_curves_id = bke::curves_new_nomain(std::move(dst_curves)); + bke::curves_copy_parameters(src_curves_id, *dst_curves_id); + geometry_set.replace_curves(dst_curves_id); }); params.set_output("Curve", geometry_set); } 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 8bab6397de4..3711f1c5631 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 @@ -5,8 +5,6 @@ #include "BKE_curves.hh" #include "BKE_curve_to_mesh.hh" -#include "BKE_grease_pencil.hh" -#include "BKE_instances.hh" #include "UI_interface.hh" #include "UI_resources.hh" @@ -19,8 +17,7 @@ namespace blender::nodes::node_geo_curve_to_mesh_cc { static void node_declare(NodeDeclarationBuilder &b) { - b.add_input("Curve").supported_type( - {GeometryComponent::Type::Curve, GeometryComponent::Type::GreasePencil}); + b.add_input("Curve").supported_type(GeometryComponent::Type::Curve); b.add_input("Profile Curve") .only_realized_data() .supported_type(GeometryComponent::Type::Curve); @@ -30,72 +27,27 @@ static void node_declare(NodeDeclarationBuilder &b) b.add_output("Mesh").propagate_all(); } -static Mesh *curve_to_mesh(const bke::CurvesGeometry &curves, - const GeometrySet &profile_set, - const bool fill_caps, - const AnonymousAttributePropagationInfo &propagation_info) +static void geometry_set_curve_to_mesh(GeometrySet &geometry_set, + const GeometrySet &profile_set, + const bool fill_caps, + const AnonymousAttributePropagationInfo &propagation_info) { - Mesh *mesh; - if (profile_set.has_curves()) { - const Curves *profile_curves = profile_set.get_curves(); - mesh = bke::curve_to_mesh_sweep( - curves, profile_curves->geometry.wrap(), fill_caps, propagation_info); + const Curves &curves = *geometry_set.get_curves(); + const Curves *profile_curves = profile_set.get_curves(); + + bke::GeometryComponentEditData::remember_deformed_curve_positions_if_necessary(geometry_set); + + if (profile_curves == nullptr) { + Mesh *mesh = bke::curve_to_wire_mesh(curves.geometry.wrap(), propagation_info); + geometry::debug_randomize_mesh_order(mesh); + geometry_set.replace_mesh(mesh); } else { - mesh = bke::curve_to_wire_mesh(curves, propagation_info); + Mesh *mesh = bke::curve_to_mesh_sweep( + curves.geometry.wrap(), profile_curves->geometry.wrap(), fill_caps, propagation_info); + geometry::debug_randomize_mesh_order(mesh); + geometry_set.replace_mesh(mesh); } - geometry::debug_randomize_mesh_order(mesh); - return mesh; -} - -static void grease_pencil_to_mesh(GeometrySet &geometry_set, - const GeometrySet &profile_set, - const bool fill_caps, - const AnonymousAttributePropagationInfo &propagation_info) -{ - using namespace blender::bke::greasepencil; - - const GreasePencil &grease_pencil = *geometry_set.get_grease_pencil(); - Array mesh_by_layer(grease_pencil.layers().size(), nullptr); - - for (const int layer_index : grease_pencil.layers().index_range()) { - const Drawing *drawing = get_eval_grease_pencil_layer_drawing(grease_pencil, layer_index); - if (drawing == nullptr) { - continue; - } - const bke::CurvesGeometry &curves = drawing->strokes(); - mesh_by_layer[layer_index] = curve_to_mesh(curves, profile_set, fill_caps, propagation_info); - } - - if (mesh_by_layer.is_empty()) { - return; - } - - InstancesComponent &instances_component = - geometry_set.get_component_for_write(); - bke::Instances *instances = instances_component.get_for_write(); - if (instances == nullptr) { - instances = new bke::Instances(); - instances_component.replace(instances); - } - for (Mesh *mesh : mesh_by_layer) { - if (!mesh) { - /* Add an empty reference so the number of layers and instances match. - * This makes it easy to reconstruct the layers afterwards and keep their attributes. - * Although in this particular case we don't propagate the attributes. */ - const int handle = instances->add_reference(bke::InstanceReference()); - instances->add_instance(handle, float4x4::identity()); - continue; - } - GeometrySet temp_set = GeometrySet::from_mesh(mesh); - const int handle = instances->add_reference(bke::InstanceReference{temp_set}); - instances->add_instance(handle, float4x4::identity()); - } - GeometrySet::propagate_attributes_from_layer_to_instances( - geometry_set.get_grease_pencil()->attributes(), - geometry_set.get_instances_for_write()->attributes_for_write(), - propagation_info); - geometry_set.replace_grease_pencil(nullptr); } static void node_geo_exec(GeoNodeExecParams params) @@ -104,18 +56,10 @@ static void node_geo_exec(GeoNodeExecParams params) GeometrySet profile_set = params.extract_input("Profile Curve"); const bool fill_caps = params.extract_input("Fill Caps"); - bke::GeometryComponentEditData::remember_deformed_positions_if_necessary(curve_set); - const AnonymousAttributePropagationInfo &propagation_info = params.get_output_propagation_info( - "Mesh"); - curve_set.modify_geometry_sets([&](GeometrySet &geometry_set) { if (geometry_set.has_curves()) { - const Curves &curves = *geometry_set.get_curves(); - Mesh *mesh = curve_to_mesh(curves.geometry.wrap(), profile_set, fill_caps, propagation_info); - geometry_set.replace_mesh(mesh); - } - if (geometry_set.has_grease_pencil()) { - grease_pencil_to_mesh(geometry_set, profile_set, fill_caps, propagation_info); + geometry_set_curve_to_mesh( + geometry_set, profile_set, fill_caps, params.get_output_propagation_info("Mesh")); } geometry_set.keep_only_during_modify({GeometryComponent::Type::Mesh}); }); diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_to_points.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_to_points.cc index 64fc5320276..faa70de5b95 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_to_points.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_to_points.cc @@ -9,8 +9,6 @@ #include "DNA_pointcloud_types.h" -#include "BKE_grease_pencil.hh" -#include "BKE_instances.hh" #include "BKE_pointcloud.h" #include "GEO_resample_curves.hh" @@ -28,13 +26,12 @@ NODE_STORAGE_FUNCS(NodeGeometryCurveToPoints) static void node_declare(NodeDeclarationBuilder &b) { - b.add_input("Curve").supported_type( - {GeometryComponent::Type::Curve, GeometryComponent::Type::GreasePencil}); + b.add_input("Curve").supported_type(GeometryComponent::Type::Curve); b.add_input("Count") .default_value(10) .min(2) - .max(100000) .field_on_all() + .max(100000) .make_available( [](bNode &node) { node_storage(node).mode = GEO_NODE_CURVE_RESAMPLE_COUNT; }); b.add_input("Length") @@ -134,25 +131,34 @@ static PointCloud *pointcloud_from_curves(bke::CurvesGeometry curves, return pointcloud; } -static void curve_to_points(GeometrySet &geometry_set, - GeoNodeExecParams params, - const GeometryNodeCurveResampleMode mode, - geometry::ResampleCurvesOutputAttributeIDs resample_attributes, - AnonymousAttributeIDPtr rotation_anonymous_id) +static void node_geo_exec(GeoNodeExecParams params) { + const NodeGeometryCurveToPoints &storage = node_storage(params.node()); + const GeometryNodeCurveResampleMode mode = (GeometryNodeCurveResampleMode)storage.mode; + GeometrySet geometry_set = params.extract_input("Curve"); + + GeometryComponentEditData::remember_deformed_curve_positions_if_necessary(geometry_set); + + AnonymousAttributeIDPtr rotation_anonymous_id = + params.get_output_anonymous_attribute_id_if_needed("Rotation"); + const bool need_tangent_and_normal = bool(rotation_anonymous_id); + AnonymousAttributeIDPtr tangent_anonymous_id = + params.get_output_anonymous_attribute_id_if_needed("Tangent", need_tangent_and_normal); + AnonymousAttributeIDPtr normal_anonymous_id = params.get_output_anonymous_attribute_id_if_needed( + "Normal", need_tangent_and_normal); + + geometry::ResampleCurvesOutputAttributeIDs resample_attributes; + resample_attributes.tangent_id = tangent_anonymous_id.get(); + resample_attributes.normal_id = normal_anonymous_id.get(); + switch (mode) { case GEO_NODE_CURVE_RESAMPLE_COUNT: { Field count = params.extract_input>("Count"); geometry_set.modify_geometry_sets([&](GeometrySet &geometry) { if (const Curves *src_curves_id = geometry.get_curves()) { const bke::CurvesGeometry &src_curves = src_curves_id->geometry.wrap(); - const bke::CurvesFieldContext field_context{src_curves, ATTR_DOMAIN_CURVE}; bke::CurvesGeometry dst_curves = geometry::resample_to_count( - src_curves, - field_context, - fn::make_constant_field(true), - count, - resample_attributes); + src_curves, fn::make_constant_field(true), count, resample_attributes); PointCloud *pointcloud = pointcloud_from_curves(std::move(dst_curves), resample_attributes.tangent_id, resample_attributes.normal_id, @@ -168,13 +174,8 @@ static void curve_to_points(GeometrySet &geometry_set, geometry_set.modify_geometry_sets([&](GeometrySet &geometry) { if (const Curves *src_curves_id = geometry.get_curves()) { const bke::CurvesGeometry &src_curves = src_curves_id->geometry.wrap(); - const bke::CurvesFieldContext field_context{src_curves, ATTR_DOMAIN_CURVE}; bke::CurvesGeometry dst_curves = geometry::resample_to_length( - src_curves, - field_context, - fn::make_constant_field(true), - length, - resample_attributes); + src_curves, fn::make_constant_field(true), length, resample_attributes); PointCloud *pointcloud = pointcloud_from_curves(std::move(dst_curves), resample_attributes.tangent_id, resample_attributes.normal_id, @@ -185,13 +186,12 @@ static void curve_to_points(GeometrySet &geometry_set, }); break; } - case GEO_NODE_CURVE_RESAMPLE_EVALUATED: { + case GEO_NODE_CURVE_RESAMPLE_EVALUATED: geometry_set.modify_geometry_sets([&](GeometrySet &geometry) { if (const Curves *src_curves_id = geometry.get_curves()) { const bke::CurvesGeometry &src_curves = src_curves_id->geometry.wrap(); - const bke::CurvesFieldContext field_context{src_curves, ATTR_DOMAIN_CURVE}; bke::CurvesGeometry dst_curves = geometry::resample_to_evaluated( - src_curves, field_context, fn::make_constant_field(true), resample_attributes); + src_curves, fn::make_constant_field(true), resample_attributes); PointCloud *pointcloud = pointcloud_from_curves(std::move(dst_curves), resample_attributes.tangent_id, resample_attributes.normal_id, @@ -201,135 +201,6 @@ static void curve_to_points(GeometrySet &geometry_set, } }); break; - } - } -} - -static void grease_pencil_to_points(GeometrySet &geometry_set, - GeoNodeExecParams params, - const GeometryNodeCurveResampleMode mode, - geometry::ResampleCurvesOutputAttributeIDs resample_attributes, - AnonymousAttributeIDPtr rotation_anonymous_id, - const AnonymousAttributePropagationInfo &propagation_info) -{ - Field count; - Field length; - - switch (mode) { - case GEO_NODE_CURVE_RESAMPLE_COUNT: - count = params.extract_input>("Count"); - break; - case GEO_NODE_CURVE_RESAMPLE_LENGTH: - length = params.extract_input>("Length"); - break; - case GEO_NODE_CURVE_RESAMPLE_EVALUATED: - break; - } - - geometry_set.modify_geometry_sets([&](GeometrySet &geometry) { - using namespace blender::bke::greasepencil; - if (geometry.has_grease_pencil()) { - const GreasePencil &grease_pencil = *geometry.get_grease_pencil(); - Vector pointcloud_by_layer(grease_pencil.layers().size(), nullptr); - for (const int layer_index : grease_pencil.layers().index_range()) { - const Drawing *drawing = get_eval_grease_pencil_layer_drawing(grease_pencil, layer_index); - if (drawing == nullptr) { - continue; - } - const bke::CurvesGeometry &src_curves = drawing->strokes(); - bke::GreasePencilLayerFieldContext field_context( - grease_pencil, ATTR_DOMAIN_CURVE, layer_index); - - bke::CurvesGeometry dst_curves; - switch (mode) { - case GEO_NODE_CURVE_RESAMPLE_COUNT: { - dst_curves = geometry::resample_to_count(src_curves, - field_context, - fn::make_constant_field(true), - count, - resample_attributes); - break; - } - case GEO_NODE_CURVE_RESAMPLE_LENGTH: { - dst_curves = geometry::resample_to_length(src_curves, - field_context, - fn::make_constant_field(true), - length, - resample_attributes); - break; - } - case GEO_NODE_CURVE_RESAMPLE_EVALUATED: { - dst_curves = geometry::resample_to_evaluated(src_curves, - field_context, - fn::make_constant_field(true), - resample_attributes); - break; - } - } - pointcloud_by_layer[layer_index] = pointcloud_from_curves(std::move(dst_curves), - resample_attributes.tangent_id, - resample_attributes.normal_id, - rotation_anonymous_id.get()); - } - if (!pointcloud_by_layer.is_empty()) { - InstancesComponent &instances_component = - geometry_set.get_component_for_write(); - bke::Instances *instances = instances_component.get_for_write(); - if (instances == nullptr) { - instances = new bke::Instances(); - instances_component.replace(instances); - } - for (PointCloud *pointcloud : pointcloud_by_layer) { - if (!pointcloud) { - /* Add an empty reference so the number of layers and instances match. - * This makes it easy to reconstruct the layers afterwards and keep their - * attributes. */ - const int handle = instances->add_reference(bke::InstanceReference()); - instances->add_instance(handle, float4x4::identity()); - continue; - } - GeometrySet temp_set = GeometrySet::from_pointcloud(pointcloud); - const int handle = instances->add_reference(bke::InstanceReference{temp_set}); - instances->add_instance(handle, float4x4::identity()); - } - GeometrySet::propagate_attributes_from_layer_to_instances( - geometry.get_grease_pencil()->attributes(), - geometry.get_instances_for_write()->attributes_for_write(), - propagation_info); - } - } - }); - geometry_set.replace_grease_pencil(nullptr); -} - -static void node_geo_exec(GeoNodeExecParams params) -{ - const NodeGeometryCurveToPoints &storage = node_storage(params.node()); - const GeometryNodeCurveResampleMode mode = (GeometryNodeCurveResampleMode)storage.mode; - GeometrySet geometry_set = params.extract_input("Curve"); - - GeometryComponentEditData::remember_deformed_positions_if_necessary(geometry_set); - - AnonymousAttributeIDPtr rotation_anonymous_id = - params.get_output_anonymous_attribute_id_if_needed("Rotation"); - const bool need_tangent_and_normal = bool(rotation_anonymous_id); - AnonymousAttributeIDPtr tangent_anonymous_id = - params.get_output_anonymous_attribute_id_if_needed("Tangent", need_tangent_and_normal); - AnonymousAttributeIDPtr normal_anonymous_id = params.get_output_anonymous_attribute_id_if_needed( - "Normal", need_tangent_and_normal); - - geometry::ResampleCurvesOutputAttributeIDs resample_attributes; - resample_attributes.tangent_id = tangent_anonymous_id.get(); - resample_attributes.normal_id = normal_anonymous_id.get(); - const AnonymousAttributePropagationInfo &propagation_info = params.get_output_propagation_info( - "Points"); - - if (geometry_set.has_curves()) { - curve_to_points(geometry_set, params, mode, resample_attributes, rotation_anonymous_id); - } - if (geometry_set.has_grease_pencil()) { - grease_pencil_to_points( - geometry_set, params, mode, resample_attributes, rotation_anonymous_id, propagation_info); } params.set_output("Points", std::move(geometry_set)); diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_topology_points_of_curve.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_topology_points_of_curve.cc index 8a4ecfb40e7..a33375aeeb8 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_topology_points_of_curve.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_topology_points_of_curve.cc @@ -4,7 +4,6 @@ #include "BKE_curves.hh" -#include "BLI_array_utils.hh" #include "BLI_task.hh" #include "node_geometry_util.hh" @@ -92,7 +91,7 @@ class PointsOfCurveInput final : public bke::CurvesFieldInput { * when accessing values in the sort weights. However, it means a separate array of * indices within the compressed array is necessary for sorting. */ sort_indices.reinitialize(points.size()); - array_utils::fill_index_range(sort_indices); + std::iota(sort_indices.begin(), sort_indices.end(), 0); std::stable_sort(sort_indices.begin(), sort_indices.end(), [&](int a, int b) { return sort_weights[a] < sort_weights[b]; }); 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 90463d9cc8f..34fb58b1339 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc @@ -3,7 +3,6 @@ * SPDX-License-Identifier: GPL-2.0-or-later */ #include "BKE_curves.hh" -#include "BKE_grease_pencil.hh" #include "BLI_task.hh" #include "UI_interface.hh" @@ -23,8 +22,7 @@ NODE_STORAGE_FUNCS(NodeGeometryCurveTrim) static void node_declare(NodeDeclarationBuilder &b) { - b.add_input("Curve").supported_type( - {GeometryComponent::Type::Curve, GeometryComponent::Type::GreasePencil}); + b.add_input("Curve").supported_type(GeometryComponent::Type::Curve); b.add_input("Selection").default_value(true).hide_value().supports_field(); b.add_input("Start") .min(0.0f) @@ -96,7 +94,7 @@ class SocketSearchOp { static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) { - const NodeDeclaration &declaration = *params.node_type().static_declaration; + const NodeDeclaration &declaration = *params.node_type().fixed_declaration; search_link_ops_for_declarations(params, declaration.outputs); search_link_ops_for_declarations(params, declaration.inputs.as_span().take_front(1)); @@ -115,18 +113,23 @@ static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) } } -static bool trim_curves(const bke::CurvesGeometry &src_curves, - const GeometryNodeCurveSampleMode mode, - const fn::FieldContext &field_context, - Field &selection_field, - Field &start_field, - Field &end_field, - const AnonymousAttributePropagationInfo &propagation_info, - bke::CurvesGeometry &dst_curves) +static void geometry_set_curve_trim(GeometrySet &geometry_set, + const GeometryNodeCurveSampleMode mode, + Field &selection_field, + Field &start_field, + Field &end_field, + const AnonymousAttributePropagationInfo &propagation_info) { - if (src_curves.curves_num() == 0) { - return false; + if (!geometry_set.has_curves()) { + return; } + const Curves &src_curves_id = *geometry_set.get_curves(); + const bke::CurvesGeometry &src_curves = src_curves_id.geometry.wrap(); + if (src_curves.curves_num() == 0) { + return; + } + + const bke::CurvesFieldContext field_context{src_curves, ATTR_DOMAIN_CURVE}; fn::FieldEvaluator evaluator{field_context, src_curves.curves_num()}; evaluator.add(selection_field); evaluator.add(start_field); @@ -138,66 +141,14 @@ static bool trim_curves(const bke::CurvesGeometry &src_curves, const VArray ends = evaluator.get_evaluated(2); if (selection.is_empty()) { - return false; + return; } - dst_curves = geometry::trim_curves(src_curves, selection, starts, ends, mode, propagation_info); - return true; -} - -static void geometry_set_curve_trim(GeometrySet &geometry_set, - const GeometryNodeCurveSampleMode mode, - Field &selection_field, - Field &start_field, - Field &end_field, - const AnonymousAttributePropagationInfo &propagation_info) -{ - if (geometry_set.has_curves()) { - const Curves &src_curves_id = *geometry_set.get_curves(); - const bke::CurvesGeometry &src_curves = src_curves_id.geometry.wrap(); - const bke::CurvesFieldContext field_context{src_curves, ATTR_DOMAIN_CURVE}; - bke::CurvesGeometry dst_curves; - if (trim_curves(src_curves, - mode, - field_context, - selection_field, - start_field, - end_field, - propagation_info, - dst_curves)) - { - Curves *dst_curves_id = bke::curves_new_nomain(std::move(dst_curves)); - bke::curves_copy_parameters(src_curves_id, *dst_curves_id); - geometry_set.replace_curves(dst_curves_id); - } - } - if (geometry_set.has_grease_pencil()) { - using namespace bke::greasepencil; - GreasePencil &grease_pencil = *geometry_set.get_grease_pencil_for_write(); - for (const int layer_index : grease_pencil.layers().index_range()) { - Drawing *drawing = get_eval_grease_pencil_layer_drawing_for_write(grease_pencil, - layer_index); - if (drawing == nullptr) { - continue; - } - const bke::CurvesGeometry &src_curves = drawing->strokes(); - const bke::GreasePencilLayerFieldContext field_context{ - grease_pencil, ATTR_DOMAIN_CURVE, layer_index}; - bke::CurvesGeometry dst_curves; - if (trim_curves(src_curves, - mode, - field_context, - selection_field, - start_field, - end_field, - propagation_info, - dst_curves)) - { - drawing->strokes_for_write() = std::move(dst_curves); - drawing->tag_topology_changed(); - } - } - } + bke::CurvesGeometry dst_curves = geometry::trim_curves( + src_curves, selection, starts, ends, mode, propagation_info); + Curves *dst_curves_id = bke::curves_new_nomain(std::move(dst_curves)); + bke::curves_copy_parameters(src_curves_id, *dst_curves_id); + geometry_set.replace_curves(dst_curves_id); } static void node_geo_exec(GeoNodeExecParams params) @@ -206,7 +157,7 @@ static void node_geo_exec(GeoNodeExecParams params) const GeometryNodeCurveSampleMode mode = (GeometryNodeCurveSampleMode)storage.mode; GeometrySet geometry_set = params.extract_input("Curve"); - GeometryComponentEditData::remember_deformed_positions_if_necessary(geometry_set); + GeometryComponentEditData::remember_deformed_curve_positions_if_necessary(geometry_set); const AnonymousAttributePropagationInfo &propagation_info = params.get_output_propagation_info( "Curve"); diff --git a/source/blender/nodes/geometry/nodes/node_geo_deform_curves_on_surface.cc b/source/blender/nodes/geometry/nodes/node_geo_deform_curves_on_surface.cc index 53ebce559e6..ed652506869 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_deform_curves_on_surface.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_deform_curves_on_surface.cc @@ -316,9 +316,14 @@ static void node_geo_exec(GeoNodeExecParams params) /* Retrieve face corner normals from each mesh. It's necessary to use face corner normals * because face normals or vertex normals may lose information (custom normals, auto smooth) in - * some cases. */ - const Span corner_normals_orig = surface_mesh_orig->corner_normals(); - const Span corner_normals_eval = surface_mesh_eval->corner_normals(); + * some cases. It isn't yet possible to retrieve lazily calculated face corner normals from a + * const mesh, so they are calculated here every time. */ + Array corner_normals_orig(surface_mesh_orig->totloop); + Array corner_normals_eval(surface_mesh_eval->totloop); + BKE_mesh_calc_normals_split_ex( + surface_mesh_orig, nullptr, reinterpret_cast(corner_normals_orig.data())); + BKE_mesh_calc_normals_split_ex( + surface_mesh_eval, nullptr, reinterpret_cast(corner_normals_eval.data())); std::atomic invalid_uv_count = 0; diff --git a/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc b/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc index 4208bce2787..aa5dcdd8558 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc @@ -7,13 +7,10 @@ #include "UI_interface.hh" #include "UI_resources.hh" -#include "DNA_grease_pencil_types.h" #include "DNA_pointcloud_types.h" #include "BKE_curves.hh" -#include "BKE_grease_pencil.hh" #include "BKE_instances.hh" -#include "BKE_mesh.hh" #include "BKE_pointcloud.h" #include "GEO_mesh_copy_selection.hh" @@ -25,15 +22,17 @@ namespace blender::nodes::node_geo_delete_geometry_cc { /** \return std::nullopt if the geometry should remain unchanged. */ -static std::optional separate_curves_selection( - const bke::CurvesGeometry &src_curves, - const fn::FieldContext &field_context, +static std::optional separate_curves_selection( + const Curves &src_curves_id, const Field &selection_field, const eAttrDomain domain, const bke::AnonymousAttributePropagationInfo &propagation_info) { + const bke::CurvesGeometry &src_curves = src_curves_id.geometry.wrap(); + const int domain_size = src_curves.attributes().domain_size(domain); - fn::FieldEvaluator evaluator{field_context, domain_size}; + const bke::CurvesFieldContext context{src_curves, domain}; + fn::FieldEvaluator evaluator{context, domain_size}; evaluator.set_selection(selection_field); evaluator.evaluate(); const IndexMask selection = evaluator.get_evaluated_selection_as_mask(); @@ -41,17 +40,23 @@ static std::optional separate_curves_selection( return std::nullopt; } if (selection.is_empty()) { - return bke::CurvesGeometry(); + return nullptr; } + Curves *dst_curves_id = nullptr; if (domain == ATTR_DOMAIN_POINT) { - return bke::curves_copy_point_selection(src_curves, selection, propagation_info); + bke::CurvesGeometry dst_curves = bke::curves_copy_point_selection( + src_curves, selection, propagation_info); + dst_curves_id = bke::curves_new_nomain(std::move(dst_curves)); } else if (domain == ATTR_DOMAIN_CURVE) { - return bke::curves_copy_curve_selection(src_curves, selection, propagation_info); + bke::CurvesGeometry dst_curves = bke::curves_copy_curve_selection( + src_curves, selection, propagation_info); + dst_curves_id = bke::curves_new_nomain(std::move(dst_curves)); } - BLI_assert_unreachable(); - return std::nullopt; + + bke::curves_copy_parameters(src_curves_id, *dst_curves_id); + return dst_curves_id; } /** \return std::nullopt if the geometry should remain unchanged. */ @@ -103,18 +108,11 @@ static void delete_selected_instances(GeometrySet &geometry_set, static std::optional separate_mesh_selection( const Mesh &mesh, - const Field &selection_field, + const Field &selection, const eAttrDomain selection_domain, const GeometryNodeDeleteGeometryMode mode, const AnonymousAttributePropagationInfo &propagation_info) { - const bke::AttributeAccessor attributes = mesh.attributes(); - const bke::MeshFieldContext context(mesh, selection_domain); - fn::FieldEvaluator evaluator(context, attributes.domain_size(selection_domain)); - evaluator.add(selection_field); - evaluator.evaluate(); - const VArray selection = evaluator.get_evaluated(0); - switch (mode) { case GEO_NODE_DELETE_GEOMETRY_MODE_ALL: return geometry::mesh_copy_selection(mesh, selection, selection_domain, propagation_info); @@ -128,44 +126,6 @@ static std::optional separate_mesh_selection( return nullptr; } -static std::optional separate_grease_pencil_layer_selection( - const GreasePencil &src_grease_pencil, - const Field &selection_field, - const AnonymousAttributePropagationInfo &propagation_info) -{ - const bke::AttributeAccessor attributes = src_grease_pencil.attributes(); - const bke::GeometryFieldContext context(src_grease_pencil); - - fn::FieldEvaluator evaluator(context, attributes.domain_size(ATTR_DOMAIN_LAYER)); - evaluator.set_selection(selection_field); - evaluator.evaluate(); - - const IndexMask selection = evaluator.get_evaluated_selection_as_mask(); - if (selection.size() == attributes.domain_size(ATTR_DOMAIN_LAYER)) { - return std::nullopt; - } - if (selection.is_empty()) { - return nullptr; - } - - GreasePencil *dst_grease_pencil = BKE_grease_pencil_new_nomain(); - BKE_grease_pencil_duplicate_drawing_array(&src_grease_pencil, dst_grease_pencil); - selection.foreach_index([&](const int index) { - const bke::greasepencil::Layer &src_layer = *src_grease_pencil.layers()[index]; - dst_grease_pencil->add_layer(src_layer); - }); - dst_grease_pencil->remove_drawings_with_no_users(); - - bke::gather_attributes(src_grease_pencil.attributes(), - ATTR_DOMAIN_LAYER, - propagation_info, - {}, - selection, - dst_grease_pencil->attributes_for_write()); - - return dst_grease_pencil; -} - } // namespace blender::nodes::node_geo_delete_geometry_cc namespace blender::nodes { @@ -200,59 +160,16 @@ void separate_geometry(GeometrySet &geometry_set, some_valid_domain = true; } } - if (const Curves *src_curves_id = geometry_set.get_curves()) { + if (const Curves *curves_id = geometry_set.get_curves()) { if (ELEM(domain, ATTR_DOMAIN_POINT, ATTR_DOMAIN_CURVE)) { - const bke::CurvesGeometry &src_curves = src_curves_id->geometry.wrap(); - const bke::CurvesFieldContext field_context{src_curves, domain}; - std::optional dst_curves = file_ns::separate_curves_selection( - src_curves, field_context, selection, domain, propagation_info); + std::optional dst_curves = file_ns::separate_curves_selection( + *curves_id, selection, domain, propagation_info); if (dst_curves) { - if (dst_curves->points_num() == 0) { - geometry_set.remove(); - } - else { - Curves *dst_curves_id = bke::curves_new_nomain(*dst_curves); - bke::curves_copy_parameters(*src_curves_id, *dst_curves_id); - geometry_set.replace_curves(dst_curves_id); - } + geometry_set.replace_curves(*dst_curves); } some_valid_domain = true; } } - if (geometry_set.get_grease_pencil()) { - using namespace blender::bke::greasepencil; - if (domain == ATTR_DOMAIN_LAYER) { - const GreasePencil &grease_pencil = *geometry_set.get_grease_pencil(); - std::optional dst_grease_pencil = - file_ns::separate_grease_pencil_layer_selection( - grease_pencil, selection, propagation_info); - if (dst_grease_pencil) { - geometry_set.replace_grease_pencil(*dst_grease_pencil); - } - some_valid_domain = true; - } - else if (ELEM(domain, ATTR_DOMAIN_POINT, ATTR_DOMAIN_CURVE)) { - GreasePencil &grease_pencil = *geometry_set.get_grease_pencil_for_write(); - for (const int layer_index : grease_pencil.layers().index_range()) { - Drawing *drawing = get_eval_grease_pencil_layer_drawing_for_write(grease_pencil, - layer_index); - if (drawing == nullptr) { - continue; - } - const bke::CurvesGeometry &src_curves = drawing->strokes(); - const bke::GreasePencilLayerFieldContext field_context( - grease_pencil, ATTR_DOMAIN_CURVE, layer_index); - std::optional dst_curves = file_ns::separate_curves_selection( - src_curves, field_context, selection, domain, propagation_info); - if (!dst_curves) { - continue; - } - drawing->strokes_for_write() = std::move(*dst_curves); - drawing->tag_topology_changed(); - some_valid_domain = true; - } - } - } if (geometry_set.has_instances()) { if (domain == ATTR_DOMAIN_INSTANCE) { file_ns::delete_selected_instances(geometry_set, selection, propagation_info); 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 index 9ceb6b9dd5f..61a099a7afe 100644 --- 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 @@ -338,36 +338,15 @@ static void compute_normal_outputs(const Mesh &mesh, const Span looptri_indices, MutableSpan r_normals) { - switch (mesh.normals_domain()) { - case bke::MeshNormalDomain::Point: { - const Span corner_verts = mesh.corner_verts(); - const Span looptris = mesh.looptris(); - const Span vert_normals = mesh.vert_normals(); - threading::parallel_for(bary_coords.index_range(), 512, [&](const IndexRange range) { - bke::mesh_surface_sample::sample_point_normals( - corner_verts, looptris, looptri_indices, bary_coords, vert_normals, range, r_normals); - }); - break; - } - case bke::MeshNormalDomain::Face: { - const Span looptri_faces = mesh.looptri_faces(); - VArray face_normals = VArray::ForSpan(mesh.face_normals()); - threading::parallel_for(bary_coords.index_range(), 512, [&](const IndexRange range) { - bke::mesh_surface_sample::sample_face_attribute( - looptri_faces, looptri_indices, face_normals, range, r_normals); - }); - break; - } - case bke::MeshNormalDomain::Corner: { - const Span looptris = mesh.looptris(); - const Span corner_normals = mesh.corner_normals(); - threading::parallel_for(bary_coords.index_range(), 512, [&](const IndexRange range) { - bke::mesh_surface_sample::sample_corner_normals( - looptris, looptri_indices, bary_coords, corner_normals, range, r_normals); - }); - break; - } - } + Array corner_normals(mesh.totloop); + BKE_mesh_calc_normals_split_ex( + &mesh, nullptr, reinterpret_cast(corner_normals.data())); + + const Span looptris = mesh.looptris(); + threading::parallel_for(bary_coords.index_range(), 512, [&](const IndexRange range) { + bke::mesh_surface_sample::sample_corner_normals( + looptris, looptri_indices, bary_coords, corner_normals, range, r_normals); + }); } static void compute_legacy_normal_outputs(const Mesh &mesh, diff --git a/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc b/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc index 0a920479c73..e76fcb3d81b 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc @@ -33,7 +33,7 @@ NODE_STORAGE_FUNCS(NodeGeometryDuplicateElements); static void node_declare(NodeDeclarationBuilder &b) { b.add_input("Geometry"); - b.add_input("Selection").default_value(true).hide_value().field_on_all(); + b.add_input("Selection").hide_value().default_value(true).field_on_all(); b.add_input("Amount").min(0).default_value(1).field_on_all().description( "The number of duplicates to create for each element"); @@ -291,7 +291,7 @@ static void duplicate_curves(GeometrySet &geometry_set, return; } geometry_set.keep_only_during_modify({GeometryComponent::Type::Curve}); - GeometryComponentEditData::remember_deformed_positions_if_necessary(geometry_set); + GeometryComponentEditData::remember_deformed_curve_positions_if_necessary(geometry_set); const Curves &curves_id = *geometry_set.get_curves(); const bke::CurvesGeometry &curves = curves_id.geometry.wrap(); @@ -536,8 +536,8 @@ static void duplicate_faces(GeometrySet &geometry_set, face_index++; } }); - array_utils::fill_index_range(new_corner_verts); - array_utils::fill_index_range(new_corner_edges); + std::iota(new_corner_verts.begin(), new_corner_verts.end(), 0); + std::iota(new_corner_edges.begin(), new_corner_edges.end(), 0); new_mesh->tag_loose_verts_none(); new_mesh->tag_loose_edges_none(); @@ -762,7 +762,7 @@ static void duplicate_points_curve(GeometrySet &geometry_set, bke::curves_copy_parameters(src_curves_id, *new_curves_id); bke::CurvesGeometry &new_curves = new_curves_id->geometry.wrap(); MutableSpan new_curve_offsets = new_curves.offsets_for_write(); - array_utils::fill_index_range(new_curve_offsets); + std::iota(new_curve_offsets.begin(), new_curve_offsets.end(), 0); for (auto &attribute : bke::retrieve_attributes_for_transfer(src_curves.attributes(), new_curves.attributes_for_write(), diff --git a/source/blender/nodes/geometry/nodes/node_geo_evaluate_at_index.cc b/source/blender/nodes/geometry/nodes/node_geo_evaluate_at_index.cc index c4bc345cfd2..badc62d1dbc 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_evaluate_at_index.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_evaluate_at_index.cc @@ -37,7 +37,8 @@ GVArray EvaluateAtIndexInput::get_varray_for_context(const bke::GeometryFieldCon return {}; } - const bke::GeometryFieldContext value_context{context, value_field_domain_}; + const bke::GeometryFieldContext value_context{ + context.geometry(), context.type(), value_field_domain_}; FieldEvaluator value_evaluator{value_context, attributes->domain_size(value_field_domain_)}; value_evaluator.add(value_field_); value_evaluator.evaluate(); @@ -125,7 +126,7 @@ static void node_update(bNodeTree *ntree, bNode *node) static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) { const bNodeType &node_type = params.node_type(); - const std::optional type = bke::socket_type_to_custom_data_type( + const std::optional type = node_data_type_to_custom_data_type( eNodeSocketDatatype(params.other_socket().type)); if (type && *type != CD_PROP_STRING) { params.add_item(IFACE_("Value"), [node_type, type](LinkSearchOpParams ¶ms) { @@ -190,8 +191,7 @@ static void node_rna(StructRNA *srna) "Domain the field is evaluated in", rna_enum_attribute_domain_items, NOD_inline_enum_accessors(custom1), - ATTR_DOMAIN_POINT, - enums::domain_experimental_grease_pencil_version3_fn); + ATTR_DOMAIN_POINT); RNA_def_node_enum(srna, "data_type", diff --git a/source/blender/nodes/geometry/nodes/node_geo_evaluate_on_domain.cc b/source/blender/nodes/geometry/nodes/node_geo_evaluate_on_domain.cc index f978ef37129..be7d03dfc9c 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_evaluate_on_domain.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_evaluate_on_domain.cc @@ -10,7 +10,6 @@ #include "UI_resources.hh" #include "BKE_attribute_math.hh" -#include "BKE_grease_pencil.hh" #include "BLI_task.hh" @@ -85,7 +84,7 @@ static void node_update(bNodeTree *ntree, bNode *node) static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) { const bNodeType &node_type = params.node_type(); - const std::optional type = bke::socket_type_to_custom_data_type( + const std::optional type = node_data_type_to_custom_data_type( eNodeSocketDatatype(params.other_socket().type)); if (type && *type != CD_PROP_STRING) { params.add_item(IFACE_("Value"), [node_type, type](LinkSearchOpParams ¶ms) { @@ -112,43 +111,17 @@ class EvaluateOnDomainInput final : public bke::GeometryFieldInput { GVArray get_varray_for_context(const bke::GeometryFieldContext &context, const IndexMask & /*mask*/) const final { - const eAttrDomain dst_domain = context.domain(); - const int dst_domain_size = context.attributes()->domain_size(dst_domain); - const CPPType &cpp_type = src_field_.cpp_type(); - - if (context.type() == GeometryComponent::Type::GreasePencil && - (src_domain_ == ATTR_DOMAIN_LAYER) != (dst_domain == ATTR_DOMAIN_LAYER)) - { - /* Evaluate field just for the current layer. */ - if (src_domain_ == ATTR_DOMAIN_LAYER) { - const bke::GeometryFieldContext src_domain_context{context, ATTR_DOMAIN_LAYER}; - const int layer_index = context.grease_pencil_layer_index(); - - const IndexMask single_layer_mask = IndexRange(layer_index, 1); - FieldEvaluator value_evaluator{src_domain_context, &single_layer_mask}; - value_evaluator.add(src_field_); - value_evaluator.evaluate(); - - const GVArray &values = value_evaluator.get_evaluated(0); - - BUFFER_FOR_CPP_TYPE_VALUE(cpp_type, value); - BLI_SCOPED_DEFER([&]() { cpp_type.destruct(value); }); - values.get_to_uninitialized(layer_index, value); - return GVArray::ForSingle(cpp_type, dst_domain_size, value); - } - /* We don't adapt from curve to layer domain currently. */ - return GVArray::ForSingleDefault(cpp_type, dst_domain_size); - } - const bke::AttributeAccessor attributes = *context.attributes(); - const bke::GeometryFieldContext other_domain_context{context, src_domain_}; + const bke::GeometryFieldContext other_domain_context{ + context.geometry(), context.type(), src_domain_}; const int64_t src_domain_size = attributes.domain_size(src_domain_); - GArray<> values(cpp_type, src_domain_size); + GArray<> values(src_field_.cpp_type(), src_domain_size); FieldEvaluator value_evaluator{other_domain_context, src_domain_size}; value_evaluator.add_with_destination(src_field_, values.as_mutable_span()); value_evaluator.evaluate(); - return attributes.adapt_domain(GVArray::ForGArray(std::move(values)), src_domain_, dst_domain); + return attributes.adapt_domain( + GVArray::ForGArray(std::move(values)), src_domain_, context.domain()); } void for_each_field_input_recursive(FunctionRef fn) const override @@ -207,8 +180,7 @@ static void node_rna(StructRNA *srna) "Domain the field is evaluated in", rna_enum_attribute_domain_items, NOD_inline_enum_accessors(custom1), - ATTR_DOMAIN_POINT, - enums::domain_experimental_grease_pencil_version3_fn); + ATTR_DOMAIN_POINT); RNA_def_node_enum(srna, "data_type", diff --git a/source/blender/nodes/geometry/nodes/node_geo_extrude_mesh.cc b/source/blender/nodes/geometry/nodes/node_geo_extrude_mesh.cc index 955607f5ae7..ca861f09e4e 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_extrude_mesh.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_extrude_mesh.cc @@ -148,6 +148,7 @@ static void expand_mesh(Mesh &mesh, mesh.face_offset_indices[mesh.faces_num] = mesh.totloop + loop_expand; } if (loop_expand != 0) { + CustomData_free_layers(&mesh.loop_data, CD_NORMAL, mesh.totloop); CustomData_free_layers(&mesh.loop_data, CD_MDISPS, mesh.totloop); CustomData_free_layers(&mesh.loop_data, CD_TANGENT, mesh.totloop); CustomData_free_layers(&mesh.loop_data, CD_PAINT_MASK, mesh.totloop); diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_mesh_face_is_planar.cc b/source/blender/nodes/geometry/nodes/node_geo_input_mesh_face_is_planar.cc index 3e11b7e75af..3e9f7440d8b 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_input_mesh_face_is_planar.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_input_mesh_face_is_planar.cc @@ -16,14 +16,14 @@ namespace blender::nodes::node_geo_input_mesh_face_is_planar_cc { static void node_declare(NodeDeclarationBuilder &b) { b.add_input("Threshold") - .default_value(0.01f) - .min(0.0f) - .subtype(PROP_DISTANCE) .field_source() + .default_value(0.01f) + .subtype(PROP_DISTANCE) .supports_field() .description( "The distance a point can be from the surface before the face is no longer " - "considered planar"); + "considered planar") + .min(0.0f); b.add_output("Planar").field_source(); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_named_attribute.cc b/source/blender/nodes/geometry/nodes/node_geo_input_named_attribute.cc index ba3cf45e38d..13580e04a25 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_input_named_attribute.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_input_named_attribute.cc @@ -65,12 +65,12 @@ static void node_update(bNodeTree *ntree, bNode *node) static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) { - const NodeDeclaration &declaration = *params.node_type().static_declaration; + const NodeDeclaration &declaration = *params.node_type().fixed_declaration; search_link_ops_for_declarations(params, declaration.inputs); const bNodeType &node_type = params.node_type(); if (params.in_out() == SOCK_OUT) { - const std::optional type = bke::socket_type_to_custom_data_type( + const std::optional type = node_data_type_to_custom_data_type( eNodeSocketDatatype(params.other_socket().type)); if (type && *type != CD_PROP_STRING) { /* The input and output sockets have the same name. */ diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_named_layer_selection.cc b/source/blender/nodes/geometry/nodes/node_geo_input_named_layer_selection.cc deleted file mode 100644 index fc77904a523..00000000000 --- a/source/blender/nodes/geometry/nodes/node_geo_input_named_layer_selection.cc +++ /dev/null @@ -1,52 +0,0 @@ -/* SPDX-FileCopyrightText: 2023 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -#include "BKE_geometry_fields.hh" - -#include "NOD_socket_search_link.hh" - -#include "node_geometry_util.hh" - -namespace blender::nodes::node_geo_input_named_layer_selection__cc { - -static void node_declare(NodeDeclarationBuilder &b) -{ - b.add_input("Name"); - b.add_output("Selection").field_source_reference_all(); -} - -static void node_geo_exec(GeoNodeExecParams params) -{ - std::string name = params.extract_input("Name"); - if (name.empty()) { - params.set_default_remaining_outputs(); - return; - } - - Field selection_field{ - std::make_shared(std::move(name))}; - params.set_output("Selection", std::move(selection_field)); -} - -static void search_link_ops(GatherLinkSearchOpParams ¶ms) -{ - if (U.experimental.use_grease_pencil_version3) { - nodes::search_link_ops_for_basic_node(params); - } -} - -static void node_register() -{ - static bNodeType ntype; - - geo_node_type_base( - &ntype, GEO_NODE_INPUT_NAMED_LAYER_SELECTION, "Named Layer Selection", NODE_CLASS_INPUT); - ntype.declare = node_declare; - ntype.geometry_node_execute = node_geo_exec; - ntype.gather_link_search_ops = search_link_ops; - nodeRegisterType(&ntype); -} -NOD_REGISTER_NODE(node_register) - -} // namespace blender::nodes::node_geo_input_named_layer_selection__cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_signed_distance.cc b/source/blender/nodes/geometry/nodes/node_geo_input_signed_distance.cc index 68659f09d3d..5220b44a2f3 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_input_signed_distance.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_input_signed_distance.cc @@ -16,7 +16,7 @@ static void node_declare(NodeDeclarationBuilder &b) static void search_link_ops(GatherLinkSearchOpParams ¶ms) { if (U.experimental.use_new_volume_nodes) { - nodes::search_link_ops_for_basic_node(params); + blender::nodes::search_link_ops_for_basic_node(params); } } 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 index c7ab7f8eb41..9a43668ece4 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_instance_on_points.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_instance_on_points.cc @@ -14,8 +14,6 @@ #include "UI_resources.hh" #include "BKE_attribute_math.hh" -#include "BKE_curves.hh" -#include "BKE_grease_pencil.hh" #include "BKE_instances.hh" #include "node_geometry_util.hh" @@ -52,20 +50,20 @@ static void node_declare(NodeDeclarationBuilder &b) static void add_instances_from_component( bke::Instances &dst_component, - const AttributeAccessor &src_attributes, + const GeometryComponent &src_component, const GeometrySet &instance, - const fn::FieldContext &field_context, const GeoNodeExecParams ¶ms, const Map &attributes_to_propagate) { const eAttrDomain domain = ATTR_DOMAIN_POINT; - const int domain_num = src_attributes.domain_size(domain); + const int domain_num = src_component.attribute_domain_size(domain); VArray pick_instance; VArray indices; VArray rotations; VArray scales; + const bke::GeometryFieldContext field_context{src_component, domain}; const Field selection_field = params.get_input>("Selection"); fn::FieldEvaluator evaluator{field_context, domain_num}; evaluator.set_selection(selection_field); @@ -81,6 +79,7 @@ static void add_instances_from_component( if (selection.is_empty()) { return; } + const AttributeAccessor src_attributes = *src_component.attributes(); /* The initial size of the component might be non-zero when this function is called for multiple * component types. */ @@ -191,8 +190,6 @@ static void node_geo_exec(GeoNodeExecParams params) GeometrySet geometry_set = params.extract_input("Points"); GeometrySet instance = params.get_input("Instance"); instance.ensure_owns_direct_data(); - const AnonymousAttributePropagationInfo &propagation_info = params.get_output_propagation_info( - "Instances"); geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { /* It's important not to invalidate the existing #InstancesComponent because it owns references @@ -213,59 +210,19 @@ static void node_geo_exec(GeoNodeExecParams params) geometry_set.gather_attributes_for_propagation(types, GeometryComponent::Type::Instance, false, - propagation_info, + params.get_output_propagation_info("Instances"), attributes_to_propagate); attributes_to_propagate.remove("position"); for (const GeometryComponent::Type type : types) { if (geometry_set.has(type)) { - const GeometryComponent &component = *geometry_set.get_component(type); - const bke::GeometryFieldContext field_context{component, ATTR_DOMAIN_POINT}; add_instances_from_component(*dst_instances, - *component.attributes(), + *geometry_set.get_component(type), instance, - field_context, params, attributes_to_propagate); } } - if (geometry_set.has_grease_pencil()) { - using namespace bke::greasepencil; - const GreasePencil &grease_pencil = *geometry_set.get_grease_pencil(); - for (const int layer_index : grease_pencil.layers().index_range()) { - const Drawing *drawing = get_eval_grease_pencil_layer_drawing(grease_pencil, layer_index); - if (drawing == nullptr) { - continue; - } - const bke::CurvesGeometry &src_curves = drawing->strokes(); - if (src_curves.curves_num() == 0) { - /* Add an empty reference so the number of layers and instances match. - * This makes it easy to reconstruct the layers afterwards and keep their attributes. - * Although in this particular case we don't propagate the attributes. */ - const int handle = dst_instances->add_reference(bke::InstanceReference()); - dst_instances->add_instance(handle, float4x4::identity()); - continue; - } - /* TODO: Attributes are not propagating from the curves or the points. */ - bke::Instances *instances = new bke::Instances(); - const bke::GreasePencilLayerFieldContext field_context( - grease_pencil, ATTR_DOMAIN_POINT, layer_index); - add_instances_from_component(*instances, - src_curves.attributes(), - instance, - field_context, - params, - attributes_to_propagate); - GeometrySet temp_set = GeometrySet::from_instances(instances); - const int handle = dst_instances->add_reference(bke::InstanceReference{temp_set}); - dst_instances->add_instance(handle, float4x4::identity()); - } - GeometrySet::propagate_attributes_from_layer_to_instances( - geometry_set.get_grease_pencil()->attributes(), - geometry_set.get_instances_for_write()->attributes_for_write(), - propagation_info); - geometry_set.replace_grease_pencil(nullptr); - } geometry_set.remove_geometry_during_modify(); }); diff --git a/source/blender/nodes/geometry/nodes/node_geo_interpolate_curves.cc b/source/blender/nodes/geometry/nodes/node_geo_interpolate_curves.cc index 70fc86b147e..d2c69aafa72 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_interpolate_curves.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_interpolate_curves.cc @@ -863,7 +863,7 @@ static void node_geo_exec(GeoNodeExecParams params) index_attribute_id, weight_attribute_id); - GeometryComponentEditData::remember_deformed_positions_if_necessary(guide_curves_geometry); + GeometryComponentEditData::remember_deformed_curve_positions_if_necessary(guide_curves_geometry); if (const auto *curve_edit_data = guide_curves_geometry.get_component()) { new_curves.add(*curve_edit_data); 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 fe4a03d33a7..8706ec40977 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc @@ -2,7 +2,7 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ -#include "GEO_join_geometries.hh" +#include "GEO_realize_instances.hh" #include "BKE_instances.hh" @@ -16,6 +16,177 @@ static void node_declare(NodeDeclarationBuilder &b) b.add_output("Geometry").propagate_all(); } +template +static Array to_base_components(Span components) +{ + return components; +} + +static Map get_final_attribute_info( + Span components, Span ignored_attributes) +{ + Map info; + + for (const GeometryComponent *component : components) { + component->attributes()->for_all( + [&](const bke::AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { + if (ignored_attributes.contains(attribute_id.name())) { + return true; + } + if (meta_data.data_type == CD_PROP_STRING) { + return true; + } + info.add_or_modify( + attribute_id, + [&](AttributeMetaData *meta_data_final) { *meta_data_final = meta_data; }, + [&](AttributeMetaData *meta_data_final) { + meta_data_final->data_type = blender::bke::attribute_data_type_highest_complexity( + {meta_data_final->data_type, meta_data.data_type}); + meta_data_final->domain = blender::bke::attribute_domain_highest_priority( + {meta_data_final->domain, meta_data.domain}); + }); + return true; + }); + } + + return info; +} + +static void fill_new_attribute(Span src_components, + const AttributeIDRef &attribute_id, + const eCustomDataType data_type, + const eAttrDomain domain, + GMutableSpan dst_span) +{ + const CPPType *cpp_type = bke::custom_data_type_to_cpp_type(data_type); + BLI_assert(cpp_type != nullptr); + + int offset = 0; + for (const GeometryComponent *component : src_components) { + const int domain_num = component->attribute_domain_size(domain); + if (domain_num == 0) { + continue; + } + GVArray read_attribute = *component->attributes()->lookup_or_default( + attribute_id, domain, data_type, nullptr); + + GVArraySpan src_span{read_attribute}; + const void *src_buffer = src_span.data(); + void *dst_buffer = dst_span[offset]; + cpp_type->copy_assign_n(src_buffer, dst_buffer, domain_num); + + offset += domain_num; + } +} + +static void join_attributes(Span src_components, + GeometryComponent &result, + Span ignored_attributes = {}) +{ + const Map info = get_final_attribute_info(src_components, + ignored_attributes); + + for (const MapItem item : info.items()) { + const AttributeIDRef attribute_id = item.key; + const AttributeMetaData &meta_data = item.value; + + GSpanAttributeWriter write_attribute = + result.attributes_for_write()->lookup_or_add_for_write_only_span( + attribute_id, meta_data.domain, meta_data.data_type); + if (!write_attribute) { + continue; + } + fill_new_attribute( + src_components, attribute_id, meta_data.data_type, meta_data.domain, write_attribute.span); + write_attribute.finish(); + } +} + +static void join_components(Span src_components, GeometrySet &result) +{ + std::unique_ptr dst_instances = std::make_unique(); + + int tot_instances = 0; + for (const InstancesComponent *src_component : src_components) { + tot_instances += src_component->get()->instances_num(); + } + dst_instances->reserve(tot_instances); + + for (const InstancesComponent *src_component : src_components) { + const bke::Instances &src_instances = *src_component->get(); + + Span src_references = src_instances.references(); + Array handle_map(src_references.size()); + for (const int src_handle : src_references.index_range()) { + handle_map[src_handle] = dst_instances->add_reference(src_references[src_handle]); + } + + Span src_transforms = src_instances.transforms(); + Span src_reference_handles = src_instances.reference_handles(); + + for (const int i : src_transforms.index_range()) { + const int src_handle = src_reference_handles[i]; + const int dst_handle = handle_map[src_handle]; + const float4x4 &transform = src_transforms[i]; + dst_instances->add_instance(dst_handle, transform); + } + } + + result.replace_instances(dst_instances.release()); + InstancesComponent &dst_component = result.get_component_for_write(); + join_attributes(to_base_components(src_components), dst_component, {"position"}); +} + +static void join_components(Span /*src_components*/, + GeometrySet & /*result*/) +{ + /* Not yet supported. Joining volume grids with the same name requires resampling of at least one + * of the grids. The cell size of the resulting volume has to be determined somehow. */ +} + +template +static void join_component_type(Span src_geometry_sets, + GeometrySet &result, + const AnonymousAttributePropagationInfo &propagation_info) +{ + Vector components; + for (const GeometrySet &geometry_set : src_geometry_sets) { + const Component *component = geometry_set.get_component(); + if (component != nullptr && !component->is_empty()) { + components.append(component); + } + } + + if (components.size() == 0) { + return; + } + if (components.size() == 1) { + result.add(*components[0]); + return; + } + + if constexpr (is_same_any_v) { + join_components(components, result); + } + else { + std::unique_ptr instances = std::make_unique(); + for (const Component *component : components) { + GeometrySet tmp_geo; + tmp_geo.add(*component); + const int handle = instances->add_reference(bke::InstanceReference{tmp_geo}); + instances->add_instance(handle, float4x4::identity()); + } + + geometry::RealizeInstancesOptions options; + options.keep_original_ids = true; + options.realize_instance_attributes = false; + options.propagation_info = propagation_info; + GeometrySet joined_components = geometry::realize_instances( + GeometrySet::from_instances(instances.release()), options); + result.add(joined_components.get_component_for_write()); + } +} + static void node_geo_exec(GeoNodeExecParams params) { Vector geometry_sets = params.extract_input>("Geometry"); @@ -24,10 +195,17 @@ static void node_geo_exec(GeoNodeExecParams params) "Geometry"); for (GeometrySet &geometry : geometry_sets) { - GeometryComponentEditData::remember_deformed_positions_if_necessary(geometry); + GeometryComponentEditData::remember_deformed_curve_positions_if_necessary(geometry); } - GeometrySet geometry_set_result = geometry::join_geometries(geometry_sets, propagation_info); + GeometrySet geometry_set_result; + join_component_type(geometry_sets, geometry_set_result, propagation_info); + join_component_type(geometry_sets, geometry_set_result, propagation_info); + join_component_type(geometry_sets, geometry_set_result, propagation_info); + join_component_type(geometry_sets, geometry_set_result, propagation_info); + join_component_type(geometry_sets, geometry_set_result, propagation_info); + join_component_type( + geometry_sets, geometry_set_result, propagation_info); params.set_output("Geometry", std::move(geometry_set_result)); } 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 ad238c4110e..03f6cbed68f 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_material_replace.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_material_replace.cc @@ -10,31 +10,18 @@ #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" -#include "BKE_grease_pencil.hh" #include "BKE_material.h" namespace blender::nodes::node_geo_material_replace_cc { static void node_declare(NodeDeclarationBuilder &b) { - b.add_input("Geometry") - .supported_type({GeometryComponent::Type::Mesh, GeometryComponent::Type::GreasePencil}); + b.add_input("Geometry").supported_type(GeometryComponent::Type::Mesh); b.add_input("Old"); b.add_input("New").translation_context(BLT_I18NCONTEXT_ID_MATERIAL); b.add_output("Geometry").propagate_all(); } -static void replace_materials(MutableSpan materials, - Material *src_material, - Material *dst_material) -{ - for (const int i : materials.index_range()) { - if (materials[i] == src_material) { - materials[i] = dst_material; - } - } -} - static void node_geo_exec(GeoNodeExecParams params) { Material *old_material = params.extract_input("Old"); @@ -44,12 +31,11 @@ static void node_geo_exec(GeoNodeExecParams params) geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { if (Mesh *mesh = geometry_set.get_mesh_for_write()) { - replace_materials({mesh->mat, mesh->totcol}, old_material, new_material); - } - if (GreasePencil *grease_pencil = geometry_set.get_grease_pencil_for_write()) { - replace_materials({grease_pencil->material_array, grease_pencil->material_array_num}, - old_material, - new_material); + for (const int i : IndexRange(mesh->totcol)) { + if (mesh->mat[i] == old_material) { + mesh->mat[i] = new_material; + } + } } }); 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 699dcfec988..854150743e1 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_material_selection.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_material_selection.cc @@ -12,8 +12,6 @@ #include "BLI_task.hh" -#include "BKE_curves.hh" -#include "BKE_grease_pencil.hh" #include "BKE_material.h" namespace blender::nodes::node_geo_material_selection_cc { @@ -24,36 +22,37 @@ static void node_declare(NodeDeclarationBuilder &b) b.add_output("Selection").field_source(); } -static VArray select_by_material(const Span materials, - const Material *material, - const AttributeAccessor &attributes, - const eAttrDomain domain, - const IndexMask &domain_mask) +static VArray select_mesh_faces_by_material(const Mesh &mesh, + const Material *material, + const IndexMask &face_mask) { - const int domain_size = attributes.domain_size(domain); Vector slots; - for (const int slot_i : materials.index_range()) { - if (materials[slot_i] == material) { + for (const int slot_i : IndexRange(mesh.totcol)) { + if (mesh.mat[slot_i] == material) { slots.append(slot_i); } } if (slots.is_empty()) { - return VArray::ForSingle(false, domain_size); + return VArray::ForSingle(false, mesh.faces_num); } + const AttributeAccessor attributes = mesh.attributes(); const VArray material_indices = *attributes.lookup_or_default( - "material_index", domain, 0); - if (const std::optional single = material_indices.get_if_single()) { - return VArray::ForSingle(slots.contains(*single), domain_size); + "material_index", ATTR_DOMAIN_FACE, 0); + if (material_indices.is_single()) { + const int slot_i = material_indices.get_internal_single(); + return VArray::ForSingle(slots.contains(slot_i), mesh.faces_num); } const VArraySpan material_indices_span(material_indices); - Array domain_selection(domain_mask.min_array_size()); - domain_mask.foreach_index_optimized(GrainSize(1024), [&](const int domain_index) { - const int slot_i = material_indices_span[domain_index]; - domain_selection[domain_index] = slots.contains(slot_i); + + Array face_selection(face_mask.min_array_size()); + face_mask.foreach_index_optimized(GrainSize(1024), [&](const int face_index) { + const int slot_i = material_indices_span[face_index]; + face_selection[face_index] = slots.contains(slot_i); }); - return VArray::ForContainer(std::move(domain_selection)); + + return VArray::ForContainer(std::move(face_selection)); } class MaterialSelectionFieldInput final : public bke::GeometryFieldInput { @@ -70,44 +69,19 @@ class MaterialSelectionFieldInput final : public bke::GeometryFieldInput { GVArray get_varray_for_context(const bke::GeometryFieldContext &context, const IndexMask &mask) const final { - switch (context.type()) { - case GeometryComponent::Type::Mesh: { - const Mesh *mesh = context.mesh(); - if (!mesh) { - return {}; - } - const eAttrDomain domain = context.domain(); - const IndexMask domain_mask = (domain == ATTR_DOMAIN_FACE) ? mask : - IndexMask(mesh->faces_num); - const AttributeAccessor attributes = mesh->attributes(); - VArray selection = select_by_material( - {mesh->mat, mesh->totcol}, material_, attributes, ATTR_DOMAIN_FACE, domain_mask); - return attributes.adapt_domain(std::move(selection), ATTR_DOMAIN_FACE, domain); - } - case GeometryComponent::Type::GreasePencil: { - const bke::CurvesGeometry *curves = context.curves_or_strokes(); - if (!curves) { - return {}; - } - const eAttrDomain domain = context.domain(); - const IndexMask domain_mask = (domain == ATTR_DOMAIN_CURVE) ? - mask : - IndexMask(curves->curves_num()); - const AttributeAccessor attributes = curves->attributes(); - const VArray material_indices = *attributes.lookup_or_default( - "material_index", ATTR_DOMAIN_CURVE, 0); - const GreasePencil &grease_pencil = *context.grease_pencil(); - VArray selection = select_by_material( - {grease_pencil.material_array, grease_pencil.material_array_num}, - material_, - attributes, - ATTR_DOMAIN_CURVE, - domain_mask); - return attributes.adapt_domain(std::move(selection), ATTR_DOMAIN_CURVE, domain); - } - default: - return {}; + if (context.type() != GeometryComponent::Type::Mesh) { + return {}; } + const Mesh *mesh = context.mesh(); + if (mesh == nullptr) { + return {}; + } + + const eAttrDomain domain = context.domain(); + const IndexMask domain_mask = (domain == ATTR_DOMAIN_FACE) ? mask : IndexMask(mesh->faces_num); + + VArray selection = select_mesh_faces_by_material(*mesh, material_, domain_mask); + return mesh->attributes().adapt_domain(std::move(selection), ATTR_DOMAIN_FACE, domain); } uint64_t hash() const override diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_face_group_boundaries.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_face_group_boundaries.cc index dc16dce94a4..eeb681bfc3d 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_face_group_boundaries.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_face_group_boundaries.cc @@ -90,7 +90,7 @@ static void node_register() static bNodeType ntype; geo_node_type_base( &ntype, GEO_NODE_MESH_FACE_GROUP_BOUNDARIES, "Face Group Boundaries", NODE_CLASS_INPUT); - bke::node_type_size_preset(&ntype, bke::eNodeSizePreset::MIDDLE); + blender::bke::node_type_size_preset(&ntype, blender::bke::eNodeSizePreset::MIDDLE); ntype.declare = node_declare; ntype.geometry_node_execute = node_geo_exec; nodeRegisterType(&ntype); diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_circle.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_circle.cc index 5363a44faaa..eda431101ef 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_circle.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_circle.cc @@ -13,8 +13,6 @@ #include "UI_interface.hh" #include "UI_resources.hh" -#include "BLI_array_utils.hh" - #include "GEO_mesh_primitive_uv_sphere.hh" #include "node_geometry_util.hh" @@ -161,8 +159,8 @@ static Mesh *create_circle_mesh(const float radius, face_offsets.first() = 0; face_offsets.last() = corner_verts.size(); - array_utils::fill_index_range(corner_verts); - array_utils::fill_index_range(corner_edges); + std::iota(corner_verts.begin(), corner_verts.end(), 0); + std::iota(corner_edges.begin(), corner_edges.end(), 0); mesh->tag_loose_edges_none(); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_ico_sphere.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_ico_sphere.cc index 15f154955fa..e1b1b974452 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_ico_sphere.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_ico_sphere.cc @@ -65,8 +65,6 @@ static Mesh *create_ico_sphere_mesh(const int subdivisions, const AttributeIDRef &uv_map_id) { if (subdivisions >= 3) { - /* Most nodes don't need this because they internally use multi-threading which triggers - * lazy-threading without any extra code. */ lazy_threading::send_hint(); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_line.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_line.cc index a9494ba37ff..2923dfe7852 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_line.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_line.cc @@ -90,7 +90,7 @@ static void node_update(bNodeTree *ntree, bNode *node) static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) { - const NodeDeclaration &declaration = *params.node_type().static_declaration; + const NodeDeclaration &declaration = *params.node_type().fixed_declaration; if (params.in_out() == SOCK_OUT) { search_link_ops_for_declarations(params, declaration.outputs); return; diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_to_sdf_volume.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_to_sdf_volume.cc index f53295c1d8e..60cf1a2d636 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_to_sdf_volume.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_to_sdf_volume.cc @@ -38,17 +38,17 @@ static void node_declare(NodeDeclarationBuilder &b) .subtype(PROP_DISTANCE); b.add_input("Voxel Amount").default_value(64.0f).min(0.0f).max(FLT_MAX); b.add_input("Half-Band Width") + .description("Half the width of the narrow band in voxel units") .default_value(3.0f) .min(1.01f) - .max(10.0f) - .description("Half the width of the narrow band in voxel units"); + .max(10.0f); b.add_output("Volume").translation_context(BLT_I18NCONTEXT_ID_ID); } static void search_link_ops(GatherLinkSearchOpParams ¶ms) { if (U.experimental.use_new_volume_nodes) { - nodes::search_link_ops_for_basic_node(params); + blender::nodes::search_link_ops_for_basic_node(params); } } diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_to_volume.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_to_volume.cc index 9893165a58e..34d7234e7f8 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_to_volume.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_to_volume.cc @@ -182,7 +182,7 @@ static void node_register() geo_node_type_base(&ntype, GEO_NODE_MESH_TO_VOLUME, "Mesh to Volume", NODE_CLASS_GEOMETRY); ntype.declare = node_declare; - bke::node_type_size(&ntype, 200, 120, 700); + blender::bke::node_type_size(&ntype, 200, 120, 700); ntype.initfunc = node_init; ntype.updatefunc = node_update; ntype.geometry_node_execute = node_geo_exec; diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_corners_of_edge.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_corners_of_edge.cc index ec6012de804..14d1e6c6e97 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_corners_of_edge.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_corners_of_edge.cc @@ -105,7 +105,7 @@ class CornersOfEdgeInput final : public bke::MeshFieldInput { * when accessing values in the sort weights. However, it means a separate array of * indices within the compressed array is necessary for sorting. */ sort_indices.reinitialize(corners.size()); - array_utils::fill_index_range(sort_indices); + std::iota(sort_indices.begin(), sort_indices.end(), 0); std::stable_sort(sort_indices.begin(), sort_indices.end(), [&](int a, int b) { return sort_weights[a] < sort_weights[b]; }); diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_corners_of_face.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_corners_of_face.cc index df09b868942..58edf07f51f 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_corners_of_face.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_corners_of_face.cc @@ -2,7 +2,6 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ -#include "BLI_array_utils.hh" #include "BLI_task.hh" #include "BKE_mesh.hh" @@ -93,7 +92,7 @@ class CornersOfFaceInput final : public bke::MeshFieldInput { * when accessing values in the sort weights. However, it means a separate array of * indices within the compressed array is necessary for sorting. */ sort_indices.reinitialize(corners.size()); - array_utils::fill_index_range(sort_indices); + std::iota(sort_indices.begin(), sort_indices.end(), 0); std::stable_sort(sort_indices.begin(), sort_indices.end(), [&](int a, int b) { return sort_weights[a] < sort_weights[b]; }); diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_corners_of_vertex.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_corners_of_vertex.cc index 7d6ed5c6cd6..45e6ea43ac6 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_corners_of_vertex.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_corners_of_vertex.cc @@ -99,7 +99,7 @@ class CornersOfVertInput final : public bke::MeshFieldInput { * when accessing values in the sort weights. However, it means a separate array of * indices within the compressed array is necessary for sorting. */ sort_indices.reinitialize(corners.size()); - array_utils::fill_index_range(sort_indices); + std::iota(sort_indices.begin(), sort_indices.end(), 0); std::stable_sort(sort_indices.begin(), sort_indices.end(), [&](int a, int b) { return sort_weights[a] < sort_weights[b]; }); diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_edges_of_vertex.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_edges_of_vertex.cc index 878f0727cb1..88d0becc1d7 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_edges_of_vertex.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_edges_of_vertex.cc @@ -103,7 +103,7 @@ class EdgesOfVertInput final : public bke::MeshFieldInput { * when accessing values in the sort weights. However, it means a separate array of * indices within the compressed array is necessary for sorting. */ sort_indices.reinitialize(edges.size()); - array_utils::fill_index_range(sort_indices); + std::iota(sort_indices.begin(), sort_indices.end(), 0); std::stable_sort(sort_indices.begin(), sort_indices.end(), [&](int a, int b) { return sort_weights[a] < sort_weights[b]; }); diff --git a/source/blender/nodes/geometry/nodes/node_geo_offset_sdf_volume.cc b/source/blender/nodes/geometry/nodes/node_geo_offset_sdf_volume.cc index 65e2134aabf..52e375a4521 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_offset_sdf_volume.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_offset_sdf_volume.cc @@ -33,7 +33,7 @@ static void node_declare(NodeDeclarationBuilder &b) static void search_link_ops(GatherLinkSearchOpParams ¶ms) { if (U.experimental.use_new_volume_nodes) { - nodes::search_link_ops_for_basic_node(params); + blender::nodes::search_link_ops_for_basic_node(params); } } diff --git a/source/blender/nodes/geometry/nodes/node_geo_points.cc b/source/blender/nodes/geometry/nodes/node_geo_points.cc index 43fe83a0e9d..fb5e2c49ef5 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_points.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_points.cc @@ -13,17 +13,19 @@ namespace blender::nodes::node_geo_points_cc { static void node_declare(NodeDeclarationBuilder &b) { - b.add_input("Count").default_value(1).min(0).description( - "The number of points to create"); + b.add_input("Count") + .default_value(1) + .description("The number of points to create") + .min(0); b.add_input("Position") - .default_value(float3(0.0f)) .supports_field() + .default_value(float3(0.0f)) .description("The positions of the new points"); b.add_input("Radius") .min(0.0f) .default_value(0.1f) - .subtype(PROP_DISTANCE) .supports_field() + .subtype(PROP_DISTANCE) .description("The radii of the new points"); b.add_output("Geometry"); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_points_to_curves.cc b/source/blender/nodes/geometry/nodes/node_geo_points_to_curves.cc index b422db14a29..d24ff921842 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_points_to_curves.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_points_to_curves.cc @@ -99,7 +99,7 @@ static Curves *curve_from_points(const AttributeAccessor attributes, return curves_id; } Array indices(domain_size); - array_utils::fill_index_range(indices); + std::iota(indices.begin(), indices.end(), 0); const VArraySpan weights(weights_varray); grouped_sort(OffsetIndices({0, domain_size}), weights, indices); bke::gather_attributes( diff --git a/source/blender/nodes/geometry/nodes/node_geo_points_to_sdf_volume.cc b/source/blender/nodes/geometry/nodes/node_geo_points_to_sdf_volume.cc index 5f9cc501fd6..9c5f6c2363c 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_points_to_sdf_volume.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_points_to_sdf_volume.cc @@ -48,7 +48,7 @@ static void node_declare(NodeDeclarationBuilder &b) static void search_link_ops(GatherLinkSearchOpParams ¶ms) { if (U.experimental.use_new_volume_nodes) { - nodes::search_link_ops_for_basic_node(params); + blender::nodes::search_link_ops_for_basic_node(params); } } @@ -133,7 +133,7 @@ static void node_register() "NodeGeometryPointsToVolume", node_free_standard_storage, node_copy_standard_storage); - bke::node_type_size(&ntype, 170, 120, 700); + blender::bke::node_type_size(&ntype, 170, 120, 700); ntype.initfunc = node_init; ntype.updatefunc = node_update; ntype.declare = node_declare; 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 index 59e9d56d9d2..a88082a723e 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_points_to_vertices.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_points_to_vertices.cc @@ -13,6 +13,8 @@ namespace blender::nodes::node_geo_points_to_vertices_cc { +using blender::Array; + static void node_declare(NodeDeclarationBuilder &b) { b.add_input("Points").supported_type(GeometryComponent::Type::PointCloud); diff --git a/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc b/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc index 1630e8ea76a..2a6151edcec 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc @@ -249,7 +249,7 @@ static void node_register() "NodeGeometryPointsToVolume", node_free_standard_storage, node_copy_standard_storage); - bke::node_type_size(&ntype, 170, 120, 700); + blender::bke::node_type_size(&ntype, 170, 120, 700); ntype.initfunc = node_init; ntype.updatefunc = node_update; ntype.declare = node_declare; diff --git a/source/blender/nodes/geometry/nodes/node_geo_raycast.cc b/source/blender/nodes/geometry/nodes/node_geo_raycast.cc index fc9e10cdc3d..80c5163e2e5 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_raycast.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_raycast.cc @@ -108,12 +108,12 @@ static void node_update(bNodeTree *ntree, bNode *node) static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) { - const NodeDeclaration &declaration = *params.node_type().static_declaration; + const NodeDeclaration &declaration = *params.node_type().fixed_declaration; search_link_ops_for_declarations(params, declaration.inputs.as_span().take_front(1)); search_link_ops_for_declarations(params, declaration.inputs.as_span().take_back(3)); search_link_ops_for_declarations(params, declaration.outputs.as_span().take_front(4)); - const std::optional type = bke::socket_type_to_custom_data_type( + const std::optional type = node_data_type_to_custom_data_type( eNodeSocketDatatype(params.other_socket().type)); if (type && *type != CD_PROP_STRING) { /* The input and output sockets have the same name. */ @@ -246,32 +246,32 @@ static GField get_input_attribute_field(GeoNodeExecParams ¶ms, const eCustom switch (data_type) { case CD_PROP_FLOAT: if (params.output_is_required("Attribute_001")) { - return params.extract_input("Attribute_001"); + return params.extract_input>("Attribute_001"); } break; case CD_PROP_FLOAT3: if (params.output_is_required("Attribute")) { - return params.extract_input("Attribute"); + return params.extract_input>("Attribute"); } break; case CD_PROP_COLOR: if (params.output_is_required("Attribute_002")) { - return params.extract_input("Attribute_002"); + return params.extract_input>("Attribute_002"); } break; case CD_PROP_BOOL: if (params.output_is_required("Attribute_003")) { - return params.extract_input("Attribute_003"); + return params.extract_input>("Attribute_003"); } break; case CD_PROP_INT32: if (params.output_is_required("Attribute_004")) { - return params.extract_input("Attribute_004"); + return params.extract_input>("Attribute_004"); } break; case CD_PROP_QUATERNION: if (params.output_is_required("Attribute_005")) { - return params.extract_input("Attribute_005"); + return params.extract_input>("Attribute_005"); } break; default: @@ -413,7 +413,7 @@ static void node_register() static bNodeType ntype; geo_node_type_base(&ntype, GEO_NODE_RAYCAST, "Raycast", NODE_CLASS_GEOMETRY); - bke::node_type_size_preset(&ntype, bke::eNodeSizePreset::MIDDLE); + blender::bke::node_type_size_preset(&ntype, blender::bke::eNodeSizePreset::MIDDLE); ntype.initfunc = node_init; ntype.updatefunc = node_update; node_type_storage( diff --git a/source/blender/nodes/geometry/nodes/node_geo_realize_instances.cc b/source/blender/nodes/geometry/nodes/node_geo_realize_instances.cc index 5ecb40cca9d..e7fdaaa5c80 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_realize_instances.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_realize_instances.cc @@ -20,7 +20,7 @@ static void node_declare(NodeDeclarationBuilder &b) static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input("Geometry"); - GeometryComponentEditData::remember_deformed_positions_if_necessary(geometry_set); + GeometryComponentEditData::remember_deformed_curve_positions_if_necessary(geometry_set); geometry::RealizeInstancesOptions options; options.keep_original_ids = false; options.realize_instance_attributes = true; diff --git a/source/blender/nodes/geometry/nodes/node_geo_remove_attribute.cc b/source/blender/nodes/geometry/nodes/node_geo_remove_attribute.cc index 22753b27ff9..755c8790966 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_remove_attribute.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_remove_attribute.cc @@ -83,7 +83,7 @@ static void node_register() geo_node_type_base( &ntype, GEO_NODE_REMOVE_ATTRIBUTE, "Remove Named Attribute", NODE_CLASS_ATTRIBUTE); ntype.declare = node_declare; - bke::node_type_size(&ntype, 170, 100, 700); + blender::bke::node_type_size(&ntype, 170, 100, 700); ntype.geometry_node_execute = node_geo_exec; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_repeat_input.cc b/source/blender/nodes/geometry/nodes/node_geo_repeat_input.cc index 22cf3dfa49a..be3d69c508d 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_repeat_input.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_repeat_input.cc @@ -12,7 +12,6 @@ #include "NOD_geometry.hh" #include "NOD_socket.hh" -#include "NOD_zone_socket_items.hh" #include "node_geometry_util.hh" @@ -20,37 +19,20 @@ namespace blender::nodes::node_geo_repeat_input_cc { NODE_STORAGE_FUNCS(NodeGeometryRepeatInput); -static void node_declare(NodeDeclarationBuilder &b) +static void node_declare_dynamic(const bNodeTree &tree, + const bNode &node, + NodeDeclaration &r_declaration) { + NodeDeclarationBuilder b{r_declaration}; b.add_input("Iterations").min(0).default_value(1); - const bNode *node = b.node_or_null(); - const bNodeTree *tree = b.tree_or_null(); - if (ELEM(nullptr, node, tree)) { - return; + const NodeGeometryRepeatInput &storage = node_storage(node); + const bNode *output_node = tree.node_by_id(storage.output_node_id); + if (output_node != nullptr) { + const NodeGeometryRepeatOutput &output_storage = + *static_cast(output_node->storage); + socket_declarations_for_repeat_items(output_storage.items_span(), r_declaration); } - - const NodeGeometryRepeatInput &storage = node_storage(*node); - const bNode *output_node = tree->node_by_id(storage.output_node_id); - if (output_node == nullptr) { - return; - } - const auto &output_storage = *static_cast( - output_node->storage); - for (const int i : IndexRange(output_storage.items_num)) { - const NodeRepeatItem &item = output_storage.items[i]; - const eNodeSocketDatatype socket_type = eNodeSocketDatatype(item.socket_type); - const StringRef name = item.name ? item.name : ""; - const std::string identifier = RepeatItemsAccessor::socket_identifier_for_item(item); - auto &input_decl = b.add_input(socket_type, name, identifier); - auto &output_decl = b.add_output(socket_type, name, identifier); - if (socket_type_supports_fields(socket_type)) { - input_decl.supports_field(); - output_decl.dependent_field({input_decl.input_index()}); - } - } - b.add_input("", "__extend__"); - b.add_output("", "__extend__"); } static void node_init(bNodeTree * /*tree*/, bNode *node) @@ -63,12 +45,40 @@ static void node_init(bNodeTree * /*tree*/, bNode *node) static bool node_insert_link(bNodeTree *ntree, bNode *node, bNodeLink *link) { - bNode *output_node = ntree->node_by_id(node_storage(*node).output_node_id); + const bNode *output_node = ntree->node_by_id(node_storage(*node).output_node_id); if (!output_node) { return true; } - return socket_items::try_add_item_via_any_extend_socket( - *ntree, *node, *output_node, *link); + auto &storage = *static_cast(output_node->storage); + if (link->tonode == node) { + if (link->tosock->identifier == StringRef("__extend__")) { + if (const NodeRepeatItem *item = storage.add_item(link->fromsock->name, + eNodeSocketDatatype(link->fromsock->type))) + { + update_node_declaration_and_sockets(*ntree, *node); + link->tosock = nodeFindSocket(node, SOCK_IN, item->identifier_str().c_str()); + return true; + } + } + else { + return true; + } + } + if (link->fromnode == node) { + if (link->fromsock->identifier == StringRef("__extend__")) { + if (const NodeRepeatItem *item = storage.add_item(link->tosock->name, + eNodeSocketDatatype(link->tosock->type))) + { + update_node_declaration_and_sockets(*ntree, *node); + link->fromsock = nodeFindSocket(node, SOCK_OUT, item->identifier_str().c_str()); + return true; + } + } + else { + return true; + } + } + return false; } static void node_register() @@ -76,7 +86,7 @@ static void node_register() static bNodeType ntype; geo_node_type_base(&ntype, GEO_NODE_REPEAT_INPUT, "Repeat Input", NODE_CLASS_INTERFACE); ntype.initfunc = node_init; - ntype.declare = node_declare; + ntype.declare_dynamic = node_declare_dynamic; ntype.gather_link_search_ops = nullptr; ntype.insert_link = node_insert_link; node_type_storage( diff --git a/source/blender/nodes/geometry/nodes/node_geo_repeat_output.cc b/source/blender/nodes/geometry/nodes/node_geo_repeat_output.cc index 910eb3f9d87..b5038bd441a 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_repeat_output.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_repeat_output.cc @@ -14,37 +14,117 @@ #include "NOD_geometry.hh" #include "NOD_socket.hh" -#include "NOD_zone_socket_items.hh" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "node_geometry_util.hh" +namespace blender::nodes { + +static std::unique_ptr socket_declaration_for_repeat_item( + const NodeRepeatItem &item, const eNodeSocketInOut in_out, const int corresponding_input = -1) +{ + const eNodeSocketDatatype socket_type = eNodeSocketDatatype(item.socket_type); + + std::unique_ptr decl; + + auto handle_field_decl = [&](SocketDeclaration &decl) { + if (in_out == SOCK_IN) { + decl.input_field_type = InputSocketFieldType::IsSupported; + } + else { + decl.output_field_dependency = OutputFieldDependency::ForPartiallyDependentField( + {corresponding_input}); + } + }; + + switch (socket_type) { + case SOCK_FLOAT: + decl = std::make_unique(); + handle_field_decl(*decl); + break; + case SOCK_VECTOR: + decl = std::make_unique(); + handle_field_decl(*decl); + break; + case SOCK_RGBA: + decl = std::make_unique(); + handle_field_decl(*decl); + break; + case SOCK_BOOLEAN: + decl = std::make_unique(); + handle_field_decl(*decl); + break; + case SOCK_ROTATION: + decl = std::make_unique(); + handle_field_decl(*decl); + break; + case SOCK_INT: + decl = std::make_unique(); + handle_field_decl(*decl); + break; + case SOCK_STRING: + decl = std::make_unique(); + break; + case SOCK_GEOMETRY: + decl = std::make_unique(); + break; + case SOCK_OBJECT: + decl = std::make_unique(); + break; + case SOCK_IMAGE: + decl = std::make_unique(); + break; + case SOCK_COLLECTION: + decl = std::make_unique(); + break; + case SOCK_MATERIAL: + decl = std::make_unique(); + break; + default: + BLI_assert_unreachable(); + break; + } + + decl->name = item.name ? item.name : ""; + decl->identifier = item.identifier_str(); + decl->in_out = in_out; + return decl; +} + +void socket_declarations_for_repeat_items(const Span items, + NodeDeclaration &r_declaration) +{ + for (const int i : items.index_range()) { + const NodeRepeatItem &item = items[i]; + SocketDeclarationPtr input_decl = socket_declaration_for_repeat_item(item, SOCK_IN); + r_declaration.inputs.append(input_decl.get()); + r_declaration.items.append(std::move(input_decl)); + + SocketDeclarationPtr output_decl = socket_declaration_for_repeat_item( + item, SOCK_OUT, r_declaration.inputs.size() - 1); + r_declaration.outputs.append(output_decl.get()); + r_declaration.items.append(std::move(output_decl)); + } + SocketDeclarationPtr input_extend_decl = decl::create_extend_declaration(SOCK_IN); + r_declaration.inputs.append(input_extend_decl.get()); + r_declaration.items.append(std::move(input_extend_decl)); + + SocketDeclarationPtr output_extend_decl = decl::create_extend_declaration(SOCK_OUT); + r_declaration.outputs.append(output_extend_decl.get()); + r_declaration.items.append(std::move(output_extend_decl)); +} +} // namespace blender::nodes namespace blender::nodes::node_geo_repeat_output_cc { NODE_STORAGE_FUNCS(NodeGeometryRepeatOutput); -static void node_declare(NodeDeclarationBuilder &b) +static void node_declare_dynamic(const bNodeTree & /*node_tree*/, + const bNode &node, + NodeDeclaration &r_declaration) { - const bNode *node = b.node_or_null(); - if (node == nullptr) { - return; - } - const NodeGeometryRepeatOutput &storage = node_storage(*node); - for (const int i : IndexRange(storage.items_num)) { - const NodeRepeatItem &item = storage.items[i]; - const eNodeSocketDatatype socket_type = eNodeSocketDatatype(item.socket_type); - const StringRef name = item.name ? item.name : ""; - const std::string identifier = RepeatItemsAccessor::socket_identifier_for_item(item); - auto &input_decl = b.add_input(socket_type, name, identifier); - auto &output_decl = b.add_output(socket_type, name, identifier); - if (socket_type_supports_fields(socket_type)) { - input_decl.supports_field(); - output_decl.dependent_field({input_decl.input_index()}); - } - } - b.add_input("", "__extend__"); - b.add_output("", "__extend__"); + const NodeGeometryRepeatOutput &storage = node_storage(node); + socket_declarations_for_repeat_items(storage.items_span(), r_declaration); } static void node_init(bNodeTree * /*tree*/, bNode *node) @@ -64,23 +144,67 @@ static void node_init(bNodeTree * /*tree*/, bNode *node) static void node_free_storage(bNode *node) { - socket_items::destruct_array(*node); + NodeGeometryRepeatOutput &storage = node_storage(*node); + for (NodeRepeatItem &item : storage.items_span()) { + MEM_SAFE_FREE(item.name); + } + MEM_SAFE_FREE(storage.items); MEM_freeN(node->storage); } static void node_copy_storage(bNodeTree * /*dst_tree*/, bNode *dst_node, const bNode *src_node) { const NodeGeometryRepeatOutput &src_storage = node_storage(*src_node); - auto *dst_storage = MEM_new(__func__, src_storage); - dst_node->storage = dst_storage; + NodeGeometryRepeatOutput *dst_storage = MEM_cnew(__func__); - socket_items::copy_array(*src_node, *dst_node); + dst_storage->items = MEM_cnew_array(src_storage.items_num, __func__); + dst_storage->items_num = src_storage.items_num; + dst_storage->active_index = src_storage.active_index; + dst_storage->next_identifier = src_storage.next_identifier; + dst_storage->inspection_index = src_storage.inspection_index; + for (const int i : IndexRange(src_storage.items_num)) { + if (char *name = src_storage.items[i].name) { + dst_storage->items[i].identifier = src_storage.items[i].identifier; + dst_storage->items[i].name = BLI_strdup(name); + dst_storage->items[i].socket_type = src_storage.items[i].socket_type; + } + } + + dst_node->storage = dst_storage; } static bool node_insert_link(bNodeTree *ntree, bNode *node, bNodeLink *link) { - return socket_items::try_add_item_via_any_extend_socket( - *ntree, *node, *node, *link); + NodeGeometryRepeatOutput &storage = node_storage(*node); + if (link->tonode == node) { + if (link->tosock->identifier == StringRef("__extend__")) { + if (const NodeRepeatItem *item = storage.add_item(link->fromsock->name, + eNodeSocketDatatype(link->fromsock->type))) + { + update_node_declaration_and_sockets(*ntree, *node); + link->tosock = nodeFindSocket(node, SOCK_IN, item->identifier_str().c_str()); + return true; + } + } + else { + return true; + } + } + if (link->fromnode == node) { + if (link->fromsock->identifier == StringRef("__extend__")) { + if (const NodeRepeatItem *item = storage.add_item(link->tosock->name, + eNodeSocketDatatype(link->tosock->type))) + { + update_node_declaration_and_sockets(*ntree, *node); + link->fromsock = nodeFindSocket(node, SOCK_OUT, item->identifier_str().c_str()); + return true; + } + } + else { + return true; + } + } + return false; } static void node_register() @@ -88,7 +212,7 @@ static void node_register() static bNodeType ntype; geo_node_type_base(&ntype, GEO_NODE_REPEAT_OUTPUT, "Repeat Output", NODE_CLASS_INTERFACE); ntype.initfunc = node_init; - ntype.declare = node_declare; + ntype.declare_dynamic = node_declare_dynamic; ntype.insert_link = node_insert_link; node_type_storage(&ntype, "NodeGeometryRepeatOutput", node_free_storage, node_copy_storage); nodeRegisterType(&ntype); @@ -106,3 +230,83 @@ blender::MutableSpan NodeGeometryRepeatOutput::items_span() { return blender::MutableSpan(items, items_num); } + +bool NodeRepeatItem::supports_type(const eNodeSocketDatatype type) +{ + return ELEM(type, + SOCK_FLOAT, + SOCK_VECTOR, + SOCK_RGBA, + SOCK_BOOLEAN, + SOCK_ROTATION, + SOCK_INT, + SOCK_STRING, + SOCK_GEOMETRY, + SOCK_OBJECT, + SOCK_MATERIAL, + SOCK_IMAGE, + SOCK_COLLECTION); +} + +std::string NodeRepeatItem::identifier_str() const +{ + return "Item_" + std::to_string(this->identifier); +} + +NodeRepeatItem *NodeGeometryRepeatOutput::add_item(const char *name, + const eNodeSocketDatatype type) +{ + if (!NodeRepeatItem::supports_type(type)) { + return nullptr; + } + const int insert_index = this->items_num; + NodeRepeatItem *old_items = this->items; + + this->items = MEM_cnew_array(this->items_num + 1, __func__); + std::copy_n(old_items, insert_index, this->items); + NodeRepeatItem &new_item = this->items[insert_index]; + std::copy_n(old_items + insert_index + 1, + this->items_num - insert_index, + this->items + insert_index + 1); + + new_item.identifier = this->next_identifier++; + this->set_item_name(new_item, name); + new_item.socket_type = type; + + this->items_num++; + MEM_SAFE_FREE(old_items); + return &new_item; +} + +void NodeGeometryRepeatOutput::set_item_name(NodeRepeatItem &item, const char *name) +{ + char unique_name[MAX_NAME + 4]; + STRNCPY(unique_name, name); + + struct Args { + NodeGeometryRepeatOutput *storage; + const NodeRepeatItem *item; + } args = {this, &item}; + + const char *default_name = nodeStaticSocketLabel(item.socket_type, 0); + BLI_uniquename_cb( + [](void *arg, const char *name) { + const Args &args = *static_cast(arg); + for (const NodeRepeatItem &item : args.storage->items_span()) { + if (&item != args.item) { + if (STREQ(item.name, name)) { + return true; + } + } + } + return false; + }, + &args, + default_name, + '.', + unique_name, + ARRAY_SIZE(unique_name)); + + MEM_SAFE_FREE(item.name); + item.name = BLI_strdup(unique_name); +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_sample_index.cc b/source/blender/nodes/geometry/nodes/node_geo_sample_index.cc index e9123c85ac3..2705daa3712 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_sample_index.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_sample_index.cc @@ -129,11 +129,11 @@ static void node_update(bNodeTree *ntree, bNode *node) static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) { - const NodeDeclaration &declaration = *params.node_type().static_declaration; + const NodeDeclaration &declaration = *params.node_type().fixed_declaration; search_link_ops_for_declarations(params, declaration.inputs.as_span().take_back(1)); search_link_ops_for_declarations(params, declaration.inputs.as_span().take_front(1)); - const std::optional type = bke::socket_type_to_custom_data_type( + const std::optional type = node_data_type_to_custom_data_type( eNodeSocketDatatype(params.other_socket().type)); if (type && *type != CD_PROP_STRING) { /* The input and output sockets have the same name. */ @@ -268,17 +268,17 @@ static GField get_input_attribute_field(GeoNodeExecParams ¶ms, const eCustom { switch (data_type) { case CD_PROP_FLOAT: - return params.extract_input("Value_Float"); + return params.extract_input>("Value_Float"); case CD_PROP_FLOAT3: - return params.extract_input("Value_Vector"); + return params.extract_input>("Value_Vector"); case CD_PROP_COLOR: - return params.extract_input("Value_Color"); + return params.extract_input>("Value_Color"); case CD_PROP_BOOL: - return params.extract_input("Value_Bool"); + return params.extract_input>("Value_Bool"); case CD_PROP_INT32: - return params.extract_input("Value_Int"); + return params.extract_input>("Value_Int"); case CD_PROP_QUATERNION: - return params.extract_input("Value_Rotation"); + return params.extract_input>("Value_Rotation"); default: BLI_assert_unreachable(); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_sample_nearest_surface.cc b/source/blender/nodes/geometry/nodes/node_geo_sample_nearest_surface.cc index a1d971be0b3..3c1e58f9958 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_sample_nearest_surface.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_sample_nearest_surface.cc @@ -91,11 +91,11 @@ static void node_update(bNodeTree *ntree, bNode *node) static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) { - const NodeDeclaration &declaration = *params.node_type().static_declaration; + const NodeDeclaration &declaration = *params.node_type().fixed_declaration; search_link_ops_for_declarations(params, declaration.inputs.as_span().take_back(2)); search_link_ops_for_declarations(params, declaration.inputs.as_span().take_front(1)); - const std::optional type = bke::socket_type_to_custom_data_type( + const std::optional type = node_data_type_to_custom_data_type( eNodeSocketDatatype(params.other_socket().type)); if (type && *type != CD_PROP_STRING) { /* The input and output sockets have the same name. */ @@ -162,17 +162,17 @@ static GField get_input_attribute_field(GeoNodeExecParams ¶ms, const eCustom { switch (data_type) { case CD_PROP_FLOAT: - return params.extract_input("Value_Float"); + return params.extract_input>("Value_Float"); case CD_PROP_FLOAT3: - return params.extract_input("Value_Vector"); + return params.extract_input>("Value_Vector"); case CD_PROP_COLOR: - return params.extract_input("Value_Color"); + return params.extract_input>("Value_Color"); case CD_PROP_BOOL: - return params.extract_input("Value_Bool"); + return params.extract_input>("Value_Bool"); case CD_PROP_INT32: - return params.extract_input("Value_Int"); + return params.extract_input>("Value_Int"); case CD_PROP_QUATERNION: - return params.extract_input("Value_Rotation"); + return params.extract_input>("Value_Rotation"); default: BLI_assert_unreachable(); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_sample_uv_surface.cc b/source/blender/nodes/geometry/nodes/node_geo_sample_uv_surface.cc index 36c173b3a46..7eee63150c9 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_sample_uv_surface.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_sample_uv_surface.cc @@ -100,12 +100,12 @@ static void node_update(bNodeTree *ntree, bNode *node) static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) { - const NodeDeclaration &declaration = *params.node_type().static_declaration; + const NodeDeclaration &declaration = *params.node_type().fixed_declaration; search_link_ops_for_declarations(params, declaration.inputs.as_span().take_back(2)); search_link_ops_for_declarations(params, declaration.inputs.as_span().take_front(1)); search_link_ops_for_declarations(params, declaration.outputs.as_span().take_back(1)); - const std::optional type = bke::socket_type_to_custom_data_type( + const std::optional type = node_data_type_to_custom_data_type( eNodeSocketDatatype(params.other_socket().type)); if (type && *type != CD_PROP_STRING) { /* The input and output sockets have the same name. */ @@ -188,17 +188,17 @@ static GField get_input_attribute_field(GeoNodeExecParams ¶ms, const eCustom { switch (data_type) { case CD_PROP_FLOAT: - return params.extract_input("Value_Float"); + return params.extract_input>("Value_Float"); case CD_PROP_FLOAT3: - return params.extract_input("Value_Vector"); + return params.extract_input>("Value_Vector"); case CD_PROP_COLOR: - return params.extract_input("Value_Color"); + return params.extract_input>("Value_Color"); case CD_PROP_BOOL: - return params.extract_input("Value_Bool"); + return params.extract_input>("Value_Bool"); case CD_PROP_INT32: - return params.extract_input("Value_Int"); + return params.extract_input>("Value_Int"); case CD_PROP_QUATERNION: - return params.extract_input("Value_Rotation"); + return params.extract_input>("Value_Rotation"); default: BLI_assert_unreachable(); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_sample_volume.cc b/source/blender/nodes/geometry/nodes/node_geo_sample_volume.cc index 4a7932bbe0b..ba9c09f78c3 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_sample_volume.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_sample_volume.cc @@ -31,8 +31,8 @@ NODE_STORAGE_FUNCS(NodeGeometrySampleVolume) static void node_declare(NodeDeclarationBuilder &b) { b.add_input(CTX_N_(BLT_I18NCONTEXT_ID_ID, "Volume")) - .supported_type(GeometryComponent::Type::Volume) - .translation_context(BLT_I18NCONTEXT_ID_ID); + .translation_context(BLT_I18NCONTEXT_ID_ID) + .supported_type(GeometryComponent::Type::Volume); std::string grid_socket_description = N_( "Expects a Named Attribute with the name of a Grid in the Volume"); @@ -85,7 +85,7 @@ static void search_link_ops(GatherLinkSearchOpParams ¶ms) if (!U.experimental.use_new_volume_nodes) { return; } - const NodeDeclaration &declaration = *params.node_type().static_declaration; + const NodeDeclaration &declaration = *params.node_type().fixed_declaration; search_link_ops_for_declarations(params, declaration.inputs.as_span().take_back(1)); search_link_ops_for_declarations(params, declaration.inputs.as_span().take_front(1)); diff --git a/source/blender/nodes/geometry/nodes/node_geo_sdf_volume_sphere.cc b/source/blender/nodes/geometry/nodes/node_geo_sdf_volume_sphere.cc index a8adda84f09..0c3bbcaa679 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_sdf_volume_sphere.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_sdf_volume_sphere.cc @@ -23,17 +23,17 @@ static void node_declare(NodeDeclarationBuilder &b) b.add_input("Radius").default_value(1.0f).min(0.0f).subtype(PROP_DISTANCE); b.add_input("Voxel Size").default_value(0.2f).min(0.01f).subtype(PROP_DISTANCE); b.add_input("Half-Band Width") + .description("Half the width of the narrow band in voxel units") .default_value(3.0f) .min(1.01f) - .max(10.0f) - .description("Half the width of the narrow band in voxel units"); + .max(10.0f); b.add_output("Volume").translation_context(BLT_I18NCONTEXT_ID_ID); } static void search_link_ops(GatherLinkSearchOpParams ¶ms) { if (U.experimental.use_new_volume_nodes) { - nodes::search_link_ops_for_basic_node(params); + blender::nodes::search_link_ops_for_basic_node(params); } } 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 7b72437a197..2880b80f90b 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_separate_components.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_separate_components.cc @@ -11,7 +11,6 @@ static void node_declare(NodeDeclarationBuilder &b) b.add_input("Geometry"); b.add_output("Mesh").propagate_all(); b.add_output("Curve").propagate_all(); - b.add_output("Grease Pencil").propagate_all(); b.add_output("Point Cloud").propagate_all(); b.add_output("Volume") .translation_context(BLT_I18NCONTEXT_ID_ID) @@ -25,7 +24,6 @@ static void node_geo_exec(GeoNodeExecParams params) GeometrySet meshes; GeometrySet curves; - GeometrySet grease_pencil; GeometrySet point_clouds; GeometrySet volumes; GeometrySet instances; @@ -36,9 +34,6 @@ static void node_geo_exec(GeoNodeExecParams params) if (geometry_set.has()) { curves.add(*geometry_set.get_component()); } - if (geometry_set.has()) { - grease_pencil.add(*geometry_set.get_component()); - } if (geometry_set.has()) { point_clouds.add(*geometry_set.get_component()); } @@ -51,7 +46,6 @@ static void node_geo_exec(GeoNodeExecParams params) params.set_output("Mesh", meshes); params.set_output("Curve", curves); - params.set_output("Grease Pencil", grease_pencil); params.set_output("Point Cloud", point_clouds); params.set_output("Volume", volumes); params.set_output("Instances", instances); diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_curve_normal.cc b/source/blender/nodes/geometry/nodes/node_geo_set_curve_normal.cc index b2a5e9ab0bc..9a7dd6ccae6 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_set_curve_normal.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_set_curve_normal.cc @@ -3,7 +3,6 @@ * SPDX-License-Identifier: GPL-2.0-or-later */ #include "BKE_curves.hh" -#include "BKE_grease_pencil.hh" #include "UI_interface.hh" #include "UI_resources.hh" @@ -18,8 +17,7 @@ namespace blender::nodes::node_geo_set_curve_normal_cc { static void node_declare(NodeDeclarationBuilder &b) { - b.add_input("Curve").supported_type( - {GeometryComponent::Type::Curve, GeometryComponent::Type::GreasePencil}); + b.add_input("Curve").supported_type(GeometryComponent::Type::Curve); b.add_input("Selection").default_value(true).hide_value().field_on_all(); b.add_output("Curve").propagate_all(); } @@ -34,11 +32,11 @@ static void node_init(bNodeTree * /*tree*/, bNode *node) node->custom1 = NORMAL_MODE_MINIMUM_TWIST; } -static void set_curve_normal(bke::CurvesGeometry &curves, - const NormalMode mode, - const fn::FieldContext field_context, - const Field &selection_field) +static void set_normal_mode(bke::CurvesGeometry &curves, + const NormalMode mode, + const Field &selection_field) { + const bke::CurvesFieldContext field_context{curves, ATTR_DOMAIN_CURVE}; fn::FieldEvaluator evaluator{field_context, curves.curves_num()}; evaluator.set_selection(selection_field); evaluator.evaluate(); @@ -47,23 +45,6 @@ static void set_curve_normal(bke::CurvesGeometry &curves, curves.tag_normals_changed(); } -static void set_grease_pencil_normal(GreasePencil &grease_pencil, - const NormalMode mode, - const Field &selection_field) -{ - using namespace blender::bke::greasepencil; - for (const int layer_index : grease_pencil.layers().index_range()) { - Drawing *drawing = get_eval_grease_pencil_layer_drawing_for_write(grease_pencil, layer_index); - if (drawing == nullptr) { - continue; - } - bke::CurvesGeometry &curves = drawing->strokes_for_write(); - const bke::GreasePencilLayerFieldContext field_context( - grease_pencil, ATTR_DOMAIN_CURVE, layer_index); - set_curve_normal(curves, mode, field_context, selection_field); - } -} - static void node_geo_exec(GeoNodeExecParams params) { const NormalMode mode = static_cast(params.node().custom1); @@ -74,11 +55,7 @@ static void node_geo_exec(GeoNodeExecParams params) geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { if (Curves *curves_id = geometry_set.get_curves_for_write()) { bke::CurvesGeometry &curves = curves_id->geometry.wrap(); - const bke::CurvesFieldContext field_context{curves, ATTR_DOMAIN_CURVE}; - set_curve_normal(curves, mode, field_context, selection_field); - } - if (geometry_set.has_grease_pencil()) { - set_grease_pencil_normal(*geometry_set.get_grease_pencil_for_write(), mode, selection_field); + set_normal_mode(curves, mode, selection_field); } }); diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_curve_radius.cc b/source/blender/nodes/geometry/nodes/node_geo_set_curve_radius.cc index 0f89b6f7de9..ed9c07a7c50 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_set_curve_radius.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_set_curve_radius.cc @@ -3,7 +3,6 @@ * SPDX-License-Identifier: GPL-2.0-or-later */ #include "BKE_curves.hh" -#include "BKE_grease_pencil.hh" #include "node_geometry_util.hh" @@ -11,19 +10,14 @@ namespace blender::nodes::node_geo_set_curve_radius_cc { static void node_declare(NodeDeclarationBuilder &b) { - b.add_input("Curve").supported_type( - {GeometryComponent::Type::Curve, GeometryComponent::Type::GreasePencil}); + b.add_input("Curve").supported_type(GeometryComponent::Type::Curve); b.add_input("Selection").default_value(true).hide_value().field_on_all(); - b.add_input("Radius") - .min(0.0f) - .default_value(0.005f) - .subtype(PROP_DISTANCE) - .field_on_all(); + b.add_input("Radius").min(0.0f).default_value(0.005f).field_on_all().subtype( + PROP_DISTANCE); b.add_output("Curve").propagate_all(); } static void set_radius(bke::CurvesGeometry &curves, - const fn::FieldContext &field_context, const Field &selection_field, const Field &radius_field) { @@ -33,6 +27,8 @@ static void set_radius(bke::CurvesGeometry &curves, MutableAttributeAccessor attributes = curves.attributes_for_write(); AttributeWriter radii = attributes.lookup_or_add_for_write("radius", ATTR_DOMAIN_POINT); + + const bke::CurvesFieldContext field_context{curves, ATTR_DOMAIN_POINT}; fn::FieldEvaluator evaluator{field_context, curves.points_num()}; evaluator.set_selection(selection_field); evaluator.add_with_destination(radius_field, radii.varray); @@ -49,25 +45,7 @@ static void node_geo_exec(GeoNodeExecParams params) geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { if (Curves *curves_id = geometry_set.get_curves_for_write()) { - bke::CurvesGeometry &curves = curves_id->geometry.wrap(); - const bke::CurvesFieldContext field_context{curves, ATTR_DOMAIN_POINT}; - set_radius(curves, field_context, selection_field, radii_field); - } - - if (GreasePencil *grease_pencil = geometry_set.get_grease_pencil_for_write()) { - using namespace blender::bke::greasepencil; - - for (const int layer_index : grease_pencil->layers().index_range()) { - Drawing *drawing = get_eval_grease_pencil_layer_drawing_for_write(*grease_pencil, - layer_index); - if (drawing == nullptr) { - continue; - } - bke::CurvesGeometry &curves = drawing->strokes_for_write(); - const bke::GreasePencilLayerFieldContext field_context( - *grease_pencil, ATTR_DOMAIN_POINT, layer_index); - set_radius(curves, field_context, selection_field, radii_field); - } + set_radius(curves_id->geometry.wrap(), selection_field, radii_field); } }); diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_curve_tilt.cc b/source/blender/nodes/geometry/nodes/node_geo_set_curve_tilt.cc index 10857cedae0..cd607dff3e2 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_set_curve_tilt.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_set_curve_tilt.cc @@ -3,7 +3,6 @@ * SPDX-License-Identifier: GPL-2.0-or-later */ #include "BKE_curves.hh" -#include "BKE_grease_pencil.hh" #include "node_geometry_util.hh" @@ -11,17 +10,15 @@ namespace blender::nodes::node_geo_set_curve_tilt_cc { static void node_declare(NodeDeclarationBuilder &b) { - b.add_input("Curve").supported_type( - {GeometryComponent::Type::Curve, GeometryComponent::Type::GreasePencil}); + b.add_input("Curve").supported_type(GeometryComponent::Type::Curve); b.add_input("Selection").default_value(true).hide_value().field_on_all(); b.add_input("Tilt").subtype(PROP_ANGLE).field_on_all(); b.add_output("Curve").propagate_all(); } -static void set_curve_tilt(bke::CurvesGeometry &curves, - const fn::FieldContext &field_context, - const Field &selection_field, - const Field &tilt_field) +static void set_tilt(bke::CurvesGeometry &curves, + const Field &selection_field, + const Field &tilt_field) { if (curves.points_num() == 0) { return; @@ -29,6 +26,8 @@ static void set_curve_tilt(bke::CurvesGeometry &curves, MutableAttributeAccessor attributes = curves.attributes_for_write(); AttributeWriter tilts = attributes.lookup_or_add_for_write("tilt", ATTR_DOMAIN_POINT); + + const bke::CurvesFieldContext field_context{curves, ATTR_DOMAIN_POINT}; fn::FieldEvaluator evaluator{field_context, curves.points_num()}; evaluator.set_selection(selection_field); evaluator.add_with_destination(tilt_field, tilts.varray); @@ -37,23 +36,6 @@ static void set_curve_tilt(bke::CurvesGeometry &curves, tilts.finish(); } -static void set_grease_pencil_tilt(GreasePencil &grease_pencil, - const Field &selection_field, - const Field &tilt_field) -{ - using namespace blender::bke::greasepencil; - for (const int layer_index : grease_pencil.layers().index_range()) { - Drawing *drawing = get_eval_grease_pencil_layer_drawing_for_write(grease_pencil, layer_index); - if (drawing == nullptr) { - continue; - } - bke::CurvesGeometry &curves = drawing->strokes_for_write(); - const bke::GreasePencilLayerFieldContext field_context( - grease_pencil, ATTR_DOMAIN_POINT, layer_index); - set_curve_tilt(curves, field_context, selection_field, tilt_field); - } -} - static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input("Curve"); @@ -62,13 +44,7 @@ static void node_geo_exec(GeoNodeExecParams params) geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { if (Curves *curves_id = geometry_set.get_curves_for_write()) { - bke::CurvesGeometry &curves = curves_id->geometry.wrap(); - const bke::CurvesFieldContext field_context{curves, ATTR_DOMAIN_POINT}; - set_curve_tilt(curves, field_context, selection_field, tilt_field); - } - if (geometry_set.has_grease_pencil()) { - set_grease_pencil_tilt( - *geometry_set.get_grease_pencil_for_write(), selection_field, tilt_field); + set_tilt(curves_id->geometry.wrap(), selection_field, tilt_field); } }); diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_material.cc b/source/blender/nodes/geometry/nodes/node_geo_set_material.cc index 5b5f8f92ee1..0c67f516e8a 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_set_material.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_set_material.cc @@ -13,8 +13,6 @@ #include "DNA_pointcloud_types.h" #include "DNA_volume_types.h" -#include "BKE_curves.hh" -#include "BKE_grease_pencil.hh" #include "BKE_material.h" #include "BKE_mesh.hh" @@ -26,48 +24,39 @@ static void node_declare(NodeDeclarationBuilder &b) .supported_type({GeometryComponent::Type::Mesh, GeometryComponent::Type::Volume, GeometryComponent::Type::PointCloud, - GeometryComponent::Type::Curve, - GeometryComponent::Type::GreasePencil}); + GeometryComponent::Type::Curve}); b.add_input("Selection").default_value(true).hide_value().field_on_all(); b.add_input("Material").hide_label(); b.add_output("Geometry").propagate_all(); } -static void assign_material_to_id_geometry(ID *id, - const fn::FieldContext &field_context, - const Field &selection_field, - MutableAttributeAccessor &attributes, - const eAttrDomain domain, - Material *material) +static void assign_material_to_faces(Mesh &mesh, const IndexMask &selection, Material *material) { - const int domain_size = attributes.domain_size(domain); - fn::FieldEvaluator selection_evaluator{field_context, domain_size}; - selection_evaluator.set_selection(selection_field); - selection_evaluator.evaluate(); - const IndexMask selection = selection_evaluator.get_evaluated_selection_as_mask(); - - if (selection.size() != attributes.domain_size(domain)) { - /* If the entire geometry isn't selected, and there is no material slot yet, add an empty + if (selection.size() != mesh.faces_num) { + /* If the entire mesh isn't selected, and there is no material slot yet, add an empty * slot so that the faces that aren't selected can still refer to the default material. */ - BKE_id_material_eval_ensure_default_slot(id); + BKE_id_material_eval_ensure_default_slot(&mesh.id); } - int new_index = -1; - const int orig_materials_num = *BKE_id_material_len_p(id); - if (Material **materials = *BKE_id_material_array_p(id)) { - new_index = Span(materials, orig_materials_num).first_index_try(material); + int new_material_index = -1; + for (const int i : IndexRange(mesh.totcol)) { + Material *other_material = mesh.mat[i]; + if (other_material == material) { + new_material_index = i; + break; + } } - - if (new_index == -1) { + if (new_material_index == -1) { /* Append a new material index. */ - new_index = orig_materials_num; - BKE_id_material_eval_assign(id, new_index + 1, material); + new_material_index = mesh.totcol; + BKE_id_material_eval_assign(&mesh.id, new_material_index + 1, material); } - SpanAttributeWriter indices = attributes.lookup_or_add_for_write_span("material_index", - domain); - index_mask::masked_fill(indices.span, new_index, selection); - indices.finish(); + MutableAttributeAccessor attributes = mesh.attributes_for_write(); + SpanAttributeWriter material_indices = attributes.lookup_or_add_for_write_span( + "material_index", ATTR_DOMAIN_FACE); + index_mask::masked_fill(material_indices.span, new_material_index, selection); + material_indices.finish(); } static void node_geo_exec(GeoNodeExecParams params) @@ -92,9 +81,12 @@ static void node_geo_exec(GeoNodeExecParams params) } else { const bke::MeshFieldContext field_context{*mesh, ATTR_DOMAIN_FACE}; - MutableAttributeAccessor attributes = mesh->attributes_for_write(); - assign_material_to_id_geometry( - &mesh->id, field_context, selection_field, attributes, ATTR_DOMAIN_FACE, material); + fn::FieldEvaluator selection_evaluator{field_context, mesh->faces_num}; + 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); } } if (Volume *volume = geometry_set.get_volume_for_write()) { @@ -115,31 +107,6 @@ static void node_geo_exec(GeoNodeExecParams params) curves_selection_warning = true; } } - if (GreasePencil *grease_pencil = geometry_set.get_grease_pencil_for_write()) { - using namespace blender::bke::greasepencil; - Vector mesh_by_layer(grease_pencil->layers().size(), nullptr); - for (const int layer_index : grease_pencil->layers().index_range()) { - Drawing *drawing = get_eval_grease_pencil_layer_drawing_for_write(*grease_pencil, - layer_index); - if (drawing == nullptr) { - continue; - } - bke::CurvesGeometry &curves = drawing->strokes_for_write(); - if (curves.curves_num() == 0) { - continue; - } - - const bke::GreasePencilLayerFieldContext field_context{ - *grease_pencil, ATTR_DOMAIN_CURVE, layer_index}; - MutableAttributeAccessor attributes = curves.attributes_for_write(); - assign_material_to_id_geometry(&grease_pencil->id, - field_context, - selection_field, - attributes, - ATTR_DOMAIN_CURVE, - material); - } - } }); if (no_faces_warning) { diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_material_index.cc b/source/blender/nodes/geometry/nodes/node_geo_set_material_index.cc index fdb0998abc1..2e6d4c0cd7b 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_set_material_index.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_set_material_index.cc @@ -4,36 +4,31 @@ #include "node_geometry_util.hh" -#include "DNA_grease_pencil_types.h" - -#include "BKE_curves.hh" -#include "BKE_grease_pencil.hh" - namespace blender::nodes::node_geo_set_material_index_cc { static void node_declare(NodeDeclarationBuilder &b) { - b.add_input("Geometry") - .supported_type({GeometryComponent::Type::Mesh, GeometryComponent::Type::GreasePencil}); + b.add_input("Geometry").supported_type(GeometryComponent::Type::Mesh); b.add_input("Selection").default_value(true).hide_value().field_on_all(); - b.add_input("Material Index").min(0).field_on_all(); + b.add_input("Material Index").field_on_all().min(0); b.add_output("Geometry").propagate_all(); } -static void set_material_index_in_geometry(const fn::FieldContext &field_context, - const Field &selection_field, - const Field &index_field, - MutableAttributeAccessor &attributes, - const eAttrDomain domain) +static void set_material_index_in_component(GeometryComponent &component, + const Field &selection_field, + const Field &index_field, + const eAttrDomain domain) { - const int domain_size = attributes.domain_size(domain); + const int domain_size = component.attribute_domain_size(domain); if (domain_size == 0) { return; } + MutableAttributeAccessor attributes = *component.attributes_for_write(); const bke::AttributeValidator validator = attributes.lookup_validator("material_index"); AttributeWriter indices = attributes.lookup_or_add_for_write("material_index", domain); + const bke::GeometryFieldContext field_context{component, domain}; fn::FieldEvaluator evaluator{field_context, domain_size}; evaluator.set_selection(selection_field); evaluator.add_with_destination(validator.validate_field_if_necessary(index_field), @@ -42,29 +37,6 @@ static void set_material_index_in_geometry(const fn::FieldContext &field_context indices.finish(); } -static void set_material_index_in_grease_pencil(GreasePencil &grease_pencil, - const Field &selection_field, - const Field &index_field) -{ - using namespace blender::bke::greasepencil; - for (const int layer_index : grease_pencil.layers().index_range()) { - Drawing *drawing = get_eval_grease_pencil_layer_drawing_for_write(grease_pencil, layer_index); - if (drawing == nullptr) { - continue; - } - bke::CurvesGeometry &curves = drawing->strokes_for_write(); - if (curves.curves_num() == 0) { - continue; - } - - MutableAttributeAccessor attributes = curves.attributes_for_write(); - const bke::GreasePencilLayerFieldContext field_context{ - grease_pencil, ATTR_DOMAIN_CURVE, layer_index}; - set_material_index_in_geometry( - field_context, selection_field, index_field, attributes, ATTR_DOMAIN_CURVE); - } -} - static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input("Geometry"); @@ -73,15 +45,10 @@ static void node_geo_exec(GeoNodeExecParams params) geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { if (geometry_set.has_mesh()) { - GeometryComponent &component = geometry_set.get_component_for_write(); - const bke::GeometryFieldContext field_context{ - geometry_set.get_component_for_write(), ATTR_DOMAIN_FACE}; - MutableAttributeAccessor attributes = *component.attributes_for_write(); - set_material_index_in_geometry( - field_context, selection_field, index_field, attributes, ATTR_DOMAIN_FACE); - } - if (GreasePencil *grease_pencil = geometry_set.get_grease_pencil_for_write()) { - set_material_index_in_grease_pencil(*grease_pencil, selection_field, index_field); + set_material_index_in_component(geometry_set.get_component_for_write(), + selection_field, + index_field, + ATTR_DOMAIN_FACE); } }); params.set_output("Geometry", std::move(geometry_set)); diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_point_radius.cc b/source/blender/nodes/geometry/nodes/node_geo_set_point_radius.cc index d86509b1eb9..f2147cdb982 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_set_point_radius.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_set_point_radius.cc @@ -12,11 +12,8 @@ static void node_declare(NodeDeclarationBuilder &b) { b.add_input("Points").supported_type(GeometryComponent::Type::PointCloud); b.add_input("Selection").default_value(true).hide_value().field_on_all(); - b.add_input("Radius") - .default_value(0.05f) - .min(0.0f) - .subtype(PROP_DISTANCE) - .field_on_all(); + b.add_input("Radius").default_value(0.05f).min(0.0f).field_on_all().subtype( + PROP_DISTANCE); b.add_output("Points").propagate_all(); } 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 10f3dc579a6..ed35167e22e 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_set_position.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_set_position.cc @@ -10,7 +10,6 @@ #include "DNA_meshdata_types.h" #include "BKE_curves.hh" -#include "BKE_grease_pencil.hh" #include "BKE_mesh.hh" #include "node_geometry_util.hh" @@ -22,16 +21,17 @@ static void node_declare(NodeDeclarationBuilder &b) b.add_input("Geometry"); b.add_input("Selection").default_value(true).hide_value().field_on_all(); b.add_input("Position").implicit_field_on_all(implicit_field_inputs::position); - b.add_input("Offset").subtype(PROP_TRANSLATION).field_on_all(); + b.add_input("Offset").field_on_all().subtype(PROP_TRANSLATION); b.add_output("Geometry").propagate_all(); } static void set_computed_position_and_offset(GeometryComponent &component, const VArray &in_positions, const VArray &in_offsets, - const IndexMask &selection, - MutableAttributeAccessor attributes) + const IndexMask &selection) { + MutableAttributeAccessor attributes = *component.attributes_for_write(); + /* Optimize the case when `in_positions` references the original positions array. */ const bke::AttributeReader positions_read_only = attributes.lookup("position"); bool positions_are_original = false; @@ -108,42 +108,6 @@ static void set_computed_position_and_offset(GeometryComponent &component, } } -static void set_position_in_grease_pencil(GreasePencilComponent &grease_pencil_component, - const Field &selection_field, - const Field &position_field, - const Field &offset_field) -{ - using namespace blender::bke::greasepencil; - GreasePencil &grease_pencil = *grease_pencil_component.get_for_write(); - /* Set position for each layer. */ - for (const int layer_index : grease_pencil.layers().index_range()) { - Drawing *drawing = bke::greasepencil::get_eval_grease_pencil_layer_drawing_for_write( - grease_pencil, layer_index); - if (drawing == nullptr || drawing->strokes().points_num() == 0) { - continue; - } - bke::GreasePencilLayerFieldContext field_context( - grease_pencil, ATTR_DOMAIN_POINT, layer_index); - fn::FieldEvaluator evaluator{field_context, drawing->strokes().points_num()}; - evaluator.set_selection(selection_field); - evaluator.add(position_field); - evaluator.add(offset_field); - evaluator.evaluate(); - - const IndexMask selection = evaluator.get_evaluated_selection_as_mask(); - if (selection.is_empty()) { - continue; - } - - MutableAttributeAccessor attributes = drawing->strokes_for_write().attributes_for_write(); - const VArray positions_input = evaluator.get_evaluated(0); - const VArray offsets_input = evaluator.get_evaluated(1); - set_computed_position_and_offset( - grease_pencil_component, positions_input, offsets_input, selection, attributes); - drawing->tag_positions_changed(); - } -} - static void set_position_in_component(GeometrySet &geometry, GeometryComponent::Type component_type, const Field &selection_field, @@ -172,11 +136,9 @@ static void set_position_in_component(GeometrySet &geometry, } GeometryComponent &mutable_component = geometry.get_component_for_write(component_type); - MutableAttributeAccessor attributes = *mutable_component.attributes_for_write(); const VArray positions_input = evaluator.get_evaluated(0); const VArray offsets_input = evaluator.get_evaluated(1); - set_computed_position_and_offset( - mutable_component, positions_input, offsets_input, selection, attributes); + set_computed_position_and_offset(mutable_component, positions_input, offsets_input, selection); } static void node_geo_exec(GeoNodeExecParams params) @@ -186,13 +148,6 @@ static void node_geo_exec(GeoNodeExecParams params) Field offset_field = params.extract_input>("Offset"); Field position_field = params.extract_input>("Position"); - if (geometry.has_grease_pencil()) { - set_position_in_grease_pencil(geometry.get_component_for_write(), - selection_field, - position_field, - offset_field); - } - for (const GeometryComponent::Type type : {GeometryComponent::Type::Mesh, GeometryComponent::Type::PointCloud, GeometryComponent::Type::Curve, diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_shade_smooth.cc b/source/blender/nodes/geometry/nodes/node_geo_set_shade_smooth.cc index 61ced20706f..014f3a5d5f5 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_set_shade_smooth.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_set_shade_smooth.cc @@ -19,7 +19,7 @@ static void node_declare(NodeDeclarationBuilder &b) { b.add_input("Geometry").supported_type(GeometryComponent::Type::Mesh); b.add_input("Selection").default_value(true).hide_value().field_on_all(); - b.add_input("Shade Smooth").default_value(true).field_on_all(); + b.add_input("Shade Smooth").field_on_all().default_value(true); b.add_output("Geometry").propagate_all(); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_spline_cyclic.cc b/source/blender/nodes/geometry/nodes/node_geo_set_spline_cyclic.cc index da6b32046f4..9f5a28037e6 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_set_spline_cyclic.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_set_spline_cyclic.cc @@ -3,7 +3,6 @@ * SPDX-License-Identifier: GPL-2.0-or-later */ #include "BKE_curves.hh" -#include "BKE_grease_pencil.hh" #include "node_geometry_util.hh" @@ -11,17 +10,15 @@ namespace blender::nodes::node_geo_set_spline_cyclic_cc { static void node_declare(NodeDeclarationBuilder &b) { - b.add_input("Geometry") - .supported_type({GeometryComponent::Type::Curve, GeometryComponent::Type::GreasePencil}); + b.add_input("Geometry").supported_type(GeometryComponent::Type::Curve); b.add_input("Selection").default_value(true).hide_value().field_on_all(); b.add_input("Cyclic").field_on_all(); b.add_output("Geometry").propagate_all(); } -static void set_curve_cyclic(bke::CurvesGeometry &curves, - const fn::FieldContext &field_context, - const Field &selection_field, - const Field &cyclic_field) +static void set_cyclic(bke::CurvesGeometry &curves, + const Field &selection_field, + const Field &cyclic_field) { if (curves.curves_num() == 0) { return; @@ -29,6 +26,8 @@ static void set_curve_cyclic(bke::CurvesGeometry &curves, MutableAttributeAccessor attributes = curves.attributes_for_write(); AttributeWriter cyclics = attributes.lookup_or_add_for_write("cyclic", ATTR_DOMAIN_CURVE); + + const bke::CurvesFieldContext field_context{curves, ATTR_DOMAIN_CURVE}; fn::FieldEvaluator evaluator{field_context, curves.curves_num()}; evaluator.set_selection(selection_field); evaluator.add_with_destination(cyclic_field, cyclics.varray); @@ -37,23 +36,6 @@ static void set_curve_cyclic(bke::CurvesGeometry &curves, cyclics.finish(); } -static void set_grease_pencil_cyclic(GreasePencil &grease_pencil, - const Field &selection_field, - const Field &cyclic_field) -{ - using namespace blender::bke::greasepencil; - for (const int layer_index : grease_pencil.layers().index_range()) { - Drawing *drawing = get_eval_grease_pencil_layer_drawing_for_write(grease_pencil, layer_index); - if (drawing == nullptr) { - continue; - } - bke::CurvesGeometry &curves = drawing->strokes_for_write(); - const bke::GreasePencilLayerFieldContext field_context( - grease_pencil, ATTR_DOMAIN_CURVE, layer_index); - set_curve_cyclic(curves, field_context, selection_field, cyclic_field); - } -} - static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input("Geometry"); @@ -62,13 +44,7 @@ static void node_geo_exec(GeoNodeExecParams params) geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { if (Curves *curves_id = geometry_set.get_curves_for_write()) { - bke::CurvesGeometry &curves = curves_id->geometry.wrap(); - const bke::CurvesFieldContext field_context{curves, ATTR_DOMAIN_CURVE}; - set_curve_cyclic(curves_id->geometry.wrap(), field_context, selection_field, cyclic_field); - } - if (geometry_set.has_grease_pencil()) { - set_grease_pencil_cyclic( - *geometry_set.get_grease_pencil_for_write(), selection_field, cyclic_field); + set_cyclic(curves_id->geometry.wrap(), selection_field, cyclic_field); } }); diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_spline_resolution.cc b/source/blender/nodes/geometry/nodes/node_geo_set_spline_resolution.cc index 81c22382b43..d531e057827 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_set_spline_resolution.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_set_spline_resolution.cc @@ -3,7 +3,6 @@ * SPDX-License-Identifier: GPL-2.0-or-later */ #include "BKE_curves.hh" -#include "BKE_grease_pencil.hh" #include "node_geometry_util.hh" @@ -11,17 +10,15 @@ namespace blender::nodes::node_geo_set_spline_resolution_cc { static void node_declare(NodeDeclarationBuilder &b) { - b.add_input("Geometry") - .supported_type({GeometryComponent::Type::Curve, GeometryComponent::Type::GreasePencil}); + b.add_input("Geometry").supported_type(GeometryComponent::Type::Curve); b.add_input("Selection").default_value(true).hide_value().field_on_all(); b.add_input("Resolution").min(1).default_value(12).field_on_all(); b.add_output("Geometry").propagate_all(); } -static void set_curve_resolution(bke::CurvesGeometry &curves, - const fn::FieldContext &field_context, - const Field &selection_field, - const Field &resolution_field) +static void set_resolution(bke::CurvesGeometry &curves, + const Field &selection_field, + const Field &resolution_field) { if (curves.curves_num() == 0) { return; @@ -31,6 +28,7 @@ static void set_curve_resolution(bke::CurvesGeometry &curves, ATTR_DOMAIN_CURVE); bke::AttributeValidator validator = attributes.lookup_validator("resolution"); + const bke::CurvesFieldContext field_context{curves, ATTR_DOMAIN_CURVE}; fn::FieldEvaluator evaluator{field_context, curves.curves_num()}; evaluator.set_selection(selection_field); evaluator.add_with_destination(validator.validate_field_if_necessary(resolution_field), @@ -40,23 +38,6 @@ static void set_curve_resolution(bke::CurvesGeometry &curves, resolutions.finish(); } -static void set_grease_pencil_resolution(GreasePencil &grease_pencil, - const Field &selection_field, - const Field &resolution_field) -{ - using namespace blender::bke::greasepencil; - for (const int layer_index : grease_pencil.layers().index_range()) { - Drawing *drawing = get_eval_grease_pencil_layer_drawing_for_write(grease_pencil, layer_index); - if (drawing == nullptr) { - continue; - } - bke::CurvesGeometry &curves = drawing->strokes_for_write(); - const bke::GreasePencilLayerFieldContext field_context( - grease_pencil, ATTR_DOMAIN_CURVE, layer_index); - set_curve_resolution(curves, field_context, selection_field, resolution_field); - } -} - static void node_geo_exec(GeoNodeExecParams params) { GeometrySet geometry_set = params.extract_input("Geometry"); @@ -65,13 +46,7 @@ static void node_geo_exec(GeoNodeExecParams params) geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { if (Curves *curves_id = geometry_set.get_curves_for_write()) { - bke::CurvesGeometry &curves = curves_id->geometry.wrap(); - const bke::CurvesFieldContext field_context{curves, ATTR_DOMAIN_CURVE}; - set_curve_resolution(curves_id->geometry.wrap(), field_context, selection, resolution); - } - if (geometry_set.has_grease_pencil()) { - set_grease_pencil_resolution( - *geometry_set.get_grease_pencil_for_write(), selection, resolution); + set_resolution(curves_id->geometry.wrap(), selection, resolution); } }); diff --git a/source/blender/nodes/geometry/nodes/node_geo_simulation_input.cc b/source/blender/nodes/geometry/nodes/node_geo_simulation_input.cc index 33bfd8ff262..ff6b9873e72 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_simulation_input.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_simulation_input.cc @@ -12,8 +12,6 @@ #include "NOD_geometry.hh" #include "NOD_socket.hh" -#include "NOD_socket_items.hh" -#include "NOD_zone_socket_items.hh" #include "node_geometry_util.hh" @@ -182,37 +180,25 @@ std::unique_ptr get_simulation_input_lazy_function( namespace blender::nodes::node_geo_simulation_input_cc { -static void node_declare(NodeDeclarationBuilder &b) +static void node_declare_dynamic(const bNodeTree &node_tree, + const bNode &node, + NodeDeclaration &r_declaration) { - b.add_output("Delta Time"); - - const bNode *node = b.node_or_null(); - const bNodeTree *node_tree = b.tree_or_null(); - if (ELEM(nullptr, node, node_tree)) { - return; - } - - const bNode *output_node = node_tree->node_by_id(node_storage(*node).output_node_id); + const bNode *output_node = node_tree.node_by_id(node_storage(node).output_node_id); if (!output_node) { return; } - const auto &output_storage = *static_cast( - output_node->storage); - for (const int i : IndexRange(output_storage.items_num)) { - const NodeSimulationItem &item = output_storage.items[i]; - const eNodeSocketDatatype socket_type = eNodeSocketDatatype(item.socket_type); - const StringRef name = item.name; - const std::string identifier = SimulationItemsAccessor::socket_identifier_for_item(item); - auto &input_decl = b.add_input(socket_type, name, identifier); - auto &output_decl = b.add_output(socket_type, name, identifier); - if (socket_type_supports_fields(socket_type)) { - input_decl.supports_field(); - output_decl.dependent_field({input_decl.input_index()}); - } - } - b.add_input("", "__extend__"); - b.add_output("", "__extend__"); + std::unique_ptr delta_time = std::make_unique(); + delta_time->identifier = "Delta Time"; + delta_time->name = DATA_("Delta Time"); + delta_time->in_out = SOCK_OUT; + r_declaration.outputs.append(delta_time.get()); + r_declaration.items.append(std::move(delta_time)); + + const NodeGeometrySimulationOutput &storage = *static_cast( + output_node->storage); + socket_declarations_for_simulation_items({storage.items, storage.items_num}, r_declaration); } static void node_init(bNodeTree * /*tree*/, bNode *node) @@ -225,12 +211,44 @@ static void node_init(bNodeTree * /*tree*/, bNode *node) static bool node_insert_link(bNodeTree *ntree, bNode *node, bNodeLink *link) { - bNode *output_node = ntree->node_by_id(node_storage(*node).output_node_id); + const bNode *output_node = ntree->node_by_id(node_storage(*node).output_node_id); if (!output_node) { return true; } - return socket_items::try_add_item_via_any_extend_socket( - *ntree, *node, *output_node, *link); + + NodeGeometrySimulationOutput &storage = *static_cast( + output_node->storage); + + if (link->tonode == node) { + if (link->tosock->identifier == StringRef("__extend__")) { + if (const NodeSimulationItem *item = NOD_geometry_simulation_output_add_item_from_socket( + &storage, link->fromnode, link->fromsock)) + { + update_node_declaration_and_sockets(*ntree, *node); + link->tosock = nodeFindSocket( + node, SOCK_IN, socket_identifier_for_simulation_item(*item).c_str()); + } + else { + return false; + } + } + } + else { + BLI_assert(link->fromnode == node); + if (link->fromsock->identifier == StringRef("__extend__")) { + if (const NodeSimulationItem *item = NOD_geometry_simulation_output_add_item_from_socket( + &storage, link->tonode, link->tosock)) + { + update_node_declaration_and_sockets(*ntree, *node); + link->fromsock = nodeFindSocket( + node, SOCK_OUT, socket_identifier_for_simulation_item(*item).c_str()); + } + else { + return false; + } + } + } + return true; } static void node_register() @@ -238,7 +256,7 @@ static void node_register() static bNodeType ntype; geo_node_type_base(&ntype, GEO_NODE_SIMULATION_INPUT, "Simulation Input", NODE_CLASS_INTERFACE); ntype.initfunc = node_init; - ntype.declare = node_declare; + ntype.declare_dynamic = node_declare_dynamic; ntype.insert_link = node_insert_link; ntype.gather_link_search_ops = nullptr; node_type_storage(&ntype, diff --git a/source/blender/nodes/geometry/nodes/node_geo_simulation_output.cc b/source/blender/nodes/geometry/nodes/node_geo_simulation_output.cc index 1abdeb5dfab..c14179fe605 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_simulation_output.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_simulation_output.cc @@ -4,7 +4,7 @@ #include "BLI_math_matrix.hh" #include "BLI_string.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_task.hh" #include "BKE_attribute_math.hh" @@ -25,7 +25,6 @@ #include "NOD_common.h" #include "NOD_geometry.hh" #include "NOD_socket.hh" -#include "NOD_zone_socket_items.hh" #include "FN_field_cpp_type.hh" @@ -50,6 +49,117 @@ namespace blender::nodes { +std::string socket_identifier_for_simulation_item(const NodeSimulationItem &item) +{ + return "Item_" + std::to_string(item.identifier); +} + +static std::unique_ptr socket_declaration_for_simulation_item( + const NodeSimulationItem &item, + const eNodeSocketInOut in_out, + const int corresponding_input = -1) +{ + const eNodeSocketDatatype socket_type = eNodeSocketDatatype(item.socket_type); + BLI_assert(NOD_geometry_simulation_output_item_socket_type_supported(socket_type)); + + std::unique_ptr decl; + switch (socket_type) { + case SOCK_FLOAT: + decl = std::make_unique(); + decl->input_field_type = InputSocketFieldType::IsSupported; + decl->output_field_dependency = OutputFieldDependency::ForPartiallyDependentField( + {corresponding_input}); + break; + case SOCK_VECTOR: + decl = std::make_unique(); + decl->input_field_type = InputSocketFieldType::IsSupported; + decl->output_field_dependency = OutputFieldDependency::ForPartiallyDependentField( + {corresponding_input}); + break; + case SOCK_RGBA: + decl = std::make_unique(); + decl->input_field_type = InputSocketFieldType::IsSupported; + decl->output_field_dependency = OutputFieldDependency::ForPartiallyDependentField( + {corresponding_input}); + break; + case SOCK_BOOLEAN: + decl = std::make_unique(); + decl->input_field_type = InputSocketFieldType::IsSupported; + decl->output_field_dependency = OutputFieldDependency::ForPartiallyDependentField( + {corresponding_input}); + break; + case SOCK_ROTATION: + decl = std::make_unique(); + decl->input_field_type = InputSocketFieldType::IsSupported; + decl->output_field_dependency = OutputFieldDependency::ForPartiallyDependentField( + {corresponding_input}); + break; + case SOCK_INT: + decl = std::make_unique(); + decl->input_field_type = InputSocketFieldType::IsSupported; + decl->output_field_dependency = OutputFieldDependency::ForPartiallyDependentField( + {corresponding_input}); + break; + case SOCK_STRING: + decl = std::make_unique(); + break; + case SOCK_GEOMETRY: + decl = std::make_unique(); + break; + default: + BLI_assert_unreachable(); + } + + decl->name = item.name ? item.name : ""; + decl->identifier = socket_identifier_for_simulation_item(item); + decl->in_out = in_out; + return decl; +} + +void socket_declarations_for_simulation_items(const Span items, + NodeDeclaration &r_declaration) +{ + const int inputs_offset = r_declaration.inputs.size(); + for (const int i : items.index_range()) { + const NodeSimulationItem &item = items[i]; + SocketDeclarationPtr input_decl = socket_declaration_for_simulation_item(item, SOCK_IN); + SocketDeclarationPtr output_decl = socket_declaration_for_simulation_item( + item, SOCK_OUT, inputs_offset + i); + r_declaration.inputs.append(input_decl.get()); + r_declaration.items.append(std::move(input_decl)); + r_declaration.outputs.append(output_decl.get()); + r_declaration.items.append(std::move(output_decl)); + } + SocketDeclarationPtr input_extend_decl = decl::create_extend_declaration(SOCK_IN); + SocketDeclarationPtr output_extend_decl = decl::create_extend_declaration(SOCK_OUT); + r_declaration.inputs.append(input_extend_decl.get()); + r_declaration.items.append(std::move(input_extend_decl)); + r_declaration.outputs.append(output_extend_decl.get()); + r_declaration.items.append(std::move(output_extend_decl)); +} + +struct SimulationItemsUniqueNameArgs { + NodeGeometrySimulationOutput *sim; + const NodeSimulationItem *item; +}; + +static bool simulation_items_unique_name_check(void *arg, const char *name) +{ + const SimulationItemsUniqueNameArgs &args = *static_cast( + arg); + for (const NodeSimulationItem &item : args.sim->items_span()) { + if (&item != args.item) { + if (STREQ(item.name, name)) { + return true; + } + } + } + if (STREQ(name, "Delta Time")) { + return true; + } + return false; +} + const CPPType &get_simulation_item_cpp_type(const eNodeSocketDatatype socket_type) { const char *socket_idname = nodeStaticSocketType(socket_type, 0); @@ -672,33 +782,23 @@ std::unique_ptr get_simulation_output_lazy_function( namespace blender::nodes::node_geo_simulation_output_cc { -static void node_declare(NodeDeclarationBuilder &b) +static void node_declare_dynamic(const bNodeTree & /*node_tree*/, + const bNode &node, + NodeDeclaration &r_declaration) { - b.add_input("Skip").description( - "Forward the output of the simulation input node directly to the output node and ignore " - "the nodes in the simulation zone"); - - const bNode *node = b.node_or_null(); - if (node == nullptr) { - return; + const NodeGeometrySimulationOutput &storage = node_storage(node); + { + SocketDeclarationPtr skip_decl = std::make_unique(); + skip_decl->name = N_("Skip"); + skip_decl->identifier = skip_decl->name; + skip_decl->description = N_( + "Forward the output of the simulation input node directly to the output node and ignore " + "the nodes in the simulation zone"); + skip_decl->in_out = SOCK_IN; + r_declaration.inputs.append(skip_decl.get()); + r_declaration.items.append(std::move(skip_decl)); } - - const NodeGeometrySimulationOutput &storage = node_storage(*node); - - for (const int i : IndexRange(storage.items_num)) { - const NodeSimulationItem &item = storage.items[i]; - const eNodeSocketDatatype socket_type = eNodeSocketDatatype(item.socket_type); - const StringRef name = item.name; - const std::string identifier = SimulationItemsAccessor::socket_identifier_for_item(item); - auto &input_decl = b.add_input(socket_type, name, identifier); - auto &output_decl = b.add_output(socket_type, name, identifier); - if (socket_type_supports_fields(socket_type)) { - input_decl.supports_field(); - output_decl.dependent_field({input_decl.input_index()}); - } - } - b.add_input("", "__extend__"); - b.add_output("", "__extend__"); + socket_declarations_for_simulation_items({storage.items, storage.items_num}, r_declaration); } static void node_init(bNodeTree * /*tree*/, bNode *node) @@ -718,17 +818,36 @@ static void node_init(bNodeTree * /*tree*/, bNode *node) static void node_free_storage(bNode *node) { - socket_items::destruct_array(*node); + if (!node->storage) { + return; + } + NodeGeometrySimulationOutput &storage = node_storage(*node); + for (NodeSimulationItem &item : MutableSpan(storage.items, storage.items_num)) { + MEM_SAFE_FREE(item.name); + } + MEM_SAFE_FREE(storage.items); MEM_freeN(node->storage); } static void node_copy_storage(bNodeTree * /*dst_tree*/, bNode *dst_node, const bNode *src_node) { const NodeGeometrySimulationOutput &src_storage = node_storage(*src_node); - auto *dst_storage = MEM_new(__func__, src_storage); - dst_node->storage = dst_storage; + NodeGeometrySimulationOutput *dst_storage = MEM_cnew(__func__); - socket_items::copy_array(*src_node, *dst_node); + dst_storage->items = MEM_cnew_array(src_storage.items_num, __func__); + dst_storage->items_num = src_storage.items_num; + dst_storage->active_index = src_storage.active_index; + dst_storage->next_identifier = src_storage.next_identifier; + for (const int i : IndexRange(src_storage.items_num)) { + if (char *name = src_storage.items[i].name) { + dst_storage->items[i].identifier = src_storage.items[i].identifier; + dst_storage->items[i].name = BLI_strdup(name); + dst_storage->items[i].socket_type = src_storage.items[i].socket_type; + dst_storage->items[i].attribute_domain = src_storage.items[i].attribute_domain; + } + } + + dst_node->storage = dst_storage; } static void node_layout_ex(uiLayout *layout, bContext *C, PointerRNA *ptr) @@ -869,8 +988,37 @@ static void node_layout_ex(uiLayout *layout, bContext *C, PointerRNA *ptr) static bool node_insert_link(bNodeTree *ntree, bNode *node, bNodeLink *link) { - return socket_items::try_add_item_via_any_extend_socket( - *ntree, *node, *node, *link); + NodeGeometrySimulationOutput &storage = node_storage(*node); + if (link->tonode == node) { + if (link->tosock->identifier == StringRef("__extend__")) { + if (const NodeSimulationItem *item = NOD_geometry_simulation_output_add_item_from_socket( + &storage, link->fromnode, link->fromsock)) + { + update_node_declaration_and_sockets(*ntree, *node); + link->tosock = nodeFindSocket( + node, SOCK_IN, socket_identifier_for_simulation_item(*item).c_str()); + } + else { + return false; + } + } + } + else { + BLI_assert(link->fromnode == node); + if (link->fromsock->identifier == StringRef("__extend__")) { + if (const NodeSimulationItem *item = NOD_geometry_simulation_output_add_item_from_socket( + &storage, link->fromnode, link->tosock)) + { + update_node_declaration_and_sockets(*ntree, *node); + link->fromsock = nodeFindSocket( + node, SOCK_OUT, socket_identifier_for_simulation_item(*item).c_str()); + } + else { + return false; + } + } + } + return true; } static void node_register() @@ -880,7 +1028,7 @@ static void node_register() geo_node_type_base( &ntype, GEO_NODE_SIMULATION_OUTPUT, "Simulation Output", NODE_CLASS_INTERFACE); ntype.initfunc = node_init; - ntype.declare = node_declare; + ntype.declare_dynamic = node_declare_dynamic; ntype.gather_link_search_ops = nullptr; ntype.insert_link = node_insert_link; ntype.draw_buttons_ex = node_layout_ex; @@ -900,3 +1048,203 @@ blender::MutableSpan NodeGeometrySimulationOutput::items_spa { return blender::MutableSpan(items, items_num); } + +blender::IndexRange NodeGeometrySimulationOutput::items_range() const +{ + return blender::IndexRange(items_num); +} + +bool NOD_geometry_simulation_output_item_socket_type_supported( + const eNodeSocketDatatype socket_type) +{ + return ELEM(socket_type, + SOCK_FLOAT, + SOCK_VECTOR, + SOCK_RGBA, + SOCK_BOOLEAN, + SOCK_ROTATION, + SOCK_INT, + SOCK_STRING, + SOCK_GEOMETRY); +} + +bNode *NOD_geometry_simulation_output_find_node_by_item(bNodeTree *ntree, + const NodeSimulationItem *item) +{ + ntree->ensure_topology_cache(); + for (bNode *node : ntree->nodes_by_type("GeometryNodeSimulationOutput")) { + NodeGeometrySimulationOutput *sim = static_cast(node->storage); + if (sim->items_span().contains_ptr(item)) { + return node; + } + } + return nullptr; +} + +bool NOD_geometry_simulation_output_item_set_unique_name(NodeGeometrySimulationOutput *sim, + NodeSimulationItem *item, + const char *name, + const char *defname) +{ + char unique_name[MAX_NAME + 4]; + STRNCPY(unique_name, name); + + blender::nodes::SimulationItemsUniqueNameArgs args{sim, item}; + const bool name_changed = BLI_uniquename_cb(blender::nodes::simulation_items_unique_name_check, + &args, + defname, + '.', + unique_name, + ARRAY_SIZE(unique_name)); + MEM_delete(item->name); + item->name = BLI_strdup(unique_name); + return name_changed; +} + +bool NOD_geometry_simulation_output_contains_item(NodeGeometrySimulationOutput *sim, + const NodeSimulationItem *item) +{ + return sim->items_span().contains_ptr(item); +} + +NodeSimulationItem *NOD_geometry_simulation_output_get_active_item( + NodeGeometrySimulationOutput *sim) +{ + if (!sim->items_range().contains(sim->active_index)) { + return nullptr; + } + return &sim->items[sim->active_index]; +} + +void NOD_geometry_simulation_output_set_active_item(NodeGeometrySimulationOutput *sim, + NodeSimulationItem *item) +{ + if (sim->items_span().contains_ptr(item)) { + sim->active_index = item - sim->items; + } +} + +NodeSimulationItem *NOD_geometry_simulation_output_find_item(NodeGeometrySimulationOutput *sim, + const char *name) +{ + for (NodeSimulationItem &item : sim->items_span()) { + if (STREQ(item.name, name)) { + return &item; + } + } + return nullptr; +} + +NodeSimulationItem *NOD_geometry_simulation_output_add_item(NodeGeometrySimulationOutput *sim, + const short socket_type, + const char *name) +{ + return NOD_geometry_simulation_output_insert_item(sim, socket_type, name, sim->items_num); +} + +NodeSimulationItem *NOD_geometry_simulation_output_insert_item(NodeGeometrySimulationOutput *sim, + const short socket_type, + const char *name, + int index) +{ + if (!NOD_geometry_simulation_output_item_socket_type_supported(eNodeSocketDatatype(socket_type))) + { + return nullptr; + } + + NodeSimulationItem *old_items = sim->items; + sim->items = MEM_cnew_array(sim->items_num + 1, __func__); + for (const int i : blender::IndexRange(index)) { + sim->items[i] = old_items[i]; + } + for (const int i : blender::IndexRange(index, sim->items_num - index)) { + sim->items[i + 1] = old_items[i]; + } + + const char *defname = nodeStaticSocketLabel(socket_type, 0); + NodeSimulationItem &added_item = sim->items[index]; + added_item.identifier = sim->next_identifier++; + NOD_geometry_simulation_output_item_set_unique_name(sim, &added_item, name, defname); + added_item.socket_type = socket_type; + + sim->items_num++; + MEM_SAFE_FREE(old_items); + + return &added_item; +} + +NodeSimulationItem *NOD_geometry_simulation_output_add_item_from_socket( + NodeGeometrySimulationOutput *sim, const bNode * /*from_node*/, const bNodeSocket *from_sock) +{ + return NOD_geometry_simulation_output_insert_item( + sim, from_sock->type, from_sock->name, sim->items_num); +} + +NodeSimulationItem *NOD_geometry_simulation_output_insert_item_from_socket( + NodeGeometrySimulationOutput *sim, + const bNode * /*from_node*/, + const bNodeSocket *from_sock, + int index) +{ + return NOD_geometry_simulation_output_insert_item(sim, from_sock->type, from_sock->name, index); +} + +void NOD_geometry_simulation_output_remove_item(NodeGeometrySimulationOutput *sim, + NodeSimulationItem *item) +{ + const int index = item - sim->items; + if (index < 0 || index >= sim->items_num) { + return; + } + + NodeSimulationItem *old_items = sim->items; + sim->items = MEM_cnew_array(sim->items_num - 1, __func__); + for (const int i : blender::IndexRange(index)) { + sim->items[i] = old_items[i]; + } + for (const int i : blender::IndexRange(index, sim->items_num - index).drop_front(1)) { + sim->items[i - 1] = old_items[i]; + } + + MEM_SAFE_FREE(old_items[index].name); + + sim->items_num--; + MEM_SAFE_FREE(old_items); +} + +void NOD_geometry_simulation_output_clear_items(NodeGeometrySimulationOutput *sim) +{ + for (NodeSimulationItem &item : sim->items_span()) { + MEM_SAFE_FREE(item.name); + } + MEM_SAFE_FREE(sim->items); + sim->items = nullptr; + sim->items_num = 0; +} + +void NOD_geometry_simulation_output_move_item(NodeGeometrySimulationOutput *sim, + int from_index, + int to_index) +{ + BLI_assert(from_index >= 0 && from_index < sim->items_num); + BLI_assert(to_index >= 0 && to_index < sim->items_num); + + if (from_index == to_index) { + return; + } + + if (from_index < to_index) { + const NodeSimulationItem tmp = sim->items[from_index]; + for (int i = from_index; i < to_index; ++i) { + sim->items[i] = sim->items[i + 1]; + } + sim->items[to_index] = tmp; + } + else /* from_index > to_index */ { + const NodeSimulationItem tmp = sim->items[from_index]; + for (int i = from_index; i > to_index; --i) { + sim->items[i] = sim->items[i - 1]; + } + sim->items[to_index] = tmp; + } +} diff --git a/source/blender/nodes/geometry/nodes/node_geo_split_to_instances.cc b/source/blender/nodes/geometry/nodes/node_geo_split_to_instances.cc deleted file mode 100644 index dc08030d2c9..00000000000 --- a/source/blender/nodes/geometry/nodes/node_geo_split_to_instances.cc +++ /dev/null @@ -1,382 +0,0 @@ -/* SPDX-FileCopyrightText: 2023 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -#include "node_geometry_util.hh" - -#include "GEO_mesh_copy_selection.hh" -#include "GEO_randomize.hh" - -#include "BKE_curves.hh" -#include "BKE_instances.hh" -#include "BKE_mesh.hh" -#include "BKE_pointcloud.h" - -#include "NOD_rna_define.hh" - -#include "UI_interface.hh" -#include "UI_resources.hh" - -#include "RNA_enum_types.hh" - -#include "BLI_array_utils.hh" - -namespace blender::nodes::node_geo_split_to_instances_cc { - -static void node_declare(NodeDeclarationBuilder &b) -{ - b.add_input("Geometry") - .supported_type({GeometryComponent::Type::Mesh, - GeometryComponent::Type::PointCloud, - GeometryComponent::Type::Curve, - GeometryComponent::Type::Instance}); - b.add_input("Selection").default_value(true).supports_field().hide_value(); - b.add_input("Group ID").supports_field().hide_value(); - b.add_output("Instances") - .propagate_all() - .description("All geometry groups as separate instances"); - b.add_output("Group ID") - .field_on_all() - .description("The group ID of each group instance"); -} - -static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) -{ - uiLayoutSetPropSep(layout, true); - uiLayoutSetPropDecorate(layout, false); - uiItemR(layout, ptr, "domain", UI_ITEM_NONE, "", ICON_NONE); -} - -static void ensure_group_geometries(Map> &geometry_by_group_id, - const Span group_ids) -{ - for (const int group_id : group_ids) { - geometry_by_group_id.lookup_or_add_cb(group_id, - []() { return std::make_unique(); }); - } -} - -struct SplitGroups { - std::optional field_context; - std::optional field_evaluator; - - VectorSet group_ids; - - IndexMaskMemory memory; - Array group_masks; -}; - -/** - * \return True, if the component is already fully handled and does not need further processing. - */ -[[nodiscard]] static bool do_common_split( - const GeometryComponent &src_component, - const eAttrDomain domain, - const Field &selection_field, - const Field &group_id_field, - Map> &geometry_by_group_id, - SplitGroups &r_groups) -{ - const int domain_size = src_component.attribute_domain_size(domain); - - r_groups.field_context.emplace(src_component, domain); - FieldEvaluator &field_evaluator = r_groups.field_evaluator.emplace(*r_groups.field_context, - domain_size); - field_evaluator.set_selection(selection_field); - field_evaluator.add(group_id_field); - field_evaluator.evaluate(); - - const IndexMask selection = field_evaluator.get_evaluated_selection_as_mask(); - if (selection.is_empty()) { - return true; - } - - const VArray group_ids_varray = field_evaluator.get_evaluated(0); - if (selection.size() == domain_size && group_ids_varray.is_single()) { - const int group_id = group_ids_varray.get_internal_single(); - ensure_group_geometries(geometry_by_group_id, {group_id}); - geometry_by_group_id.lookup(group_id)->add(src_component); - return true; - } - - const VArraySpan group_ids = group_ids_varray; - selection.foreach_index_optimized( - [&](const int i) { r_groups.group_ids.add(group_ids[i]); }); - - r_groups.group_masks.reinitialize(r_groups.group_ids.size()); - IndexMask::from_groups( - selection, - r_groups.memory, - [&](const int i) { return r_groups.group_ids.index_of(group_ids[i]); }, - r_groups.group_masks); - - ensure_group_geometries(geometry_by_group_id, r_groups.group_ids); - return false; -} - -static void split_mesh_groups(const MeshComponent &component, - const eAttrDomain domain, - const Field &selection_field, - const Field &group_id_field, - const AnonymousAttributePropagationInfo &propagation_info, - Map> &geometry_by_group_id) -{ - SplitGroups split_groups; - if (do_common_split( - component, domain, selection_field, group_id_field, geometry_by_group_id, split_groups)) - { - return; - } - const Mesh &src_mesh = *component.get(); - const int domain_size = component.attribute_domain_size(domain); - - threading::EnumerableThreadSpecific> group_selection_per_thread{ - [&]() { return Array(domain_size, false); }}; - - threading::parallel_for(split_groups.group_masks.index_range(), 16, [&](const IndexRange range) { - /* Need task isolation because of the thread local variable. */ - threading::isolate_task([&]() { - MutableSpan group_selection = group_selection_per_thread.local(); - const VArray group_selection_varray = VArray::ForSpan(group_selection); - for (const int group_index : range) { - const IndexMask &mask = split_groups.group_masks[group_index]; - index_mask::masked_fill(group_selection, true, mask); - const int group_id = split_groups.group_ids[group_index]; - - /* Using #mesh_copy_selection here is not ideal, because it can lead to O(n^2) behavior - * when there are many groups. */ - std::optional group_mesh_opt = geometry::mesh_copy_selection( - src_mesh, group_selection_varray, domain, propagation_info); - GeometrySet &group_geometry = *geometry_by_group_id.lookup(group_id); - if (group_mesh_opt.has_value()) { - if (Mesh *group_mesh = *group_mesh_opt) { - group_geometry.replace_mesh(group_mesh); - } - else { - group_geometry.replace_mesh(nullptr); - } - } - else { - group_geometry.add(component); - } - - index_mask::masked_fill(group_selection, false, mask); - } - }); - }); -} - -static void split_pointcloud_groups(const PointCloudComponent &component, - const Field &selection_field, - const Field &group_id_field, - const AnonymousAttributePropagationInfo &propagation_info, - Map> &geometry_by_group_id) -{ - SplitGroups split_groups; - if (do_common_split(component, - ATTR_DOMAIN_POINT, - selection_field, - group_id_field, - geometry_by_group_id, - split_groups)) - { - return; - } - const PointCloud &src_pointcloud = *component.get(); - threading::parallel_for(split_groups.group_ids.index_range(), 16, [&](const IndexRange range) { - for (const int group_index : range) { - const IndexMask &mask = split_groups.group_masks[group_index]; - const int group_id = split_groups.group_ids[group_index]; - - PointCloud *group_pointcloud = BKE_pointcloud_new_nomain(mask.size()); - - const AttributeAccessor src_attributes = src_pointcloud.attributes(); - MutableAttributeAccessor dst_attributes = group_pointcloud->attributes_for_write(); - bke::gather_attributes( - src_attributes, ATTR_DOMAIN_POINT, propagation_info, {}, mask, dst_attributes); - - GeometrySet &group_geometry = *geometry_by_group_id.lookup(group_id); - group_geometry.replace_pointcloud(group_pointcloud); - } - }); -} - -static void split_curve_groups(const bke::CurveComponent &component, - const eAttrDomain domain, - const Field &selection_field, - const Field &group_id_field, - const AnonymousAttributePropagationInfo &propagation_info, - Map> &geometry_by_group_id) -{ - SplitGroups split_groups; - if (do_common_split( - component, domain, selection_field, group_id_field, geometry_by_group_id, split_groups)) - { - return; - } - const bke::CurvesGeometry &src_curves = component.get()->geometry.wrap(); - threading::parallel_for(split_groups.group_ids.index_range(), 16, [&](const IndexRange range) { - for (const int group_index : range) { - const IndexMask &mask = split_groups.group_masks[group_index]; - const int group_id = split_groups.group_ids[group_index]; - - bke::CurvesGeometry group_curves; - if (domain == ATTR_DOMAIN_POINT) { - group_curves = bke::curves_copy_point_selection(src_curves, mask, propagation_info); - } - else { - group_curves = bke::curves_copy_curve_selection(src_curves, mask, propagation_info); - } - Curves *group_curves_id = bke::curves_new_nomain(std::move(group_curves)); - GeometrySet &group_geometry = *geometry_by_group_id.lookup(group_id); - group_geometry.replace_curves(group_curves_id); - } - }); -} - -static void split_instance_groups(const InstancesComponent &component, - const Field &selection_field, - const Field &group_id_field, - const AnonymousAttributePropagationInfo &propagation_info, - Map> &geometry_by_group_id) -{ - SplitGroups split_groups; - if (do_common_split(component, - ATTR_DOMAIN_INSTANCE, - selection_field, - group_id_field, - geometry_by_group_id, - split_groups)) - { - return; - } - const bke::Instances &src_instances = *component.get(); - threading::parallel_for(split_groups.group_ids.index_range(), 16, [&](const IndexRange range) { - for (const int group_index : range) { - const IndexMask &mask = split_groups.group_masks[group_index]; - const int group_id = split_groups.group_ids[group_index]; - - bke::Instances *group_instances = new bke::Instances(); - group_instances->resize(mask.size()); - - for (const bke::InstanceReference &reference : src_instances.references()) { - group_instances->add_reference(reference); - } - - array_utils::gather(src_instances.transforms(), mask, group_instances->transforms()); - array_utils::gather( - src_instances.reference_handles(), mask, group_instances->reference_handles()); - bke::gather_attributes(src_instances.attributes(), - ATTR_DOMAIN_INSTANCE, - propagation_info, - {}, - mask, - group_instances->attributes_for_write()); - group_instances->remove_unused_references(); - - GeometrySet &group_geometry = *geometry_by_group_id.lookup(group_id); - group_geometry.replace_instances(group_instances); - } - }); -} - -static void node_geo_exec(GeoNodeExecParams params) -{ - const bNode &node = params.node(); - const eAttrDomain domain = eAttrDomain(node.custom1); - - GeometrySet src_geometry = params.extract_input("Geometry"); - const Field selection_field = params.extract_input>("Selection"); - const Field group_id_field = params.extract_input>("Group ID"); - - const AnonymousAttributePropagationInfo &propagation_info = params.get_output_propagation_info( - "Instances"); - - Map> geometry_by_group_id; - - if (src_geometry.has_mesh() && - ELEM(domain, ATTR_DOMAIN_POINT, ATTR_DOMAIN_EDGE, ATTR_DOMAIN_FACE)) { - const auto &component = *src_geometry.get_component(); - split_mesh_groups(component, - domain, - selection_field, - group_id_field, - propagation_info, - geometry_by_group_id); - } - if (src_geometry.has_pointcloud() && domain == ATTR_DOMAIN_POINT) { - const auto &component = *src_geometry.get_component(); - split_pointcloud_groups( - component, selection_field, group_id_field, propagation_info, geometry_by_group_id); - } - if (src_geometry.has_curves() && ELEM(domain, ATTR_DOMAIN_POINT, ATTR_DOMAIN_CURVE)) { - const auto &component = *src_geometry.get_component(); - split_curve_groups(component, - domain, - selection_field, - group_id_field, - propagation_info, - geometry_by_group_id); - } - if (src_geometry.has_instances() && domain == ATTR_DOMAIN_INSTANCE) { - const auto &component = *src_geometry.get_component(); - split_instance_groups( - component, selection_field, group_id_field, propagation_info, geometry_by_group_id); - } - - bke::Instances *dst_instances = new bke::Instances(); - GeometrySet dst_geometry = GeometrySet::from_instances(dst_instances); - const int total_groups_num = geometry_by_group_id.size(); - dst_instances->resize(total_groups_num); - - AnonymousAttributeIDPtr dst_group_id_attribute_id = - params.get_output_anonymous_attribute_id_if_needed("Group ID"); - if (dst_group_id_attribute_id) { - SpanAttributeWriter dst_group_id = - dst_instances->attributes_for_write().lookup_or_add_for_write_span( - *dst_group_id_attribute_id, ATTR_DOMAIN_INSTANCE); - std::copy(geometry_by_group_id.keys().begin(), - geometry_by_group_id.keys().end(), - dst_group_id.span.begin()); - dst_group_id.finish(); - } - - dst_instances->transforms().fill(float4x4::identity()); - array_utils::fill_index_range(dst_instances->reference_handles()); - - for (auto item : geometry_by_group_id.items()) { - std::unique_ptr &group_geometry = item.value; - dst_instances->add_reference(std::move(group_geometry)); - } - - geometry::debug_randomize_instance_order(dst_instances); - - params.set_output("Instances", std::move(dst_geometry)); -} - -static void node_rna(StructRNA *srna) -{ - RNA_def_node_enum(srna, - "domain", - "Domain", - "Attribute domain for the selection and group id inputs", - rna_enum_attribute_domain_without_corner_items, - NOD_inline_enum_accessors(custom1), - ATTR_DOMAIN_POINT); -} - -static void node_register() -{ - static bNodeType ntype; - geo_node_type_base( - &ntype, GEO_NODE_SPLIT_TO_INSTANCES, "Split to Instances", NODE_CLASS_GEOMETRY); - ntype.geometry_node_execute = node_geo_exec; - ntype.declare = node_declare; - ntype.draw_buttons = node_layout; - nodeRegisterType(&ntype); - - node_rna(ntype.rna_ext.srna); -} -NOD_REGISTER_NODE(node_register) - -} // namespace blender::nodes::node_geo_split_to_instances_cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_store_named_attribute.cc b/source/blender/nodes/geometry/nodes/node_geo_store_named_attribute.cc index 77f930e724c..c0f305d82bc 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_store_named_attribute.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_store_named_attribute.cc @@ -82,12 +82,12 @@ static void node_update(bNodeTree *ntree, bNode *node) static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) { - const NodeDeclaration &declaration = *params.node_type().static_declaration; + const NodeDeclaration &declaration = *params.node_type().fixed_declaration; search_link_ops_for_declarations(params, declaration.inputs.as_span().take_front(2)); search_link_ops_for_declarations(params, declaration.outputs.as_span().take_front(1)); if (params.in_out() == SOCK_IN) { - const std::optional type = bke::socket_type_to_custom_data_type( + const std::optional type = node_data_type_to_custom_data_type( eNodeSocketDatatype(params.other_socket().type)); if (type && *type != CD_PROP_STRING) { /* The input and output sockets have the same name. */ @@ -126,33 +126,33 @@ static void node_geo_exec(GeoNodeExecParams params) GField field; switch (data_type) { case CD_PROP_FLOAT: - field = params.extract_input("Value_Float"); + field = params.get_input>("Value_Float"); break; case CD_PROP_FLOAT2: { - field = params.extract_input("Value_Vector"); + field = params.get_input>("Value_Vector"); field = bke::get_implicit_type_conversions().try_convert(field, CPPType::get()); break; } case CD_PROP_FLOAT3: - field = params.extract_input("Value_Vector"); + field = params.get_input>("Value_Vector"); break; case CD_PROP_COLOR: - field = params.extract_input("Value_Color"); + field = params.get_input>("Value_Color"); break; case CD_PROP_BYTE_COLOR: { - field = params.extract_input("Value_Color"); + field = params.get_input>("Value_Color"); field = bke::get_implicit_type_conversions().try_convert(field, CPPType::get()); break; } case CD_PROP_BOOL: - field = params.extract_input("Value_Bool"); + field = params.get_input>("Value_Bool"); break; case CD_PROP_INT32: - field = params.extract_input("Value_Int"); + field = params.get_input>("Value_Int"); break; case CD_PROP_QUATERNION: - field = params.extract_input("Value_Rotation"); + field = params.get_input>("Value_Rotation"); break; default: break; @@ -176,8 +176,7 @@ static void node_geo_exec(GeoNodeExecParams params) geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { for (const GeometryComponent::Type type : {GeometryComponent::Type::Mesh, GeometryComponent::Type::PointCloud, - GeometryComponent::Type::Curve, - GeometryComponent::Type::GreasePencil}) + GeometryComponent::Type::Curve}) { if (geometry_set.has(type)) { GeometryComponent &component = geometry_set.get_component_for_write(type); @@ -229,8 +228,7 @@ static void node_rna(StructRNA *srna) "Which domain to store the data in", rna_enum_attribute_domain_items, NOD_storage_enum_accessors(domain), - ATTR_DOMAIN_POINT, - enums::domain_experimental_grease_pencil_version3_fn); + ATTR_DOMAIN_POINT); } static void node_register() diff --git a/source/blender/nodes/geometry/nodes/node_geo_subdivision_surface.cc b/source/blender/nodes/geometry/nodes/node_geo_subdivision_surface.cc index 14d4fb9643d..df05462c4ea 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_subdivision_surface.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_subdivision_surface.cc @@ -36,14 +36,14 @@ static void node_declare(NodeDeclarationBuilder &b) .default_value(0.0f) .min(0.0f) .max(1.0f) - .subtype(PROP_FACTOR) - .field_on_all(); + .field_on_all() + .subtype(PROP_FACTOR); b.add_input("Vertex Crease") .default_value(0.0f) .min(0.0f) .max(1.0f) - .subtype(PROP_FACTOR) - .field_on_all(); + .field_on_all() + .subtype(PROP_FACTOR); b.add_output("Mesh").propagate_all(); } @@ -227,7 +227,7 @@ static void node_register() ntype.geometry_node_execute = node_geo_exec; ntype.draw_buttons = node_layout; ntype.initfunc = node_init; - bke::node_type_size_preset(&ntype, bke::eNodeSizePreset::MIDDLE); + blender::bke::node_type_size_preset(&ntype, blender::bke::eNodeSizePreset::MIDDLE); node_type_storage(&ntype, "NodeGeometrySubdivisionSurface", node_free_standard_storage, diff --git a/source/blender/nodes/geometry/nodes/node_geo_transform_geometry.cc b/source/blender/nodes/geometry/nodes/node_geo_transform_geometry.cc index 11cf15a64d1..adb5cbec593 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_transform_geometry.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_transform_geometry.cc @@ -16,7 +16,6 @@ #include "DNA_volume_types.h" #include "BKE_curves.hh" -#include "BKE_grease_pencil.hh" #include "BKE_instances.hh" #include "BKE_mesh.hh" #include "BKE_pointcloud.h" @@ -99,28 +98,6 @@ static void transform_pointcloud(PointCloud &pointcloud, const float4x4 &transfo position.finish(); } -static void translate_greasepencil(GreasePencil &grease_pencil, const float3 translation) -{ - using namespace blender::bke::greasepencil; - for (const int layer_index : grease_pencil.layers().index_range()) { - if (Drawing *drawing = get_eval_grease_pencil_layer_drawing_for_write(grease_pencil, - layer_index)) { - drawing->strokes_for_write().translate(translation); - } - } -} - -static void transform_greasepencil(GreasePencil &grease_pencil, const float4x4 &transform) -{ - using namespace blender::bke::greasepencil; - for (const int layer_index : grease_pencil.layers().index_range()) { - if (Drawing *drawing = get_eval_grease_pencil_layer_drawing_for_write(grease_pencil, - layer_index)) { - drawing->strokes_for_write().transform(transform); - } - } -} - static void translate_instances(bke::Instances &instances, const float3 translation) { MutableSpan transforms = instances.transforms(); @@ -239,9 +216,6 @@ static void translate_geometry_set(GeoNodeExecParams ¶ms, if (PointCloud *pointcloud = geometry.get_pointcloud_for_write()) { translate_pointcloud(*pointcloud, translation); } - if (GreasePencil *grease_pencil = geometry.get_grease_pencil_for_write()) { - translate_greasepencil(*grease_pencil, translation); - } if (Volume *volume = geometry.get_volume_for_write()) { translate_volume(params, *volume, translation, depsgraph); } @@ -267,9 +241,6 @@ void transform_geometry_set(GeoNodeExecParams ¶ms, if (PointCloud *pointcloud = geometry.get_pointcloud_for_write()) { transform_pointcloud(*pointcloud, transform); } - if (GreasePencil *grease_pencil = geometry.get_grease_pencil_for_write()) { - transform_greasepencil(*grease_pencil, transform); - } if (Volume *volume = geometry.get_volume_for_write()) { transform_volume(params, *volume, transform, depsgraph); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_viewer.cc b/source/blender/nodes/geometry/nodes/node_geo_viewer.cc index 2c763a7a11b..fb0ddc69b84 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_viewer.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_viewer.cc @@ -96,8 +96,8 @@ static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) ed::viewer_path::activate_geometry_node(*bmain, *snode, viewer_node); }; - const eNodeSocketDatatype socket_type = eNodeSocketDatatype(params.other_socket().type); - const std::optional type = bke::socket_type_to_custom_data_type(socket_type); + const std::optional type = node_socket_to_custom_data_type( + params.other_socket()); if (params.in_out() == SOCK_OUT) { /* The viewer node only has inputs. */ return; diff --git a/source/blender/nodes/geometry/nodes/node_geo_volume_cube.cc b/source/blender/nodes/geometry/nodes/node_geo_volume_cube.cc index b3b62f3c8fc..0d5f8c606be 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_volume_cube.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_volume_cube.cc @@ -26,30 +26,30 @@ namespace blender::nodes::node_geo_volume_cube_cc { static void node_declare(NodeDeclarationBuilder &b) { b.add_input("Density") - .default_value(1.0f) .description("Volume density per voxel") - .supports_field(); + .supports_field() + .default_value(1.0f); b.add_input("Background").description("Value for voxels outside of the cube"); b.add_input("Min") - .default_value(float3(-1.0f)) - .description("Minimum boundary of volume"); + .description("Minimum boundary of volume") + .default_value(float3(-1.0f)); b.add_input("Max") - .default_value(float3(1.0f)) - .description("Maximum boundary of volume"); + .description("Maximum boundary of volume") + .default_value(float3(1.0f)); b.add_input("Resolution X") + .description("Number of voxels in the X axis") .default_value(32) - .min(2) - .description("Number of voxels in the X axis"); + .min(2); b.add_input("Resolution Y") + .description("Number of voxels in the Y axis") .default_value(32) - .min(2) - .description("Number of voxels in the Y axis"); + .min(2); b.add_input("Resolution Z") + .description("Number of voxels in the Z axis") .default_value(32) - .min(2) - .description("Number of voxels in the Z axis"); + .min(2); b.add_output("Volume").translation_context(BLT_I18NCONTEXT_ID_ID); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_volume_to_mesh.cc b/source/blender/nodes/geometry/nodes/node_geo_volume_to_mesh.cc index 0204857e7a4..5adbaca360d 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_volume_to_mesh.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_volume_to_mesh.cc @@ -35,8 +35,8 @@ NODE_STORAGE_FUNCS(NodeGeometryVolumeToMesh) static void node_declare(NodeDeclarationBuilder &b) { b.add_input("Volume") - .supported_type(GeometryComponent::Type::Volume) - .translation_context(BLT_I18NCONTEXT_ID_ID); + .translation_context(BLT_I18NCONTEXT_ID_ID) + .supported_type(GeometryComponent::Type::Volume); b.add_input("Voxel Size") .default_value(0.3f) .min(0.01f) diff --git a/source/blender/nodes/intern/geometry_nodes_execute.cc b/source/blender/nodes/intern/geometry_nodes_execute.cc index 1bd77e2d270..4dc5b862a95 100644 --- a/source/blender/nodes/intern/geometry_nodes_execute.cc +++ b/source/blender/nodes/intern/geometry_nodes_execute.cc @@ -11,11 +11,9 @@ #include "BLI_math_quaternion.hh" #include "BLI_string.h" -#include "NOD_geometry.hh" #include "NOD_geometry_nodes_execute.hh" #include "NOD_geometry_nodes_lazy_function.hh" #include "NOD_node_declaration.hh" -#include "NOD_socket.hh" #include "BKE_compute_contexts.hh" #include "BKE_geometry_fields.hh" @@ -44,7 +42,7 @@ StringRef input_attribute_name_suffix() bool socket_type_has_attribute_toggle(const eNodeSocketDatatype type) { - return socket_type_supports_fields(type); + return ELEM(type, SOCK_FLOAT, SOCK_VECTOR, SOCK_BOOLEAN, SOCK_RGBA, SOCK_INT, SOCK_ROTATION); } bool input_has_attribute_toggle(const bNodeTree &node_tree, const int socket_index) @@ -120,10 +118,6 @@ std::unique_ptr id_property_create_f return property; } case SOCK_BOOLEAN: { - if (is_layer_selection_field(socket)) { - /* We can't use the value from the socket here since it doesn't storing a string. */ - return bke::idprop::create(identifier, ""); - } const bNodeSocketValueBoolean *value = static_cast( socket.socket_data); auto property = bke::idprop::create_bool(identifier, value->value); @@ -205,9 +199,6 @@ bool id_property_type_matches_socket(const bNodeTreeInterfaceSocket &socket, return property.type == IDP_ARRAY && ELEM(property.subtype, IDP_INT, IDP_FLOAT, IDP_DOUBLE) && property.len == 4; case SOCK_BOOLEAN: - if (is_layer_selection_field(socket)) { - return property.type == IDP_STRING; - } return property.type == IDP_BOOLEAN; case SOCK_STRING: return property.type == IDP_STRING; @@ -406,17 +397,6 @@ static void initialize_group_input(const bNodeTree &tree, BLI_assert(value_or_field_cpp_type != nullptr); value_or_field_cpp_type->construct_from_field(r_value, std::move(attribute_field)); } - else if (is_layer_selection_field(io_input)) { - const IDProperty *property_layer_name = IDP_GetPropertyFromGroup(properties, - io_input.identifier); - StringRef layer_name = IDP_String(property_layer_name); - const fn::GField selection_field( - std::make_shared(layer_name), 0); - const auto *value_or_field_cpp_type = fn::ValueOrFieldCPPType::get_from_self( - *typeinfo->geometry_nodes_cpp_type); - BLI_assert(value_or_field_cpp_type != nullptr); - value_or_field_cpp_type->construct_from_field(r_value, std::move(selection_field)); - } else { init_socket_cpp_value_from_property(*property, socket_data_type, r_value); } diff --git a/source/blender/nodes/intern/geometry_nodes_lazy_function.cc b/source/blender/nodes/intern/geometry_nodes_lazy_function.cc index cf4192f7f2d..1be5fa7f540 100644 --- a/source/blender/nodes/intern/geometry_nodes_lazy_function.cc +++ b/source/blender/nodes/intern/geometry_nodes_lazy_function.cc @@ -25,7 +25,6 @@ #include "NOD_multi_function.hh" #include "NOD_node_declaration.hh" -#include "BLI_array_utils.hh" #include "BLI_bit_group_vector.hh" #include "BLI_bit_span_ops.hh" #include "BLI_cpp_types.hh" @@ -107,7 +106,7 @@ static void lazy_function_interface_from_node(const bNode &node, type = get_vector_type(*type); } r_lf_index_by_bsocket[socket->index_in_tree()] = r_inputs.append_and_get_index_as( - socket->name, *type, input_usage); + socket->identifier, *type, input_usage); } for (const bNodeSocket *socket : node.output_sockets()) { if (!socket->is_available()) { @@ -118,7 +117,7 @@ static void lazy_function_interface_from_node(const bNode &node, continue; } r_lf_index_by_bsocket[socket->index_in_tree()] = r_outputs.append_and_get_index_as( - socket->name, *type); + socket->identifier, *type); } } @@ -361,7 +360,7 @@ class LazyFunctionForGeometryNode : public LazyFunction { own_lf_graph_info_.mapping .lf_input_index_for_output_bsocket_usage[bsocket->index_in_all_outputs()]; if (index == lf_index) { - return StringRef("Use Output '") + bsocket->name + "'"; + return StringRef("Use Output '") + bsocket->identifier + "'"; } } { @@ -369,7 +368,7 @@ class LazyFunctionForGeometryNode : public LazyFunction { own_lf_graph_info_.mapping.lf_input_index_for_attribute_propagation_to_output [bsocket->index_in_all_outputs()]; if (index == lf_index) { - return StringRef("Propagate to '") + bsocket->name + "'"; + return StringRef("Propagate to '") + bsocket->identifier + "'"; } } } @@ -1479,10 +1478,75 @@ struct RepeatEvalStorage { std::optional graph_executor; void *graph_executor_storage = nullptr; bool multi_threading_enabled = false; - Vector input_index_map; + IndexRange input_index_map; Vector output_index_map; }; +class ParamsForRepeatZoneGraph : public lf::Params { + private: + lf::Params &zone_params_; + RepeatEvalStorage &eval_storage_; + + public: + ParamsForRepeatZoneGraph(RepeatEvalStorage &eval_storage, lf::Params &zone_params) + : lf::Params(*eval_storage.graph_executor, eval_storage.multi_threading_enabled), + zone_params_{zone_params}, + eval_storage_(eval_storage) + { + } + + int map_input_index(const int index) const + { + return eval_storage_.input_index_map[index]; + } + + int map_output_index(const int index) const + { + return eval_storage_.output_index_map[index]; + } + + void *try_get_input_data_ptr_impl(const int index) const + { + return zone_params_.try_get_input_data_ptr(this->map_input_index(index)); + } + + void *try_get_input_data_ptr_or_request_impl(const int index) + { + return zone_params_.try_get_input_data_ptr_or_request(this->map_input_index(index)); + } + void *get_output_data_ptr_impl(const int index) + { + return zone_params_.get_output_data_ptr(this->map_output_index(index)); + } + void output_set_impl(const int index) + { + return zone_params_.output_set(this->map_output_index(index)); + } + bool output_was_set_impl(const int index) const + { + return zone_params_.output_was_set(this->map_output_index(index)); + } + lf::ValueUsage get_output_usage_impl(const int index) const + { + return zone_params_.get_output_usage(this->map_output_index(index)); + } + void set_input_unused_impl(const int index) + { + return zone_params_.set_input_unused(this->map_input_index(index)); + } + bool try_enable_multi_threading_impl() + { + if (eval_storage_.multi_threading_enabled) { + return true; + } + if (zone_params_.try_enable_multi_threading()) { + eval_storage_.multi_threading_enabled = true; + return true; + } + return false; + } +}; + class LazyFunctionForRepeatZone : public LazyFunction { private: const bNodeTreeZone &zone_; @@ -1585,11 +1649,7 @@ class LazyFunctionForRepeatZone : public LazyFunction { } /* Execute the graph for the repeat zone. */ - lf::RemappedParams eval_graph_params{*eval_storage.graph_executor, - params, - eval_storage.input_index_map, - eval_storage.output_index_map, - eval_storage.multi_threading_enabled}; + ParamsForRepeatZoneGraph eval_graph_params{eval_storage, params}; lf::Context eval_graph_context{ eval_storage.graph_executor_storage, context.user_data, context.local_user_data}; eval_storage.graph_executor->execute(eval_graph_params, eval_graph_context); @@ -1754,18 +1814,17 @@ class LazyFunctionForRepeatZone : public LazyFunction { * zone. The main complexity below stems from the fact that the iterations input is handled * outside of this graph. */ eval_storage.output_index_map.reinitialize(outputs_.size() - 1); - eval_storage.input_index_map.resize(inputs_.size() - 1); - array_utils::fill_index_range(eval_storage.input_index_map, 1); + eval_storage.input_index_map = inputs_.index_range().drop_front(1); Vector lf_graph_inputs = lf_inputs.as_span().drop_front(1); const int iteration_usage_index = zone_info_.indices.outputs.input_usages[0]; - array_utils::fill_index_range( - eval_storage.output_index_map.as_mutable_span().take_front(iteration_usage_index)); - array_utils::fill_index_range( - eval_storage.output_index_map.as_mutable_span().drop_front(iteration_usage_index), - iteration_usage_index + 1); - + std::iota(eval_storage.output_index_map.begin(), + eval_storage.output_index_map.begin() + iteration_usage_index, + 0); + std::iota(eval_storage.output_index_map.begin() + iteration_usage_index, + eval_storage.output_index_map.end(), + iteration_usage_index + 1); Vector lf_graph_outputs = lf_outputs.as_span().take_front( iteration_usage_index); lf_graph_outputs.extend(lf_outputs.as_span().drop_front(iteration_usage_index + 1)); @@ -2077,7 +2136,7 @@ struct GeometryNodesLazyFunctionBuilder { { /* Build nested zones first. */ Array zone_build_order(tree_zones_->zones.size()); - array_utils::fill_index_range(zone_build_order); + std::iota(zone_build_order.begin(), zone_build_order.end(), 0); std::sort( zone_build_order.begin(), zone_build_order.end(), [&](const int zone_a, const int zone_b) { return tree_zones_->zones[zone_a]->depth > tree_zones_->zones[zone_b]->depth; @@ -2277,9 +2336,9 @@ struct GeometryNodesLazyFunctionBuilder { for (const bNodeSocket *bsocket : zone.input_node->output_sockets().drop_back(1)) { lf::GraphInputSocket &lf_input = lf_body_graph.add_input( - *bsocket->typeinfo->geometry_nodes_cpp_type, bsocket->name); + *bsocket->typeinfo->geometry_nodes_cpp_type, bsocket->identifier); lf::GraphOutputSocket &lf_input_usage = lf_body_graph.add_output( - CPPType::get(), "Usage: " + StringRef(bsocket->name)); + CPPType::get(), "Usage: " + StringRef(bsocket->identifier)); lf_main_inputs.append(&lf_input); lf_main_input_usages.append(&lf_input_usage); graph_params.lf_output_by_bsocket.add_new(bsocket, &lf_input); @@ -2297,9 +2356,9 @@ struct GeometryNodesLazyFunctionBuilder { for (const bNodeSocket *bsocket : zone.output_node->input_sockets().drop_back(1)) { lf::GraphOutputSocket &lf_output = lf_body_graph.add_output( - *bsocket->typeinfo->geometry_nodes_cpp_type, bsocket->name); + *bsocket->typeinfo->geometry_nodes_cpp_type, bsocket->identifier); lf::GraphInputSocket &lf_output_usage = lf_body_graph.add_input( - CPPType::get(), "Usage: " + StringRef(bsocket->name)); + CPPType::get(), "Usage: " + StringRef(bsocket->identifier)); graph_params.lf_inputs_by_bsocket.add(bsocket, &lf_output); graph_params.usage_by_bsocket.add(bsocket, &lf_output_usage); lf_main_outputs.append(&lf_output); @@ -2397,7 +2456,7 @@ struct GeometryNodesLazyFunctionBuilder { for (const bNodeLink *border_link : zone.border_links) { lf_graph_inputs.append( &lf_graph.add_input(*border_link->tosock->typeinfo->geometry_nodes_cpp_type, - StringRef("Link from ") + border_link->fromsock->name)); + StringRef("Link from ") + border_link->fromsock->identifier)); } return lf_graph_inputs; } @@ -2407,8 +2466,9 @@ struct GeometryNodesLazyFunctionBuilder { { Vector lf_graph_outputs; for (const bNodeLink *border_link : zone.border_links) { - lf_graph_outputs.append(&lf_graph.add_output( - CPPType::get(), StringRef("Usage: Link from ") + border_link->fromsock->name)); + lf_graph_outputs.append(&lf_graph.add_output(CPPType::get(), + StringRef("Usage: Link from ") + + border_link->fromsock->identifier)); } return lf_graph_outputs; } @@ -3412,7 +3472,7 @@ struct GeometryNodesLazyFunctionBuilder { mapping_->bsockets_by_lf_socket_map.add(&lf_socket, bsocket); } - mapping_->possible_side_effect_node_map.add(&bnode, &lf_viewer_node); + mapping_->viewer_node_map.add(&bnode, &lf_viewer_node); { auto &usage_lazy_function = scope_.construct( @@ -3765,7 +3825,7 @@ struct GeometryNodesLazyFunctionBuilder { if (socket_decl->input_field_type != InputSocketFieldType::Implicit) { return false; } - const ImplicitInputValueFn *implicit_input_fn = socket_decl->implicit_input_fn.get(); + const ImplicitInputValueFn *implicit_input_fn = socket_decl->implicit_input_fn(); if (implicit_input_fn == nullptr) { return false; } diff --git a/source/blender/nodes/intern/geometry_nodes_log.cc b/source/blender/nodes/intern/geometry_nodes_log.cc index 6b63d9958a0..7257d342a43 100644 --- a/source/blender/nodes/intern/geometry_nodes_log.cc +++ b/source/blender/nodes/intern/geometry_nodes_log.cc @@ -64,7 +64,6 @@ GeometryInfoLog::GeometryInfoLog(const bke::GeometrySet &geometry_set) bke::GeometryComponent::Type::Instance, bke::GeometryComponent::Type::Mesh, bke::GeometryComponent::Type::PointCloud, - bke::GeometryComponent::Type::GreasePencil, bke::GeometryComponent::Type::Volume}; /* Keep track handled attribute names to make sure that we do not return the same name twice. @@ -129,10 +128,7 @@ GeometryInfoLog::GeometryInfoLog(const bke::GeometrySet &geometry_set) break; } case bke::GeometryComponent::Type::GreasePencil: { - const auto &grease_pencil_component = *static_cast( - component); - GreasePencilInfo &info = this->grease_pencil_info.emplace(); - info.layers_num = grease_pencil_component.attribute_domain_size(ATTR_DOMAIN_LAYER); + /* TODO. Do nothing for now. */ break; } } @@ -578,9 +574,28 @@ const ViewerNodeLog *GeoModifierLog::find_viewer_node_log_for_path(const ViewerP ComputeContextBuilder compute_context_builder; compute_context_builder.push(parsed_path->modifier_name); for (const ViewerPathElem *elem : parsed_path->node_path) { - if (!ed::viewer_path::add_compute_context_for_viewer_path_elem(*elem, compute_context_builder)) - { - return nullptr; + switch (elem->type) { + case VIEWER_PATH_ELEM_TYPE_GROUP_NODE: { + const auto &typed_elem = *reinterpret_cast(elem); + compute_context_builder.push(typed_elem.node_id); + break; + } + case VIEWER_PATH_ELEM_TYPE_SIMULATION_ZONE: { + const auto &typed_elem = *reinterpret_cast(elem); + compute_context_builder.push( + typed_elem.sim_output_node_id); + break; + } + case VIEWER_PATH_ELEM_TYPE_REPEAT_ZONE: { + const auto &typed_elem = *reinterpret_cast(elem); + compute_context_builder.push( + typed_elem.repeat_output_node_id, typed_elem.iteration); + break; + } + default: { + BLI_assert_unreachable(); + break; + } } } const ComputeContextHash context_hash = compute_context_builder.hash(); diff --git a/source/blender/nodes/intern/node_common.cc b/source/blender/nodes/intern/node_common.cc index 8a8f286fe5c..9cc399d5d55 100644 --- a/source/blender/nodes/intern/node_common.cc +++ b/source/blender/nodes/intern/node_common.cc @@ -357,42 +357,11 @@ static PanelDeclarationPtr declaration_for_interface_panel(const bNodeTree & /*n return dst; } -static void set_default_input_field(const bNodeTreeInterfaceSocket &input, SocketDeclaration &decl) +void node_group_declare_dynamic(const bNodeTree & /*node_tree*/, + const bNode &node, + NodeDeclaration &r_declaration) { - if (dynamic_cast(&decl)) { - if (input.default_input == GEO_NODE_DEFAULT_FIELD_INPUT_NORMAL_FIELD) { - decl.implicit_input_fn = std::make_unique( - implicit_field_inputs::normal); - decl.hide_value = true; - } - else if (input.default_input == GEO_NODE_DEFAULT_FIELD_INPUT_POSITION_FIELD) { - decl.implicit_input_fn = std::make_unique( - implicit_field_inputs::position); - decl.hide_value = true; - } - } - else if (dynamic_cast(&decl)) { - if (input.default_input == GEO_NODE_DEFAULT_FIELD_INPUT_INDEX_FIELD) { - decl.implicit_input_fn = std::make_unique( - implicit_field_inputs::index); - decl.hide_value = true; - } - else if (input.default_input == GEO_NODE_DEFAULT_FIELD_INPUT_ID_INDEX_FIELD) { - decl.implicit_input_fn = std::make_unique( - implicit_field_inputs::id_or_index); - decl.hide_value = true; - } - } -} - -void node_group_declare(NodeDeclarationBuilder &b) -{ - const bNode *node = b.node_or_null(); - if (node == nullptr) { - return; - } - NodeDeclaration &r_declaration = b.declaration(); - const bNodeTree *group = reinterpret_cast(node->id); + const bNodeTree *group = reinterpret_cast(node.id); if (!group) { return; } @@ -440,22 +409,6 @@ void node_group_declare(NodeDeclarationBuilder &b) } return true; }); - - if (group->type == NTREE_GEOMETRY) { - group->ensure_interface_cache(); - const Span inputs = group->interface_inputs(); - const FieldInferencingInterface &field_interface = - *group->runtime->field_inferencing_interface; - for (const int i : inputs.index_range()) { - SocketDeclaration &decl = *r_declaration.inputs[i]; - decl.input_field_type = field_interface.inputs[i]; - set_default_input_field(*inputs[i], decl); - } - - for (const int i : r_declaration.outputs.index_range()) { - r_declaration.outputs[i]->output_field_dependency = field_interface.outputs[i]; - } - } } } // namespace blender::nodes @@ -652,21 +605,18 @@ bNodeSocket *node_group_input_find_socket(bNode *node, const char *identifier) namespace blender::nodes { -static void group_input_declare(NodeDeclarationBuilder &b) +static void group_input_declare_dynamic(const bNodeTree &node_tree, + const bNode & /*node*/, + NodeDeclaration &r_declaration) { - const bNodeTree *node_tree = b.tree_or_null(); - if (node_tree == nullptr) { - return; - } - NodeDeclaration &r_declaration = b.declaration(); - node_tree->tree_interface.foreach_item([&](const bNodeTreeInterfaceItem &item) { + node_tree.tree_interface.foreach_item([&](const bNodeTreeInterfaceItem &item) { switch (item.item_type) { case NODE_INTERFACE_SOCKET: { const bNodeTreeInterfaceSocket &socket = node_interface::get_item_as(item); if (socket.flag & NODE_INTERFACE_SOCKET_INPUT) { SocketDeclarationPtr socket_decl = declaration_for_interface_socket( - *node_tree, socket, SOCK_OUT); + node_tree, socket, SOCK_OUT); r_declaration.outputs.append(socket_decl.get()); r_declaration.items.append(std::move(socket_decl)); } @@ -680,21 +630,18 @@ static void group_input_declare(NodeDeclarationBuilder &b) r_declaration.items.append(std::move(extend_decl)); } -static void group_output_declare(NodeDeclarationBuilder &b) +static void group_output_declare_dynamic(const bNodeTree &node_tree, + const bNode & /*node*/, + NodeDeclaration &r_declaration) { - const bNodeTree *node_tree = b.tree_or_null(); - if (node_tree == nullptr) { - return; - } - NodeDeclaration &r_declaration = b.declaration(); - node_tree->tree_interface.foreach_item([&](const bNodeTreeInterfaceItem &item) { + node_tree.tree_interface.foreach_item([&](const bNodeTreeInterfaceItem &item) { switch (item.item_type) { case NODE_INTERFACE_SOCKET: { const bNodeTreeInterfaceSocket &socket = node_interface::get_item_as(item); if (socket.flag & NODE_INTERFACE_SOCKET_OUTPUT) { SocketDeclarationPtr socket_decl = declaration_for_interface_socket( - *node_tree, socket, SOCK_IN); + node_tree, socket, SOCK_IN); r_declaration.inputs.append(socket_decl.get()); r_declaration.items.append(std::move(socket_decl)); } @@ -760,7 +707,7 @@ void register_node_type_group_input() blender::bke::node_type_base(ntype, NODE_GROUP_INPUT, "Group Input", NODE_CLASS_INTERFACE); blender::bke::node_type_size(ntype, 140, 80, 400); - ntype->declare = blender::nodes::group_input_declare; + ntype->declare_dynamic = blender::nodes::group_input_declare_dynamic; ntype->insert_link = blender::nodes::group_input_insert_link; nodeRegisterType(ntype); @@ -784,7 +731,7 @@ void register_node_type_group_output() blender::bke::node_type_base(ntype, NODE_GROUP_OUTPUT, "Group Output", NODE_CLASS_INTERFACE); blender::bke::node_type_size(ntype, 140, 80, 400); - ntype->declare = blender::nodes::group_output_declare; + ntype->declare_dynamic = blender::nodes::group_output_declare_dynamic; ntype->insert_link = blender::nodes::group_output_insert_link; ntype->no_muting = true; diff --git a/source/blender/nodes/intern/node_declaration.cc b/source/blender/nodes/intern/node_declaration.cc index f7300d13d3d..6640f9cd11f 100644 --- a/source/blender/nodes/intern/node_declaration.cc +++ b/source/blender/nodes/intern/node_declaration.cc @@ -15,35 +15,36 @@ namespace blender::nodes { -static void reset_declaration(NodeDeclaration &declaration) +void build_node_declaration(const bNodeType &typeinfo, NodeDeclaration &r_declaration) { - std::destroy_at(&declaration); - new (&declaration) NodeDeclaration(); -} - -void build_node_declaration(const bNodeType &typeinfo, - NodeDeclaration &r_declaration, - const bNodeTree *ntree, - const bNode *node) -{ - reset_declaration(r_declaration); - NodeDeclarationBuilder node_decl_builder{r_declaration, ntree, node}; + NodeDeclarationBuilder node_decl_builder{r_declaration}; typeinfo.declare(node_decl_builder); node_decl_builder.finalize(); } +void build_node_declaration_dynamic(const bNodeTree &node_tree, + const bNode &node, + NodeDeclaration &r_declaration) +{ + r_declaration.items.clear(); + r_declaration.inputs.clear(); + r_declaration.outputs.clear(); + r_declaration.skip_updating_sockets = false; + node.typeinfo->declare_dynamic(node_tree, node, r_declaration); +} + void NodeDeclarationBuilder::finalize() { if (is_function_node_) { for (std::unique_ptr &socket_builder : socket_builders_) { - if (SocketDeclaration *socket_decl = socket_builder->decl_in_base_) { + if (SocketDeclaration *socket_decl = socket_builder->input_declaration()) { if (socket_decl->input_field_type != InputSocketFieldType::Implicit) { socket_decl->input_field_type = InputSocketFieldType::IsSupported; } } } for (std::unique_ptr &socket_builder : socket_builders_) { - if (SocketDeclaration *socket_decl = socket_builder->decl_out_base_) { + if (SocketDeclaration *socket_decl = socket_builder->output_declaration()) { socket_decl->output_field_dependency = OutputFieldDependency::ForDependentField(); socket_builder->reference_pass_all_ = true; } @@ -64,7 +65,7 @@ void NodeDeclarationBuilder::finalize() } for (std::unique_ptr &socket_builder : socket_builders_) { - if (!socket_builder->decl_in_base_) { + if (!socket_builder->input_declaration()) { continue; } @@ -77,7 +78,7 @@ void NodeDeclarationBuilder::finalize() } } for (std::unique_ptr &socket_builder : socket_builders_) { - if (!socket_builder->decl_out_base_) { + if (!socket_builder->output_declaration()) { continue; } @@ -110,26 +111,6 @@ void NodeDeclarationBuilder::finalize() BLI_assert(declaration_.is_valid()); } -NodeDeclarationBuilder::NodeDeclarationBuilder(NodeDeclaration &declaration, - const bNodeTree *ntree, - const bNode *node) - : declaration_(declaration), ntree_(ntree), node_(node) -{ -} - -void NodeDeclarationBuilder::use_custom_socket_order(bool enable) -{ - declaration_.use_custom_socket_order = enable; -} - -Span NodeDeclaration::sockets(eNodeSocketInOut in_out) const -{ - if (in_out == SOCK_IN) { - return inputs; - } - return outputs; -} - void NodeDeclarationBuilder::set_active_panel_builder(const PanelDeclarationBuilder *panel_builder) { if (panel_builders_.is_empty()) { @@ -412,496 +393,6 @@ void PanelDeclaration::update_or_build(const bNodePanelState &old_panel, SET_FLAG_FROM_TEST(new_panel.flag, old_panel.is_collapsed(), NODE_PANEL_COLLAPSED); } -std::unique_ptr make_declaration_for_socket_type( - const eNodeSocketDatatype socket_type) -{ - switch (socket_type) { - case SOCK_FLOAT: - return std::make_unique(); - case SOCK_VECTOR: - return std::make_unique(); - case SOCK_RGBA: - return std::make_unique(); - case SOCK_BOOLEAN: - return std::make_unique(); - case SOCK_ROTATION: - return std::make_unique(); - case SOCK_INT: - return std::make_unique(); - case SOCK_STRING: - return std::make_unique(); - case SOCK_GEOMETRY: - return std::make_unique(); - case SOCK_OBJECT: - return std::make_unique(); - case SOCK_IMAGE: - return std::make_unique(); - case SOCK_COLLECTION: - return std::make_unique(); - case SOCK_MATERIAL: - return std::make_unique(); - default: - return {}; - } -} - -BaseSocketDeclarationBuilder &NodeDeclarationBuilder::add_input( - const eNodeSocketDatatype socket_type, const StringRef name, const StringRef identifier) -{ - switch (socket_type) { - case SOCK_FLOAT: - return this->add_input(name, identifier); - case SOCK_VECTOR: - return this->add_input(name, identifier); - case SOCK_RGBA: - return this->add_input(name, identifier); - case SOCK_BOOLEAN: - return this->add_input(name, identifier); - case SOCK_ROTATION: - return this->add_input(name, identifier); - case SOCK_INT: - return this->add_input(name, identifier); - case SOCK_STRING: - return this->add_input(name, identifier); - case SOCK_GEOMETRY: - return this->add_input(name, identifier); - case SOCK_OBJECT: - return this->add_input(name, identifier); - case SOCK_IMAGE: - return this->add_input(name, identifier); - case SOCK_COLLECTION: - return this->add_input(name, identifier); - case SOCK_MATERIAL: - return this->add_input(name, identifier); - default: - BLI_assert_unreachable(); - return this->add_input("", ""); - } -} - -BaseSocketDeclarationBuilder &NodeDeclarationBuilder::add_output( - const eNodeSocketDatatype socket_type, const StringRef name, const StringRef identifier) -{ - switch (socket_type) { - case SOCK_FLOAT: - return this->add_output(name, identifier); - case SOCK_VECTOR: - return this->add_output(name, identifier); - case SOCK_RGBA: - return this->add_output(name, identifier); - case SOCK_BOOLEAN: - return this->add_output(name, identifier); - case SOCK_ROTATION: - return this->add_output(name, identifier); - case SOCK_INT: - return this->add_output(name, identifier); - case SOCK_STRING: - return this->add_output(name, identifier); - case SOCK_GEOMETRY: - return this->add_output(name, identifier); - case SOCK_OBJECT: - return this->add_output(name, identifier); - case SOCK_IMAGE: - return this->add_output(name, identifier); - case SOCK_COLLECTION: - return this->add_output(name, identifier); - case SOCK_MATERIAL: - return this->add_output(name, identifier); - default: - BLI_assert_unreachable(); - return this->add_output("", ""); - } -} - -BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::supports_field() -{ - if (decl_in_base_) { - decl_in_base_->input_field_type = InputSocketFieldType::IsSupported; - } - return *this; -} - -BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::dependent_field( - Vector input_dependencies) -{ - this->reference_pass(input_dependencies); - if (decl_out_base_) { - decl_out_base_->output_field_dependency = OutputFieldDependency::ForPartiallyDependentField( - std::move(input_dependencies)); - } - return *this; -} - -BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::hide_label(bool value) -{ - if (decl_in_base_) { - decl_in_base_->hide_label = value; - } - if (decl_out_base_) { - decl_out_base_->hide_label = value; - } - return *this; -} - -BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::hide_value(bool value) -{ - if (decl_in_base_) { - decl_in_base_->hide_value = value; - } - if (decl_out_base_) { - decl_out_base_->hide_value = value; - } - return *this; -} - -BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::multi_input(bool value) -{ - if (decl_in_base_) { - decl_in_base_->is_multi_input = value; - } - return *this; -} - -BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::reference_pass( - const Span input_indices) -{ - aal::RelationsInNode &relations = node_decl_builder_->get_anonymous_attribute_relations(); - for (const int from_input : input_indices) { - aal::ReferenceRelation relation; - relation.from_field_input = from_input; - relation.to_field_output = index_out_; - relations.reference_relations.append(relation); - } - return *this; -} - -BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::field_on(const Span indices) -{ - aal::RelationsInNode &relations = node_decl_builder_->get_anonymous_attribute_relations(); - if (decl_in_base_) { - this->supports_field(); - for (const int input_index : indices) { - aal::EvalRelation relation; - relation.field_input = index_in_; - relation.geometry_input = input_index; - relations.eval_relations.append(relation); - } - } - if (decl_out_base_) { - this->field_source(); - for (const int output_index : indices) { - aal::AvailableRelation relation; - relation.field_output = index_out_; - relation.geometry_output = output_index; - relations.available_relations.append(relation); - } - } - return *this; -} - -BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::short_label(std::string value) -{ - if (decl_in_base_) { - decl_in_base_->description = std::move(value); - } - if (decl_out_base_) { - decl_out_base_->description = std::move(value); - } - return *this; -} - -BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::description(std::string value) -{ - if (decl_in_base_) { - decl_in_base_->description = std::move(value); - } - if (decl_out_base_) { - decl_out_base_->description = std::move(value); - } - return *this; -} - -BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::translation_context(std::string value) -{ - if (decl_in_base_) { - decl_in_base_->translation_context = value; - } - if (decl_out_base_) { - decl_out_base_->translation_context = std::move(value); - } - return *this; -} - -BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::no_muted_links(bool value) -{ - if (decl_in_base_) { - decl_in_base_->no_mute_links = value; - } - if (decl_out_base_) { - decl_out_base_->no_mute_links = value; - } - return *this; -} - -BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::unavailable(bool value) -{ - if (decl_in_base_) { - decl_in_base_->is_unavailable = value; - } - if (decl_out_base_) { - decl_out_base_->is_unavailable = value; - } - return *this; -} - -BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::is_attribute_name(bool value) -{ - if (decl_in_base_) { - decl_in_base_->is_attribute_name = value; - } - if (decl_out_base_) { - decl_out_base_->is_attribute_name = value; - } - return *this; -} - -BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::is_default_link_socket(bool value) -{ - if (decl_in_base_) { - decl_in_base_->is_default_link_socket = value; - } - if (decl_out_base_) { - decl_out_base_->is_default_link_socket = value; - } - return *this; -} - -BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::field_on_all() -{ - if (decl_in_base_) { - this->supports_field(); - } - if (decl_out_base_) { - this->field_source(); - } - field_on_all_ = true; - return *this; -} - -BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::field_source() -{ - if (decl_out_base_) { - decl_out_base_->output_field_dependency = OutputFieldDependency::ForFieldSource(); - } - return *this; -} - -BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::implicit_field(ImplicitInputValueFn fn) -{ - this->hide_value(); - if (decl_in_base_) { - decl_in_base_->input_field_type = InputSocketFieldType::Implicit; - decl_in_base_->implicit_input_fn = std::make_unique(std::move(fn)); - } - return *this; -} - -BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::implicit_field_on_all( - ImplicitInputValueFn fn) -{ - this->implicit_field(fn); - field_on_all_ = true; - return *this; -} - -BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::implicit_field_on( - ImplicitInputValueFn fn, const Span input_indices) -{ - this->field_on(input_indices); - this->implicit_field(fn); - return *this; -} - -BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::dependent_field() -{ - if (decl_out_base_) { - decl_out_base_->output_field_dependency = OutputFieldDependency::ForDependentField(); - } - this->reference_pass_all(); - return *this; -} - -BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::field_source_reference_all() -{ - this->field_source(); - this->reference_pass_all(); - return *this; -} - -BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::reference_pass_all() -{ - reference_pass_all_ = true; - return *this; -} - -BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::propagate_all() -{ - propagate_from_all_ = true; - return *this; -} - -BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::compositor_realization_options( - CompositorInputRealizationOptions value) -{ - if (decl_in_base_) { - decl_in_base_->compositor_realization_options_ = value; - } - if (decl_out_base_) { - decl_out_base_->compositor_realization_options_ = value; - } - return *this; -} - -BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::compositor_domain_priority( - int priority) -{ - if (decl_in_base_) { - decl_in_base_->compositor_domain_priority_ = priority; - } - if (decl_out_base_) { - decl_out_base_->compositor_domain_priority_ = priority; - } - return *this; -} - -BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::compositor_expects_single_value( - bool value) -{ - if (decl_in_base_) { - decl_in_base_->compositor_expects_single_value_ = value; - } - if (decl_out_base_) { - decl_out_base_->compositor_expects_single_value_ = value; - } - return *this; -} - -BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::make_available( - std::function fn) -{ - if (decl_in_base_) { - decl_in_base_->make_available_fn_ = std::move(fn); - } - if (decl_out_base_) { - decl_out_base_->make_available_fn_ = std::move(fn); - } - return *this; -} - -OutputFieldDependency OutputFieldDependency::ForFieldSource() -{ - OutputFieldDependency field_dependency; - field_dependency.type_ = OutputSocketFieldType::FieldSource; - return field_dependency; -} - -OutputFieldDependency OutputFieldDependency::ForDataSource() -{ - OutputFieldDependency field_dependency; - field_dependency.type_ = OutputSocketFieldType::None; - return field_dependency; -} - -OutputFieldDependency OutputFieldDependency::ForDependentField() -{ - OutputFieldDependency field_dependency; - field_dependency.type_ = OutputSocketFieldType::DependentField; - return field_dependency; -} - -OutputFieldDependency OutputFieldDependency::ForPartiallyDependentField(Vector 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; -} - -OutputSocketFieldType OutputFieldDependency::field_type() const -{ - return type_; -} - -Span OutputFieldDependency::linked_input_indices() const -{ - return linked_input_indices_; -} - -bool operator==(const OutputFieldDependency &a, const OutputFieldDependency &b) -{ - return a.type_ == b.type_ && a.linked_input_indices_ == b.linked_input_indices_; -} - -bool operator!=(const OutputFieldDependency &a, const OutputFieldDependency &b) -{ - return !(a == b); -} - -bool operator==(const FieldInferencingInterface &a, const FieldInferencingInterface &b) -{ - return a.inputs == b.inputs && a.outputs == b.outputs; -} - -bool operator!=(const FieldInferencingInterface &a, const FieldInferencingInterface &b) -{ - return !(a == b); -} - -const CompositorInputRealizationOptions &SocketDeclaration::compositor_realization_options() const -{ - return compositor_realization_options_; -} - -int SocketDeclaration::compositor_domain_priority() const -{ - return compositor_domain_priority_; -} - -bool SocketDeclaration::compositor_expects_single_value() const -{ - return compositor_expects_single_value_; -} - -void SocketDeclaration::make_available(bNode &node) const -{ - if (make_available_fn_) { - make_available_fn_(node); - } -} - -PanelDeclarationBuilder &PanelDeclarationBuilder::description(std::string value) -{ - decl_->description = std::move(value); - return *this; -} - -PanelDeclarationBuilder &PanelDeclarationBuilder::default_closed(bool closed) -{ - decl_->default_collapsed = closed; - return *this; -} - -PanelDeclarationBuilder &PanelDeclarationBuilder::draw_buttons(PanelDrawButtonsFunction func) -{ - decl_->draw_buttons = func; - return *this; -} - namespace implicit_field_inputs { void position(const bNode & /*node*/, void *r_value) diff --git a/source/blender/nodes/intern/node_socket.cc b/source/blender/nodes/intern/node_socket.cc index 7b2e8b717ad..c605ff28d4a 100644 --- a/source/blender/nodes/intern/node_socket.cc +++ b/source/blender/nodes/intern/node_socket.cc @@ -604,23 +604,15 @@ static void refresh_node(bNodeTree &ntree, void update_node_declaration_and_sockets(bNodeTree &ntree, bNode &node) { - if (node.typeinfo->declare) { - if (node.typeinfo->static_declaration->is_context_dependent) { - if (!node.runtime->declaration) { - node.runtime->declaration = new NodeDeclaration(); - } - build_node_declaration(*node.typeinfo, *node.runtime->declaration, &ntree, &node); + if (node.typeinfo->declare_dynamic) { + if (!node.runtime->declaration) { + node.runtime->declaration = new NodeDeclaration(); } + build_node_declaration_dynamic(ntree, node, *node.runtime->declaration); } refresh_node(ntree, node, *node.runtime->declaration, true); } -bool socket_type_supports_fields(const eNodeSocketDatatype socket_type) -{ - return ELEM( - socket_type, SOCK_FLOAT, SOCK_VECTOR, SOCK_RGBA, SOCK_BOOLEAN, SOCK_INT, SOCK_ROTATION); -} - } // namespace blender::nodes void node_verify_sockets(bNodeTree *ntree, bNode *node, bool do_id_user) @@ -629,7 +621,7 @@ void node_verify_sockets(bNodeTree *ntree, bNode *node, bool do_id_user) if (ntype == nullptr) { return; } - if (ntype->declare) { + if (ntype->declare || ntype->declare_dynamic) { blender::bke::nodeDeclarationEnsureOnOutdatedNode(ntree, node); refresh_node(*ntree, *node, *node->runtime->declaration, do_id_user); return; diff --git a/source/blender/nodes/intern/node_zone_socket_items.cc b/source/blender/nodes/intern/node_zone_socket_items.cc deleted file mode 100644 index 03c6b104ca1..00000000000 --- a/source/blender/nodes/intern/node_zone_socket_items.cc +++ /dev/null @@ -1,59 +0,0 @@ -/* SPDX-FileCopyrightText: 2023 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -#include "RNA_prototypes.h" - -#include "NOD_zone_socket_items.hh" - -#include "BKE_node.hh" - -#include "BLO_read_write.hh" - -namespace blender::nodes { - -/* Defined here to avoid including the relevant headers in the header. */ - -StructRNA *SimulationItemsAccessor::item_srna = &RNA_SimulationStateItem; -int SimulationItemsAccessor::node_type = GEO_NODE_SIMULATION_OUTPUT; - -void SimulationItemsAccessor::blend_write(BlendWriter *writer, const bNode &node) -{ - const auto &storage = *static_cast(node.storage); - BLO_write_struct_array(writer, NodeSimulationItem, storage.items_num, storage.items); - for (const NodeSimulationItem &item : Span(storage.items, storage.items_num)) { - BLO_write_string(writer, item.name); - } -} - -void SimulationItemsAccessor::blend_read_data(BlendDataReader *reader, bNode &node) -{ - auto &storage = *static_cast(node.storage); - BLO_read_data_address(reader, &storage.items); - for (const NodeSimulationItem &item : Span(storage.items, storage.items_num)) { - BLO_read_data_address(reader, &item.name); - } -} - -StructRNA *RepeatItemsAccessor::item_srna = &RNA_RepeatItem; -int RepeatItemsAccessor::node_type = GEO_NODE_REPEAT_OUTPUT; - -void RepeatItemsAccessor::blend_write(BlendWriter *writer, const bNode &node) -{ - const auto &storage = *static_cast(node.storage); - BLO_write_struct_array(writer, NodeRepeatItem, storage.items_num, storage.items); - for (const NodeRepeatItem &item : Span(storage.items, storage.items_num)) { - BLO_write_string(writer, item.name); - } -} - -void RepeatItemsAccessor::blend_read_data(BlendDataReader *reader, bNode &node) -{ - auto &storage = *static_cast(node.storage); - BLO_read_data_address(reader, &storage.items); - for (const NodeRepeatItem &item : Span(storage.items, storage.items_num)) { - BLO_read_data_address(reader, &item.name); - } -} - -} // namespace blender::nodes diff --git a/source/blender/nodes/intern/socket_search_link.cc b/source/blender/nodes/intern/socket_search_link.cc index 15f1641d373..0edb8c2589d 100644 --- a/source/blender/nodes/intern/socket_search_link.cc +++ b/source/blender/nodes/intern/socket_search_link.cc @@ -141,10 +141,18 @@ void search_link_ops_for_declarations(GatherLinkSearchOpParams ¶ms, void search_link_ops_for_basic_node(GatherLinkSearchOpParams ¶ms) { const bNodeType &node_type = params.node_type(); - if (!node_type.static_declaration) { + if (!node_type.declare) { return; } - const NodeDeclaration &declaration = *node_type.static_declaration; + + if (node_type.declare_dynamic) { + /* Dynamic declarations aren't supported here, but avoid crashing in release builds. */ + BLI_assert_unreachable(); + return; + } + + const NodeDeclaration &declaration = *node_type.fixed_declaration; + search_link_ops_for_declarations(params, declaration.sockets(params.in_out())); } diff --git a/source/blender/nodes/shader/CMakeLists.txt b/source/blender/nodes/shader/CMakeLists.txt index 86e73ae527c..0caf0e164c5 100644 --- a/source/blender/nodes/shader/CMakeLists.txt +++ b/source/blender/nodes/shader/CMakeLists.txt @@ -140,15 +140,15 @@ set(LIB if(WITH_MATERIALX) add_definitions(-DWITH_MATERIALX) list(APPEND SRC - materialx/group_nodes.cc materialx/material.cc materialx/node_item.cc materialx/node_parser.cc + materialx/group_nodes.cc - materialx/group_nodes.h materialx/material.h materialx/node_item.h materialx/node_parser.h + materialx/group_nodes.h ) list(APPEND LIB MaterialXCore diff --git a/source/blender/nodes/shader/nodes/node_shader_bsdf_principled.cc b/source/blender/nodes/shader/nodes/node_shader_bsdf_principled.cc index 50ac820ebd8..ade0412151b 100644 --- a/source/blender/nodes/shader/nodes/node_shader_bsdf_principled.cc +++ b/source/blender/nodes/shader/nodes/node_shader_bsdf_principled.cc @@ -57,44 +57,44 @@ static void node_declare(NodeDeclarationBuilder &b) uiItemR(layout, ptr, "subsurface_method", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE); }); sss.add_input("Subsurface Weight") + .short_label("Weight") .default_value(0.0f) .min(0.0f) .max(1.0f) .subtype(PROP_FACTOR) - .short_label("Weight") .description( "Blend between diffuse surface and subsurface scattering. " "Typically should be zero or one (either fully diffuse or subsurface)"); #define SOCK_SUBSURFACE_WEIGHT_ID 7 sss.add_input("Subsurface Radius") + .short_label("Radius") .default_value({1.0f, 0.2f, 0.1f}) .min(0.0f) .max(100.0f) - .short_label("Radius") .description("Scattering radius to use for subsurface component (multiplied with Scale)"); #define SOCK_SUBSURFACE_RADIUS_ID 8 sss.add_input("Subsurface Scale") + .short_label("Scale") .default_value(0.05f) .min(0.0f) .max(10.0f) .subtype(PROP_DISTANCE) - .short_label("Scale") .description("Scale of the subsurface scattering (multiplied with Radius)"); #define SOCK_SUBSURFACE_SCALE_ID 9 sss.add_input("Subsurface IOR") + .short_label("IOR") .default_value(1.4f) .min(1.01f) .max(3.8f) .subtype(PROP_FACTOR) - .short_label("IOR") .description("Index of refraction used for rays that enter the subsurface component"); #define SOCK_SUBSURFACE_IOR_ID 10 sss.add_input("Subsurface Anisotropy") + .short_label("Anisotropy") .default_value(0.0f) .min(0.0f) .max(1.0f) - .subtype(PROP_FACTOR) - .short_label("Anisotropy"); + .subtype(PROP_FACTOR); #define SOCK_SUBSURFACE_ANISOTROPY_ID 11 /* Panel for Specular settings. */ @@ -105,19 +105,19 @@ static void node_declare(NodeDeclarationBuilder &b) uiItemR(layout, ptr, "distribution", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE); }); spec.add_input("Specular IOR Level") + .short_label("IOR Level") .default_value(0.5f) .min(0.0f) .max(1.0f) .subtype(PROP_FACTOR) - .short_label("IOR Level") .description( "Adjustment to the IOR to increase or decrease specular intensity " "(0.5 means no adjustment, 0 removes all reflections, 1 doubles them at normal " "incidence)"); #define SOCK_SPECULAR_ID 12 spec.add_input("Specular Tint") - .default_value({1.0f, 1.0f, 1.0f, 1.0f}) .short_label("Tint") + .default_value({1.0f, 1.0f, 1.0f, 1.0f}) .description( "Tint dielectric reflection at normal incidence for artistic control, and metallic " "reflection at near-grazing incidence to simulate complex index of refraction") @@ -141,46 +141,46 @@ static void node_declare(NodeDeclarationBuilder &b) /* Panel for Transmission settings. */ PanelDeclarationBuilder &transmission = b.add_panel("Transmission").default_closed(true); transmission.add_input("Transmission Weight") + .short_label("Weight") .default_value(0.0f) .min(0.0f) .max(1.0f) .subtype(PROP_FACTOR) - .short_label("Weight") .description("Blend between transmission and other base layer components"); #define SOCK_TRANSMISSION_WEIGHT_ID 17 /* Panel for Coat settings. */ PanelDeclarationBuilder &coat = b.add_panel("Coat").default_closed(true); coat.add_input("Coat Weight") + .short_label("Weight") .default_value(0.0f) .min(0.0f) .max(1.0f) .subtype(PROP_FACTOR) - .short_label("Weight") .description( "Controls the intensity of the coat layer, both the reflection and the tinting. " "Typically should be zero or one for physically-based materials"); #define SOCK_COAT_WEIGHT_ID 18 coat.add_input("Coat Roughness") + .short_label("Roughness") .default_value(0.03f) .min(0.0f) .max(1.0f) .subtype(PROP_FACTOR) - .short_label("Roughness") .description("The roughness of the coat layer"); #define SOCK_COAT_ROUGHNESS_ID 19 coat.add_input("Coat IOR") + .short_label("IOR") .default_value(1.5f) .min(1.0f) .max(4.0f) - .short_label("IOR") .description( "The index of refraction of the coat layer " "(affects its reflectivity as well as the falloff of coat tinting)"); #define SOCK_COAT_IOR_ID 20 coat.add_input("Coat Tint") - .default_value({1.0f, 1.0f, 1.0f, 1.0f}) .short_label("Tint") + .default_value({1.0f, 1.0f, 1.0f, 1.0f}) .description( "Adds a colored tint to the coat layer by modeling absorption in the layer. " "Saturation increases at shallower angles, as the light travels farther " @@ -193,36 +193,36 @@ static void node_declare(NodeDeclarationBuilder &b) /* Panel for Sheen settings. */ PanelDeclarationBuilder &sheen = b.add_panel("Sheen").default_closed(true); sheen.add_input("Sheen Weight") + .short_label("Weight") .default_value(0.0f) .min(0.0f) .max(1.0f) - .subtype(PROP_FACTOR) - .short_label("Weight"); + .subtype(PROP_FACTOR); #define SOCK_SHEEN_WEIGHT_ID 23 sheen.add_input("Sheen Roughness") + .short_label("Roughness") .default_value(0.5f) .min(0.0f) .max(1.0f) - .subtype(PROP_FACTOR) - .short_label("Roughness"); + .subtype(PROP_FACTOR); #define SOCK_SHEEN_ROUGHNESS_ID 24 sheen.add_input("Sheen Tint") + .short_label("Tint") .default_value({1.0f, 1.0f, 1.0f, 1.0f}) - .translation_context(BLT_I18NCONTEXT_ID_NODETREE) - .short_label("Tint"); + .translation_context(BLT_I18NCONTEXT_ID_NODETREE); #define SOCK_SHEEN_TINT_ID 25 /* Panel for Emission settings. */ PanelDeclarationBuilder &emis = b.add_panel("Emission").default_closed(true); emis.add_input("Emission Color") - .default_value({1.0f, 1.0f, 1.0f, 1.0f}) - .short_label("Color"); + .short_label("Color") + .default_value({1.0f, 1.0f, 1.0f, 1.0f}); #define SOCK_EMISSION_ID 26 emis.add_input("Emission Strength") + .short_label("Strength") .default_value(0.0) .min(0.0f) - .max(1000000.0f) - .short_label("Strength"); + .max(1000000.0f); #define SOCK_EMISSION_STRENGTH_ID 27 } diff --git a/source/blender/nodes/shader/nodes/node_shader_common.cc b/source/blender/nodes/shader/nodes/node_shader_common.cc index c21b6daf0be..3561e5aa940 100644 --- a/source/blender/nodes/shader/nodes/node_shader_common.cc +++ b/source/blender/nodes/shader/nodes/node_shader_common.cc @@ -97,7 +97,7 @@ void register_node_type_sh_group() blender::bke::node_type_size(&ntype, 140, 60, 400); ntype.labelfunc = node_group_label; - ntype.declare = blender::nodes::node_group_declare; + ntype.declare_dynamic = blender::nodes::node_group_declare_dynamic; ntype.gpu_fn = gpu_group_execute; nodeRegisterType(&ntype); @@ -112,6 +112,6 @@ void register_node_type_sh_custom_group(bNodeType *ntype) if (ntype->insert_link == nullptr) { ntype->insert_link = node_insert_link_default; } - ntype->declare = blender::nodes::node_group_declare; + ntype->declare_dynamic = blender::nodes::node_group_declare_dynamic; ntype->gpu_fn = gpu_group_execute; } diff --git a/source/blender/nodes/shader/nodes/node_shader_curves.cc b/source/blender/nodes/shader/nodes/node_shader_curves.cc index a7df66a36c5..7bc4c5cade3 100644 --- a/source/blender/nodes/shader/nodes/node_shader_curves.cc +++ b/source/blender/nodes/shader/nodes/node_shader_curves.cc @@ -21,12 +21,8 @@ namespace blender::nodes::node_shader_curves_cc::vec { static void sh_node_curve_vec_declare(NodeDeclarationBuilder &b) { b.is_function_node(); - b.add_input("Fac") - .min(0.0f) - .max(1.0f) - .default_value(1.0f) - .subtype(PROP_FACTOR) - .no_muted_links(); + b.add_input("Fac").no_muted_links().min(0.0f).max(1.0f).default_value(1.0f).subtype( + PROP_FACTOR); b.add_input("Vector").min(-1.0f).max(1.0f); b.add_output("Vector"); } @@ -149,12 +145,8 @@ namespace blender::nodes::node_shader_curves_cc::rgb { static void sh_node_curve_rgb_declare(NodeDeclarationBuilder &b) { b.is_function_node(); - b.add_input("Fac") - .min(0.0f) - .max(1.0f) - .default_value(1.0f) - .subtype(PROP_FACTOR) - .no_muted_links(); + b.add_input("Fac").no_muted_links().min(0.0f).max(1.0f).default_value(1.0f).subtype( + PROP_FACTOR); b.add_input("Color").default_value({1.0f, 1.0f, 1.0f, 1.0f}); b.add_output("Color"); } @@ -306,11 +298,11 @@ static void sh_node_curve_float_declare(NodeDeclarationBuilder &b) { b.is_function_node(); b.add_input("Factor") + .no_muted_links() .min(0.0f) .max(1.0f) .default_value(1.0f) - .subtype(PROP_FACTOR) - .no_muted_links(); + .subtype(PROP_FACTOR); b.add_input("Value").default_value(1.0f).is_default_link_socket(); b.add_output("Value"); } diff --git a/source/blender/nodes/shader/nodes/node_shader_mix.cc b/source/blender/nodes/shader/nodes/node_shader_mix.cc index 37d2a445302..c4c60d44aea 100644 --- a/source/blender/nodes/shader/nodes/node_shader_mix.cc +++ b/source/blender/nodes/shader/nodes/node_shader_mix.cc @@ -41,15 +41,15 @@ static void sh_node_mix_declare(NodeDeclarationBuilder &b) * Input socket indices must be kept in sync with ntree_shader_disconnect_inactive_mix_branches */ b.add_input("Factor", "Factor_Float") + .no_muted_links() .default_value(0.5f) .min(0.0f) .max(1.0f) - .subtype(PROP_FACTOR) - .no_muted_links(); + .subtype(PROP_FACTOR); b.add_input("Factor", "Factor_Vector") + .no_muted_links() .default_value(float3(0.5f)) - .subtype(PROP_FACTOR) - .no_muted_links(); + .subtype(PROP_FACTOR); b.add_input("A", "A_Float") .min(-10000.0f) diff --git a/source/blender/nodes/shader/nodes/node_shader_normal_map.cc b/source/blender/nodes/shader/nodes/node_shader_normal_map.cc index 1420a095a8f..d6c6b7953d6 100644 --- a/source/blender/nodes/shader/nodes/node_shader_normal_map.cc +++ b/source/blender/nodes/shader/nodes/node_shader_normal_map.cc @@ -37,7 +37,7 @@ static void node_shader_buts_normal_map(uiLayout *layout, bContext *C, PointerRN Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); DEG_get_evaluated_rna_pointer(depsgraph, &obptr, &eval_obptr); PointerRNA dataptr = RNA_pointer_get(&eval_obptr, "data"); - uiItemPointerR(layout, ptr, "uv_map", &dataptr, "uv_layers", "", ICON_GROUP_UVS); + uiItemPointerR(layout, ptr, "uv_map", &dataptr, "uv_layers", "", ICON_NONE); } else { uiItemR(layout, ptr, "uv_map", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE); diff --git a/source/blender/nodes/shader/nodes/node_shader_output_material.cc b/source/blender/nodes/shader/nodes/node_shader_output_material.cc index 1cf832b5c77..9995292c9a0 100644 --- a/source/blender/nodes/shader/nodes/node_shader_output_material.cc +++ b/source/blender/nodes/shader/nodes/node_shader_output_material.cc @@ -13,7 +13,7 @@ static void node_declare(NodeDeclarationBuilder &b) b.add_input("Surface"); b.add_input("Volume").translation_context(BLT_I18NCONTEXT_ID_ID); b.add_input("Displacement").hide_value(); - b.add_input("Thickness").hide_value(); + b.add_input("Thickness").hide_value().unavailable(); /* Not used for now. */ } static int node_shader_gpu_output_material(GPUMaterial *mat, diff --git a/source/blender/nodes/shader/nodes/node_shader_tangent.cc b/source/blender/nodes/shader/nodes/node_shader_tangent.cc index ba48bf69650..eda4c94cabb 100644 --- a/source/blender/nodes/shader/nodes/node_shader_tangent.cc +++ b/source/blender/nodes/shader/nodes/node_shader_tangent.cc @@ -23,7 +23,13 @@ static void node_declare(NodeDeclarationBuilder &b) static void node_shader_buts_tangent(uiLayout *layout, bContext *C, PointerRNA *ptr) { - uiItemR(layout, ptr, "direction_type", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE); + uiLayout *split, *row; + + split = uiLayoutSplit(layout, 0.0f, false); + + uiItemR(split, ptr, "direction_type", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE); + + row = uiLayoutRow(split, false); if (RNA_enum_get(ptr, "direction_type") == SHD_TANGENT_UVMAP) { PointerRNA obptr = CTX_data_pointer_get(C, "active_object"); @@ -34,15 +40,14 @@ static void node_shader_buts_tangent(uiLayout *layout, bContext *C, PointerRNA * Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); DEG_get_evaluated_rna_pointer(depsgraph, &obptr, &eval_obptr); PointerRNA dataptr = RNA_pointer_get(&eval_obptr, "data"); - uiItemPointerR(layout, ptr, "uv_map", &dataptr, "uv_layers", "", ICON_GROUP_UVS); + uiItemPointerR(row, ptr, "uv_map", &dataptr, "uv_layers", "", ICON_NONE); } else { - uiItemR(layout, ptr, "uv_map", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_GROUP_UVS); + uiItemR(row, ptr, "uv_map", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE); } } else { - uiItemR( - layout, ptr, "axis", UI_ITEM_R_SPLIT_EMPTY_NAME | UI_ITEM_R_EXPAND, nullptr, ICON_NONE); + uiItemR(row, ptr, "axis", UI_ITEM_R_SPLIT_EMPTY_NAME | UI_ITEM_R_EXPAND, nullptr, ICON_NONE); } } diff --git a/source/blender/nodes/shader/nodes/node_shader_tex_sky.cc b/source/blender/nodes/shader/nodes/node_shader_tex_sky.cc index bbd8b4fab3b..90ebb59e00c 100644 --- a/source/blender/nodes/shader/nodes/node_shader_tex_sky.cc +++ b/source/blender/nodes/shader/nodes/node_shader_tex_sky.cc @@ -286,7 +286,7 @@ static void node_shader_update_sky(bNodeTree *ntree, bNode *node) static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) { - const NodeDeclaration &declaration = *params.node_type().static_declaration; + const NodeDeclaration &declaration = *params.node_type().fixed_declaration; if (params.in_out() == SOCK_OUT) { search_link_ops_for_declarations(params, declaration.outputs); return; 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 e8f424887fe..0951e757174 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 @@ -185,69 +185,6 @@ static void sh_node_noise_build_multi_function(NodeMultiFunctionBuilder &builder builder.construct_and_set_matching_fn(int(node.custom1)); } -NODE_SHADER_MATERIALX_BEGIN -#ifdef WITH_MATERIALX -{ - /* MaterialX cellnoise node rounds float value of texture coordinate. - * Therefore it changes at different integer coordinates. - * The simple trick would be to multiply the texture coordinate by a large number. */ - const float LARGE_NUMBER = 10000.0f; - - NodeItem noise = empty(); - NodeItem vector = empty(); - NodeItem w = empty(); - - int dimension = node_->custom1; - switch (dimension) { - case 1: - w = get_input_value("W", NodeItem::Type::Vector2); - noise = create_node( - "cellnoise2d", NodeItem::Type::Float, {{"texcoord", w * val(LARGE_NUMBER)}}); - break; - case 2: - vector = get_input_link("Vector", NodeItem::Type::Vector2); - if (!vector) { - vector = texcoord_node(); - } - noise = create_node( - "cellnoise2d", NodeItem::Type::Float, {{"texcoord", vector * val(LARGE_NUMBER)}}); - break; - case 3: - vector = get_input_link("Vector", NodeItem::Type::Vector3); - if (!vector) { - vector = texcoord_node(NodeItem::Type::Vector3); - } - noise = create_node( - "cellnoise3d", NodeItem::Type::Float, {{"position", vector * val(LARGE_NUMBER)}}); - break; - case 4: - vector = get_input_link("Vector", NodeItem::Type::Vector3); - if (!vector) { - vector = texcoord_node(NodeItem::Type::Vector3); - } - w = get_input_value("W", NodeItem::Type::Float); - noise = create_node( - "cellnoise3d", NodeItem::Type::Float, {{"position", (vector + w) * val(LARGE_NUMBER)}}); - break; - default: - BLI_assert_unreachable(); - break; - } - - if (STREQ(socket_out_->name, "Value")) { - return noise; - } - - /* NOTE: cellnoise node doesn't have colored output, so we create hsvtorgb node and put - * noise in first (Hue) channel to generate color. */ - NodeItem combine = create_node("combine3", - NodeItem::Type::Color3, - {{"in1", noise}, {"in2", val(1.0f)}, {"in3", val(0.5f)}}); - return create_node("hsvtorgb", NodeItem::Type::Color3, {{"in", combine}}); -} -#endif -NODE_SHADER_MATERIALX_END - } // namespace blender::nodes::node_shader_tex_white_noise_cc void register_node_type_sh_tex_white_noise() @@ -263,7 +200,6 @@ void register_node_type_sh_tex_white_noise() ntype.gpu_fn = file_ns::gpu_shader_tex_white_noise; ntype.updatefunc = file_ns::node_shader_update_tex_white_noise; ntype.build_multi_function = file_ns::sh_node_noise_build_multi_function; - ntype.materialx_fn = file_ns::node_shader_materialx; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/shader/nodes/node_shader_uvmap.cc b/source/blender/nodes/shader/nodes/node_shader_uvmap.cc index dfe0d71625e..87e36f0581b 100644 --- a/source/blender/nodes/shader/nodes/node_shader_uvmap.cc +++ b/source/blender/nodes/shader/nodes/node_shader_uvmap.cc @@ -36,7 +36,7 @@ static void node_shader_buts_uvmap(uiLayout *layout, bContext *C, PointerRNA *pt Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); DEG_get_evaluated_rna_pointer(depsgraph, &obptr, &eval_obptr); PointerRNA dataptr = RNA_pointer_get(&eval_obptr, "data"); - uiItemPointerR(layout, ptr, "uv_map", &dataptr, "uv_layers", "", ICON_GROUP_UVS); + uiItemPointerR(layout, ptr, "uv_map", &dataptr, "uv_layers", "", ICON_NONE); } } } diff --git a/source/blender/nodes/texture/nodes/node_texture_common.cc b/source/blender/nodes/texture/nodes/node_texture_common.cc index d74caef9a96..5c2cb3d562f 100644 --- a/source/blender/nodes/texture/nodes/node_texture_common.cc +++ b/source/blender/nodes/texture/nodes/node_texture_common.cc @@ -156,7 +156,7 @@ void register_node_type_tex_group() blender::bke::node_type_size(&ntype, 140, 60, 400); ntype.labelfunc = node_group_label; - ntype.declare = blender::nodes::node_group_declare; + ntype.declare_dynamic = blender::nodes::node_group_declare_dynamic; ntype.init_exec_fn = group_initexec; ntype.free_exec_fn = group_freeexec; ntype.exec_fn = group_execute; diff --git a/source/blender/python/bmesh/bmesh_py_types.cc b/source/blender/python/bmesh/bmesh_py_types.cc index 7cef2b95964..fb5cd5ef905 100644 --- a/source/blender/python/bmesh/bmesh_py_types.cc +++ b/source/blender/python/bmesh/bmesh_py_types.cc @@ -11,7 +11,7 @@ #include "BLI_math_vector.h" #include "BLI_sort.h" #include "BLI_string.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "DNA_material_types.h" #include "DNA_mesh_types.h" diff --git a/source/blender/python/generic/CMakeLists.txt b/source/blender/python/generic/CMakeLists.txt index 6fd75fb46a1..77148216078 100644 --- a/source/blender/python/generic/CMakeLists.txt +++ b/source/blender/python/generic/CMakeLists.txt @@ -11,6 +11,7 @@ set(INC ) set(INC_SYS + ${Epoxy_INCLUDE_DIRS} ${PYTHON_INCLUDE_DIRS} ) @@ -40,6 +41,7 @@ set(SRC ) set(LIB + ${Epoxy_LIBRARIES} ${PYTHON_LINKFLAGS} ${PYTHON_LIBRARIES} PRIVATE bf::blenlib @@ -47,17 +49,6 @@ set(LIB PRIVATE bf::intern::guardedalloc ) -# NOTE: use irrespective of `WITH_OPENGL_BACKEND` as the `bgl` module uses this for constants. -list(APPEND INC_SYS - ${Epoxy_INCLUDE_DIRS} -) - -if(WITH_OPENGL_BACKEND) - list(APPEND LIB - ${Epoxy_LIBRARIES} - ) -endif() - if(WITH_PYTHON_MODULE) add_definitions(-DWITH_PYTHON_MODULE) endif() diff --git a/source/blender/python/gpu/CMakeLists.txt b/source/blender/python/gpu/CMakeLists.txt index 868eb1c713c..aa15280cad1 100644 --- a/source/blender/python/gpu/CMakeLists.txt +++ b/source/blender/python/gpu/CMakeLists.txt @@ -11,6 +11,7 @@ set(INC ) set(INC_SYS + ${Epoxy_INCLUDE_DIRS} ${PYTHON_INCLUDE_DIRS} ) @@ -56,6 +57,7 @@ set(SRC ) set(LIB + ${Epoxy_LIBRARIES} ${PYTHON_LINKFLAGS} ${PYTHON_LIBRARIES} PRIVATE bf::blenlib @@ -63,13 +65,4 @@ set(LIB PRIVATE bf::intern::guardedalloc ) -if(WITH_OPENGL_BACKEND) - list(APPEND INC_SYS - ${Epoxy_INCLUDE_DIRS} - ) - list(APPEND LIB - ${Epoxy_LIBRARIES} - ) -endif() - blender_add_lib(bf_python_gpu "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") diff --git a/source/blender/python/gpu/gpu_py_shader.cc b/source/blender/python/gpu/gpu_py_shader.cc index 23f601f0ab5..fe54c1bc49d 100644 --- a/source/blender/python/gpu/gpu_py_shader.cc +++ b/source/blender/python/gpu/gpu_py_shader.cc @@ -251,7 +251,7 @@ PyDoc_STRVAR(pygpu_shader_uniform_vector_float_doc, "\n" " :arg location: Location of the uniform variable to be modified.\n" " :type location: int\n" - " :arg buffer: The data that should be set. Can support the buffer protocol.\n" + " :arg buffer: The data that should be set. Can support the buffer protocol.\n" " :type buffer: sequence of floats\n" " :arg length: Size of the uniform data type:\n\n" " - 1: float\n" diff --git a/source/blender/python/intern/CMakeLists.txt b/source/blender/python/intern/CMakeLists.txt index df43a7ea8a5..ee3626443ce 100644 --- a/source/blender/python/intern/CMakeLists.txt +++ b/source/blender/python/intern/CMakeLists.txt @@ -126,7 +126,6 @@ set(LIB bf_editor_interface bf_editor_space_api PRIVATE bf::intern::guardedalloc - PRIVATE bf::animrig bf_python_gpu ${PYTHON_LINKFLAGS} @@ -153,6 +152,7 @@ if(WITH_PYTHON AND WITH_PYTHON_INSTALL AND WITH_INSTALL_PORTABLE AND (NOT WIN32) find_python_module_file("certifi/cacert.pem" PYTHON_SSL_CERT_FILE _python_ssl_cert_file_relative) mark_as_advanced(PYTHON_SSL_CERT_FILE) if (PYTHON_SSL_CERT_FILE) + # This always moves up one level (even if there is a trailing slash). add_definitions(-DPYTHON_SSL_CERT_FILE="${_python_ssl_cert_file_relative}") else() message(WARNING diff --git a/source/blender/python/intern/bpy.cc b/source/blender/python/intern/bpy.cc index 30568703cc3..052f333caee 100644 --- a/source/blender/python/intern/bpy.cc +++ b/source/blender/python/intern/bpy.cc @@ -16,7 +16,7 @@ #include #include "BLI_string.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_utildefines.h" #include "BKE_appdir.h" diff --git a/source/blender/python/intern/bpy_app_translations.cc b/source/blender/python/intern/bpy_app_translations.cc index 6943f495594..48fc9617518 100644 --- a/source/blender/python/intern/bpy_app_translations.cc +++ b/source/blender/python/intern/bpy_app_translations.cc @@ -672,7 +672,7 @@ PyDoc_STRVAR( app_translations_locale_explode_doc, ".. method:: locale_explode(locale)\n" "\n" - " Return all components and their combinations of the given ISO locale string.\n" + " Return all components and their combinations of the given ISO locale string.\n" "\n" " >>> bpy.app.translations.locale_explode(\"sr_RS@latin\")\n" " (\"sr\", \"RS\", \"latin\", \"sr_RS\", \"sr@latin\")\n" diff --git a/source/blender/python/intern/bpy_capi_utils.cc b/source/blender/python/intern/bpy_capi_utils.cc index c4dff5e0b75..d5f2208396a 100644 --- a/source/blender/python/intern/bpy_capi_utils.cc +++ b/source/blender/python/intern/bpy_capi_utils.cc @@ -33,7 +33,7 @@ short BPy_reports_to_error(ReportList *reports, PyObject *exception, const bool report_str = BKE_reports_string(reports, RPT_ERROR); if (clear == true) { - BKE_reports_free(reports); + BKE_reports_clear(reports); } if (report_str) { diff --git a/source/blender/python/intern/bpy_capi_utils.h b/source/blender/python/intern/bpy_capi_utils.h index 93d59c0a9ca..3624f97dd01 100644 --- a/source/blender/python/intern/bpy_capi_utils.h +++ b/source/blender/python/intern/bpy_capi_utils.h @@ -18,10 +18,7 @@ extern "C" { struct ReportList; -/** Error reporting: convert BKE_report (#ReportList) reports into python errors. - * - * \param clear: When `true`, #BKE_reports_free is called on the given `reports`, which should - * then be considered as 'freed' data and not used anymore. */ +/* error reporting */ short BPy_reports_to_error(struct ReportList *reports, PyObject *exception, bool clear); /** * A version of #BKE_report_write_file_fp that uses Python's stdout. diff --git a/source/blender/python/intern/bpy_interface_run.cc b/source/blender/python/intern/bpy_interface_run.cc index 4adb0413b2d..25ed35bb321 100644 --- a/source/blender/python/intern/bpy_interface_run.cc +++ b/source/blender/python/intern/bpy_interface_run.cc @@ -281,9 +281,11 @@ static bool bpy_run_string_impl(bContext *C, ReportList *wm_reports = CTX_wm_reports(C); if (wm_reports) { - BKE_reports_move_to_reports(wm_reports, &reports); + BLI_movelisttolist(&wm_reports->list, &reports.list); + } + else { + BKE_reports_clear(&reports); } - BKE_reports_free(&reports); } else { Py_DECREF(retval); diff --git a/source/blender/python/intern/bpy_library_load.cc b/source/blender/python/intern/bpy_library_load.cc index 2032d9ec620..dc802e4bc8d 100644 --- a/source/blender/python/intern/bpy_library_load.cc +++ b/source/blender/python/intern/bpy_library_load.cc @@ -575,8 +575,6 @@ static PyObject *bpy_lib_exit(BPy_Library *self, PyObject * /*args*/) BKE_blendfile_link_append_context_free(lapp_context); BKE_main_id_tag_all(bmain, LIB_TAG_PRE_EXISTING, false); - BKE_reports_free(&self->reports); - Py_RETURN_NONE; } diff --git a/source/blender/python/intern/bpy_library_write.cc b/source/blender/python/intern/bpy_library_write.cc index 5ee1803fb88..07bdf50b731 100644 --- a/source/blender/python/intern/bpy_library_write.cc +++ b/source/blender/python/intern/bpy_library_write.cc @@ -187,18 +187,17 @@ static PyObject *bpy_lib_write(BPy_PropertyRNA *self, PyObject *args, PyObject * if (retval) { BKE_reports_print(&reports, RPT_ERROR_ALL); + BKE_reports_clear(&reports); ret = Py_None; Py_INCREF(ret); } else { - if (BPy_reports_to_error(&reports, PyExc_IOError, false) == 0) { + if (BPy_reports_to_error(&reports, PyExc_IOError, true) == 0) { PyErr_SetString(PyExc_IOError, "Unknown error writing library data"); } ret = nullptr; } - BKE_reports_free(&reports); - finally: /* clear all flags for ID's added to the store (may run on error too) */ diff --git a/source/blender/python/intern/bpy_operator.cc b/source/blender/python/intern/bpy_operator.cc index 22d4a758168..ec86353984e 100644 --- a/source/blender/python/intern/bpy_operator.cc +++ b/source/blender/python/intern/bpy_operator.cc @@ -263,7 +263,6 @@ static PyObject *pyop_call(PyObject * /*self*/, PyObject *args) BKE_reports_clear(reports); if ((reports->flag & RPT_FREE) == 0) { - BKE_reports_free(reports); MEM_freeN(reports); } else { diff --git a/source/blender/python/intern/bpy_rna.cc b/source/blender/python/intern/bpy_rna.cc index 3b622b8fd2b..c1292f26f0b 100644 --- a/source/blender/python/intern/bpy_rna.cc +++ b/source/blender/python/intern/bpy_rna.cc @@ -6613,7 +6613,7 @@ static PyObject *pyrna_func_call(BPy_FunctionRNA *self, PyObject *args, PyObject #ifdef DEBUG_STRING_FREE # if 0 if (PyList_GET_SIZE(string_free_ls)) { - printf("%.200s.%.200s(): has %d strings\n", + printf("%.200s.%.200s(): has %d strings\n", RNA_struct_identifier(self_ptr->type), RNA_function_identifier(self_func), int(PyList_GET_SIZE(string_free_ls))); @@ -8710,7 +8710,7 @@ static int bpy_class_call(bContext *C, PointerRNA *ptr, FunctionRNA *func, Param else if (ret_len == 1) { err = pyrna_py_to_prop(&funcptr, pret_single, retdata_single, ret, ""); - /* When calling operator functions only gives `Function.result` with no line number + /* When calling operator functions only gives `Function.result` with no line number * since the function has finished calling on error, re-raise the exception with more * information since it would be slow to create prefix on every call * (when there are no errors). */ @@ -8792,8 +8792,7 @@ static int bpy_class_call(bContext *C, PointerRNA *ptr, FunctionRNA *func, Param BKE_reports_init(&reports_temp, reports->flag | RPT_PRINT_HANDLED_BY_OWNER); reports_temp.storelevel = reports->storelevel; BPy_errors_to_report(&reports_temp); - BKE_reports_move_to_reports(reports, &reports_temp); - BKE_reports_free(&reports_temp); + BLI_movelisttolist(&reports->list, &reports_temp.list); } /* Also print in the console for Python. */ @@ -9007,12 +9006,11 @@ static PyObject *pyrna_register_class(PyObject * /*self*/, PyObject *py_class) if (!has_error) { BPy_reports_write_stdout(&reports, error_prefix); } + BKE_reports_clear(&reports); if (has_error) { - BKE_reports_free(&reports); return nullptr; } } - BKE_reports_free(&reports); /* Python errors validating are not converted into reports so the check above will fail. * the cause for returning nullptr will be printed as an error */ diff --git a/source/blender/python/intern/bpy_rna_anim.cc b/source/blender/python/intern/bpy_rna_anim.cc index 4a271df024b..d4339ae42c9 100644 --- a/source/blender/python/intern/bpy_rna_anim.cc +++ b/source/blender/python/intern/bpy_rna_anim.cc @@ -14,7 +14,7 @@ #include "MEM_guardedalloc.h" #include "BLI_string.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_utildefines.h" #include "DNA_anim_types.h" @@ -23,8 +23,6 @@ #include "ED_keyframes_edit.hh" #include "ED_keyframing.hh" -#include "ANIM_keyframing.hh" - #include "BKE_anim_data.h" #include "BKE_animsys.h" #include "BKE_context.h" @@ -368,14 +366,14 @@ PyObject *pyrna_struct_keyframe_insert(BPy_StructRNA *self, PyObject *args, PyOb if (prop) { NlaStrip *strip = static_cast(ptr.data); FCurve *fcu = BKE_fcurve_find(&strip->fcurves, RNA_property_identifier(prop), index); - result = blender::animrig::insert_keyframe_direct(&reports, - ptr, - prop, - fcu, - &anim_eval_context, - eBezTriple_KeyframeType(keytype), - nullptr, - eInsertKeyFlags(options)); + result = insert_keyframe_direct(&reports, + ptr, + prop, + fcu, + &anim_eval_context, + eBezTriple_KeyframeType(keytype), + nullptr, + eInsertKeyFlags(options)); } else { BKE_reportf(&reports, RPT_ERROR, "Could not resolve path (%s)", path_full); @@ -385,16 +383,17 @@ PyObject *pyrna_struct_keyframe_insert(BPy_StructRNA *self, PyObject *args, PyOb ID *id = self->ptr.owner_id; BLI_assert(BKE_id_is_in_global_main(id)); - result = (blender::animrig::insert_keyframe(G_MAIN, - &reports, - id, - nullptr, - group_name, - path_full, - index, - &anim_eval_context, - eBezTriple_KeyframeType(keytype), - eInsertKeyFlags(options)) != 0); + result = (insert_keyframe(G_MAIN, + &reports, + id, + nullptr, + group_name, + path_full, + index, + &anim_eval_context, + eBezTriple_KeyframeType(keytype), + nullptr, + eInsertKeyFlags(options)) != 0); } MEM_freeN((void *)path_full); @@ -514,7 +513,7 @@ PyObject *pyrna_struct_keyframe_delete(BPy_StructRNA *self, PyObject *args, PyOb } } else { - result = (blender::animrig::delete_keyframe( + result = (delete_keyframe( G.main, &reports, self->ptr.owner_id, nullptr, path_full, index, cfra) != 0); } diff --git a/source/blender/render/RE_engine.h b/source/blender/render/RE_engine.h index 5a8303f52ad..4d837c0451d 100644 --- a/source/blender/render/RE_engine.h +++ b/source/blender/render/RE_engine.h @@ -275,6 +275,7 @@ void RE_engine_gpu_context_unlock(struct RenderEngine *engine); /* Engine Types */ void RE_engines_init(void); +void RE_engines_init_experimental(void); void RE_engines_exit(void); void RE_engines_register(RenderEngineType *render_type); diff --git a/source/blender/render/intern/bake.cc b/source/blender/render/intern/bake.cc index a5921a0ae2b..9c6d8d07824 100644 --- a/source/blender/render/intern/bake.cc +++ b/source/blender/render/intern/bake.cc @@ -491,14 +491,17 @@ static TriTessFace *mesh_calc_tri_tessface(Mesh *me, bool tangent, Mesh *me_eval } const TSpace *tspace = nullptr; - blender::Span corner_normals; + const float(*loop_normals)[3] = nullptr; if (tangent) { + BKE_mesh_ensure_normals_for_display(me_eval); + BKE_mesh_calc_normals_split(me_eval); BKE_mesh_calc_loop_tangents(me_eval, true, nullptr, 0); tspace = static_cast(CustomData_get_layer(&me_eval->loop_data, CD_TANGENT)); BLI_assert(tspace); - corner_normals = me_eval->corner_normals(); + loop_normals = static_cast( + CustomData_get_layer(&me_eval->loop_data, CD_NORMAL)); } const blender::Span vert_normals = me->vert_normals(); @@ -521,10 +524,10 @@ static TriTessFace *mesh_calc_tri_tessface(Mesh *me, bool tangent, Mesh *me_eval triangles[i].tspace[2] = &tspace[lt->tri[2]]; } - if (!corner_normals.is_empty()) { - triangles[i].loop_normal[0] = corner_normals[lt->tri[0]]; - triangles[i].loop_normal[1] = corner_normals[lt->tri[1]]; - triangles[i].loop_normal[2] = corner_normals[lt->tri[2]]; + if (loop_normals) { + triangles[i].loop_normal[0] = loop_normals[lt->tri[0]]; + triangles[i].loop_normal[1] = loop_normals[lt->tri[1]]; + triangles[i].loop_normal[2] = loop_normals[lt->tri[2]]; } if (calculate_normal) { diff --git a/source/blender/render/intern/compositor.cc b/source/blender/render/intern/compositor.cc index 4239ad6a187..b9ea8fc4fb9 100644 --- a/source/blender/render/intern/compositor.cc +++ b/source/blender/render/intern/compositor.cc @@ -32,77 +32,32 @@ namespace blender::render { -/** - * Render Texture Pool - * - * TODO: should share pool with draw manager. It needs some globals initialization figured out - * there first. - */ -class TexturePool : public realtime_compositor::TexturePool { - private: - /** Textures that are not yet used and are available to be acquired. After evaluation, any - * texture in this map should be freed because it was not acquired in the evaluation and is thus - * unused. Textures removed from this map should be moved to the textures_in_use_ map when - * acquired. */ - Map> available_textures_; - /** Textures that were acquired in this compositor evaluation. After evaluation, those textures - * are moved to the available_textures_ map to be acquired in the next evaluation. */ - Map> textures_in_use_; +/* Render Texture Pool */ +class TexturePool : public realtime_compositor::TexturePool { public: + Vector textures_; + virtual ~TexturePool() { - for (Vector &available_textures : available_textures_.values()) { - for (GPUTexture *texture : available_textures) { - GPU_texture_free(texture); - } - } - - for (Vector &textures_in_use : textures_in_use_.values()) { - for (GPUTexture *texture : textures_in_use) { - GPU_texture_free(texture); - } + for (GPUTexture *texture : textures_) { + GPU_texture_free(texture); } } GPUTexture *allocate_texture(int2 size, eGPUTextureFormat format) override { - const realtime_compositor::TexturePoolKey key(size, format); - Vector &available_textures = available_textures_.lookup_or_add_default(key); - GPUTexture *texture = nullptr; - if (available_textures.is_empty()) { - texture = GPU_texture_create_2d("compositor_texture_pool", - size.x, - size.y, - 1, - format, - GPU_TEXTURE_USAGE_GENERAL, - nullptr); - } - else { - texture = available_textures.pop_last(); - } - - textures_in_use_.lookup_or_add_default(key).append(texture); + /* TODO: should share pool with draw manager. It needs some globals + * initialization figured out there first. */ +#if 0 + DrawEngineType *owner = (DrawEngineType *)this; + return DRW_texture_pool_query_2d(size.x, size.y, format, owner); +#else + GPUTexture *texture = GPU_texture_create_2d( + "compositor_texture_pool", size.x, size.y, 1, format, GPU_TEXTURE_USAGE_GENERAL, nullptr); + textures_.append(texture); return texture; - } - - /** Should be called after compositor evaluation to free unused textures and reset the texture - * pool. */ - void free_unused_and_reset() - { - /* Free all textures in the available textures vectors. The fact that they still exist in those - * vectors after evaluation means they were not acquired during the evaluation, and are thus - * consequently no longer used. */ - for (Vector &available_textures : available_textures_.values()) { - for (GPUTexture *texture : available_textures) { - GPU_texture_free(texture); - } - } - - /* Move all textures in-use to be available textures for the next evaluation. */ - available_textures_ = textures_in_use_; - textures_in_use_.clear(); +#endif } }; @@ -147,12 +102,14 @@ class Context : public realtime_compositor::Context { /* Viewer output texture. */ GPUTexture *viewer_output_texture_ = nullptr; - /* Cached textures that the compositor took ownership of. */ - Vector textures_; + /* Texture pool. */ + TexturePool &render_texture_pool_; public: Context(const ContextInputData &input_data, TexturePool &texture_pool) - : realtime_compositor::Context(texture_pool), input_data_(input_data) + : realtime_compositor::Context(texture_pool), + input_data_(input_data), + render_texture_pool_(texture_pool) { } @@ -160,9 +117,6 @@ class Context : public realtime_compositor::Context { { GPU_TEXTURE_FREE_SAFE(output_texture_); GPU_TEXTURE_FREE_SAFE(viewer_output_texture_); - for (GPUTexture *texture : textures_) { - GPU_texture_free(texture); - } } void update_input_data(const ContextInputData &input_data) @@ -212,7 +166,8 @@ class Context : public realtime_compositor::Context { GPUTexture *get_output_texture() override { - /* TODO: just a temporary hack, needs to get stored in RenderResult, + /* TODO: support outputting for previews. + * TODO: just a temporary hack, needs to get stored in RenderResult, * once that supports GPU buffers. */ if (output_texture_ == nullptr) { const int2 size = get_render_size(); @@ -228,8 +183,13 @@ class Context : public realtime_compositor::Context { return output_texture_; } - GPUTexture *get_viewer_output_texture(int2 size) override + GPUTexture *get_viewer_output_texture() override { + /* TODO: support outputting previews. + * TODO: just a temporary hack, needs to get stored in RenderResult, + * once that supports GPU buffers. */ + const int2 size = get_render_size(); + /* Re-create texture if the viewer size changes. */ if (viewer_output_texture_) { const int current_width = GPU_texture_width(viewer_output_texture_); @@ -241,8 +201,6 @@ class Context : public realtime_compositor::Context { } } - /* TODO: just a temporary hack, needs to get stored in RenderResult, - * once that supports GPU buffers. */ if (viewer_output_texture_ == nullptr) { viewer_output_texture_ = GPU_texture_create_2d("compositor_viewer_output_texture", size.x, @@ -273,7 +231,8 @@ class Context : public realtime_compositor::Context { if (view_layer) { RenderLayer *rl = RE_GetRenderLayer(rr, view_layer->name); if (rl) { - RenderPass *rpass = RE_pass_find_by_name(rl, pass_name, get_view_name().data()); + RenderPass *rpass = (RenderPass *)BLI_findstring( + &rl->passes, pass_name, offsetof(RenderPass, name)); if (rpass && rpass->ibuf && rpass->ibuf->float_buffer.data) { input_texture = RE_pass_ensure_gpu_texture_cache(re, rpass); @@ -281,7 +240,7 @@ class Context : public realtime_compositor::Context { if (input_texture) { /* Don't assume render keeps texture around, add our own reference. */ GPU_texture_ref(input_texture); - textures_.append(input_texture); + render_texture_pool_.textures_.append(input_texture); } } } @@ -376,13 +335,12 @@ class Context : public realtime_compositor::Context { void *lock; ImBuf *image_buffer = BKE_image_acquire_ibuf(image, &image_user, &lock); - const int2 size = int2(GPU_texture_width(viewer_output_texture_), - GPU_texture_height(viewer_output_texture_)); - if (image_buffer->x != size.x || image_buffer->y != size.y) { + const int2 render_size = get_render_size(); + if (image_buffer->x != render_size.x || image_buffer->y != render_size.y) { imb_freerectImBuf(image_buffer); imb_freerectfloatImBuf(image_buffer); - image_buffer->x = size.x; - image_buffer->y = size.y; + image_buffer->x = render_size.x; + image_buffer->y = render_size.y; imb_addrectfloatImBuf(image_buffer, 4); image_buffer->userflags |= IB_DISPLAY_BUFFER_INVALID; } @@ -393,8 +351,9 @@ class Context : public realtime_compositor::Context { GPU_memory_barrier(GPU_BARRIER_TEXTURE_UPDATE); float *output_buffer = (float *)GPU_texture_read(viewer_output_texture_, GPU_DATA_FLOAT, 0); - std::memcpy( - image_buffer->float_buffer.data, output_buffer, size.x * size.y * 4 * sizeof(float)); + std::memcpy(image_buffer->float_buffer.data, + output_buffer, + render_size.x * render_size.y * 4 * sizeof(float)); MEM_freeN(output_buffer); @@ -466,7 +425,6 @@ class RealtimeCompositor { context_->output_to_render_result(); context_->viewer_output_to_viewer_image(); - texture_pool_->free_unused_and_reset(); DRW_render_context_disable(&render_); } }; diff --git a/source/blender/render/intern/engine.cc b/source/blender/render/intern/engine.cc index f999c9754b4..21e45ec6812 100644 --- a/source/blender/render/intern/engine.cc +++ b/source/blender/render/intern/engine.cc @@ -66,6 +66,11 @@ void RE_engines_init() DRW_engines_register(); } +void RE_engines_init_experimental() +{ + DRW_engines_register_experimental(); +} + void RE_engines_exit() { RenderEngineType *type, *next; diff --git a/source/blender/render/intern/multires_bake.cc b/source/blender/render/intern/multires_bake.cc index 6026379c28a..c90f34d7dce 100644 --- a/source/blender/render/intern/multires_bake.cc +++ b/source/blender/render/intern/multires_bake.cc @@ -519,7 +519,6 @@ static void do_multires_bake(MultiresBakeRender *bkr, if (require_tangent) { if (CustomData_get_layer_index(&dm->loopData, CD_TANGENT) == -1) { - const blender::Span corner_normals = temp_mesh->corner_normals(); BKE_mesh_calc_loop_tangent_ex( reinterpret_cast(positions.data()), faces, @@ -535,7 +534,7 @@ static void do_multires_bake(MultiresBakeRender *bkr, 0, reinterpret_cast(vert_normals.data()), reinterpret_cast(face_normals.data()), - reinterpret_cast(corner_normals.data()), + (const float(*)[3])dm->getLoopDataArray(dm, CD_NORMAL), (const float(*)[3])dm->getVertDataArray(dm, CD_ORCO), /* May be nullptr. */ /* result */ &dm->loopData, diff --git a/source/blender/render/intern/pipeline.cc b/source/blender/render/intern/pipeline.cc index 68388c79c38..2981ab6fc44 100644 --- a/source/blender/render/intern/pipeline.cc +++ b/source/blender/render/intern/pipeline.cc @@ -64,7 +64,7 @@ #include "BKE_sound.h" #include "BKE_writeavi.h" /* <------ should be replaced once with generic movie module */ -#include "NOD_composite.hh" +#include "NOD_composite.h" #include "DEG_depsgraph.hh" #include "DEG_depsgraph_build.hh" @@ -442,7 +442,7 @@ void RE_AcquireResultImage(Render *re, RenderResult *rr, const int view_id) /* The render result uses shallow initialization, and the caller is not expected to * explicitly free it. So simply assign the buffers as a shallow copy here as well. * - * The thread safety is ensured via the `re->resultmutex`. */ + * The thread safety is ensured via the re->resultmutex. */ rr->ibuf = rv->ibuf; /* active layer */ diff --git a/source/blender/render/intern/render_result.cc b/source/blender/render/intern/render_result.cc index 79a3b25e224..251acaac89e 100644 --- a/source/blender/render/intern/render_result.cc +++ b/source/blender/render/intern/render_result.cc @@ -20,7 +20,7 @@ #include "BLI_path_util.h" #include "BLI_rect.h" #include "BLI_string.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_threads.h" #include "BLI_utildefines.h" diff --git a/source/blender/render/intern/texture_image.cc b/source/blender/render/intern/texture_image.cc index e47774abfc0..6144dc2a8bf 100644 --- a/source/blender/render/intern/texture_image.cc +++ b/source/blender/render/intern/texture_image.cc @@ -713,13 +713,13 @@ static int ibuf_get_color_clip(float col[4], ImBuf *ibuf, int x, int y, int extf } /* TXF alpha: clip = 1; } */ if (x >= ibuf->x) { x = ibuf->x - 1; - } /* TXF alpha: clip = 1; } */ + } /* TXF alpha: clip = 1; } */ if (y < 0) { y = 0; - } /* TXF alpha: clip = 1; } */ + } /* TXF alpha: clip = 1; } */ if (y >= ibuf->y) { y = ibuf->y - 1; - } /* TXF alpha: clip = 1; } */ + } /* TXF alpha: clip = 1; } */ } } diff --git a/source/blender/render/intern/texture_margin.cc b/source/blender/render/intern/texture_margin.cc index 7e51f0656ca..32e52efbb1f 100644 --- a/source/blender/render/intern/texture_margin.cc +++ b/source/blender/render/intern/texture_margin.cc @@ -145,7 +145,7 @@ class TextureMarginMap { /* The map contains 2 kinds of pixels: DijkstraPixels and face indices. The top bit determines * what kind it is. With the top bit set, it is a 'dijkstra' pixel. The bottom 4 bits encode the * direction of the shortest path and the remaining 27 bits are used to store the distance. If - * the top bit is not set, the rest of the bits is used to store the face index. + * the top bit is not set, the rest of the bits is used to store the face index. */ #define PackDijkstraPixel(dist, dir) (0x80000000 + ((dist) << 4) + (dir)) #define DijkstraPixelGetDistance(dp) (((dp) ^ 0x80000000) >> 4) diff --git a/source/blender/render/intern/texture_pointdensity.cc b/source/blender/render/intern/texture_pointdensity.cc index c7ad337b81a..47d7a7b2d1c 100644 --- a/source/blender/render/intern/texture_pointdensity.cc +++ b/source/blender/render/intern/texture_pointdensity.cc @@ -853,7 +853,10 @@ void RE_point_density_minmax(Depsgraph *depsgraph, } else { const float radius[3] = {pd->radius, pd->radius, pd->radius}; - if (const std::optional bb = BKE_object_boundbox_get(object)) { + const BoundBox *bb = BKE_object_boundbox_get(object); + + if (bb != nullptr) { + BLI_assert((bb->flag & BOUNDBOX_DIRTY) == 0); copy_v3_v3(r_min, bb->vec[0]); copy_v3_v3(r_max, bb->vec[6]); /* Adjust texture space to include density points on the boundaries. */ diff --git a/source/blender/render/intern/texture_procedural.cc b/source/blender/render/intern/texture_procedural.cc index 6ca7d1c2768..aa2edf6ceb2 100644 --- a/source/blender/render/intern/texture_procedural.cc +++ b/source/blender/render/intern/texture_procedural.cc @@ -199,9 +199,9 @@ static float tex_tri(float a) static float wood_int(const Tex *tex, float x, float y, float z) { float wi = 0; - /* wave form: TEX_SIN=0, TEX_SAW=1, TEX_TRI=2. */ + /* wave form: TEX_SIN=0, TEX_SAW=1, TEX_TRI=2 */ short wf = tex->noisebasis2; - /* wood type: TEX_BAND=0, TEX_RING=1, TEX_BANDNOISE=2, TEX_RINGNOISE=3. */ + /* wood type: TEX_BAND=0, TEX_RING=1, TEX_BANDNOISE=2, TEX_RINGNOISE=3 */ short wt = tex->stype; float (*waveform[3])(float); /* create array of pointers to waveform functions */ diff --git a/source/blender/sequencer/SEQ_proxy.h b/source/blender/sequencer/SEQ_proxy.h index 50f4e977e54..65e7afa85e4 100644 --- a/source/blender/sequencer/SEQ_proxy.h +++ b/source/blender/sequencer/SEQ_proxy.h @@ -21,7 +21,6 @@ struct Scene; struct SeqIndexBuildContext; struct SeqRenderData; struct Sequence; -struct wmJobWorkerStatus; bool SEQ_proxy_rebuild_context(struct Main *bmain, struct Depsgraph *depsgraph, @@ -30,7 +29,10 @@ bool SEQ_proxy_rebuild_context(struct Main *bmain, struct GSet *file_list, struct ListBase *queue, bool build_only_on_bad_performance); -void SEQ_proxy_rebuild(struct SeqIndexBuildContext *context, wmJobWorkerStatus *worker_status); +void SEQ_proxy_rebuild(struct SeqIndexBuildContext *context, + bool *stop, + bool *do_update, + float *progress); void SEQ_proxy_rebuild_finish(struct SeqIndexBuildContext *context, bool stop); void SEQ_proxy_set(struct Sequence *seq, bool value); bool SEQ_can_use_proxy(const struct SeqRenderData *context, struct Sequence *seq, int psize); diff --git a/source/blender/sequencer/intern/modifier.cc b/source/blender/sequencer/intern/modifier.cc index e28348dd402..1918da830ee 100644 --- a/source/blender/sequencer/intern/modifier.cc +++ b/source/blender/sequencer/intern/modifier.cc @@ -13,7 +13,7 @@ #include "BLI_listbase.h" #include "BLI_string.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_utildefines.h" #include "BLT_translation.h" diff --git a/source/blender/sequencer/intern/prefetch.cc b/source/blender/sequencer/intern/prefetch.cc index 1c55c1fc73c..7305123c640 100644 --- a/source/blender/sequencer/intern/prefetch.cc +++ b/source/blender/sequencer/intern/prefetch.cc @@ -580,9 +580,10 @@ void seq_prefetch_start(const SeqRenderData *context, float timeline_frame) bool running = seq_prefetch_job_is_running(scene); seq_prefetch_resume(scene); /* conditions to start: - * prefetch enabled, prefetch not running, not scrubbing, not playing, + * prefetch enabled, prefetch not running, not scrubbing, not playing, * cache storage enabled, has strips to render, not rendering, not doing modal transform - - * important, see D7820. */ + * important, see D7820. + */ if ((ed->cache_flag & SEQ_CACHE_PREFETCH_ENABLE) && !running && !scrubbing && !playing && ed->cache_flag & SEQ_CACHE_ALL_TYPES && has_strips && !G.is_rendering && !G.moving) { diff --git a/source/blender/sequencer/intern/proxy.cc b/source/blender/sequencer/intern/proxy.cc index 9562eb4a8d7..f99f34aa357 100644 --- a/source/blender/sequencer/intern/proxy.cc +++ b/source/blender/sequencer/intern/proxy.cc @@ -34,8 +34,6 @@ #include "DEG_depsgraph.hh" -#include "WM_types.hh" - #include "IMB_colormanagement.h" #include "IMB_imbuf.h" #include "IMB_imbuf_types.h" @@ -510,7 +508,7 @@ bool SEQ_proxy_rebuild_context(Main *bmain, return true; } -void SEQ_proxy_rebuild(SeqIndexBuildContext *context, wmJobWorkerStatus *worker_status) +void SEQ_proxy_rebuild(SeqIndexBuildContext *context, bool *stop, bool *do_update, float *progress) { const bool overwrite = context->overwrite; SeqRenderData render_context; @@ -521,10 +519,7 @@ void SEQ_proxy_rebuild(SeqIndexBuildContext *context, wmJobWorkerStatus *worker_ if (seq->type == SEQ_TYPE_MOVIE) { if (context->index_context) { - IMB_anim_index_rebuild(context->index_context, - &worker_status->stop, - &worker_status->do_update, - &worker_status->progress); + IMB_anim_index_rebuild(context->index_context, stop, do_update, progress); } return; @@ -570,12 +565,12 @@ void SEQ_proxy_rebuild(SeqIndexBuildContext *context, wmJobWorkerStatus *worker_ seq_proxy_build_frame(&render_context, &state, seq, timeline_frame, 100, overwrite); } - worker_status->progress = float(timeline_frame - SEQ_time_left_handle_frame_get(scene, seq)) / - (SEQ_time_right_handle_frame_get(scene, seq) - - SEQ_time_left_handle_frame_get(scene, seq)); - worker_status->do_update = true; + *progress = float(timeline_frame - SEQ_time_left_handle_frame_get(scene, seq)) / + (SEQ_time_right_handle_frame_get(scene, seq) - + SEQ_time_left_handle_frame_get(scene, seq)); + *do_update = true; - if (worker_status->stop || G.is_break) { + if (*stop || G.is_break) { break; } } diff --git a/source/blender/sequencer/intern/proxy_job.cc b/source/blender/sequencer/intern/proxy_job.cc index 49af62ad688..cc16cff18d1 100644 --- a/source/blender/sequencer/intern/proxy_job.cc +++ b/source/blender/sequencer/intern/proxy_job.cc @@ -42,16 +42,16 @@ static void proxy_freejob(void *pjv) } /* Only this runs inside thread. */ -static void proxy_startjob(void *pjv, wmJobWorkerStatus *worker_status) +static void proxy_startjob(void *pjv, bool *stop, bool *do_update, float *progress) { ProxyJob *pj = static_cast(pjv); LISTBASE_FOREACH (LinkData *, link, &pj->queue) { SeqIndexBuildContext *context = static_cast(link->data); - SEQ_proxy_rebuild(context, worker_status); + SEQ_proxy_rebuild(context, stop, do_update, progress); - if (worker_status->stop) { + if (*stop) { pj->stop = true; fprintf(stderr, "Canceling proxy rebuild on users request...\n"); break; diff --git a/source/blender/simulation/intern/SIM_mass_spring.cc b/source/blender/simulation/intern/SIM_mass_spring.cc index fe589443ab0..78a5d9a36c2 100644 --- a/source/blender/simulation/intern/SIM_mass_spring.cc +++ b/source/blender/simulation/intern/SIM_mass_spring.cc @@ -244,7 +244,7 @@ void SIM_cloth_solver_set_volume(ClothModifierData *clmd) /* Init constraint matrix * This is part of the modified CG method suggested by Baraff/Witkin in - * "Large Steps in Cloth Simulation" (SIGGRAPH 1998) + * "Large Steps in Cloth Simulation" (Siggraph 1998) */ static void cloth_setup_constraints(ClothModifierData *clmd) { @@ -566,7 +566,7 @@ static void hair_get_boundbox(ClothModifierData *clmd, float gmin[3], float gmax static void cloth_calc_force( Scene *scene, ClothModifierData *clmd, float /*frame*/, ListBase *effectors, float time) { - /* Collect forces and derivatives: F, dFdX, dFdV. */ + /* Collect forces and derivatives: F, dFdX, dFdV */ Cloth *cloth = clmd->clothObject; ClothSimSettings *parms = clmd->sim_parms; Implicit_Data *data = cloth->implicit; diff --git a/source/blender/windowmanager/CMakeLists.txt b/source/blender/windowmanager/CMakeLists.txt index 6435005fc22..e413ba89f58 100644 --- a/source/blender/windowmanager/CMakeLists.txt +++ b/source/blender/windowmanager/CMakeLists.txt @@ -103,7 +103,6 @@ set(LIB PRIVATE bf::dna bf_editor_screen PRIVATE bf::intern::guardedalloc - PRIVATE bf::animrig bf_sequencer bf_intern_ghost ) @@ -175,8 +174,10 @@ if(WITH_INPUT_NDOF) add_definitions(-DWITH_INPUT_NDOF) endif() -if(WITH_INPUT_IME) - add_definitions(-DWITH_INPUT_IME) +if(WIN32 OR APPLE) + if(WITH_INPUT_IME) + add_definitions(-DWITH_INPUT_IME) + endif() endif() if(WITH_COMPOSITOR_CPU) diff --git a/source/blender/windowmanager/WM_api.hh b/source/blender/windowmanager/WM_api.hh index 090cbb63b3c..8de51e45a34 100644 --- a/source/blender/windowmanager/WM_api.hh +++ b/source/blender/windowmanager/WM_api.hh @@ -52,7 +52,6 @@ struct wmGizmo; struct wmGizmoMap; struct wmGizmoMapType; struct wmJob; -struct wmJobWorkerStatus; struct wmOperator; struct wmOperatorType; struct wmPaintCursor; @@ -172,8 +171,6 @@ enum eWM_CapabilitiesFlag { WM_CAPABILITY_CLIPBOARD_IMAGES = (1 << 4), /** Ability to sample a color outside of Blender windows. */ WM_CAPABILITY_DESKTOP_SAMPLE = (1 << 5), - /** Support for IME input methods. */ - WM_CAPABILITY_INPUT_IME = (1 << 6), /** The initial value, indicates the value needs to be set by inspecting GHOST. */ WM_CAPABILITY_INITIALIZED = (1 << 31), }; @@ -593,19 +590,6 @@ void WM_report_banner_show(wmWindowManager *wm, wmWindow *win) ATTR_NONNULL(1); * Hide all currently displayed banners and abort their timer. */ void WM_report_banners_cancel(Main *bmain); -/** Move a whole list of reports to the WM ReportList, and show the banner. - * - * \note In case the given \a reports is a `nullptr`, or has its #RPT_OP_HOLD flag set, this - * function does nothing. - * - * \note The list of reports from given \a reports is moved into the list of WM's reports, so the - * given \a reports will be empty after calling this function. The \a reports #ReportList data - * itself is not freed or cleared though, and remains fully usable after this call. - * - * \params reports The #ReportList from which to move reports to the WM one, may be `nullptr`. - * \params wm the WindowManager to add given \a reports to. If `nullptr`, the first WM of current - * #G_MAIN will be used. */ -void WM_reports_from_reports_move(wmWindowManager *wm, ReportList *reports); void WM_report(eReportType type, const char *message); void WM_reportf(eReportType type, const char *format, ...) ATTR_PRINTF_FORMAT(2, 3); @@ -1530,7 +1514,10 @@ void WM_jobs_customdata_set(wmJob *, void *customdata, void (*free)(void *)); void WM_jobs_timer(wmJob *, double timestep, unsigned int note, unsigned int endnote); void WM_jobs_delay_start(wmJob *, double delay_time); -using wm_jobs_start_callback = void (*)(void *custom_data, wmJobWorkerStatus *worker_status); +using wm_jobs_start_callback = void (*)(void *custom_data, + bool *stop, + bool *do_update, + float *progress); void WM_jobs_callbacks(wmJob *, wm_jobs_start_callback startjob, void (*initjob)(void *), @@ -1553,11 +1540,11 @@ void WM_jobs_start(wmWindowManager *wm, wmJob *); /** * Signal job(s) from this owner or callback to stop, timer is required to get handled. */ -void WM_jobs_stop(wmWindowManager *wm, const void *owner, wm_jobs_start_callback startjob); +void WM_jobs_stop(wmWindowManager *wm, const void *owner, void *startjob); /** * Actually terminate thread and job timer. */ -void WM_jobs_kill(wmWindowManager *wm, void *owner, wm_jobs_start_callback startjob); +void WM_jobs_kill(wmWindowManager *wm, void *owner, void (*)(void *, bool *, bool *, float *)); /** * Wait until every job ended. */ @@ -1637,7 +1624,7 @@ void WM_draw_region_viewport_unbind(ARegion *region); /* Region drawing */ -void WM_draw_region_free(ARegion *region); +void WM_draw_region_free(ARegion *region, bool hide); GPUViewport *WM_draw_region_get_viewport(ARegion *region); GPUViewport *WM_draw_region_get_bound_viewport(ARegion *region); diff --git a/source/blender/windowmanager/WM_types.hh b/source/blender/windowmanager/WM_types.hh index cd560372794..7e2be7731e2 100644 --- a/source/blender/windowmanager/WM_types.hh +++ b/source/blender/windowmanager/WM_types.hh @@ -99,7 +99,6 @@ struct bContext; struct bContextStore; struct GreasePencil; struct GreasePencilLayer; -struct ReportList; struct wmDrag; struct wmDropBox; struct wmEvent; @@ -779,6 +778,11 @@ struct wmEvent { uint8_t prev_press_modifier; /** The `keymodifier` at the point of the press action. */ short prev_press_keymodifier; + /** + * The time when the key is pressed, see #PIL_check_seconds_timer. + * Used to detect double-click events. + */ + double prev_press_time; }; /** @@ -918,38 +922,6 @@ struct wmTimer { bool sleep; }; -/** Communication/status data owned by the wmJob, and passed to the worker code when calling - * `startjob` callback. - * - * 'OUTPUT' members mean that they are defined by the worker thread, and read/used by the wmJob - * management code from the main thread. And vice-versa for `INPUT' members. - * - * \warning There is currently no thread-safety or synchronization when accessing these values. - * This is fine as long as: - * - All members are independent of each other, value-wise. - * - Each member is 'simple enough' that accessing it or setting it can be considered as atomic. - * - There is no requirement of immediate synchronization of these values between the main - * controlling thread (i.e. wmJob management code) and the worker thread. - */ -struct wmJobWorkerStatus { - /** OUTPUT - Set to true by the worker to request update processing from the main thread (as part - * of the wmJob 'event loop', see #wm_jobs_timer). */ - bool do_update; - - /** INPUT - Set by the wmJob management code to request a worker to stop/abort its processing. - * - * \note Some job types (rendering or baking ones e.g.) also use the #Global.is_break flag to - * cancel their processing. */ - bool stop; - - /** OUTPUT - Progress as reported by the worker, from `0.0f` to `1.0f`. */ - float progress; - - /** OUTPUT - Storage of reports generated during this job's run. Contains its own locking for - * thread-safety. */ - ReportList *reports; -}; - struct wmOperatorType { /** Text for UI, undo (should not exceed #OP_MAX_TYPENAME). */ const char *name; @@ -968,7 +940,7 @@ struct wmOperatorType { * any interface code or input device state. * See defines below for return values. */ - int (*exec)(bContext *C, wmOperator *op) ATTR_WARN_UNUSED_RESULT; + int (*exec)(bContext *, wmOperator *) ATTR_WARN_UNUSED_RESULT; /** * This callback executes on a running operator whenever as property @@ -976,7 +948,7 @@ struct wmOperatorType { * invalid settings in exceptional cases. * Boolean return value, True denotes a change has been made and to redraw. */ - bool (*check)(bContext *C, wmOperator *op); + bool (*check)(bContext *, wmOperator *); /** * For modal temporary operators, initially invoke is called, then @@ -984,13 +956,13 @@ struct wmOperatorType { * canceled due to some external reason, cancel is called * See defines below for return values. */ - int (*invoke)(bContext *C, wmOperator *op, const wmEvent *event) ATTR_WARN_UNUSED_RESULT; + int (*invoke)(bContext *, wmOperator *, const wmEvent *) ATTR_WARN_UNUSED_RESULT; /** * Called when a modal operator is canceled (not used often). * Internal cleanup can be done here if needed. */ - void (*cancel)(bContext *C, wmOperator *op); + void (*cancel)(bContext *, wmOperator *); /** * Modal is used for operators which continuously run. Fly mode, knife tool, circle select are @@ -998,13 +970,13 @@ struct wmOperatorType { * or execute other operators. They keep running until they don't return * `OPERATOR_RUNNING_MODAL`. */ - int (*modal)(bContext *C, wmOperator *op, const wmEvent *event) ATTR_WARN_UNUSED_RESULT; + int (*modal)(bContext *, wmOperator *, const wmEvent *) ATTR_WARN_UNUSED_RESULT; /** * Verify if the operator can be executed in the current context. Note * that the operator may still fail to execute even if this returns true. */ - bool (*poll)(bContext *C) ATTR_WARN_UNUSED_RESULT; + bool (*poll)(bContext *) ATTR_WARN_UNUSED_RESULT; /** * Used to check if properties should be displayed in auto-generated UI. @@ -1015,12 +987,12 @@ struct wmOperatorType { const PropertyRNA *prop) ATTR_WARN_UNUSED_RESULT; /** Optional panel for redo and repeat, auto-generated if not set. */ - void (*ui)(bContext *C, wmOperator *op); + void (*ui)(bContext *, wmOperator *); /** * Optional check for whether the #ui callback should be called (usually to create the redo * panel interface). */ - bool (*ui_poll)(wmOperatorType *ot, PointerRNA *ptr); + bool (*ui_poll)(wmOperatorType *, PointerRNA *); /** * Return a different name to use in the user interface, based on property values. @@ -1030,13 +1002,13 @@ struct wmOperatorType { * any definition of an operator button through the layout API will fail to execute it). See * #112253 for details. */ - std::string (*get_name)(wmOperatorType *ot, PointerRNA *ptr); + std::string (*get_name)(wmOperatorType *, PointerRNA *); /** * Return a different description to use in the user interface, based on property values. * The returned string is expected to be translated if needed. */ - std::string (*get_description)(bContext *C, wmOperatorType *ot, PointerRNA *ptr); + std::string (*get_description)(bContext *C, wmOperatorType *, PointerRNA *); /** RNA for properties */ StructRNA *srna; @@ -1060,7 +1032,7 @@ struct wmOperatorType { wmKeyMap *modalkeymap; /** Python needs the operator type as well. */ - bool (*pyop_poll)(bContext *C, wmOperatorType *ot) ATTR_WARN_UNUSED_RESULT; + bool (*pyop_poll)(bContext *, wmOperatorType *ot) ATTR_WARN_UNUSED_RESULT; /** RNA integration */ ExtensionRNA rna_ext; @@ -1085,8 +1057,7 @@ struct wmOperatorCallParams { #ifdef WITH_INPUT_IME /* *********** Input Method Editor (IME) *********** */ /** - * \warning this is a duplicate of #GHOST_TEventImeData. - * All members must remain aligned and the struct size match! + * \note similar to #GHOST_TEventImeData. */ struct wmIMEData { size_t result_len, composite_len; @@ -1102,6 +1073,8 @@ struct wmIMEData { int sel_start; /** End of the selection. */ int sel_end; + + bool is_ime_composing; }; #endif @@ -1183,15 +1156,13 @@ struct wmDragGreasePencilLayer { GreasePencilLayer *layer; }; -using WMDropboxTooltipFunc = char *(*)(bContext *C, - wmDrag *drag, - const int xy[2], - wmDropBox *drop); +using WMDropboxTooltipFunc = char *(*)(bContext *, wmDrag *, const int xy[2], wmDropBox *drop); struct wmDragActiveDropState { /** * Informs which dropbox is activated with the drag item. - * When this value changes, the #on_enter() and #on_exit() dropbox callbacks are triggered. + * When this value changes, the #draw_activate and #draw_deactivate dropbox callbacks are + * triggered. */ wmDropBox *active_dropbox; @@ -1262,13 +1233,6 @@ struct wmDropBox { * So this callback is called on every dropbox that is registered in the current screen. */ void (*on_drag_start)(bContext *C, wmDrag *drag); - /** Called when poll returns true the first time. Typically used to setup some drawing data. */ - void (*on_enter)(wmDropBox *drop, wmDrag *drag); - - /** Called when poll returns false the first time or when the drag event ends (successful drop or - * canceled). Typically used to cleanup resources or end drawing. */ - void (*on_exit)(wmDropBox *drop, wmDrag *drag); - /** Before exec, this copies drag info to #wmDrop properties. */ void (*copy)(bContext *C, wmDrag *drag, wmDropBox *drop); @@ -1293,6 +1257,12 @@ struct wmDropBox { */ void (*draw_in_view)(bContext *C, wmWindow *win, wmDrag *drag, const int xy[2]); + /** Called when poll returns true the first time. */ + void (*draw_activate)(wmDropBox *drop, wmDrag *drag); + + /** Called when poll returns false the first time or when the drag event ends. */ + void (*draw_deactivate)(wmDropBox *drop, wmDrag *drag); + /** Custom data for drawing. */ void *draw_data; diff --git a/source/blender/windowmanager/gizmo/intern/wm_gizmo_group.cc b/source/blender/windowmanager/gizmo/intern/wm_gizmo_group.cc index aff76d6eb9b..025e59140be 100644 --- a/source/blender/windowmanager/gizmo/intern/wm_gizmo_group.cc +++ b/source/blender/windowmanager/gizmo/intern/wm_gizmo_group.cc @@ -102,7 +102,7 @@ void wm_gizmogroup_free(bContext *C, wmGizmoGroup *gzgroup) #endif if (gzgroup->reports && (gzgroup->reports->flag & RPT_FREE)) { - BKE_reports_free(gzgroup->reports); + BKE_reports_clear(gzgroup->reports); MEM_freeN(gzgroup->reports); } diff --git a/source/blender/windowmanager/gizmo/intern/wm_gizmo_target_props.cc b/source/blender/windowmanager/gizmo/intern/wm_gizmo_target_props.cc index 99c821d0622..ed4c730dbe4 100644 --- a/source/blender/windowmanager/gizmo/intern/wm_gizmo_target_props.cc +++ b/source/blender/windowmanager/gizmo/intern/wm_gizmo_target_props.cc @@ -24,8 +24,6 @@ #include "ED_screen.hh" #include "ED_view3d.hh" -#include "ANIM_keyframing.hh" - /* own includes */ #include "wm_gizmo_intern.h" #include "wm_gizmo_wmapi.h" @@ -345,8 +343,7 @@ void WM_gizmo_target_property_anim_autokey(bContext *C, Scene *scene = CTX_data_scene(C); const float cfra = float(scene->r.cfra); const int index = gz_prop->index == -1 ? 0 : gz_prop->index; - blender::animrig::autokeyframe_property( - C, scene, &gz_prop->ptr, gz_prop->prop, index, cfra, false); + ED_autokeyframe_property(C, scene, &gz_prop->ptr, gz_prop->prop, index, cfra, false); } } diff --git a/source/blender/windowmanager/intern/wm.cc b/source/blender/windowmanager/intern/wm.cc index ef05693b2d4..d0384b8bb7a 100644 --- a/source/blender/windowmanager/intern/wm.cc +++ b/source/blender/windowmanager/intern/wm.cc @@ -167,12 +167,10 @@ static void window_manager_blend_read_data(BlendDataReader *reader, ID *id) win->ghostwin = nullptr; win->gpuctx = nullptr; win->eventstate = nullptr; - win->eventstate_prev_press_time_ms = 0; win->event_last_handled = nullptr; win->cursor_keymap_status = nullptr; #if defined(WIN32) || defined(__APPLE__) win->ime_data = nullptr; - win->ime_data_is_composing = false; #endif BLI_listbase_clear(&win->event_queue); @@ -293,7 +291,7 @@ void WM_operator_free(wmOperator *op) } if (op->reports && (op->reports->flag & RPT_FREE)) { - BKE_reports_free(op->reports); + BKE_reports_clear(op->reports); MEM_freeN(op->reports); } @@ -344,7 +342,7 @@ void WM_operator_type_set(wmOperator *op, wmOperatorType *ot) static void wm_reports_free(wmWindowManager *wm) { - BKE_reports_free(&wm->reports); + BKE_reports_clear(&wm->reports); WM_event_timer_remove(wm, nullptr, wm->reports.reporttimer); } @@ -524,8 +522,6 @@ void wm_add_default(Main *bmain, bContext *C) WorkSpace *workspace; WorkSpaceLayout *layout = BKE_workspace_layout_find_global(bmain, screen, &workspace); - BKE_reports_init(&wm->reports, RPT_STORE); - CTX_wm_manager_set(C, wm); win = wm_window_new(bmain, wm, nullptr, false); win->scene = CTX_data_scene(C); diff --git a/source/blender/windowmanager/intern/wm_dragdrop.cc b/source/blender/windowmanager/intern/wm_dragdrop.cc index dc8b2f5dc53..3074ad9612b 100644 --- a/source/blender/windowmanager/intern/wm_dragdrop.cc +++ b/source/blender/windowmanager/intern/wm_dragdrop.cc @@ -312,8 +312,8 @@ void WM_drag_data_free(eWM_DragDataType dragtype, void *poin) void WM_drag_free(wmDrag *drag) { - if (drag->drop_state.active_dropbox && drag->drop_state.active_dropbox->on_exit) { - drag->drop_state.active_dropbox->on_exit(drag->drop_state.active_dropbox, drag); + if (drag->drop_state.active_dropbox && drag->drop_state.active_dropbox->draw_deactivate) { + drag->drop_state.active_dropbox->draw_deactivate(drag->drop_state.active_dropbox, drag); } if (drag->flags & WM_DRAG_FREE_DATA) { WM_drag_data_free(drag->type, drag->poin); @@ -447,12 +447,12 @@ static void wm_drop_update_active(bContext *C, wmDrag *drag, const wmEvent *even wmDropBox *drop_prev = drag->drop_state.active_dropbox; wmDropBox *drop = wm_dropbox_active(C, drag, event); if (drop != drop_prev) { - if (drop_prev && drop_prev->on_exit) { - drop_prev->on_exit(drop_prev, drag); + if (drop_prev && drop_prev->draw_deactivate) { + drop_prev->draw_deactivate(drop_prev, drag); BLI_assert(drop_prev->draw_data == nullptr); } - if (drop && drop->on_enter) { - drop->on_enter(drop, drag); + if (drop && drop->draw_activate) { + drop->draw_activate(drop, drag); } drag->drop_state.active_dropbox = drop; drag->drop_state.area_from = drop ? CTX_wm_area(C) : nullptr; diff --git a/source/blender/windowmanager/intern/wm_draw.cc b/source/blender/windowmanager/intern/wm_draw.cc index f30ee18b3a0..62040ee8f8f 100644 --- a/source/blender/windowmanager/intern/wm_draw.cc +++ b/source/blender/windowmanager/intern/wm_draw.cc @@ -1166,7 +1166,7 @@ static void wm_draw_window(bContext *C, wmWindow *win) bScreen *screen = WM_window_get_active_screen(win); bool stereo = WM_stereo3d_enabled(win, false); - /* Avoid any BGL call issued before this to alter the window drawing. */ + /* Avoid any BGL call issued before this to alter the window drawin. */ GPU_bgl_end(); /* Draw area regions into their own frame-buffer. This way we can redraw @@ -1591,9 +1591,12 @@ void wm_draw_region_clear(wmWindow *win, ARegion * /*region*/) screen->do_draw = true; } -void WM_draw_region_free(ARegion *region) +void WM_draw_region_free(ARegion *region, bool hide) { wm_draw_region_buffer_free(region); + if (hide) { + region->visible = 0; + } } void wm_draw_region_test(bContext *C, ScrArea *area, ARegion *region) diff --git a/source/blender/windowmanager/intern/wm_event_system.cc b/source/blender/windowmanager/intern/wm_event_system.cc index 9c9ff99f601..2c58c19d951 100644 --- a/source/blender/windowmanager/intern/wm_event_system.cc +++ b/source/blender/windowmanager/intern/wm_event_system.cc @@ -64,6 +64,8 @@ #include "UI_interface.hh" +#include "PIL_time.h" + #include "WM_api.hh" #include "WM_message.hh" #include "WM_toolsystem.h" @@ -95,11 +97,6 @@ */ #define USE_GIZMO_MOUSE_PRIORITY_HACK -#ifdef WITH_INPUT_IME -BLI_STATIC_ASSERT(sizeof(GHOST_TEventImeData) == sizeof(wmIMEData), - "These structs must match exactly!"); -#endif - /** * Return value of handler-operator call. */ @@ -129,9 +126,7 @@ static wmEvent *wm_event_add_mousemove_to_head(wmWindow *win); static void wm_operator_free_for_fileselect(wmOperator *file_operator); static void wm_event_state_update_and_click_set_ex(wmEvent *event, - uint64_t event_time_ms, wmEvent *event_state, - uint64_t *event_state_prev_press_time_ms_p, const bool is_keyboard, const bool check_double_click); @@ -180,15 +175,7 @@ wmEvent *WM_event_add_simulate(wmWindow *win, const wmEvent *event_to_add) copy_v2_v2_int(event->prev_xy, win->eventstate->xy); } else if (ISKEYBOARD_OR_BUTTON(event->type)) { - /* Dummy time for simulated events. */ - const uint64_t event_time_ms = UINT64_MAX; - uint64_t eventstate_prev_press_time_ms = 0; - wm_event_state_update_and_click_set_ex(event, - event_time_ms, - win->eventstate, - &eventstate_prev_press_time_ms, - ISKEYBOARD(event->type), - false); + wm_event_state_update_and_click_set_ex(event, win->eventstate, ISKEYBOARD(event->type), false); } return event; } @@ -928,21 +915,17 @@ void WM_ndof_deadzone_set(float deadzone) } #endif -void WM_reports_from_reports_move(wmWindowManager *wm, ReportList *reports) +static void wm_add_reports(ReportList *reports) { /* If the caller owns them, handle this. */ - if (!reports || BLI_listbase_is_empty(&reports->list) || (reports->flag & RPT_OP_HOLD) != 0) { - return; + if (reports->list.first && (reports->flag & RPT_OP_HOLD) == 0) { + wmWindowManager *wm = static_cast(G_MAIN->wm.first); + + /* Add reports to the global list, otherwise they are not seen. */ + BLI_movelisttolist(&wm->reports.list, &reports->list); + + WM_report_banner_show(wm, nullptr); } - - if (!wm) { - wm = static_cast(G_MAIN->wm.first); - } - - /* Add reports to the global list, otherwise they are not seen. */ - BKE_reports_move_to_reports(&wm->reports, reports); - - WM_report_banner_show(wm, nullptr); } void WM_report(eReportType type, const char *message) @@ -952,9 +935,9 @@ void WM_report(eReportType type, const char *message) BKE_report_print_level_set(&reports, RPT_WARNING); BKE_report(&reports, type, message); - WM_reports_from_reports_move(nullptr, &reports); + wm_add_reports(&reports); - BKE_reports_free(&reports); + BKE_reports_clear(&reports); } void WM_reportf(eReportType type, const char *format, ...) @@ -1116,7 +1099,7 @@ static void wm_operator_reports(bContext *C, WM_event_add_notifier(C, NC_SPACE | ND_SPACE_INFO_REPORT, nullptr); } /* If the caller owns them, handle this. */ - WM_reports_from_reports_move(CTX_wm_manager(C), op->reports); + wm_add_reports(op->reports); } /** @@ -2502,7 +2485,7 @@ static eHandlerActionFlag wm_handler_operator_call(bContext *C, else { /* Not very common, but modal operators may report before finishing. */ if (!BLI_listbase_is_empty(&op->reports->list)) { - WM_reports_from_reports_move(wm, op->reports); + wm_add_reports(op->reports); } } @@ -2835,7 +2818,12 @@ static eHandlerActionFlag wm_handler_fileselect_do(bContext *C, BKE_report_print_level_set(handler->op->reports, RPT_WARNING); UI_popup_menu_reports(C, handler->op->reports); - WM_reports_from_reports_move(CTX_wm_manager(C), handler->op->reports); + /* XXX: copied from #wm_operator_finished(). */ + /* Add reports to the global list, otherwise they are not seen. */ + BLI_movelisttolist(&CTX_wm_reports(C)->list, &handler->op->reports->list); + + /* More hacks, since we meddle with reports, banner display doesn't happen automatic. */ + WM_report_banner_show(CTX_wm_manager(C), CTX_wm_window(C)); CTX_wm_window_set(C, win_prev); CTX_wm_area_set(C, area_prev); @@ -5247,9 +5235,7 @@ static wmWindow *wm_event_cursor_other_windows(wmWindowManager *wm, wmWindow *wi return nullptr; } -static bool wm_event_is_double_click(const wmEvent *event, - const uint64_t event_time_ms, - const uint64_t event_prev_press_time_ms) +static bool wm_event_is_double_click(const wmEvent *event) { if ((event->type == event->prev_type) && (event->prev_val == KM_RELEASE) && (event->val == KM_PRESS)) @@ -5258,7 +5244,7 @@ static bool wm_event_is_double_click(const wmEvent *event, /* Pass. */ } else { - if ((event_time_ms - event_prev_press_time_ms) < uint64_t(U.dbl_click_time)) { + if ((PIL_check_seconds_timer() - event->prev_press_time) * 1000 < U.dbl_click_time) { return true; } } @@ -5276,16 +5262,14 @@ static void wm_event_prev_values_set(wmEvent *event, wmEvent *event_state) event->prev_type = event_state->prev_type = event_state->type; } -static void wm_event_prev_click_set(uint64_t event_time_ms, - wmEvent *event_state, - uint64_t *r_event_state_prev_press_time_ms) +static void wm_event_prev_click_set(wmEvent *event_state) { + event_state->prev_press_time = PIL_check_seconds_timer(); event_state->prev_press_type = event_state->type; event_state->prev_press_modifier = event_state->modifier; event_state->prev_press_keymodifier = event_state->keymodifier; event_state->prev_press_xy[0] = event_state->xy[0]; event_state->prev_press_xy[1] = event_state->xy[1]; - *r_event_state_prev_press_time_ms = event_time_ms; } static wmEvent *wm_event_add_mousemove(wmWindow *win, const wmEvent *event) @@ -5368,9 +5352,7 @@ static wmEvent *wm_event_add_trackpad(wmWindow *win, const wmEvent *event, int d * Needed for event simulation where the time of click events is not so predictable. */ static void wm_event_state_update_and_click_set_ex(wmEvent *event, - const uint64_t event_time_ms, wmEvent *event_state, - uint64_t *event_state_prev_press_time_ms_p, const bool is_keyboard, const bool check_double_click) { @@ -5396,33 +5378,24 @@ static void wm_event_state_update_and_click_set_ex(wmEvent *event, * since the `event_state` and the `event` are not kept in sync. */ /* Double click test. */ - if (check_double_click && - wm_event_is_double_click(event, event_time_ms, *event_state_prev_press_time_ms_p)) - { + if (check_double_click && wm_event_is_double_click(event)) { CLOG_INFO(WM_LOG_HANDLERS, 1, "DBL_CLICK: detected"); event->val = KM_DBL_CLICK; } else if (event->val == KM_PRESS) { if ((event->flag & WM_EVENT_IS_REPEAT) == 0) { - wm_event_prev_click_set(event_time_ms, event_state, event_state_prev_press_time_ms_p); + wm_event_prev_click_set(event_state); } } } static void wm_event_state_update_and_click_set(wmEvent *event, - uint64_t event_time_ms, wmEvent *event_state, - uint64_t *event_state_prev_press_time_ms_p, const GHOST_TEventType type) { const bool is_keyboard = ELEM(type, GHOST_kEventKeyDown, GHOST_kEventKeyUp); const bool check_double_click = true; - wm_event_state_update_and_click_set_ex(event, - event_time_ms, - event_state, - event_state_prev_press_time_ms_p, - is_keyboard, - check_double_click); + wm_event_state_update_and_click_set_ex(event, event_state, is_keyboard, check_double_click); } /* Returns true when the two events corresponds to a press of the same key with the same modifiers. @@ -5472,11 +5445,7 @@ static bool wm_event_is_ignorable_key_press(const wmWindow *win, const wmEvent & return wm_event_is_same_key_press(last_event, event); } -void wm_event_add_ghostevent(wmWindowManager *wm, - wmWindow *win, - const int type, - const void *customdata, - const uint64_t event_time_ms) +void wm_event_add_ghostevent(wmWindowManager *wm, wmWindow *win, const int type, void *customdata) { if (UNLIKELY(G.f & G_FLAG_EVENT_SIMULATE)) { return; @@ -5493,7 +5462,6 @@ void wm_event_add_ghostevent(wmWindowManager *wm, * - Data added to event and \a event_state stays and is handled immediately. */ wmEvent event, *event_state = win->eventstate; - uint64_t *event_state_prev_press_time_ms_p = &win->eventstate_prev_press_time_ms; /* Initialize and copy state (only mouse x y and modifiers). */ event = *event_state; @@ -5543,11 +5511,9 @@ void wm_event_add_ghostevent(wmWindowManager *wm, switch (type) { /* Mouse move, also to inactive window (X11 does this). */ case GHOST_kEventCursorMove: { - const GHOST_TEventCursorData *cd = static_cast(customdata); + GHOST_TEventCursorData *cd = static_cast(customdata); copy_v2_v2_int(event.xy, &cd->x); - wm_cursor_position_from_ghost_screen_coords(win, &event.xy[0], &event.xy[1]); - wm_stereo3d_mouse_offset_apply(win, event.xy); wm_tablet_data_from_ghost(&cd->tablet, &event.tablet); @@ -5586,15 +5552,12 @@ void wm_event_add_ghostevent(wmWindowManager *wm, break; } case GHOST_kEventTrackpad: { - const GHOST_TEventTrackpadData *pd = static_cast( - customdata); - - int delta[2] = {pd->deltaX, -pd->deltaY}; + GHOST_TEventTrackpadData *pd = static_cast(customdata); switch (pd->subtype) { case GHOST_kTrackpadEventMagnify: event.type = MOUSEZOOM; - delta[0] = -delta[0]; - delta[1] = -delta[1]; + pd->deltaX = -pd->deltaX; + pd->deltaY = -pd->deltaY; break; case GHOST_kTrackpadEventSmartMagnify: event.type = MOUSESMARTZOOM; @@ -5608,9 +5571,8 @@ void wm_event_add_ghostevent(wmWindowManager *wm, break; } - copy_v2_v2_int(event.xy, &pd->x); - wm_cursor_position_from_ghost_screen_coords(win, &event.xy[0], &event.xy[1]); - copy_v2_v2_int(event_state->xy, event.xy); + event.xy[0] = event_state->xy[0] = pd->x; + event.xy[1] = event_state->xy[1] = pd->y; event.val = KM_NOTHING; /* The direction is inverted from the device due to system preferences. */ @@ -5618,13 +5580,13 @@ void wm_event_add_ghostevent(wmWindowManager *wm, event.flag |= WM_EVENT_SCROLL_INVERT; } - wm_event_add_trackpad(win, &event, delta[0], delta[1]); + wm_event_add_trackpad(win, &event, pd->deltaX, -pd->deltaY); break; } /* Mouse button. */ case GHOST_kEventButtonDown: case GHOST_kEventButtonUp: { - const GHOST_TEventButtonData *bd = static_cast(customdata); + GHOST_TEventButtonData *bd = static_cast(customdata); /* Get value and type from Ghost. */ event.val = (type == GHOST_kEventButtonDown) ? KM_PRESS : KM_RELEASE; @@ -5655,11 +5617,7 @@ void wm_event_add_ghostevent(wmWindowManager *wm, wm_tablet_data_from_ghost(&bd->tablet, &event.tablet); wm_eventemulation(&event, false); - wm_event_state_update_and_click_set(&event, - event_time_ms, - event_state, - event_state_prev_press_time_ms_p, - (GHOST_TEventType)type); + wm_event_state_update_and_click_set(&event, event_state, (GHOST_TEventType)type); /* Add to other window if event is there (not to both!). */ wmWindow *win_other = wm_event_cursor_other_windows(wm, win, &event); @@ -5691,7 +5649,7 @@ void wm_event_add_ghostevent(wmWindowManager *wm, /* Keyboard. */ case GHOST_kEventKeyDown: case GHOST_kEventKeyUp: { - const GHOST_TEventKeyData *kd = static_cast(customdata); + GHOST_TEventKeyData *kd = static_cast(customdata); event.type = convert_key(kd->key); if (UNLIKELY(event.type == EVENT_NONE)) { break; @@ -5830,11 +5788,7 @@ void wm_event_add_ghostevent(wmWindowManager *wm, } /* It's important `event.modifier` has been initialized first. */ - wm_event_state_update_and_click_set(&event, - event_time_ms, - event_state, - event_state_prev_press_time_ms_p, - (GHOST_TEventType)type); + wm_event_state_update_and_click_set(&event, event_state, (GHOST_TEventType)type); /* If test_break set, it catches this. Do not set with modifier presses. * Exclude modifiers because MS-Windows uses these to bring up the task manager. @@ -5855,8 +5809,7 @@ void wm_event_add_ghostevent(wmWindowManager *wm, } case GHOST_kEventWheel: { - const GHOST_TEventWheelData *wheelData = static_cast( - customdata); + GHOST_TEventWheelData *wheelData = static_cast(customdata); if (wheelData->z > 0) { event.type = WHEELUPMOUSE; @@ -5870,6 +5823,16 @@ void wm_event_add_ghostevent(wmWindowManager *wm, break; } + case GHOST_kEventTimer: { + event.type = TIMER; + event.custom = EVT_DATA_TIMER; + event.customdata = customdata; + event.val = KM_NOTHING; + event.keymodifier = 0; + wm_event_add(win, &event); + + break; + } #ifdef WITH_INPUT_NDOF case GHOST_kEventNDOFMotion: { @@ -5883,8 +5846,7 @@ void wm_event_add_ghostevent(wmWindowManager *wm, } case GHOST_kEventNDOFButton: { - const GHOST_TEventNDOFButtonData *e = static_cast( - customdata); + GHOST_TEventNDOFButtonData *e = static_cast(customdata); event.type = NDOF_BUTTON_INDEX_AS_EVENT(e->button); @@ -5902,11 +5864,7 @@ void wm_event_add_ghostevent(wmWindowManager *wm, event.custom = 0; event.customdata = nullptr; - wm_event_state_update_and_click_set(&event, - event_time_ms, - event_state, - event_state_prev_press_time_ms_p, - (GHOST_TEventType)type); + wm_event_state_update_and_click_set(&event, event_state, (GHOST_TEventType)type); wm_event_add(win, &event); @@ -5928,9 +5886,8 @@ void wm_event_add_ghostevent(wmWindowManager *wm, #ifdef WITH_INPUT_IME case GHOST_kEventImeCompositionStart: { event.val = KM_PRESS; - win->ime_data = static_cast(customdata); - BLI_assert(win->ime_data != nullptr); - win->ime_data_is_composing = true; + win->ime_data = static_cast(customdata); + win->ime_data->is_ime_composing = true; event.type = WM_IME_COMPOSITE_START; wm_event_add(win, &event); break; @@ -5943,7 +5900,9 @@ void wm_event_add_ghostevent(wmWindowManager *wm, } case GHOST_kEventImeCompositionEnd: { event.val = KM_PRESS; - win->ime_data_is_composing = false; + if (win->ime_data) { + win->ime_data->is_ime_composing = false; + } event.type = WM_IME_COMPOSITE_END; wm_event_add(win, &event); break; diff --git a/source/blender/windowmanager/intern/wm_files.cc b/source/blender/windowmanager/intern/wm_files.cc index 60db952a200..ac17b26ebc2 100644 --- a/source/blender/windowmanager/intern/wm_files.cc +++ b/source/blender/windowmanager/intern/wm_files.cc @@ -4278,7 +4278,7 @@ static uiBlock *block_create__close_file_dialog(bContext *C, ARegion *region, vo has_extra_checkboxes = true; } - BKE_reports_free(&reports); + BKE_reports_clear(&reports); uiItemS_ex(layout, has_extra_checkboxes ? 2.0f : 4.0f); diff --git a/source/blender/windowmanager/intern/wm_files_link.cc b/source/blender/windowmanager/intern/wm_files_link.cc index d712bc22e96..a28d6f5933a 100644 --- a/source/blender/windowmanager/intern/wm_files_link.cc +++ b/source/blender/windowmanager/intern/wm_files_link.cc @@ -99,8 +99,8 @@ static int wm_link_append_invoke(bContext *C, wmOperator *op, const wmEvent * /* { if (!RNA_struct_property_is_set(op->ptr, "filepath")) { const char *blendfile_path = BKE_main_blendfile_path_from_global(); - if (G.filepath_last_library[0] != '\0') { - RNA_string_set(op->ptr, "filepath", G.filepath_last_library); + if (G.lib[0] != '\0') { + RNA_string_set(op->ptr, "filepath", G.lib); } else if (blendfile_path[0] != '\0') { char dirpath[FILE_MAX]; @@ -388,9 +388,8 @@ static int wm_link_append_exec(bContext *C, wmOperator *op) /* recreate dependency graph to include new objects */ DEG_relations_tag_update(bmain); - /* TODO: align `G.filepath_last_library` with other directory storage - * (like last opened image, etc). */ - STRNCPY(G.filepath_last_library, root); + /* XXX TODO: align G.lib with other directory storage (like last opened image etc...) */ + STRNCPY(G.lib, root); WM_event_add_notifier(C, NC_WINDOW, nullptr); @@ -787,9 +786,8 @@ static int wm_lib_relocate_exec_do(bContext *C, wmOperator *op, bool do_reload) BKE_blendfile_link_append_context_free(lapp_context); - /* TODO: align `G.filepath_last_library` with other directory storage - * (like last opened image, etc). */ - STRNCPY(G.filepath_last_library, root); + /* XXX TODO: align G.lib with other directory storage (like last opened image etc...) */ + STRNCPY(G.lib, root); BKE_main_lib_objects_recalc_all(bmain); IMB_colormanagement_check_file_config(bmain); diff --git a/source/blender/windowmanager/intern/wm_init_exit.cc b/source/blender/windowmanager/intern/wm_init_exit.cc index b733529e977..bbf7d4595dd 100644 --- a/source/blender/windowmanager/intern/wm_init_exit.cc +++ b/source/blender/windowmanager/intern/wm_init_exit.cc @@ -120,7 +120,7 @@ #include "GPU_init_exit.h" #include "GPU_material.h" -#include "COM_compositor.hh" +#include "COM_compositor.h" #include "DEG_depsgraph.hh" #include "DEG_depsgraph_query.hh" @@ -137,6 +137,19 @@ CLG_LOGREF_DECLARE_GLOBAL(WM_LOG_MSGBUS_SUB, "wm.msgbus.sub"); static void wm_init_scripts_extensions_once(bContext *C); +static void wm_init_reports(bContext *C) +{ + ReportList *reports = CTX_wm_reports(C); + + BLI_assert(!reports || BLI_listbase_is_empty(&reports->list)); + + BKE_reports_init(reports, RPT_STORE); +} +static void wm_free_reports(wmWindowManager *wm) +{ + BKE_reports_clear(&wm->reports); +} + static bool wm_start_with_console = false; void WM_init_state_start_with_console_set(bool value) @@ -252,6 +265,10 @@ void WM_init(bContext *C, int argc, const char **argv) BKE_icons_init(BIFICONID_LAST_STATIC); BKE_preview_images_init(); + /* Reports can't be initialized before the window-manager, + * but keep before file reading, since that may report errors */ + wm_init_reports(C); + WM_msgbus_types_init(); /* Studio-lights needs to be init before we read the home-file, @@ -346,7 +363,7 @@ void WM_init(bContext *C, int argc, const char **argv) blender::ui::string_search::read_recent_searches_file(); } - STRNCPY(G.filepath_last_library, BKE_main_blendfile_path_from_global()); + STRNCPY(G.lib, BKE_main_blendfile_path_from_global()); CTX_py_init_set(C, true); @@ -610,13 +627,17 @@ void WM_exit_ex(bContext *C, const bool do_python_exit, const bool do_user_exit_ ED_preview_restart_queue_free(); ED_assetlist_storage_exit(); + if (wm) { + /* Before BKE_blender_free! - since the ListBases get freed there. */ + wm_free_reports(wm); + } + SEQ_clipboard_free(); /* `sequencer.cc` */ BKE_tracking_clipboard_free(); BKE_mask_clipboard_free(); BKE_vfont_clipboard_free(); ED_node_clipboard_free(); UV_clipboard_free(); - wm_clipboard_free(); #ifdef WITH_COMPOSITOR_CPU COM_deinitialize(); diff --git a/source/blender/windowmanager/intern/wm_jobs.cc b/source/blender/windowmanager/intern/wm_jobs.cc index 4e66f4b992b..9bc32d325e6 100644 --- a/source/blender/windowmanager/intern/wm_jobs.cc +++ b/source/blender/windowmanager/intern/wm_jobs.cc @@ -20,7 +20,6 @@ #include "BKE_context.h" #include "BKE_global.h" -#include "BKE_report.h" #include "SEQ_prefetch.h" @@ -113,9 +112,8 @@ struct wmJob { eWM_JobFlag flag; bool suspended, running, ready; eWM_JobType job_type; - - /** Data shared with the worker code, so can be accessed and edited from several threads. */ - wmJobWorkerStatus worker_status; + bool do_update, stop; + float progress; /** For display in header, identification */ char name[128]; @@ -207,10 +205,6 @@ wmJob *WM_jobs_get(wmWindowManager *wm, wm_job->main_thread_mutex = BLI_ticket_mutex_alloc(); WM_job_main_thread_lock_acquire(wm_job); - - wm_job->worker_status.reports = MEM_new(__func__); - BKE_reports_init(wm_job->worker_status.reports, RPT_STORE | RPT_PRINT); - BKE_report_print_level_set(wm_job->worker_status.reports, RPT_WARNING); } /* else: a running job, be careful */ @@ -245,7 +239,7 @@ float WM_jobs_progress(const wmWindowManager *wm, const void *owner) const wmJob *wm_job = wm_job_find(wm, owner, WM_JOB_TYPE_ANY); if (wm_job && wm_job->flag & WM_JOB_PROGRESS) { - return wm_job->worker_status.progress; + return wm_job->progress; } return 0.0; @@ -261,7 +255,7 @@ static void wm_jobs_update_progress_bars(wmWindowManager *wm) if (wm_job->flag & WM_JOB_PROGRESS) { /* accumulate global progress for running jobs */ jobs_progress++; - total_progress += wm_job->worker_status.progress; + total_progress += wm_job->progress; } } } @@ -322,7 +316,7 @@ bool WM_jobs_is_running(const wmJob *wm_job) bool WM_jobs_is_stopped(const wmWindowManager *wm, const void *owner) { wmJob *wm_job = wm_job_find(wm, owner, WM_JOB_TYPE_ANY); - return wm_job ? wm_job->worker_status.stop : true; /* XXX to be redesigned properly. */ + return wm_job ? wm_job->stop : true; /* XXX to be redesigned properly. */ } void *WM_jobs_customdata_get(wmJob *wm_job) @@ -345,7 +339,7 @@ void WM_jobs_customdata_set(wmJob *wm_job, void *customdata, void (*free)(void * if (wm_job->running) { /* signal job to end */ - wm_job->worker_status.stop = true; + wm_job->stop = true; } } @@ -386,16 +380,11 @@ void WM_jobs_callbacks_ex(wmJob *wm_job, wm_job->canceled = canceled; } -static void wm_jobs_reports_update(wmWindowManager *wm, wmJob *wm_job) -{ - WM_reports_from_reports_move(wm, wm_job->worker_status.reports); -} - static void *do_job_thread(void *job_v) { wmJob *wm_job = static_cast(job_v); - wm_job->startjob(wm_job->run_customdata, &wm_job->worker_status); + wm_job->startjob(wm_job->run_customdata, &wm_job->stop, &wm_job->do_update, &wm_job->progress); wm_job->ready = true; return nullptr; @@ -437,7 +426,7 @@ static void wm_jobs_test_suspend_stop(wmWindowManager *wm, wmJob *test) /* if this job has higher priority, stop others */ if (test->flag & WM_JOB_PRIORITY) { - wm_job->worker_status.stop = true; + wm_job->stop = true; // printf("job stopped: %s\n", wm_job->name); } } @@ -456,7 +445,7 @@ void WM_jobs_start(wmWindowManager *wm, wmJob *wm_job) { if (wm_job->running) { /* signal job to end and restart */ - wm_job->worker_status.stop = true; + wm_job->stop = true; // printf("job started a running job, ending... %s\n", wm_job->name); } else { @@ -479,9 +468,9 @@ void WM_jobs_start(wmWindowManager *wm, wmJob *wm_job) wm_job->initjob(wm_job->run_customdata); } - wm_job->worker_status.stop = false; + wm_job->stop = false; wm_job->ready = false; - wm_job->worker_status.progress = 0.0; + wm_job->progress = 0.0; // printf("job started: %s\n", wm_job->name); @@ -506,7 +495,7 @@ void WM_jobs_start(wmWindowManager *wm, wmJob *wm_job) } } -static void wm_job_end(wmWindowManager *wm, wmJob *wm_job) +static void wm_job_end(wmJob *wm_job) { BLI_assert_msg(BLI_thread_is_main(), "wm_job_end should only be called from the main thread"); if (wm_job->endjob) { @@ -516,15 +505,12 @@ static void wm_job_end(wmWindowManager *wm, wmJob *wm_job) /* Do the final callback based on whether the job was run to completion or not. * Not all jobs have the same way of signaling cancellation (i.e. rendering stops when * `G.is_break == true`, but doesn't set any wm_job properties to cancel the WM job). */ - const bool was_canceled = wm_job->worker_status.stop || G.is_break; + const bool was_canceled = wm_job->stop || G.is_break; void (*final_callback)(void *) = (wm_job->ready && !was_canceled) ? wm_job->completed : wm_job->canceled; if (final_callback) { final_callback(wm_job->run_customdata); } - - /* Ensure all reports have been moved to WM. */ - wm_jobs_reports_update(wm, wm_job); } static void wm_job_free(wmWindowManager *wm, wmJob *wm_job) @@ -532,10 +518,6 @@ static void wm_job_free(wmWindowManager *wm, wmJob *wm_job) BLI_remlink(&wm->jobs, wm_job); WM_job_main_thread_lock_release(wm_job); BLI_ticket_mutex_free(wm_job->main_thread_mutex); - - BLI_assert(BLI_listbase_is_empty(&wm_job->worker_status.reports->list)); - BKE_reports_free(wm_job->worker_status.reports); - MEM_delete(wm_job->worker_status.reports); MEM_freeN(wm_job); } @@ -546,12 +528,12 @@ static void wm_jobs_kill_job(wmWindowManager *wm, wmJob *wm_job) if (wm_job->running) { /* signal job to end */ - wm_job->worker_status.stop = true; + wm_job->stop = true; WM_job_main_thread_lock_release(wm_job); BLI_threadpool_end(&wm_job->threads); WM_job_main_thread_lock_acquire(wm_job); - wm_job_end(wm, wm_job); + wm_job_end(wm_job); } if (wm_job->wt) { @@ -607,18 +589,20 @@ void WM_jobs_kill_type(wmWindowManager *wm, const void *owner, int job_type) } } -void WM_jobs_stop(wmWindowManager *wm, const void *owner, wm_jobs_start_callback startjob) +void WM_jobs_stop(wmWindowManager *wm, const void *owner, void *startjob) { LISTBASE_FOREACH (wmJob *, wm_job, &wm->jobs) { if (wm_job->owner == owner || wm_job->startjob == startjob) { if (wm_job->running) { - wm_job->worker_status.stop = true; + wm_job->stop = true; } } } } -void WM_jobs_kill(wmWindowManager *wm, void *owner, wm_jobs_start_callback startjob) +void WM_jobs_kill(wmWindowManager *wm, + void *owner, + void (*startjob)(void *, bool *, bool *, float *)) { LISTBASE_FOREACH_MUTABLE (wmJob *, wm_job, &wm->jobs) { if (wm_job->owner == owner || wm_job->startjob == startjob) { @@ -646,7 +630,7 @@ void wm_jobs_timer(wmWindowManager *wm, wmTimer *wt) wm_job_main_thread_yield(wm_job); /* always call note and update when ready */ - if (wm_job->worker_status.do_update || wm_job->ready) { + if (wm_job->do_update || wm_job->ready) { if (wm_job->update) { wm_job->update(wm_job->run_customdata); } @@ -657,11 +641,11 @@ void wm_jobs_timer(wmWindowManager *wm, wmTimer *wt) if (wm_job->flag & WM_JOB_PROGRESS) { WM_event_add_notifier_ex(wm, wm_job->win, NC_WM | ND_JOB, nullptr); } - wm_job->worker_status.do_update = false; + wm_job->do_update = false; } if (wm_job->ready) { - wm_job_end(wm, wm_job); + wm_job_end(wm_job); /* free own data */ wm_job->run_free(wm_job->run_customdata); @@ -706,18 +690,12 @@ void wm_jobs_timer(wmWindowManager *wm, wmTimer *wt) /* remove wm_job */ wm_job_free(wm, wm_job); - wm_job = nullptr; } } } else if (wm_job->suspended) { WM_jobs_start(wm, wm_job); } - - /* Move pending reports generated by the worker thread to the WM main list. */ - if (wm_job) { - wm_jobs_reports_update(wm, wm_job); - } } /* Update progress bars in windows. */ diff --git a/source/blender/windowmanager/intern/wm_keymap.cc b/source/blender/windowmanager/intern/wm_keymap.cc index 7e98caede4e..c10363d79fe 100644 --- a/source/blender/windowmanager/intern/wm_keymap.cc +++ b/source/blender/windowmanager/intern/wm_keymap.cc @@ -21,7 +21,7 @@ #include "MEM_guardedalloc.h" #include "BLI_blenlib.h" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_utildefines.h" #include "BLF_api.h" diff --git a/source/blender/windowmanager/intern/wm_operators.cc b/source/blender/windowmanager/intern/wm_operators.cc index 5b983c4d257..cc8bcdf7b99 100644 --- a/source/blender/windowmanager/intern/wm_operators.cc +++ b/source/blender/windowmanager/intern/wm_operators.cc @@ -42,7 +42,7 @@ #include "BLI_dynstr.h" /* For #WM_operator_pystring. */ #include "BLI_math_rotation.h" #include "BLI_math_vector_types.hh" -#include "BLI_string_utils.hh" +#include "BLI_string_utils.h" #include "BLI_utildefines.h" #include "BKE_anim_data.h" @@ -76,7 +76,6 @@ #include "ED_fileselect.hh" #include "ED_gpencil_legacy.hh" -#include "ED_grease_pencil.hh" #include "ED_numinput.hh" #include "ED_screen.hh" #include "ED_undo.hh" @@ -827,6 +826,8 @@ static bool operator_last_properties_init_impl(wmOperator *op, IDProperty *last_ IDPropertyTemplate val = {0}; IDProperty *replaceprops = IDP_New(IDP_GROUP, &val, "wmOperatorProperties"); + CLOG_INFO(WM_LOG_OPERATORS, 1, "loading previous properties for '%s'", op->type->idname); + PropertyRNA *iterprop = RNA_struct_iterator_property(op->type->srna); RNA_PROP_BEGIN (op->ptr, itemptr, iterprop) { @@ -852,9 +853,6 @@ static bool operator_last_properties_init_impl(wmOperator *op, IDProperty *last_ } RNA_PROP_END; - if (changed) { - CLOG_INFO(WM_LOG_OPERATORS, 1, "loading previous properties for '%s'", op->type->idname); - } IDP_MergeGroup(op->properties, replaceprops, true); IDP_FreeProperty(replaceprops); return changed; @@ -883,9 +881,7 @@ bool WM_operator_last_properties_store(wmOperator *op) } if (op->properties) { - if (!BLI_listbase_is_empty(&op->properties->data.group)) { - CLOG_INFO(WM_LOG_OPERATORS, 1, "storing properties for '%s'", op->type->idname); - } + CLOG_INFO(WM_LOG_OPERATORS, 1, "storing properties for '%s'", op->type->idname); op->type->last_properties = IDP_CopyProperty(op->properties); } @@ -2360,10 +2356,14 @@ static void radial_control_set_initial_mouse(bContext *C, RadialControl *rc, con d[0] *= zoom[0]; d[1] *= zoom[1]; } - rc->scale_fac = 1.0f; + /* Grease pencil draw tool needs to rescale the cursor size. If we don't do that + * the size of the radial is not equals to the actual stroke size. */ if (rc->ptr.owner_id && GS(rc->ptr.owner_id->name) == ID_BR && rc->prop == &rna_Brush_size) { - Brush *brush = reinterpret_cast(rc->ptr.owner_id); - rc->scale_fac = ED_gpencil_radial_control_scale(C, brush, rc->initial_value, event->mval); + rc->scale_fac = ED_gpencil_radial_control_scale( + C, (Brush *)rc->ptr.owner_id, rc->initial_value, event->mval); + } + else { + rc->scale_fac = 1.0f; } rc->initial_mouse[0] -= d[0]; diff --git a/source/blender/windowmanager/intern/wm_playanim.cc b/source/blender/windowmanager/intern/wm_playanim.cc index 2e32ee3fe3b..b4e2adb692e 100644 --- a/source/blender/windowmanager/intern/wm_playanim.cc +++ b/source/blender/windowmanager/intern/wm_playanim.cc @@ -66,7 +66,7 @@ #include "wm_window_private.h" -#include "WM_api.hh" /* Only for #WM_main_playanim. */ +#include "WM_api.hh" /* only for WM_main_playanim */ #ifdef WITH_AUDASPACE # include @@ -74,15 +74,13 @@ # include # include -static struct { - AUD_Sound *source; - AUD_Handle *playback_handle; - AUD_Handle *scrub_handle; - AUD_Device *audio_device; -} g_audaspace = {nullptr}; +static AUD_Sound *source = nullptr; +static AUD_Handle *playback_handle = nullptr; +static AUD_Handle *scrub_handle = nullptr; +static AUD_Device *audio_device = nullptr; #endif -/* Simple limiter to avoid flooding memory. */ +/* simple limiter to avoid flooding memory */ #define USE_FRAME_CACHE_LIMIT #ifdef USE_FRAME_CACHE_LIMIT # define PLAY_FRAME_CACHE_MAX 30 @@ -103,15 +101,12 @@ static bool playanim_window_font_scale_from_dpi(PlayState *ps); * \param r_mem: Optional, when nullptr, don't allocate memory (just set the size). * \param r_size: The file-size of `filepath`. */ -static bool buffer_from_filepath(const char *filepath, - void **r_mem, - size_t *r_size, - char **r_error_message) +static bool buffer_from_filepath(const char *filepath, void **r_mem, size_t *r_size) { errno = 0; const int file = BLI_open(filepath, O_BINARY | O_RDONLY, 0); if (UNLIKELY(file == -1)) { - *r_error_message = BLI_sprintfN("failure '%s' to open file", strerror(errno)); + CLOG_WARN(&LOG, "failure '%s' to open file '%s'", strerror(errno), filepath); return false; } @@ -120,18 +115,18 @@ static bool buffer_from_filepath(const char *filepath, const size_t size = BLI_file_descriptor_size(file); int64_t size_read; if (UNLIKELY(size == size_t(-1))) { - *r_error_message = BLI_sprintfN("failure '%s' to access size", strerror(errno)); + CLOG_WARN(&LOG, "failure '%s' to access size '%s'", strerror(errno), filepath); } else if (r_mem && UNLIKELY(!(mem = static_cast(MEM_mallocN(size, __func__))))) { - *r_error_message = BLI_sprintfN("error allocating buffer %" PRIu64 " size", uint64_t(size)); + CLOG_WARN(&LOG, "error allocating buffer for '%s'", filepath); } else if (r_mem && UNLIKELY((size_read = BLI_read(file, mem, size)) != size)) { - *r_error_message = BLI_sprintfN( - "error '%s' reading file " - "(expected %" PRIu64 ", was %" PRId64 ")", - strerror(errno), - uint64_t(size), - size_read); + CLOG_WARN(&LOG, + "error '%s' while reading '%s' (expected %" PRIu64 ", was %" PRId64 ")", + strerror(errno), + filepath, + size, + size_read); } else { close(file); @@ -179,11 +174,6 @@ struct GhostData { eWS_Qual qual; }; -struct PlayArgs { - int argc; - char **argv; -}; - /** * The minimal context necessary for displaying an image. * Used while displaying images both on load and while playing. @@ -221,13 +211,13 @@ struct PlayState { /** Play forwards/backwards. */ bool pingpong; /** Disable frame skipping. */ - bool no_frame_skip; + bool noskip; /** Display current frame over the window. */ - bool show_frame_indicator; + bool indicator; /** Single-frame stepping has been enabled (frame loading and update pending). */ - bool single_step; + bool sstep; /** Playback has stopped the image has been displayed. */ - bool wait; + bool wait2; /** Playback stopped state once stop/start variables have been handled. */ bool stopped; /** @@ -243,23 +233,19 @@ struct PlayState { bool draw_flip[2]; /** The number of frames to step each update (default to 1, command line argument). */ - int frame_step; - - /** Picture #PlayAnimPict, list (both image-sequence or videos) in-memory. */ - ListBase picsbase; + int fstep; /** Current frame (picture). */ struct PlayAnimPict *picture; /** Image size in pixels, set once at the start. */ - int ibuf_size[2]; + int ibufx, ibufy; /** Mono-space font ID. */ - int font_id; + int fontid; int font_size; /** Restarts player for file drop (drag & drop). */ - int argc_next; - char **argv_next; + char dropped_file[FILE_MAX]; /** Force update when scrubbing with the cursor. */ bool need_frame_update; @@ -277,9 +263,9 @@ static void print_ps(PlayState *ps) printf(" direction=%d,\n", int(ps->direction)); printf(" once=%d,\n", ps->once); printf(" pingpong=%d,\n", ps->pingpong); - printf(" no_frame_skip=%d,\n", ps->no_frame_skip); - printf(" single_step=%d,\n", ps->single_step); - printf(" wait=%d,\n", ps->wait); + printf(" noskip=%d,\n", ps->noskip); + printf(" sstep=%d,\n", ps->sstep); + printf(" wait2=%d,\n", ps->wait2); printf(" stopped=%d,\n", ps->stopped); printf(" go=%d,\n\n", ps->go); fflush(stdout); @@ -294,9 +280,9 @@ static void playanim_window_get_size(GHOST_WindowHandle ghost_window, int *r_wid GHOST_DisposeRectangle(bounds); } -static void playanim_gpu_matrix() +static void playanim_gl_matrix() { - /* Unified matrix, note it affects offset for drawing. */ + /* unified matrix, note it affects offset for drawing */ /* NOTE: cannot use GPU_matrix_ortho_2d_set here because shader ignores. */ GPU_matrix_ortho_set(0.0f, 1.0f, 0.0f, 1.0f, -1.0, 1.0f); } @@ -334,8 +320,6 @@ struct PlayAnimPict { size_t size; /** The allocated file-path to the image. */ const char *filepath; - /** The allocated error message to show if the file cannot be loaded. */ - char *error_message; ImBuf *ibuf; struct anim *anim; int frame; @@ -348,26 +332,13 @@ struct PlayAnimPict { #endif }; -/** - * Various globals relating to playback. - * \note Avoid adding members here where possible, - * prefer #PlayState or one of it's members where possible. - */ -static struct { - bool from_disk; - double swap_time; - double total_time; +static ListBase picsbase = {nullptr, nullptr}; +/* frames in memory - store them here to for easy deallocation later */ +static bool fromdisk = false; +static double ptottime = 0.0, swaptime = 0.04; #ifdef WITH_AUDASPACE - double fps_movie; +static double fps_movie; #endif -} g_playanim = { - /*from_disk*/ false, - /*swap_time*/ 0.04, - /*total_time*/ 0.0, -#ifdef WITH_AUDASPACE - /*fps_movie*/ 0.0, -#endif -}; #ifdef USE_FRAME_CACHE_LIMIT static struct { @@ -457,11 +428,11 @@ static ImBuf *ibuf_from_picture(PlayAnimPict *pic) ibuf = IMB_anim_absolute(pic->anim, pic->frame, IMB_TC_NONE, IMB_PROXY_NONE); } else if (pic->mem) { - /* Use correct color-space here. */ + /* use correct colorspace here */ ibuf = IMB_ibImageFromMemory(pic->mem, pic->size, pic->IB_flags, nullptr, pic->filepath); } else { - /* Use correct color-space here. */ + /* use correct colorspace here */ ibuf = IMB_loadiffname(pic->filepath, pic->IB_flags, nullptr); } @@ -489,9 +460,9 @@ static int pupdate_time() double time = PIL_check_seconds_timer(); - g_playanim.total_time += (time - time_last); + ptottime += (time - time_last); time_last = time; - return (g_playanim.total_time < 0.0); + return (ptottime < 0); } static void *ocio_transform_ibuf(const PlayDisplayContext *display_ctx, @@ -647,22 +618,22 @@ static void draw_display_buffer(const PlayDisplayContext *display_ctx, } /** - * \param font_id: ID of the font to display (-1 when no text should be displayed). - * \param frame_step: Frame step (may be used in text display). + * \param fontid: ID of the font to display (-1 when no text should be displayed). + * \param fstep: Frame step (may be used in text display). * \param draw_zoom: Default to 1.0 (no zoom). * \param draw_flip: X/Y flipping (ignored when null). - * \param frame_indicator_factor: Display a vertical frame-indicator (ignored when -1). + * \param indicator_factor: Display a vertical indicator (ignored when -1). */ static void playanim_toscreen_ex(GHOST_WindowHandle ghost_window, const PlayDisplayContext *display_ctx, const PlayAnimPict *picture, ImBuf *ibuf, /* Run-time drawing arguments (not used on-load). */ - const int font_id, - const int frame_step, + const int fontid, + const int fstep, const float draw_zoom, const bool draw_flip[2], - const float frame_indicator_factor) + const float indicator_factor) { GHOST_ActivateWindowDrawingContext(ghost_window); GPU_render_begin(); @@ -677,14 +648,14 @@ static void playanim_toscreen_ex(GHOST_WindowHandle ghost_window, float span_x = (draw_zoom * ibuf->x) / float(display_ctx->size[0]); float span_y = (draw_zoom * ibuf->y) / float(display_ctx->size[1]); - /* Offset within window. */ + /* offset within window */ float offs_x = 0.5f * (1.0f - span_x); float offs_y = 0.5f * (1.0f - span_y); CLAMP(offs_x, 0.0f, 1.0f); CLAMP(offs_y, 0.0f, 1.0f); - /* Checkerboard for case alpha. */ + /* checkerboard for case alpha */ if (ibuf->planes == 32) { GPU_blend(GPU_BLEND_ALPHA); @@ -706,47 +677,44 @@ static void playanim_toscreen_ex(GHOST_WindowHandle ghost_window, pupdate_time(); - if ((font_id != -1) && picture) { + if ((fontid != -1) && picture) { const int font_margin = int(10 * display_ctx->ui_scale); int sizex, sizey; float fsizex_inv, fsizey_inv; char label[32 + FILE_MAX]; if (ibuf) { - SNPRINTF(label, "%s | %.2f frames/s", picture->filepath, frame_step / g_playanim.swap_time); + SNPRINTF(label, "%s | %.2f frames/s", picture->filepath, fstep / swaptime); } else { - SNPRINTF(label, - "%s | %s", - picture->filepath, - picture->error_message ? picture->error_message : ""); + SNPRINTF(label, "%s | ", picture->filepath); } playanim_window_get_size(ghost_window, &sizex, &sizey); fsizex_inv = 1.0f / sizex; fsizey_inv = 1.0f / sizey; - BLF_color4f(font_id, 1.0, 1.0, 1.0, 1.0); + BLF_color4f(fontid, 1.0, 1.0, 1.0, 1.0); /* FIXME(@ideasman42): Font positioning doesn't work because the aspect causes the position * to be rounded to zero, investigate making BLF support this, * for now use GPU matrix API to adjust the text position. */ #if 0 - BLF_enable(font_id, BLF_ASPECT); - BLF_aspect(font_id, fsizex_inv, fsizey_inv, 1.0f); - BLF_position(font_id, font_margin * fsizex_inv, font_margin * fsizey_inv, 0.0f); - BLF_draw(font_id, label, sizeof(label)); + BLF_enable(fontid, BLF_ASPECT); + BLF_aspect(fontid, fsizex_inv, fsizey_inv, 1.0f); + BLF_position(fontid, font_margin * fsizex_inv, font_margin * fsizey_inv, 0.0f); + BLF_draw(fontid, label, sizeof(label)); #else GPU_matrix_push(); GPU_matrix_scale_2f(fsizex_inv, fsizey_inv); GPU_matrix_translate_2f(font_margin, font_margin); - BLF_position(font_id, 0, 0, 0.0f); - BLF_draw(font_id, label, sizeof(label)); + BLF_position(fontid, 0, 0, 0.0f); + BLF_draw(fontid, label, sizeof(label)); GPU_matrix_pop(); #endif } - if (frame_indicator_factor != -1.0f) { - float fac = frame_indicator_factor; + if (indicator_factor != -1.0f) { + float fac = indicator_factor; fac = 2.0f * fac - 1.0f; GPU_matrix_push_projection(); GPU_matrix_identity_projection_set(); @@ -779,43 +747,35 @@ static void playanim_toscreen_on_load(GHOST_WindowHandle ghost_window, ImBuf *ibuf) { const int font_id = -1; /* Don't draw text. */ - const int frame_step = -1; + const int fstep = -1; const float zoom = 1.0f; - const float frame_indicator_factor = -1.0f; + const float indicator_factor = -1.0f; const bool *draw_flip = nullptr; - playanim_toscreen_ex(ghost_window, - display_ctx, - picture, - ibuf, - font_id, - frame_step, - zoom, - draw_flip, - frame_indicator_factor); + playanim_toscreen_ex( + ghost_window, display_ctx, picture, ibuf, font_id, fstep, zoom, draw_flip, indicator_factor); } static void playanim_toscreen(PlayState *ps, const PlayAnimPict *picture, ImBuf *ibuf) { - float frame_indicator_factor = -1.0f; - if (ps->show_frame_indicator) { - const int frame_range = static_cast(ps->picsbase.last)->frame - - static_cast(ps->picsbase.first)->frame; + float indicator_factor = -1.0f; + if (ps->indicator) { + const int frame_range = static_cast(picsbase.last)->frame - + static_cast(picsbase.first)->frame; if (frame_range > 0) { - frame_indicator_factor = float(double(picture->frame) / double(frame_range)); + indicator_factor = float(double(picture->frame) / double(frame_range)); } else { - BLI_assert_msg(BLI_listbase_is_single(&ps->picsbase), - "Multiple frames without a valid range!"); + BLI_assert_msg(BLI_listbase_is_single(&picsbase), "Multiple frames without a valid range!"); } } - int font_id = -1; + int fontid = -1; if ((ps->ghost_data.qual & (WS_QUAL_SHIFT | WS_QUAL_LMOUSE)) || /* Always inform the user of an error, this should be an exceptional case. */ (ibuf == nullptr)) { - font_id = ps->font_id; + fontid = ps->fontid; } BLI_assert(ps->loading == false); @@ -823,20 +783,19 @@ static void playanim_toscreen(PlayState *ps, const PlayAnimPict *picture, ImBuf &ps->display_ctx, picture, ibuf, - font_id, - ps->frame_step, + fontid, + ps->fstep, ps->zoom, ps->draw_flip, - frame_indicator_factor); + indicator_factor); } -static void build_pict_list_from_anim(ListBase *picsbase, - GhostData *ghost_data, +static void build_pict_list_from_anim(GhostData *ghost_data, const PlayDisplayContext *display_ctx, const char *filepath_first, const int frame_offset) { - /* OCIO_TODO: support different input color space. */ + /* OCIO_TODO: support different input color space */ anim *anim = IMB_open_anim(filepath_first, IB_rect, 0, nullptr); if (anim == nullptr) { CLOG_WARN(&LOG, "couldn't open anim '%s'", filepath_first); @@ -850,39 +809,36 @@ static void build_pict_list_from_anim(ListBase *picsbase, } for (int pic = 0; pic < IMB_anim_get_duration(anim, IMB_TC_NONE); pic++) { - PlayAnimPict *picture = static_cast(MEM_callocN(sizeof(PlayAnimPict), "Pict")); + PlayAnimPict *picture = (PlayAnimPict *)MEM_callocN(sizeof(PlayAnimPict), "Pict"); picture->anim = anim; picture->frame = pic + frame_offset; picture->IB_flags = IB_rect; picture->filepath = BLI_sprintfN("%s : %4.d", filepath_first, pic + 1); - BLI_addtail(picsbase, picture); + BLI_addtail(&picsbase, picture); } - const PlayAnimPict *picture = static_cast(picsbase->last); + const PlayAnimPict *picture = (const PlayAnimPict *)picsbase.last; if (!(picture && picture->anim == anim)) { IMB_close_anim(anim); CLOG_WARN(&LOG, "no frames added for: '%s'", filepath_first); } } -static void build_pict_list_from_image_sequence(ListBase *picsbase, - GhostData *ghost_data, +static void build_pict_list_from_image_sequence(GhostData *ghost_data, const PlayDisplayContext *display_ctx, const char *filepath_first, const int frame_offset, const int totframes, - const int frame_step, + const int fstep, const bool *loading_p) { - /* Load images into cache until the cache is full, - * this resolves choppiness for images that are slow to load, see: #81751. */ - bool fill_cache = ( +/* Load images into cache until the cache is full, + * this resolves choppiness for images that are slow to load, see: #81751. */ #ifdef USE_FRAME_CACHE_LIMIT - true + bool fill_cache = true; #else - false + bool fill_cache = false; #endif - ); int fp_framenr; struct { @@ -900,45 +856,34 @@ static void build_pict_list_from_image_sequence(ListBase *picsbase, &fp_decoded.digits); pupdate_time(); - g_playanim.total_time = 1.0; + ptottime = 1.0; for (int pic = 0; pic < totframes; pic++) { if (!IMB_ispic(filepath)) { break; } - bool has_error = false; - char *error_message = nullptr; void *mem = nullptr; size_t size = -1; - if (!buffer_from_filepath( - filepath, g_playanim.from_disk ? nullptr : &mem, &size, &error_message)) { - has_error = true; - size = 0; + if (!buffer_from_filepath(filepath, fromdisk ? nullptr : &mem, &size)) { + /* A warning will have been logged. */ + break; } - PlayAnimPict *picture = static_cast( - MEM_callocN(sizeof(PlayAnimPict), "picture")); + PlayAnimPict *picture = (PlayAnimPict *)MEM_callocN(sizeof(PlayAnimPict), "picture"); picture->size = size; picture->IB_flags = IB_rect; picture->mem = static_cast(mem); picture->filepath = BLI_strdup(filepath); - picture->error_message = error_message; picture->frame = pic + frame_offset; - BLI_addtail(picsbase, picture); + BLI_addtail(&picsbase, picture); pupdate_time(); - const bool display_imbuf = g_playanim.total_time > 1.0; + const bool display_imbuf = ptottime > 1.0; - if (has_error) { - CLOG_WARN(&LOG, - "Picture %s failed: %s", - filepath, - error_message ? error_message : ""); - } - else if (display_imbuf || fill_cache) { - /* OCIO_TODO: support different input color space. */ + if (display_imbuf || fill_cache) { + /* OCIO_TODO: support different input color space */ ImBuf *ibuf = ibuf_from_picture(picture); if (ibuf) { @@ -960,12 +905,12 @@ static void build_pict_list_from_image_sequence(ListBase *picsbase, if (display_imbuf) { pupdate_time(); - g_playanim.total_time = 0.0; + ptottime = 0.0; } } /* Create a new file-path each time. */ - fp_framenr += frame_step; + fp_framenr += fstep; BLI_path_sequence_encode(filepath, sizeof(filepath), fp_decoded.head, @@ -982,12 +927,11 @@ static void build_pict_list_from_image_sequence(ListBase *picsbase, } } -static void build_pict_list(ListBase *picsbase, - GhostData *ghost_data, +static void build_pict_list(GhostData *ghost_data, const PlayDisplayContext *display_ctx, const char *filepath_first, const int totframes, - const int frame_step, + const int fstep, bool *loading_p) { *loading_p = true; @@ -996,14 +940,14 @@ static void build_pict_list(ListBase *picsbase, * it's important the frame number increases each time. Otherwise playing `*.png` * in a directory will expand into many arguments, each calling this function adding * a frame that's set to zero. */ - const PlayAnimPict *picture_last = static_cast(picsbase->last); + const PlayAnimPict *picture_last = (PlayAnimPict *)picsbase.last; const int frame_offset = picture_last ? (picture_last->frame + 1) : 0; bool do_image_load = false; if (IMB_isanim(filepath_first)) { - build_pict_list_from_anim(picsbase, ghost_data, display_ctx, filepath_first, frame_offset); + build_pict_list_from_anim(ghost_data, display_ctx, filepath_first, frame_offset); - if (picsbase->last == picture_last) { + if (picsbase.last == picture_last) { /* FFMPEG detected JPEG2000 as a video which would load with zero duration. * Resolve this by using images as a fallback when a video file has no frames to display. */ do_image_load = true; @@ -1014,14 +958,8 @@ static void build_pict_list(ListBase *picsbase, } if (do_image_load) { - build_pict_list_from_image_sequence(picsbase, - ghost_data, - display_ctx, - filepath_first, - frame_offset, - totframes, - frame_step, - loading_p); + build_pict_list_from_image_sequence( + ghost_data, display_ctx, filepath_first, frame_offset, totframes, fstep, loading_p); } *loading_p = false; @@ -1030,78 +968,76 @@ static void build_pict_list(ListBase *picsbase, static void update_sound_fps() { #ifdef WITH_AUDASPACE - if (g_audaspace.playback_handle) { - /* Swap-time stores the 1.0/fps ratio. */ - double speed = 1.0 / (g_playanim.swap_time * g_playanim.fps_movie); + if (playback_handle) { + /* swaptime stores the 1.0/fps ratio */ + double speed = 1.0 / (swaptime * fps_movie); - AUD_Handle_setPitch(g_audaspace.playback_handle, speed); + AUD_Handle_setPitch(playback_handle, speed); } #endif } -static void playanim_change_frame_tag(PlayState *ps, int cx) +static void tag_change_frame(PlayState *ps, int cx) { ps->need_frame_update = true; ps->frame_cursor_x = cx; } -static void playanim_change_frame(PlayState *ps) +static void change_frame(PlayState *ps) { if (!ps->need_frame_update) { return; } - if (BLI_listbase_is_empty(&ps->picsbase)) { + + int sizex, sizey; + int i, i_last; + + if (BLI_listbase_is_empty(&picsbase)) { return; } - int sizex, sizey; playanim_window_get_size(ps->ghost_data.window, &sizex, &sizey); - const int i_last = static_cast(ps->picsbase.last)->frame; - /* Without this the frame-indicator location isn't closest to the cursor. */ - const int correct_rounding = (sizex / i_last) / 2; - const int i = clamp_i((i_last * (ps->frame_cursor_x + correct_rounding)) / sizex, 0, i_last); + i_last = ((PlayAnimPict *)picsbase.last)->frame; + i = (i_last * ps->frame_cursor_x) / sizex; + CLAMP(i, 0, i_last); #ifdef WITH_AUDASPACE - if (g_audaspace.scrub_handle) { - AUD_Handle_stop(g_audaspace.scrub_handle); - g_audaspace.scrub_handle = nullptr; + if (scrub_handle) { + AUD_Handle_stop(scrub_handle); + scrub_handle = nullptr; } - if (g_audaspace.playback_handle) { - AUD_Status status = AUD_Handle_getStatus(g_audaspace.playback_handle); + if (playback_handle) { + AUD_Status status = AUD_Handle_getStatus(playback_handle); if (status != AUD_STATUS_PLAYING) { - AUD_Handle_stop(g_audaspace.playback_handle); - g_audaspace.playback_handle = AUD_Device_play( - g_audaspace.audio_device, g_audaspace.source, 1); - if (g_audaspace.playback_handle) { - AUD_Handle_setPosition(g_audaspace.playback_handle, i / g_playanim.fps_movie); - g_audaspace.scrub_handle = AUD_pauseAfter(g_audaspace.playback_handle, - 1.0 / g_playanim.fps_movie); + AUD_Handle_stop(playback_handle); + playback_handle = AUD_Device_play(audio_device, source, 1); + if (playback_handle) { + AUD_Handle_setPosition(playback_handle, i / fps_movie); + scrub_handle = AUD_pauseAfter(playback_handle, 1 / fps_movie); } update_sound_fps(); } else { - AUD_Handle_setPosition(g_audaspace.playback_handle, i / g_playanim.fps_movie); - g_audaspace.scrub_handle = AUD_pauseAfter(g_audaspace.playback_handle, - 1.0 / g_playanim.fps_movie); + AUD_Handle_setPosition(playback_handle, i / fps_movie); + scrub_handle = AUD_pauseAfter(playback_handle, 1 / fps_movie); } } - else if (g_audaspace.source) { - g_audaspace.playback_handle = AUD_Device_play(g_audaspace.audio_device, g_audaspace.source, 1); - if (g_audaspace.playback_handle) { - AUD_Handle_setPosition(g_audaspace.playback_handle, i / g_playanim.fps_movie); - g_audaspace.scrub_handle = AUD_pauseAfter(g_audaspace.playback_handle, - 1.0 / g_playanim.fps_movie); + else if (source) { + playback_handle = AUD_Device_play(audio_device, source, 1); + if (playback_handle) { + AUD_Handle_setPosition(playback_handle, i / fps_movie); + scrub_handle = AUD_pauseAfter(playback_handle, 1 / fps_movie); } update_sound_fps(); } #endif - ps->picture = static_cast(BLI_findlink(&ps->picsbase, i)); + ps->picture = static_cast(BLI_findlink(&picsbase, i)); BLI_assert(ps->picture != nullptr); - ps->single_step = true; - ps->wait = false; + ps->sstep = true; + ps->wait2 = false; ps->next_frame = 0; ps->need_frame_update = false; @@ -1111,13 +1047,13 @@ static void playanim_audio_resume(PlayState *ps) { #ifdef WITH_AUDASPACE /* TODO: store in ps direct? */ - const int i = BLI_findindex(&ps->picsbase, ps->picture); - if (g_audaspace.playback_handle) { - AUD_Handle_stop(g_audaspace.playback_handle); + const int i = BLI_findindex(&picsbase, ps->picture); + if (playback_handle) { + AUD_Handle_stop(playback_handle); } - g_audaspace.playback_handle = AUD_Device_play(g_audaspace.audio_device, g_audaspace.source, 1); - if (g_audaspace.playback_handle) { - AUD_Handle_setPosition(g_audaspace.playback_handle, i / g_playanim.fps_movie); + playback_handle = AUD_Device_play(audio_device, source, 1); + if (playback_handle) { + AUD_Handle_setPosition(playback_handle, i / fps_movie); } update_sound_fps(); #else @@ -1128,18 +1064,17 @@ static void playanim_audio_resume(PlayState *ps) static void playanim_audio_stop(PlayState * /*ps*/) { #ifdef WITH_AUDASPACE - if (g_audaspace.playback_handle) { - AUD_Handle_stop(g_audaspace.playback_handle); - g_audaspace.playback_handle = nullptr; + if (playback_handle) { + AUD_Handle_stop(playback_handle); + playback_handle = nullptr; } #endif } static bool ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr ps_void) { - PlayState *ps = static_cast(ps_void); + PlayState *ps = (PlayState *)ps_void; const GHOST_TEventType type = GHOST_GetEventType(evt); - GHOST_TEventDataPtr data = GHOST_GetEventData(evt); /* Convert ghost event into value keyboard or mouse. */ const int val = ELEM(type, GHOST_kEventKeyDown, GHOST_kEventButtonDown); GHOST_SystemHandle ghost_system = ps->ghost_data.system; @@ -1149,12 +1084,14 @@ static bool ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr ps_void) playanim_event_qual_update(&ps->ghost_data); - /* First check if we're busy loading files. */ + /* first check if we're busy loading files */ if (ps->loading) { switch (type) { case GHOST_kEventKeyDown: case GHOST_kEventKeyUp: { - const GHOST_TEventKeyData *key_data = static_cast(data); + GHOST_TEventKeyData *key_data; + + key_data = (GHOST_TEventKeyData *)GHOST_GetEventData(evt); switch (key_data->key) { case GHOST_kKeyEsc: ps->loading = false; @@ -1170,28 +1107,30 @@ static bool ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr ps_void) return true; } - if (ps->wait && ps->stopped == false) { + if (ps->wait2 && ps->stopped == false) { ps->stopped = true; } - if (ps->wait) { + if (ps->wait2) { pupdate_time(); - g_playanim.total_time = 0.0; + ptottime = 0; } switch (type) { case GHOST_kEventKeyDown: case GHOST_kEventKeyUp: { - const GHOST_TEventKeyData *key_data = static_cast(data); + GHOST_TEventKeyData *key_data; + + key_data = (GHOST_TEventKeyData *)GHOST_GetEventData(evt); switch (key_data->key) { case GHOST_kKeyA: if (val) { - ps->no_frame_skip = !ps->no_frame_skip; + ps->noskip = !ps->noskip; } break; case GHOST_kKeyI: if (val) { - ps->show_frame_indicator = !ps->show_frame_indicator; + ps->indicator = !ps->indicator; } break; case GHOST_kKeyP: @@ -1209,78 +1148,78 @@ static bool ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr ps_void) case GHOST_kKey1: case GHOST_kKeyNumpad1: if (val) { - g_playanim.swap_time = ps->frame_step / 60.0; + swaptime = ps->fstep / 60.0; update_sound_fps(); } break; case GHOST_kKey2: case GHOST_kKeyNumpad2: if (val) { - g_playanim.swap_time = ps->frame_step / 50.0; + swaptime = ps->fstep / 50.0; update_sound_fps(); } break; case GHOST_kKey3: case GHOST_kKeyNumpad3: if (val) { - g_playanim.swap_time = ps->frame_step / 30.0; + swaptime = ps->fstep / 30.0; update_sound_fps(); } break; case GHOST_kKey4: case GHOST_kKeyNumpad4: if (ps->ghost_data.qual & WS_QUAL_SHIFT) { - g_playanim.swap_time = ps->frame_step / 24.0; + swaptime = ps->fstep / 24.0; update_sound_fps(); } else { - g_playanim.swap_time = ps->frame_step / 25.0; + swaptime = ps->fstep / 25.0; update_sound_fps(); } break; case GHOST_kKey5: case GHOST_kKeyNumpad5: if (val) { - g_playanim.swap_time = ps->frame_step / 20.0; + swaptime = ps->fstep / 20.0; update_sound_fps(); } break; case GHOST_kKey6: case GHOST_kKeyNumpad6: if (val) { - g_playanim.swap_time = ps->frame_step / 15.0; + swaptime = ps->fstep / 15.0; update_sound_fps(); } break; case GHOST_kKey7: case GHOST_kKeyNumpad7: if (val) { - g_playanim.swap_time = ps->frame_step / 12.0; + swaptime = ps->fstep / 12.0; update_sound_fps(); } break; case GHOST_kKey8: case GHOST_kKeyNumpad8: if (val) { - g_playanim.swap_time = ps->frame_step / 10.0; + swaptime = ps->fstep / 10.0; update_sound_fps(); } break; case GHOST_kKey9: case GHOST_kKeyNumpad9: if (val) { - g_playanim.swap_time = ps->frame_step / 6.0; + swaptime = ps->fstep / 6.0; update_sound_fps(); } break; case GHOST_kKeyLeftArrow: if (val) { - ps->single_step = true; - ps->wait = false; + ps->sstep = true; + ps->wait2 = false; playanim_audio_stop(ps); if (ps->ghost_data.qual & WS_QUAL_SHIFT) { - ps->picture = static_cast(ps->picsbase.first); + ps->picture = static_cast(picsbase.first); ps->next_frame = 0; } else { @@ -1290,7 +1229,7 @@ static bool ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr ps_void) break; case GHOST_kKeyDownArrow: if (val) { - ps->wait = false; + ps->wait2 = false; playanim_audio_stop(ps); if (ps->ghost_data.qual & WS_QUAL_SHIFT) { @@ -1298,18 +1237,18 @@ static bool ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr ps_void) } else { ps->next_frame = -10; - ps->single_step = true; + ps->sstep = true; } } break; case GHOST_kKeyRightArrow: if (val) { - ps->single_step = true; - ps->wait = false; + ps->sstep = true; + ps->wait2 = false; playanim_audio_stop(ps); if (ps->ghost_data.qual & WS_QUAL_SHIFT) { - ps->picture = static_cast(ps->picsbase.last); + ps->picture = static_cast(picsbase.last); ps->next_frame = 0; } else { @@ -1319,16 +1258,17 @@ static bool ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr ps_void) break; case GHOST_kKeyUpArrow: if (val) { - ps->wait = false; + ps->wait2 = false; + if (ps->ghost_data.qual & WS_QUAL_SHIFT) { ps->next_frame = ps->direction = 1; - if (ps->single_step == false) { + if (ps->sstep == false) { playanim_audio_resume(ps); } } else { ps->next_frame = 10; - ps->single_step = true; + ps->sstep = true; playanim_audio_stop(ps); } } @@ -1341,11 +1281,11 @@ static bool ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr ps_void) if (ps->picture && ps->picture->ibuf) { printf(" Name: %s | Speed: %.2f frames/s\n", ps->picture->ibuf->filepath, - ps->frame_step / g_playanim.swap_time); + ps->fstep / swaptime); } } else { - g_playanim.swap_time = ps->frame_step / 5.0; + swaptime = ps->fstep / 5.0; update_sound_fps(); } } @@ -1354,25 +1294,25 @@ static bool ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr ps_void) case GHOST_kKeyNumpad0: if (val) { if (ps->once) { - ps->once = ps->wait = false; + ps->once = ps->wait2 = false; } else { ps->picture = nullptr; ps->once = true; - ps->wait = false; + ps->wait2 = false; } } break; case GHOST_kKeySpace: if (val) { - if (ps->wait || ps->single_step) { - ps->wait = ps->single_step = false; + if (ps->wait2 || ps->sstep) { + ps->wait2 = ps->sstep = false; playanim_audio_resume(ps); } else { - ps->single_step = true; - ps->wait = true; + ps->sstep = true; + ps->wait2 = true; playanim_audio_stop(ps); } } @@ -1380,19 +1320,19 @@ static bool ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr ps_void) case GHOST_kKeyEnter: case GHOST_kKeyNumpadEnter: if (val) { - ps->wait = ps->single_step = false; + ps->wait2 = ps->sstep = false; playanim_audio_resume(ps); } break; case GHOST_kKeyPeriod: case GHOST_kKeyNumpadPeriod: if (val) { - if (ps->single_step) { - ps->wait = false; + if (ps->sstep) { + ps->wait2 = false; } else { - ps->single_step = true; - ps->wait = !ps->wait; + ps->sstep = true; + ps->wait2 = !ps->wait2; playanim_audio_stop(ps); } } @@ -1407,8 +1347,8 @@ static bool ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr ps_void) playanim_window_zoom(ps, 0.1f); } else { - if (g_playanim.swap_time > ps->frame_step / 60.0) { - g_playanim.swap_time /= 1.1; + if (swaptime > ps->fstep / 60.0) { + swaptime /= 1.1; update_sound_fps(); } } @@ -1423,8 +1363,8 @@ static bool ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr ps_void) playanim_window_zoom(ps, -0.1f); } else { - if (g_playanim.swap_time < ps->frame_step / 5.0) { - g_playanim.swap_time *= 1.1; + if (swaptime < ps->fstep / 5.0) { + swaptime *= 1.1; update_sound_fps(); } } @@ -1440,7 +1380,8 @@ static bool ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr ps_void) } case GHOST_kEventButtonDown: case GHOST_kEventButtonUp: { - const GHOST_TEventButtonData *bd = static_cast(data); + GHOST_TEventButtonData *bd = reinterpret_cast( + GHOST_GetEventData(evt)); int cx, cy, sizex, sizey; playanim_window_get_size(ghost_window, &sizex, &sizey); @@ -1452,7 +1393,7 @@ static bool ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr ps_void) if (type == GHOST_kEventButtonDown) { if (inside_window) { ps->ghost_data.qual |= WS_QUAL_LMOUSE; - playanim_change_frame_tag(ps, cx); + tag_change_frame(ps, cx); } } else { @@ -1483,7 +1424,8 @@ static bool ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr ps_void) } case GHOST_kEventCursorMove: { if (ps->ghost_data.qual & WS_QUAL_LMOUSE) { - const GHOST_TEventCursorData *cd = static_cast(data); + GHOST_TEventCursorData *cd = reinterpret_cast( + GHOST_GetEventData(evt)); int cx, cy; /* Ignore 'in-between' events, since they can make scrubbing lag. @@ -1495,13 +1437,13 @@ static bool ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr ps_void) if (GHOST_GetCursorPosition(ghost_system, ghost_window, &cx, &cy) == GHOST_kSuccess) { GHOST_ScreenToClient(ghost_window, cd->x, cd->y, &x_test, &y_test); if (cx != x_test || cy != y_test) { - /* We're not the last event... skipping. */ + /* we're not the last event... skipping */ break; } } } - playanim_change_frame_tag(ps, cx); + tag_change_frame(ps, cx); } break; } @@ -1517,18 +1459,18 @@ static bool ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr ps_void) playanim_window_get_size(ghost_window, &ps->display_ctx.size[0], &ps->display_ctx.size[1]); GHOST_ActivateWindowDrawingContext(ghost_window); - zoomx = float(ps->display_ctx.size[0]) / ps->ibuf_size[0]; - zoomy = float(ps->display_ctx.size[1]) / ps->ibuf_size[1]; + zoomx = float(ps->display_ctx.size[0]) / ps->ibufx; + zoomy = float(ps->display_ctx.size[1]) / ps->ibufy; - /* Zoom always show entire image. */ + /* zoom always show entire image */ ps->zoom = MIN2(zoomx, zoomy); GPU_viewport(0, 0, ps->display_ctx.size[0], ps->display_ctx.size[1]); GPU_scissor(0, 0, ps->display_ctx.size[0], ps->display_ctx.size[1]); - playanim_gpu_matrix(); + playanim_gl_matrix(); - g_playanim.total_time = 0.0; + ptottime = 0.0; playanim_toscreen(ps, ps->picture, ps->picture ? ps->picture->ibuf : nullptr); @@ -1545,23 +1487,24 @@ static bool ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr ps_void) break; } case GHOST_kEventDraggingDropDone: { - const GHOST_TEventDragnDropData *ddd = static_cast(data); + GHOST_TEventDragnDropData *ddd = reinterpret_cast( + GHOST_GetEventData(evt)); if (ddd->dataType == GHOST_kDragnDropTypeFilenames) { - const GHOST_TStringArray *stra = static_cast(ddd->data); - ps->argc_next = stra->count; - ps->argv_next = static_cast( - MEM_mallocN(sizeof(char **) * ps->argc_next, __func__)); - for (int i = 0; i < stra->count; i++) { - ps->argv_next[i] = BLI_strdup(reinterpret_cast(stra->strings[i])); + GHOST_TStringArray *stra = reinterpret_cast(ddd->data); + int a; + + for (a = 0; a < stra->count; a++) { + STRNCPY(ps->dropped_file, (char *)stra->strings[a]); + ps->go = false; + printf("drop file %s\n", stra->strings[a]); + break; /* only one drop element supported now */ } - ps->go = false; - printf("dropped %s, %d file(s)\n", ps->argv_next[0], ps->argc_next); } break; } default: - /* Quiet warnings. */ + /* quiet warnings */ break; } @@ -1633,23 +1576,23 @@ static GHOST_WindowHandle playanim_window_open( static void playanim_window_zoom(PlayState *ps, const float zoom_offset) { - int size[2]; - // int ofs[2]; /* UNUSED */ + int sizex, sizey; + // int ofsx, ofsy; /* UNUSED */ if (ps->zoom + zoom_offset > 0.0f) { ps->zoom += zoom_offset; } - // playanim_window_get_position(&ofs[0], &ofs[1]); - // playanim_window_get_size(ps->ghost_data.window, &size[0], &size[1]); - // ofs[0] += size[0] / 2; /* UNUSED */ - // ofs[1] += size[1] / 2; /* UNUSED */ - size[0] = ps->zoom * ps->ibuf_size[0]; - size[1] = ps->zoom * ps->ibuf_size[1]; - // ofs[0] -= size[0] / 2; /* UNUSED */ - // ofs[1] -= size[1] / 2; /* UNUSED */ - // window_set_position(ps->ghost_data.window, size[0], size[1]); - GHOST_SetClientSize(ps->ghost_data.window, size[0], size[1]); + // playanim_window_get_position(&ofsx, &ofsy); + playanim_window_get_size(ps->ghost_data.window, &sizex, &sizey); + // ofsx += sizex / 2; /* UNUSED */ + // ofsy += sizey / 2; /* UNUSED */ + sizex = ps->zoom * ps->ibufx; + sizey = ps->zoom * ps->ibufy; + // ofsx -= sizex / 2; /* UNUSED */ + // ofsy -= sizey / 2; /* UNUSED */ + // window_set_position(ps->ghost_data.window, sizex, sizey); + GHOST_SetClientSize(ps->ghost_data.window, sizex, sizey); } static bool playanim_window_font_scale_from_dpi(PlayState *ps) @@ -1659,7 +1602,7 @@ static bool playanim_window_font_scale_from_dpi(PlayState *ps) const int font_size = int(font_size_base * scale) + 0.5f; bool changed = false; if (ps->font_size != font_size) { - BLF_size(ps->font_id, font_size); + BLF_size(ps->fontid, font_size); ps->font_size = font_size; changed = true; } @@ -1670,15 +1613,18 @@ static bool playanim_window_font_scale_from_dpi(PlayState *ps) } /** - * \return True when `args_next` is filled with arguments used to re-run this function - * (used for drag & drop). + * \return The a path used to restart the animation player or nullptr to exit. */ -static bool wm_main_playanim_intern(int argc, const char **argv, PlayArgs *args_next) +static char *wm_main_playanim_intern(int argc, const char **argv) { ImBuf *ibuf = nullptr; - int window_pos[2] = {0, 0}; - int frame_start = -1; - int frame_end = -1; + static char filepath[FILE_MAX]; /* abused to return dropped file path */ + int i; + /* This was done to disambiguate the name for use under c++. */ + int start_x = 0, start_y = 0; + int sfra = -1; + int efra = -1; + int totblock; PlayState ps{}; @@ -1687,22 +1633,21 @@ static bool wm_main_playanim_intern(int argc, const char **argv, PlayArgs *args_ ps.next_frame = 1; ps.once = false; ps.pingpong = false; - ps.no_frame_skip = false; - ps.single_step = false; - ps.wait = false; + ps.noskip = false; + ps.sstep = false; + ps.wait2 = false; ps.stopped = false; ps.loading = false; ps.picture = nullptr; - ps.show_frame_indicator = false; - ps.argc_next = 0; - ps.argv_next = nullptr; + ps.indicator = false; + ps.dropped_file[0] = 0; ps.zoom = 1.0f; ps.draw_flip[0] = false; ps.draw_flip[1] = false; - ps.frame_step = 1; + ps.fstep = 1; - ps.font_id = -1; + ps.fontid = -1; STRNCPY(ps.display_ctx.display_settings.display_device, IMB_colormanagement_role_colorspace_name_get(COLOR_ROLE_DEFAULT_BYTE)); @@ -1710,91 +1655,94 @@ static bool wm_main_playanim_intern(int argc, const char **argv, PlayArgs *args_ &ps.display_ctx.display_settings); ps.display_ctx.ui_scale = 1.0f; - while ((argc > 0) && (argv[0][0] == '-')) { - switch (argv[0][1]) { - case 'm': { - g_playanim.from_disk = true; - break; - } - case 'p': { - if (argc > 2) { - window_pos[0] = atoi(argv[1]); - window_pos[1] = atoi(argv[2]); - argc -= 2; - argv += 2; - } - else { - printf("too few arguments for -p (need 2): skipping\n"); - } - break; - } - case 'f': { - if (argc > 2) { - double fps = atof(argv[1]); - double fps_base = atof(argv[2]); - if (fps == 0.0) { - fps = 1; - printf("invalid fps, forcing 1\n"); + /* Skip the first argument which is assumed to be '-a' (used to launch this player). */ + while (argc > 1) { + if (argv[1][0] == '-') { + switch (argv[1][1]) { + case 'm': + fromdisk = true; + break; + case 'p': + if (argc > 3) { + start_x = atoi(argv[2]); + start_y = atoi(argv[3]); + argc -= 2; + argv += 2; } - g_playanim.swap_time = fps_base / fps; - argc -= 2; - argv += 2; - } - else { - printf("too few arguments for -f (need 2): skipping\n"); - } - break; - } - case 's': { - frame_start = atoi(argv[1]); - CLAMP(frame_start, 1, MAXFRAME); - argc--; - argv++; - break; - } - case 'e': { - frame_end = atoi(argv[1]); - CLAMP(frame_end, 1, MAXFRAME); - argc--; - argv++; - break; - } - case 'j': { - ps.frame_step = atoi(argv[1]); - CLAMP(ps.frame_step, 1, MAXFRAME); - g_playanim.swap_time *= ps.frame_step; - argc--; - argv++; - break; - } - case 'c': { + else { + printf("too few arguments for -p (need 2): skipping\n"); + } + break; + case 'f': + if (argc > 3) { + double fps = atof(argv[2]); + double fps_base = atof(argv[3]); + if (fps == 0.0) { + fps = 1; + printf( + "invalid fps," + "forcing 1\n"); + } + swaptime = fps_base / fps; + argc -= 2; + argv += 2; + } + else { + printf("too few arguments for -f (need 2): skipping\n"); + } + break; + case 's': + sfra = atoi(argv[2]); + CLAMP(sfra, 1, MAXFRAME); + argc--; + argv++; + break; + case 'e': + efra = atoi(argv[2]); + CLAMP(efra, 1, MAXFRAME); + argc--; + argv++; + break; + case 'j': + ps.fstep = atoi(argv[2]); + CLAMP(ps.fstep, 1, MAXFRAME); + swaptime *= ps.fstep; + argc--; + argv++; + break; + case 'c': { #ifdef USE_FRAME_CACHE_LIMIT - const int memory_in_mb = max_ii(0, atoi(argv[1])); - g_frame_cache.memory_limit = size_t(memory_in_mb) * (1024 * 1024); + const int memory_in_mb = max_ii(0, atoi(argv[2])); + g_frame_cache.memory_limit = size_t(memory_in_mb) * (1024 * 1024); #endif - argc--; - argv++; - break; - } - default: { - printf("unknown option '%c': skipping\n", argv[0][1]); - break; + argc--; + argv++; + break; + } + default: + printf("unknown option '%c': skipping\n", argv[1][1]); + break; } + argc--; + argv++; + } + else { + break; } - argc--; - argv++; } - if (argc == 0) { + if (argc > 1) { + STRNCPY(filepath, argv[1]); + } + else { printf("%s: no filepath argument given\n", __func__); exit(EXIT_FAILURE); } - const char *filepath = argv[0]; - if (IMB_isanim(filepath)) { - /* OCIO_TODO: support different input color spaces. */ - anim *anim = IMB_open_anim(filepath, IB_rect, 0, nullptr); + /* OCIO_TODO: support different input color spaces */ + anim *anim; + anim = IMB_open_anim(filepath, IB_rect, 0, nullptr); if (anim) { ibuf = IMB_anim_absolute(anim, 0, IMB_TC_NONE, IMB_PROXY_NONE); IMB_close_anim(anim); @@ -1807,7 +1755,7 @@ static bool wm_main_playanim_intern(int argc, const char **argv, PlayArgs *args_ } if (ibuf == nullptr) { - /* OCIO_TODO: support different input color space. */ + /* OCIO_TODO: support different input color space */ ibuf = IMB_loadiffname(filepath, IB_rect, nullptr); } @@ -1837,12 +1785,8 @@ static bool wm_main_playanim_intern(int argc, const char **argv, PlayArgs *args_ GHOST_AddEventConsumer(ps.ghost_data.system, ghost_event_consumer); - ps.ghost_data.window = playanim_window_open(ps.ghost_data.system, - "Blender Animation Player", - window_pos[0], - window_pos[1], - ibuf->x, - ibuf->y); + ps.ghost_data.window = playanim_window_open( + ps.ghost_data.system, "Blender Animation Player", start_x, start_y, ibuf->x, ibuf->y); } // GHOST_ActivateWindowDrawingContext(ps.ghost_data.window); @@ -1851,18 +1795,18 @@ static bool wm_main_playanim_intern(int argc, const char **argv, PlayArgs *args_ ps.ghost_data.gpu_context = GPU_context_create(ps.ghost_data.window, nullptr); GPU_init(); - /* Initialize the font. */ + /* initialize the font */ BLF_init(); - ps.font_id = BLF_load_mono_default(false); + ps.fontid = BLF_load_mono_default(false); ps.font_size = -1; /* Force update. */ playanim_window_font_scale_from_dpi(&ps); - ps.ibuf_size[0] = ibuf->x; - ps.ibuf_size[1] = ibuf->y; + ps.ibufx = ibuf->x; + ps.ibufy = ibuf->y; - ps.display_ctx.size[0] = ps.ibuf_size[0]; - ps.display_ctx.size[1] = ps.ibuf_size[1]; + ps.display_ctx.size[0] = ps.ibufx; + ps.display_ctx.size[1] = ps.ibufy; GPU_render_begin(); GPU_render_step(); @@ -1873,70 +1817,51 @@ static bool wm_main_playanim_intern(int argc, const char **argv, PlayArgs *args_ playanim_window_get_size(ps.ghost_data.window, &window_size[0], &window_size[1]); GPU_viewport(0, 0, window_size[0], window_size[1]); GPU_scissor(0, 0, window_size[0], window_size[1]); - playanim_gpu_matrix(); + playanim_gl_matrix(); } GHOST_SwapWindowBuffers(ps.ghost_data.window); GPU_render_end(); - /* One of the frames was invalid or not passed in. */ - if (frame_start == -1 || frame_end == -1) { - frame_start = 1; - if (argc == 1) { - /* A single file was passed in, attempt to load all images from an image sequence. - * (if it is an image sequence). */ - frame_end = MAXFRAME; - } - else { - /* Multiple files passed in, show each file without expanding image sequences. - * This occurs when dropping multiple files. */ - frame_end = 1; - } + if (sfra == -1 || efra == -1) { + /* one of the frames was invalid, just use all images */ + sfra = 1; + efra = MAXFRAME; } - build_pict_list(&ps.picsbase, - &ps.ghost_data, - &ps.display_ctx, - filepath, - (frame_end - frame_start) + 1, - ps.frame_step, - &ps.loading); + build_pict_list( + &ps.ghost_data, &ps.display_ctx, filepath, (efra - sfra) + 1, ps.fstep, &ps.loading); #ifdef WITH_AUDASPACE - g_audaspace.source = AUD_Sound_file(filepath); - if (!BLI_listbase_is_empty(&ps.picsbase)) { - anim *anim_movie = static_cast(ps.picsbase.first)->anim; + source = AUD_Sound_file(filepath); + if (!BLI_listbase_is_empty(&picsbase)) { + anim *anim_movie = ((PlayAnimPict *)picsbase.first)->anim; if (anim_movie) { short frs_sec = 25; float frs_sec_base = 1.0; IMB_anim_get_fps(anim_movie, &frs_sec, &frs_sec_base, true); - g_playanim.fps_movie = double(frs_sec) / double(frs_sec_base); - /* Enforce same fps for movie as sound. */ - g_playanim.swap_time = ps.frame_step / g_playanim.fps_movie; + fps_movie = double(frs_sec) / double(frs_sec_base); + /* enforce same fps for movie as sound */ + swaptime = ps.fstep / fps_movie; } } #endif - for (int i = 1; i < argc; i++) { - filepath = argv[i]; - build_pict_list(&ps.picsbase, - &ps.ghost_data, - &ps.display_ctx, - filepath, - (frame_end - frame_start) + 1, - ps.frame_step, - &ps.loading); + for (i = 2; i < argc; i++) { + STRNCPY(filepath, argv[i]); + build_pict_list( + &ps.ghost_data, &ps.display_ctx, filepath, (efra - sfra) + 1, ps.fstep, &ps.loading); } IMB_freeImBuf(ibuf); ibuf = nullptr; pupdate_time(); - g_playanim.total_time = 0.0; + ptottime = 0; -/* Newly added in 2.6x, without this images never get freed. */ +/* newly added in 2.6x, without this images never get freed */ #define USE_IMB_CACHE while (ps.go) { @@ -1945,10 +1870,10 @@ static bool wm_main_playanim_intern(int argc, const char **argv, PlayArgs *args_ } if (ps.direction == 1) { - ps.picture = static_cast(ps.picsbase.first); + ps.picture = static_cast(picsbase.first); } else { - ps.picture = static_cast(ps.picsbase.last); + ps.picture = static_cast(picsbase.last); } if (ps.picture == nullptr) { @@ -1963,15 +1888,15 @@ static bool wm_main_playanim_intern(int argc, const char **argv, PlayArgs *args_ ps.picture = ps.picture->prev; } } - if (g_playanim.total_time > 0.0) { - g_playanim.total_time = 0.0; + if (ptottime > 0.0) { + ptottime = 0.0; } #ifdef WITH_AUDASPACE - if (g_audaspace.playback_handle) { - AUD_Handle_stop(g_audaspace.playback_handle); + if (playback_handle) { + AUD_Handle_stop(playback_handle); } - g_audaspace.playback_handle = AUD_Device_play(g_audaspace.audio_device, g_audaspace.source, 1); + playback_handle = AUD_Device_play(audio_device, source, 1); update_sound_fps(); #endif @@ -2011,16 +1936,16 @@ static bool wm_main_playanim_intern(int argc, const char **argv, PlayArgs *args_ while (pupdate_time()) { PIL_sleep_ms(1); } - g_playanim.total_time -= g_playanim.swap_time; + ptottime -= swaptime; playanim_toscreen(&ps, ps.picture, ibuf); } if (ps.once) { if (ps.picture->next == nullptr) { - ps.wait = true; + ps.wait2 = true; } else if (ps.picture->prev == nullptr) { - ps.wait = true; + ps.wait2 = true; } } @@ -2032,17 +1957,17 @@ static bool wm_main_playanim_intern(int argc, const char **argv, PlayArgs *args_ if (ps.go == false) { break; } - playanim_change_frame(&ps); + change_frame(&ps); if (!has_event) { PIL_sleep_ms(1); } - if (ps.wait) { + if (ps.wait2) { continue; } - ps.wait = ps.single_step; + ps.wait2 = ps.sstep; - if (ps.wait == false && ps.stopped) { + if (ps.wait2 == false && ps.stopped) { ps.stopped = false; } @@ -2056,19 +1981,19 @@ static bool wm_main_playanim_intern(int argc, const char **argv, PlayArgs *args_ if (ps.once && ps.picture != nullptr) { if (ps.picture->next == nullptr) { - ps.wait = true; + ps.wait2 = true; } else if (ps.picture->prev == nullptr) { - ps.wait = true; + ps.wait2 = true; } } - if (ps.wait || g_playanim.total_time < g_playanim.swap_time || ps.no_frame_skip) { + if (ps.wait2 || ptottime < swaptime || ps.noskip) { break; } - g_playanim.total_time -= g_playanim.swap_time; + ptottime -= swaptime; } - if (ps.picture == nullptr && ps.single_step) { + if (ps.picture == nullptr && ps.sstep) { ps.picture = playanim_step(ps.picture, ps.next_frame); } } @@ -2077,7 +2002,7 @@ static bool wm_main_playanim_intern(int argc, const char **argv, PlayArgs *args_ } } } - while ((ps.picture = static_cast(BLI_pophead(&ps.picsbase)))) { + while ((ps.picture = static_cast(BLI_pophead(&picsbase)))) { if (ps.picture->anim) { if ((ps.picture->next == nullptr) || (ps.picture->next->anim != ps.picture->anim)) { IMB_close_anim(ps.picture->anim); @@ -2090,10 +2015,8 @@ static bool wm_main_playanim_intern(int argc, const char **argv, PlayArgs *args_ if (ps.picture->mem) { MEM_freeN(ps.picture->mem); } - if (ps.picture->error_message) { - MEM_freeN(static_cast(ps.picture->error_message)); - } - MEM_freeN(const_cast(ps.picture->filepath)); + + MEM_freeN((void *)ps.picture->filepath); MEM_freeN(ps.picture); } @@ -2104,6 +2027,8 @@ static bool wm_main_playanim_intern(int argc, const char **argv, PlayArgs *args_ } #endif + BLI_freelistN(&picsbase); + #ifdef USE_FRAME_CACHE_LIMIT BLI_freelistN(&g_frame_cache.pics); g_frame_cache.pics_len = 0; @@ -2111,20 +2036,20 @@ static bool wm_main_playanim_intern(int argc, const char **argv, PlayArgs *args_ #endif #ifdef WITH_AUDASPACE - if (g_audaspace.playback_handle) { - AUD_Handle_stop(g_audaspace.playback_handle); - g_audaspace.playback_handle = nullptr; + if (playback_handle) { + AUD_Handle_stop(playback_handle); + playback_handle = nullptr; } - if (g_audaspace.scrub_handle) { - AUD_Handle_stop(g_audaspace.scrub_handle); - g_audaspace.scrub_handle = nullptr; + if (scrub_handle) { + AUD_Handle_stop(scrub_handle); + scrub_handle = nullptr; } - AUD_Sound_free(g_audaspace.source); - g_audaspace.source = nullptr; + AUD_Sound_free(source); + source = nullptr; #endif - /* We still miss freeing a lot! - * But many areas could skip initialization too for anim play. */ + /* we still miss freeing a lot!, + * but many areas could skip initialization too for anim play */ DEG_free_node_types(); @@ -2141,31 +2066,33 @@ static bool wm_main_playanim_intern(int argc, const char **argv, PlayArgs *args_ GHOST_DisposeWindow(ps.ghost_data.system, ps.ghost_data.window); - /* Early exit, IMB and BKE should be exited only in end. */ - if (ps.argv_next) { - args_next->argc = ps.argc_next; - args_next->argv = ps.argv_next; - return true; + /* early exit, IMB and BKE should be exited only in end */ + if (ps.dropped_file[0]) { + STRNCPY(filepath, ps.dropped_file); + return filepath; } GHOST_DisposeSystem(ps.ghost_data.system); IMB_exit(); -#if 0 - const int totblock = MEM_get_memory_blocks_in_use(); + totblock = MEM_get_memory_blocks_in_use(); if (totblock != 0) { - /* Prints many `bAKey`, `bArgument` messages which are tricky to fix. */ +/* prints many bAKey, bArgument's which are tricky to fix */ +#if 0 printf("Error Totblock: %d\n", totblock); MEM_printmemlist(); - } #endif + } - return false; + return nullptr; } void WM_main_playanim(int argc, const char **argv) { + const char *argv_next[2]; + bool looping = true; + #ifdef WITH_AUDASPACE { AUD_DeviceSpecs specs; @@ -2176,36 +2103,30 @@ void WM_main_playanim(int argc, const char **argv) AUD_initOnce(); - if (!(g_audaspace.audio_device = AUD_init(nullptr, specs, 1024, "Blender"))) { - g_audaspace.audio_device = AUD_init("None", specs, 0, "Blender"); + if (!(audio_device = AUD_init(nullptr, specs, 1024, "Blender"))) { + audio_device = AUD_init("None", specs, 0, "Blender"); } } #endif - PlayArgs args_next = {0}; - do { - PlayArgs args_free = args_next; - args_next = {0}; + while (looping) { + const char *filepath = wm_main_playanim_intern(argc, argv); - if (wm_main_playanim_intern(argc, argv, &args_next)) { - argc = args_next.argc; - argv = const_cast(args_next.argv); + if (filepath) { /* use simple args */ + argv_next[0] = argv[0]; + argv_next[1] = filepath; + argc = 2; + + /* continue with new args */ + argv = argv_next; } else { - argc = 0; - argv = nullptr; + looping = false; } - - if (args_free.argv) { - for (int i = 0; i < args_free.argc; i++) { - MEM_freeN(args_free.argv[i]); - } - MEM_freeN(args_free.argv); - } - } while (argv != nullptr); + } #ifdef WITH_AUDASPACE - AUD_exit(g_audaspace.audio_device); + AUD_exit(audio_device); AUD_exitOnce(); #endif } diff --git a/source/blender/windowmanager/intern/wm_window.cc b/source/blender/windowmanager/intern/wm_window.cc index 70599343582..c5304718c8b 100644 --- a/source/blender/windowmanager/intern/wm_window.cc +++ b/source/blender/windowmanager/intern/wm_window.cc @@ -480,54 +480,48 @@ void wm_window_close(bContext *C, wmWindowManager *wm, wmWindow *win) void wm_window_title(wmWindowManager *wm, wmWindow *win) { - if (win->ghostwin == nullptr) { - return; - } - if (WM_window_is_temp_screen(win)) { /* Nothing to do for 'temp' windows, * because #WM_window_open always sets window title. */ - return; } + else if (win->ghostwin) { + GHOST_WindowHandle handle = static_cast(win->ghostwin); - GHOST_WindowHandle handle = static_cast(win->ghostwin); + const char *filepath = BKE_main_blendfile_path_from_global(); + const char *filename = BLI_path_basename(filepath); - const char *filepath = BKE_main_blendfile_path_from_global(); - const char *filename = BLI_path_basename(filepath); + const bool has_filepath = filepath[0] != '\0'; + const bool include_filepath = has_filepath && (filepath != filename) && + (GHOST_SetPath(handle, filepath) == GHOST_kFailure); - const bool has_filepath = filepath[0] != '\0'; - const bool include_filepath = has_filepath && (filepath != filename) && - (GHOST_SetPath(handle, filepath) == GHOST_kFailure); + std::string str; + str += wm->file_saved ? " " : "* "; + if (has_filepath) { + const size_t filename_no_ext_len = BLI_path_extension_or_end(filename) - filename; + str.append(filename, filename_no_ext_len); + } + else { + str += IFACE_("(Unsaved)"); + } - std::string str; - str += wm->file_saved ? " " : "* "; - if (has_filepath) { - const size_t filename_no_ext_len = BLI_path_extension_or_end(filename) - filename; - str.append(filename, filename_no_ext_len); + if (G_MAIN->recovered) { + str += IFACE_(" (Recovered)"); + } + + if (include_filepath) { + str += " [" + std::string(filepath) + "]"; + } + + str += " - Blender "; + str += BKE_blender_version_string_compact(); + + GHOST_SetTitle(handle, str.c_str()); + + /* Informs GHOST of unsaved changes to set the window modified visual indicator (macOS) + * and to give a hint of unsaved changes for a user warning mechanism in case of OS application + * terminate request (e.g., OS Shortcut Alt+F4, Command+Q, (...) or session end). */ + GHOST_SetWindowModifiedState(handle, bool(!wm->file_saved)); } - else { - str += IFACE_("(Unsaved)"); - } - - if (G_MAIN->recovered) { - str += IFACE_(" (Recovered)"); - } - - if (include_filepath) { - str += " ["; - str += filepath; - str += "]"; - } - - str += " - Blender "; - str += BKE_blender_version_string_compact(); - - GHOST_SetTitle(handle, str.c_str()); - - /* Informs GHOST of unsaved changes to set the window modified visual indicator (macOS) - * and to give a hint of unsaved changes for a user warning mechanism in case of OS application - * terminate request (e.g., OS Shortcut Alt+F4, Command+Q, (...) or session end). */ - GHOST_SetWindowModifiedState(handle, bool(!wm->file_saved)); } void WM_window_set_dpi(const wmWindow *win) @@ -586,9 +580,7 @@ void WM_window_set_dpi(const wmWindow *win) * inactive window must run #wm_window_update_eventstate_modifiers first to ensure no modifier * keys are held. See: #105277. */ -static void wm_window_update_eventstate_modifiers(wmWindowManager *wm, - wmWindow *win, - const uint64_t event_time_ms) +static void wm_window_update_eventstate_modifiers(wmWindowManager *wm, wmWindow *win) { const uint8_t keymodifier_sided[2] = { wm_ghost_modifier_query(MOD_SIDE_LEFT), @@ -607,7 +599,7 @@ static void wm_window_update_eventstate_modifiers(wmWindowManager *wm, for (int side = 0; side < 2; side++) { if ((keymodifier_sided[side] & g_modifier_table[i].flag) == 0) { kdata.key = g_modifier_table[i].ghost_key_pair[side]; - wm_event_add_ghostevent(wm, win, GHOST_kEventKeyUp, &kdata, event_time_ms); + wm_event_add_ghostevent(wm, win, GHOST_kEventKeyUp, &kdata); /* Only ever send one release event * (currently releasing multiple isn't needed and only confuses logic). */ break; @@ -620,7 +612,7 @@ static void wm_window_update_eventstate_modifiers(wmWindowManager *wm, for (int side = 0; side < 2; side++) { if (keymodifier_sided[side] & g_modifier_table[i].flag) { kdata.key = g_modifier_table[i].ghost_key_pair[side]; - wm_event_add_ghostevent(wm, win, GHOST_kEventKeyDown, &kdata, event_time_ms); + wm_event_add_ghostevent(wm, win, GHOST_kEventKeyDown, &kdata); } } } @@ -641,9 +633,7 @@ static void wm_window_update_eventstate_modifiers(wmWindowManager *wm, * NOTE(@ideasman42): Events generated for non-active windows are rare, * this happens when using the mouse-wheel over an unfocused window, see: #103722. */ -static void wm_window_update_eventstate_modifiers_clear(wmWindowManager *wm, - wmWindow *win, - const uint64_t event_time_ms) +static void wm_window_update_eventstate_modifiers_clear(wmWindowManager *wm, wmWindow *win) { /* Release all held modifiers before de-activating the window. */ if (win->eventstate->modifier != 0) { @@ -670,7 +660,7 @@ static void wm_window_update_eventstate_modifiers_clear(wmWindowManager *wm, for (int side = 0; side < 2; side++) { if ((keymodifier_sided[side] & g_modifier_table[i].flag) == 0) { kdata.key = g_modifier_table[i].ghost_key_pair[side]; - wm_event_add_ghostevent(wm, win, GHOST_kEventKeyUp, &kdata, event_time_ms); + wm_event_add_ghostevent(wm, win, GHOST_kEventKeyUp, &kdata); } } } @@ -1300,11 +1290,15 @@ static bool ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr C_void_pt bContext *C = static_cast(C_void_ptr); wmWindowManager *wm = CTX_wm_manager(C); GHOST_TEventType type = GHOST_GetEventType(evt); - - GHOST_WindowHandle ghostwin = GHOST_GetEventWindow(evt); +#if 0 + /* We may want to use time from ghost, currently `PIL_check_seconds_timer` is used instead. */ + uint64_t time = GHOST_GetEventTime(evt); +#endif if (type == GHOST_kEventQuitRequest) { /* Find an active window to display quit dialog in. */ + GHOST_WindowHandle ghostwin = GHOST_GetEventWindow(evt); + wmWindow *win; if (ghostwin && GHOST_ValidWindow(g_system, ghostwin)) { win = static_cast(GHOST_GetWindowUserData(ghostwin)); @@ -1320,307 +1314,321 @@ static bool ghost_event_proc(GHOST_EventHandle evt, GHOST_TUserDataPtr C_void_pt else { wm_exit_schedule_delayed(C); } - return true; } + else { + GHOST_WindowHandle ghostwin = GHOST_GetEventWindow(evt); + GHOST_TEventDataPtr data = GHOST_GetEventData(evt); - GHOST_TEventDataPtr data = GHOST_GetEventData(evt); - const uint64_t event_time_ms = GHOST_GetEventTime(evt); - - /* Ghost now can call this function for life resizes, - * but it should return if WM didn't initialize yet. - * Can happen on file read (especially full size window). */ - if ((wm->init_flag & WM_INIT_FLAG_WINDOW) == 0) { - return true; - } - if (!ghostwin) { - /* XXX: should be checked, why are we getting an event here, and what is it? */ - puts(" event has no window"); - return true; - } - if (!GHOST_ValidWindow(g_system, ghostwin)) { - /* XXX: should be checked, why are we getting an event here, and what is it? */ - puts(" event has invalid window"); - return true; - } - - wmWindow *win = static_cast(GHOST_GetWindowUserData(ghostwin)); - - switch (type) { - case GHOST_kEventWindowDeactivate: { - wm_window_update_eventstate_modifiers_clear(wm, win, event_time_ms); - - wm_event_add_ghostevent(wm, win, type, data, event_time_ms); - win->active = 0; - break; + /* Ghost now can call this function for life resizes, + * but it should return if WM didn't initialize yet. + * Can happen on file read (especially full size window). */ + if ((wm->init_flag & WM_INIT_FLAG_WINDOW) == 0) { + return true; } - case GHOST_kEventWindowActivate: { - /* Ensure the event state matches modifiers (window was inactive). */ - wm_window_update_eventstate_modifiers(wm, win, event_time_ms); - - /* Entering window, update mouse position (without sending an event). */ - wm_window_update_eventstate(win); - - /* No context change! `C->wm->windrawable` is drawable, or for area queues. */ - wm->winactive = win; - win->active = 1; - - /* keymodifier zero, it hangs on hotkeys that open windows otherwise */ - win->eventstate->keymodifier = 0; - - win->addmousemove = 1; /* enables highlighted buttons */ - - wm_window_make_drawable(wm, win); - - /* window might be focused by mouse click in configuration of window manager - * when focus is not following mouse - * click could have been done on a button and depending on window manager settings - * click would be passed to blender or not, but in any case button under cursor - * should be activated, so at max next click on button without moving mouse - * would trigger its handle function - * currently it seems to be common practice to generate new event for, but probably - * we'll need utility function for this? (sergey) - */ - wmEvent event; - wm_event_init_from_window(win, &event); - event.type = MOUSEMOVE; - event.val = KM_NOTHING; - copy_v2_v2_int(event.prev_xy, event.xy); - event.flag = eWM_EventFlag(0); - - wm_event_add(win, &event); - - break; + if (!ghostwin) { + /* XXX: should be checked, why are we getting an event here, and what is it? */ + puts(" event has no window"); + return true; } - case GHOST_kEventWindowClose: { - wm_window_close(C, wm, win); - break; + if (!GHOST_ValidWindow(g_system, ghostwin)) { + /* XXX: should be checked, why are we getting an event here, and what is it? */ + puts(" event has invalid window"); + return true; } - case GHOST_kEventWindowUpdate: { - if (G.debug & G_DEBUG_EVENTS) { - printf("%s: ghost redraw %d\n", __func__, win->winid); + wmWindow *win = static_cast(GHOST_GetWindowUserData(ghostwin)); + + switch (type) { + case GHOST_kEventWindowDeactivate: { + wm_window_update_eventstate_modifiers_clear(wm, win); + + wm_event_add_ghostevent(wm, win, type, data); + win->active = 0; + break; } + case GHOST_kEventWindowActivate: { + /* Ensure the event state matches modifiers (window was inactive). */ + wm_window_update_eventstate_modifiers(wm, win); - wm_window_make_drawable(wm, win); - WM_event_add_notifier(C, NC_WINDOW, nullptr); + /* Entering window, update mouse position (without sending an event). */ + wm_window_update_eventstate(win); - break; - } - case GHOST_kEventWindowUpdateDecor: { - if (G.debug & G_DEBUG_EVENTS) { - printf("%s: ghost redraw decor %d\n", __func__, win->winid); + /* No context change! `C->wm->windrawable` is drawable, or for area queues. */ + wm->winactive = win; + win->active = 1; + + /* keymodifier zero, it hangs on hotkeys that open windows otherwise */ + win->eventstate->keymodifier = 0; + + win->addmousemove = 1; /* enables highlighted buttons */ + + wm_window_make_drawable(wm, win); + + /* window might be focused by mouse click in configuration of window manager + * when focus is not following mouse + * click could have been done on a button and depending on window manager settings + * click would be passed to blender or not, but in any case button under cursor + * should be activated, so at max next click on button without moving mouse + * would trigger its handle function + * currently it seems to be common practice to generate new event for, but probably + * we'll need utility function for this? (sergey) + */ + wmEvent event; + wm_event_init_from_window(win, &event); + event.type = MOUSEMOVE; + event.val = KM_NOTHING; + copy_v2_v2_int(event.prev_xy, event.xy); + event.flag = eWM_EventFlag(0); + + wm_event_add(win, &event); + + break; } + case GHOST_kEventWindowClose: { + wm_window_close(C, wm, win); + break; + } + case GHOST_kEventWindowUpdate: { + if (G.debug & G_DEBUG_EVENTS) { + printf("%s: ghost redraw %d\n", __func__, win->winid); + } - wm_window_make_drawable(wm, win); + wm_window_make_drawable(wm, win); + WM_event_add_notifier(C, NC_WINDOW, nullptr); + + break; + } + case GHOST_kEventWindowUpdateDecor: { + if (G.debug & G_DEBUG_EVENTS) { + printf("%s: ghost redraw decor %d\n", __func__, win->winid); + } + + wm_window_make_drawable(wm, win); #if 0 /* NOTE(@ideasman42): Ideally we could swap-buffers to avoid a full redraw. * however this causes window flickering on resize with LIBDECOR under WAYLAND. */ wm_window_swap_buffers(win); #else - WM_event_add_notifier(C, NC_WINDOW, nullptr); + WM_event_add_notifier(C, NC_WINDOW, nullptr); #endif - break; - } - case GHOST_kEventWindowSize: - case GHOST_kEventWindowMove: { - GHOST_TWindowState state = GHOST_GetWindowState( - static_cast(win->ghostwin)); - win->windowstate = state; + break; + } + case GHOST_kEventWindowSize: + case GHOST_kEventWindowMove: { + GHOST_TWindowState state = GHOST_GetWindowState( + static_cast(win->ghostwin)); + win->windowstate = state; - WM_window_set_dpi(win); + WM_window_set_dpi(win); - /* win32: gives undefined window size when minimized */ - if (state != GHOST_kWindowStateMinimized) { - /* - * Ghost sometimes send size or move events when the window hasn't changed. - * One case of this is using COMPIZ on Linux. - * To alleviate the problem we ignore all such event here. - * - * It might be good to eventually do that at GHOST level, but that is for another time. - */ - if (wm_window_update_size_position(win)) { - const bScreen *screen = WM_window_get_active_screen(win); + /* win32: gives undefined window size when minimized */ + if (state != GHOST_kWindowStateMinimized) { + /* + * Ghost sometimes send size or move events when the window hasn't changed. + * One case of this is using COMPIZ on Linux. + * To alleviate the problem we ignore all such event here. + * + * It might be good to eventually do that at GHOST level, but that is for another time. + */ + if (wm_window_update_size_position(win)) { + const bScreen *screen = WM_window_get_active_screen(win); - /* debug prints */ - if (G.debug & G_DEBUG_EVENTS) { - const char *state_str; - state = GHOST_GetWindowState(static_cast(win->ghostwin)); + /* debug prints */ + if (G.debug & G_DEBUG_EVENTS) { + const char *state_str; + state = GHOST_GetWindowState(static_cast(win->ghostwin)); - if (state == GHOST_kWindowStateNormal) { - state_str = "normal"; - } - else if (state == GHOST_kWindowStateMinimized) { - state_str = "minimized"; - } - else if (state == GHOST_kWindowStateMaximized) { - state_str = "maximized"; - } - else if (state == GHOST_kWindowStateFullScreen) { - state_str = "full-screen"; - } - else { - state_str = ""; + if (state == GHOST_kWindowStateNormal) { + state_str = "normal"; + } + else if (state == GHOST_kWindowStateMinimized) { + state_str = "minimized"; + } + else if (state == GHOST_kWindowStateMaximized) { + state_str = "maximized"; + } + else if (state == GHOST_kWindowStateFullScreen) { + state_str = "full-screen"; + } + else { + state_str = ""; + } + + printf("%s: window %d state = %s\n", __func__, win->winid, state_str); + + if (type != GHOST_kEventWindowSize) { + printf("win move event pos %d %d size %d %d\n", + win->posx, + win->posy, + win->sizex, + win->sizey); + } } - printf("%s: window %d state = %s\n", __func__, win->winid, state_str); - - if (type != GHOST_kEventWindowSize) { - printf("win move event pos %d %d size %d %d\n", - win->posx, - win->posy, - win->sizex, - win->sizey); - } - } - - wm_window_make_drawable(wm, win); - BKE_icon_changed(screen->id.icon_id); - WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, nullptr); - WM_event_add_notifier(C, NC_WINDOW | NA_EDITED, nullptr); + wm_window_make_drawable(wm, win); + BKE_icon_changed(screen->id.icon_id); + WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, nullptr); + WM_event_add_notifier(C, NC_WINDOW | NA_EDITED, nullptr); #if defined(__APPLE__) || defined(WIN32) - /* MACOS and WIN32 don't return to the main-loop while resize. */ - int dummy_sleep_ms = 0; - wm_window_timers_process(C, &dummy_sleep_ms); - wm_event_do_handlers(C); - wm_event_do_notifiers(C); - wm_draw_update(C); + /* MACOS and WIN32 don't return to the main-loop while resize. */ + int dummy_sleep_ms = 0; + wm_window_timers_process(C, &dummy_sleep_ms); + wm_event_do_handlers(C); + wm_event_do_notifiers(C); + wm_draw_update(C); #endif + } } + break; } - break; - } - case GHOST_kEventWindowDPIHintChanged: { - WM_window_set_dpi(win); - /* font's are stored at each DPI level, without this we can easy load 100's of fonts */ - BLF_cache_clear(); + case GHOST_kEventWindowDPIHintChanged: { + WM_window_set_dpi(win); + /* font's are stored at each DPI level, without this we can easy load 100's of fonts */ + BLF_cache_clear(); - WM_main_add_notifier(NC_WINDOW, nullptr); /* full redraw */ - WM_main_add_notifier(NC_SCREEN | NA_EDITED, nullptr); /* refresh region sizes */ - break; - } - - case GHOST_kEventOpenMainFile: { - const char *path = static_cast(data); - - if (path) { - wmOperatorType *ot = WM_operatortype_find("WM_OT_open_mainfile", false); - /* operator needs a valid window in context, ensures - * it is correctly set */ - CTX_wm_window_set(C, win); - - PointerRNA props_ptr; - WM_operator_properties_create_ptr(&props_ptr, ot); - RNA_string_set(&props_ptr, "filepath", path); - RNA_boolean_set(&props_ptr, "display_file_selector", false); - WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &props_ptr, nullptr); - WM_operator_properties_free(&props_ptr); - - CTX_wm_window_set(C, nullptr); + WM_main_add_notifier(NC_WINDOW, nullptr); /* full redraw */ + WM_main_add_notifier(NC_SCREEN | NA_EDITED, nullptr); /* refresh region sizes */ + break; } - break; - } - case GHOST_kEventDraggingDropDone: { - const GHOST_TEventDragnDropData *ddd = static_cast(data); - /* Ensure the event state matches modifiers (window was inactive). */ - wm_window_update_eventstate_modifiers(wm, win, event_time_ms); - /* Entering window, update mouse position (without sending an event). */ - wm_window_update_eventstate(win); + case GHOST_kEventOpenMainFile: { + const char *path = static_cast(GHOST_GetEventData(evt)); - wmEvent event; - wm_event_init_from_window(win, &event); /* copy last state, like mouse coords */ + if (path) { + wmOperatorType *ot = WM_operatortype_find("WM_OT_open_mainfile", false); + /* operator needs a valid window in context, ensures + * it is correctly set */ + CTX_wm_window_set(C, win); - /* activate region */ - event.type = MOUSEMOVE; - event.val = KM_NOTHING; - copy_v2_v2_int(event.prev_xy, event.xy); + PointerRNA props_ptr; + WM_operator_properties_create_ptr(&props_ptr, ot); + RNA_string_set(&props_ptr, "filepath", path); + RNA_boolean_set(&props_ptr, "display_file_selector", false); + WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &props_ptr, nullptr); + WM_operator_properties_free(&props_ptr); - copy_v2_v2_int(event.xy, &ddd->x); - wm_cursor_position_from_ghost_screen_coords(win, &event.xy[0], &event.xy[1]); - - /* The values from #wm_window_update_eventstate may not match (under WAYLAND they don't) - * Write this into the event state. */ - copy_v2_v2_int(win->eventstate->xy, event.xy); - - event.flag = eWM_EventFlag(0); - - /* No context change! `C->wm->windrawable` is drawable, or for area queues. */ - wm->winactive = win; - win->active = 1; - - wm_event_add(win, &event); - - /* make blender drop event with custom data pointing to wm drags */ - event.type = EVT_DROP; - event.val = KM_RELEASE; - event.custom = EVT_DATA_DRAGDROP; - event.customdata = &wm->drags; - event.customdata_free = true; - - wm_event_add(win, &event); - - // printf("Drop detected\n"); - - /* add drag data to wm for paths: */ - - if (ddd->dataType == GHOST_kDragnDropTypeFilenames) { - const GHOST_TStringArray *stra = static_cast(ddd->data); - - for (int a = 0; a < stra->count; a++) { - printf("drop file %s\n", stra->strings[a]); - /* try to get icon type from extension */ - int icon = ED_file_extension_icon((char *)stra->strings[a]); - wmDragPath *path_data = WM_drag_create_path_data((char *)stra->strings[a]); - WM_event_start_drag(C, icon, WM_DRAG_PATH, path_data, 0.0, WM_DRAG_NOP); - /* Void pointer should point to string, it makes a copy. */ - break; /* only one drop element supported now */ + CTX_wm_window_set(C, nullptr); } + break; } + case GHOST_kEventDraggingDropDone: { + GHOST_TEventDragnDropData *ddd = static_cast( + GHOST_GetEventData(evt)); - break; - } - case GHOST_kEventNativeResolutionChange: { - /* Only update if the actual pixel size changes. */ - float prev_pixelsize = U.pixelsize; - WM_window_set_dpi(win); - - if (U.pixelsize != prev_pixelsize) { - BKE_icon_changed(WM_window_get_active_screen(win)->id.icon_id); - - /* Close all popups since they are positioned with the pixel - * size baked in and it's difficult to correct them. */ - CTX_wm_window_set(C, win); - UI_popup_handlers_remove_all(C, &win->modalhandlers); - CTX_wm_window_set(C, nullptr); - - wm_window_make_drawable(wm, win); - - WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, nullptr); - WM_event_add_notifier(C, NC_WINDOW | NA_EDITED, nullptr); - } - - break; - } - case GHOST_kEventButtonDown: - case GHOST_kEventButtonUp: { - if (win->active == 0) { - /* Entering window, update cursor/tablet state & modifiers. - * (ghost sends win-activate *after* the mouse-click in window!) */ - wm_window_update_eventstate_modifiers(wm, win, event_time_ms); + /* Ensure the event state matches modifiers (window was inactive). */ + wm_window_update_eventstate_modifiers(wm, win); + /* Entering window, update mouse position (without sending an event). */ wm_window_update_eventstate(win); - } - wm_event_add_ghostevent(wm, win, type, data, event_time_ms); - break; - } - default: { - wm_event_add_ghostevent(wm, win, type, data, event_time_ms); - break; + wmEvent event; + wm_event_init_from_window(win, &event); /* copy last state, like mouse coords */ + + /* activate region */ + event.type = MOUSEMOVE; + event.val = KM_NOTHING; + copy_v2_v2_int(event.prev_xy, event.xy); + + wm_cursor_position_from_ghost_screen_coords(win, &ddd->x, &ddd->y); + event.xy[0] = ddd->x; + event.xy[1] = ddd->y; + + /* The values from #wm_window_update_eventstate may not match (under WAYLAND they don't) + * Write this into the event state. */ + copy_v2_v2_int(win->eventstate->xy, event.xy); + + event.flag = eWM_EventFlag(0); + + /* No context change! `C->wm->windrawable` is drawable, or for area queues. */ + wm->winactive = win; + win->active = 1; + + wm_event_add(win, &event); + + /* make blender drop event with custom data pointing to wm drags */ + event.type = EVT_DROP; + event.val = KM_RELEASE; + event.custom = EVT_DATA_DRAGDROP; + event.customdata = &wm->drags; + event.customdata_free = true; + + wm_event_add(win, &event); + + // printf("Drop detected\n"); + + /* add drag data to wm for paths: */ + + if (ddd->dataType == GHOST_kDragnDropTypeFilenames) { + GHOST_TStringArray *stra = static_cast(ddd->data); + + for (int a = 0; a < stra->count; a++) { + printf("drop file %s\n", stra->strings[a]); + /* try to get icon type from extension */ + int icon = ED_file_extension_icon((char *)stra->strings[a]); + wmDragPath *path_data = WM_drag_create_path_data((char *)stra->strings[a]); + WM_event_start_drag(C, icon, WM_DRAG_PATH, path_data, 0.0, WM_DRAG_NOP); + /* Void pointer should point to string, it makes a copy. */ + break; /* only one drop element supported now */ + } + } + + break; + } + case GHOST_kEventNativeResolutionChange: { + /* Only update if the actual pixel size changes. */ + float prev_pixelsize = U.pixelsize; + WM_window_set_dpi(win); + + if (U.pixelsize != prev_pixelsize) { + BKE_icon_changed(WM_window_get_active_screen(win)->id.icon_id); + + /* Close all popups since they are positioned with the pixel + * size baked in and it's difficult to correct them. */ + CTX_wm_window_set(C, win); + UI_popup_handlers_remove_all(C, &win->modalhandlers); + CTX_wm_window_set(C, nullptr); + + wm_window_make_drawable(wm, win); + + WM_event_add_notifier(C, NC_SCREEN | NA_EDITED, nullptr); + WM_event_add_notifier(C, NC_WINDOW | NA_EDITED, nullptr); + } + + break; + } + case GHOST_kEventTrackpad: { + GHOST_TEventTrackpadData *pd = static_cast(data); + + wm_cursor_position_from_ghost_screen_coords(win, &pd->x, &pd->y); + wm_event_add_ghostevent(wm, win, type, data); + break; + } + case GHOST_kEventCursorMove: { + GHOST_TEventCursorData *cd = static_cast(data); + + wm_cursor_position_from_ghost_screen_coords(win, &cd->x, &cd->y); + wm_event_add_ghostevent(wm, win, type, data); + break; + } + case GHOST_kEventButtonDown: + case GHOST_kEventButtonUp: { + if (win->active == 0) { + /* Entering window, update cursor/tablet state & modifiers. + * (ghost sends win-activate *after* the mouse-click in window!) */ + wm_window_update_eventstate_modifiers(wm, win); + wm_window_update_eventstate(win); + } + + wm_event_add_ghostevent(wm, win, type, data); + break; + } + default: { + wm_event_add_ghostevent(wm, win, type, data); + break; + } } } - return true; } @@ -1977,9 +1985,6 @@ eWM_CapabilitiesFlag WM_capabilities_flag() if (ghost_flag & GHOST_kCapabilityDesktopSample) { flag |= WM_CAPABILITY_DESKTOP_SAMPLE; } - if (ghost_flag & GHOST_kCapabilityInputIME) { - flag |= WM_CAPABILITY_INPUT_IME; - } return flag; } @@ -2110,70 +2115,6 @@ void WM_event_timer_remove_notifier(wmWindowManager *wm, wmWindow *win, wmTimer /** \} */ -/* -------------------------------------------------------------------- */ -/** \name Clipboard Wrappers - * - * GHOST function wrappers that support a "fake" clipboard used when simulating events. - * This is useful user actions can be simulated while the system is in use without the system's - * clipboard getting overwritten. - * \{ */ - -struct { - char *buffers[2]; -} *g_wm_clipboard_text_simulate = nullptr; - -void wm_clipboard_free() -{ - if (g_wm_clipboard_text_simulate == nullptr) { - return; - } - for (int i = 0; i < ARRAY_SIZE(g_wm_clipboard_text_simulate->buffers); i++) { - char *buf = g_wm_clipboard_text_simulate->buffers[i]; - if (buf) { - MEM_freeN(buf); - } - } - MEM_freeN(g_wm_clipboard_text_simulate); - g_wm_clipboard_text_simulate = nullptr; -} - -static char *wm_clipboard_text_get_impl(bool selection) -{ - if (UNLIKELY(G.f & G_FLAG_EVENT_SIMULATE)) { - if (g_wm_clipboard_text_simulate == nullptr) { - return nullptr; - } - const char *buf_src = g_wm_clipboard_text_simulate->buffers[int(selection)]; - if (buf_src == nullptr) { - return nullptr; - } - size_t size = strlen(buf_src) + 1; - char *buf = static_cast(malloc(size)); - memcpy(buf, buf_src, size); - return buf; - } - - return GHOST_getClipboard(selection); -} - -static void wm_clipboard_text_set_impl(const char *buf, bool selection) -{ - if (UNLIKELY(G.f & G_FLAG_EVENT_SIMULATE)) { - if (g_wm_clipboard_text_simulate == nullptr) { - g_wm_clipboard_text_simulate = static_cast( - MEM_callocN(sizeof(*g_wm_clipboard_text_simulate), __func__)); - } - char **buf_src_p = &(g_wm_clipboard_text_simulate->buffers[int(selection)]); - MEM_SAFE_FREE(*buf_src_p); - *buf_src_p = BLI_strdup(buf); - return; - } - - GHOST_putClipboard(buf, selection); -} - -/** \} */ - /* -------------------------------------------------------------------- */ /** \name Clipboard * \{ */ @@ -2188,7 +2129,7 @@ static char *wm_clipboard_text_get_ex(bool selection, return nullptr; } - char *buf = wm_clipboard_text_get_impl(selection); + char *buf = GHOST_getClipboard(selection); if (!buf) { *r_len = 0; return nullptr; @@ -2277,10 +2218,10 @@ void WM_clipboard_text_set(const char *buf, bool selection) } *p2 = '\0'; - wm_clipboard_text_set_impl(newbuf, selection); + GHOST_putClipboard(newbuf, selection); MEM_freeN(newbuf); #else - wm_clipboard_text_set_impl(buf, selection); + GHOST_putClipboard(buf, selection); #endif } } @@ -2806,9 +2747,6 @@ bool WM_window_is_temp_screen(const wmWindow *win) void wm_window_IME_begin(wmWindow *win, int x, int y, int w, int h, bool complete) { BLI_assert(win); - if ((WM_capabilities_flag() & WM_CAPABILITY_INPUT_IME) == 0) { - return; - } /* Convert to native OS window coordinates. */ float fac = GHOST_GetNativePixelSize(static_cast(win->ghostwin)); @@ -2820,20 +2758,10 @@ void wm_window_IME_begin(wmWindow *win, int x, int y, int w, int h, bool complet void wm_window_IME_end(wmWindow *win) { - if ((WM_capabilities_flag() & WM_CAPABILITY_INPUT_IME) == 0) { - return; - } + BLI_assert(win && win->ime_data); - BLI_assert(win); - /* NOTE(@ideasman42): on WAYLAND a call to "begin" must be closed by an "end" call. - * Even if no IME events were generated (which assigned `ime_data`). - * TODO: check if #GHOST_EndIME can run on WIN32 & APPLE without causing problems. */ -# if defined(WIN32) || defined(__APPLE__) - BLI_assert(win->ime_data); -# endif GHOST_EndIME(static_cast(win->ghostwin)); win->ime_data = nullptr; - win->ime_data_is_composing = false; } #endif /* WITH_INPUT_IME */ diff --git a/source/blender/windowmanager/wm_event_system.h b/source/blender/windowmanager/wm_event_system.h index d4be459533b..b658ecf1350 100644 --- a/source/blender/windowmanager/wm_event_system.h +++ b/source/blender/windowmanager/wm_event_system.h @@ -134,11 +134,7 @@ void wm_event_do_handlers(bContext *C); /** * Windows store own event queues #wmWindow.event_queue (no #bContext here). */ -void wm_event_add_ghostevent(wmWindowManager *wm, - wmWindow *win, - int type, - const void *customdata, - const uint64_t event_time_ms); +void wm_event_add_ghostevent(wmWindowManager *wm, wmWindow *win, int type, void *customdata); #ifdef WITH_XR_OPENXR void wm_event_add_xrevent(wmWindow *win, wmXrActionData *actiondata, short val); #endif diff --git a/source/blender/windowmanager/wm_window.hh b/source/blender/windowmanager/wm_window.hh index 46da785eafc..602e851510e 100644 --- a/source/blender/windowmanager/wm_window.hh +++ b/source/blender/windowmanager/wm_window.hh @@ -28,8 +28,6 @@ void wm_ghost_init(bContext *C); void wm_ghost_init_background(); void wm_ghost_exit(); -void wm_clipboard_free(); - /** * This one should correctly check for apple top header... * done for Cocoa: returns window contents (and not frame) max size. diff --git a/source/creator/CMakeLists.txt b/source/creator/CMakeLists.txt index 443e190e2d9..8c8c88326b7 100644 --- a/source/creator/CMakeLists.txt +++ b/source/creator/CMakeLists.txt @@ -897,13 +897,6 @@ elseif(WIN32) ALL ) - if(WITH_VULKAN_BACKEND) - windows_install_shared_manifest( - FILES ${LIBDIR}/vulkan/bin/vulkan-1.dll - ALL - ) - endif() - if(WITH_OPENMP AND MSVC_CLANG) windows_install_shared_manifest( FILES ${CLANG_OPENMP_DLL} diff --git a/source/creator/creator.cc b/source/creator/creator.cc index 75562a114dc..a336df873fe 100644 --- a/source/creator/creator.cc +++ b/source/creator/creator.cc @@ -10,7 +10,7 @@ #include #ifdef WIN32 -# include "utfconv.hh" +# include "utfconv.h" # include #endif @@ -536,6 +536,9 @@ int main(int argc, WM_init(C, argc, (const char **)argv); + /* Need to be after WM init so that userpref are loaded. */ + RE_engines_init_experimental(); + #ifndef WITH_PYTHON printf( "\n* WARNING * - Blender compiled without Python!\n" diff --git a/source/creator/creator_args.cc b/source/creator/creator_args.cc index 1b2f2973509..1c27e987dfb 100644 --- a/source/creator/creator_args.cc +++ b/source/creator/creator_args.cc @@ -1224,23 +1224,26 @@ static int arg_handle_debug_gpu_renderdoc_set(int /*argc*/, static const char arg_handle_gpu_backend_set_doc_all[] = "\n" "\tForce to use a specific GPU backend. Valid options: " - "'vulkan' (experimental), " + "'vulkan', " "'metal', " "'opengl'."; static const char arg_handle_gpu_backend_set_doc[] = "\n" "\tForce to use a specific GPU backend. Valid options: " -# ifdef WITH_OPENGL_BACKEND - "'opengl'" -# if defined(WITH_VULKAN_BACKEND) - " or " -# endif -# endif # ifdef WITH_VULKAN_BACKEND - "'vulkan' (experimental)" + "'vulkan'" +# if defined(WITH_METAL_BACKEND) || defined(WITH_OPENGL_BACKEND) + ", " +# endif # endif # ifdef WITH_METAL_BACKEND "'metal'" +# if defined(WITH_OPENGL_BACKEND) + ", " +# endif +# endif +# ifdef WITH_OPENGL_BACKEND + "'opengl'" # endif "."; static int arg_handle_gpu_backend_set(int argc, const char **argv, void * /*data*/) @@ -1394,8 +1397,8 @@ static int arg_handle_playback_mode(int argc, const char **argv, void * /*data*/ IMB_ffmpeg_init(); # endif - /* Skip this argument (`-a`). */ - WM_main_playanim(argc - 1, argv + 1); + /* This function knows to skip this argument ('-a'). */ + WM_main_playanim(argc, argv); exit(EXIT_SUCCESS); } @@ -1815,7 +1818,7 @@ static int arg_handle_render_frame(int argc, const char **argv, void *data) } } RE_SetReports(re, nullptr); - BKE_reports_free(&reports); + BKE_reports_clear(&reports); MEM_freeN(frame_range_arr); return 1; } @@ -1842,7 +1845,7 @@ static int arg_handle_render_animation(int /*argc*/, const char ** /*argv*/, voi RE_RenderAnim( re, bmain, scene, nullptr, nullptr, scene->r.sfra, scene->r.efra, scene->r.frame_step); RE_SetReports(re, nullptr); - BKE_reports_free(&reports); + BKE_reports_clear(&reports); } else { fprintf(stderr, "\nError: no blend loaded. cannot use '-a'.\n"); @@ -2180,7 +2183,7 @@ static bool handle_load_file(bContext *C, const char *filepath_arg, const bool l BKE_reports_init(&reports, RPT_PRINT); WM_file_autoexec_init(filepath); const bool success = WM_file_read(C, filepath, &reports); - BKE_reports_free(&reports); + BKE_reports_clear(&reports); if (success) { if (G.background) { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index f8dd0c73c0f..33a41ca3e5b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -27,9 +27,7 @@ endif() # To suit different needs, the user can pass `-DTEST_PYTHON_EXE=/path/to/python` to CMake. if(NOT TEST_PYTHON_EXE) set(TEST_PYTHON_EXE ${PYTHON_EXECUTABLE}) - if(FIRST_RUN) - message(STATUS "Tests: Using Python executable: ${TEST_PYTHON_EXE}") - endif() + message(STATUS "Tests: Using Python executable: ${TEST_PYTHON_EXE}") elseif(NOT EXISTS ${TEST_PYTHON_EXE}) message(FATAL_ERROR "Tests: TEST_PYTHON_EXE ${TEST_PYTHON_EXE} does not exist") endif() diff --git a/tests/python/CMakeLists.txt b/tests/python/CMakeLists.txt index 9221a3dd640..16048e3b72b 100644 --- a/tests/python/CMakeLists.txt +++ b/tests/python/CMakeLists.txt @@ -680,18 +680,6 @@ if(WITH_CYCLES OR WITH_OPENGL_RENDER_TESTS) ) endforeach() - # Eevee Next - foreach(render_test ${render_tests}) - add_python_test( - eevee_next_${render_test}_test - ${CMAKE_CURRENT_LIST_DIR}/eevee_next_render_tests.py - -blender "${TEST_BLENDER_EXE}" - -testdir "${TEST_SRC_DIR}/render/${render_test}" - -idiff "${OPENIMAGEIO_IDIFF}" - -outdir "${TEST_OUT_DIR}/eevee_next" - ) - endforeach() - foreach(render_test ${render_tests}) # Workbench add_python_test( diff --git a/tests/python/bl_usd_export_test.py b/tests/python/bl_usd_export_test.py index 90a5c96627f..5691e095d58 100644 --- a/tests/python/bl_usd_export_test.py +++ b/tests/python/bl_usd_export_test.py @@ -97,11 +97,11 @@ class USDExportTest(AbstractUSDTest): # if prims are missing, the exporter must have skipped some objects stats = UsdUtils.ComputeUsdStageStats(str(export_path)) - self.assertEqual(stats["totalPrimCount"], 16, "Unexpected number of prims") + self.assertEqual(stats["totalPrimCount"], 15, "Unexpected number of prims") # validate the overall world bounds of the scene stage = Usd.Stage.Open(str(export_path)) - scenePrim = stage.GetPrimAtPath("/root/scene") + scenePrim = stage.GetPrimAtPath("/scene") bboxcache = UsdGeom.BBoxCache(Usd.TimeCode.Default(), [UsdGeom.Tokens.default_]) bounds = bboxcache.ComputeWorldBound(scenePrim) bound_min = bounds.GetRange().GetMin() @@ -110,15 +110,15 @@ class USDExportTest(AbstractUSDTest): self.compareVec3d(bound_max, Gf.Vec3d(1, 2.9515805244, 2.7985136508)) # validate the locally authored extents - prim = stage.GetPrimAtPath("/root/scene/BigCube/BigCubeMesh") + prim = stage.GetPrimAtPath("/scene/BigCube/BigCubeMesh") extent = UsdGeom.Boundable(prim).GetExtentAttr().Get() self.compareVec3d(Gf.Vec3d(extent[0]), Gf.Vec3d(-1, -1, -2.7985137)) self.compareVec3d(Gf.Vec3d(extent[1]), Gf.Vec3d(1, 1, 2.7985137)) - prim = stage.GetPrimAtPath("/root/scene/LittleCube/LittleCubeMesh") + prim = stage.GetPrimAtPath("/scene/LittleCube/LittleCubeMesh") extent = UsdGeom.Boundable(prim).GetExtentAttr().Get() self.compareVec3d(Gf.Vec3d(extent[0]), Gf.Vec3d(-1, -1, -1)) self.compareVec3d(Gf.Vec3d(extent[1]), Gf.Vec3d(1, 1, 1)) - prim = stage.GetPrimAtPath("/root/scene/Volume/Volume") + prim = stage.GetPrimAtPath("/scene/Volume/Volume") extent = UsdGeom.Boundable(prim).GetExtentAttr().Get() self.compareVec3d( Gf.Vec3d(extent[0]), Gf.Vec3d(-0.7313742, -0.68043584, -0.5801515) @@ -143,7 +143,7 @@ class USDExportTest(AbstractUSDTest): # Inspect and validate the exported USD for the opaque blend case. stage = Usd.Stage.Open(str(export_path)) - shader_prim = stage.GetPrimAtPath("/root/_materials/Material/Principled_BSDF") + shader_prim = stage.GetPrimAtPath("/_materials/Material/Principled_BSDF") shader = UsdShade.Shader(shader_prim) opacity_input = shader.GetInput('opacity') self.assertEqual(opacity_input.HasConnectedSource(), False, @@ -170,7 +170,7 @@ class USDExportTest(AbstractUSDTest): # Inspect and validate the exported USD for the alpha clip case. stage = Usd.Stage.Open(str(export_path)) - shader_prim = stage.GetPrimAtPath("/root/_materials/Material/Principled_BSDF") + shader_prim = stage.GetPrimAtPath("/_materials/Material/Principled_BSDF") shader = UsdShade.Shader(shader_prim) opacity_input = shader.GetInput('opacity') opacity_thres_input = shader.GetInput('opacityThreshold') @@ -189,7 +189,7 @@ class USDExportTest(AbstractUSDTest): # Inspect and validate the exported USD for the alpha blend case. stage = Usd.Stage.Open(str(export_path)) - shader_prim = stage.GetPrimAtPath("/root/_materials/Material/Principled_BSDF") + shader_prim = stage.GetPrimAtPath("/_materials/Material/Principled_BSDF") shader = UsdShade.Shader(shader_prim) opacity_input = shader.GetInput('opacity') opacity_thres_input = shader.GetInput('opacityThreshold') diff --git a/tests/python/bl_usd_import_test.py b/tests/python/bl_usd_import_test.py index 595600a7eb8..ad3fbd31ec0 100644 --- a/tests/python/bl_usd_import_test.py +++ b/tests/python/bl_usd_import_test.py @@ -46,12 +46,8 @@ class USDImportTest(AbstractUSDTest): self.assertEqual({'FINISHED'}, res, f"Unable to import USD file {infile}") infile = str(self.testdir / "this_file_doesn't_exist.usda") - # RPT_ERROR Reports from operators generate `RuntimeError` python exceptions. - try: - res = bpy.ops.wm.usd_import(filepath=infile) - self.assertEqual({'CANCELLED'}, res, "Was somehow able to import a non-existent USD file!") - except RuntimeError as e: - self.assertTrue(e.args[0].startswith("Error: USD Import: unable to open stage to read")) + res = bpy.ops.wm.usd_import(filepath=infile) + self.assertEqual({'CANCELLED'}, res, "Was somehow able to import a non-existent USD file!") def test_import_prim_hierarchy(self): """Test importing a simple object hierarchy from a USDA file.""" diff --git a/tests/python/eevee_next_render_tests.py b/tests/python/eevee_next_render_tests.py deleted file mode 100644 index ca5d533495c..00000000000 --- a/tests/python/eevee_next_render_tests.py +++ /dev/null @@ -1,147 +0,0 @@ -#!/usr/bin/env python3 -# SPDX-FileCopyrightText: 2015-2022 Blender Authors -# -# SPDX-License-Identifier: Apache-2.0 - -import argparse -import os -import pathlib -import subprocess -import sys -from pathlib import Path - - -def setup(): - import bpy - - for scene in bpy.data.scenes: - scene.render.engine = 'BLENDER_EEVEE_NEXT' - - # Enable Eevee features - scene = bpy.context.scene - eevee = scene.eevee - - eevee.gtao_distance = 1 - eevee.use_volumetric_shadows = True - eevee.volumetric_tile_size = '2' - eevee.use_motion_blur = True - - # Does not work in edit mode - try: - # Simple probe setup - bpy.ops.object.lightprobe_add(type='SPHERE', location=(0.5, 0, 1.5)) - cubemap = bpy.context.selected_objects[0] - cubemap.scale = (2.5, 2.5, 1.0) - cubemap.data.falloff = 0 - cubemap.data.clip_start = 2.4 - - bpy.ops.object.lightprobe_add(type='VOLUME', location=(0, 0, 0.25)) - grid = bpy.context.selected_objects[0] - grid.scale = (1.735, 1.735, 1.735) - grid.data.bake_samples = 256 - except: - pass - - # Only include the plane in probes - for ob in scene.objects: - if ob.name != 'Plane' and ob.type != 'LIGHT': - ob.hide_probe_volume = True - ob.hide_probe_cubemap = True - - bpy.ops.scene.light_cache_bake() - - -# When run from inside Blender, render and exit. -try: - import bpy - inside_blender = True -except ImportError: - inside_blender = False - -if inside_blender: - try: - setup() - except Exception as e: - print(e) - sys.exit(1) - - -def get_gpu_device_type(blender): - # TODO: This always fails. - command = [ - blender, - "-noaudio", - "--background" - "--factory-startup" - "--python", - str(pathlib.Path(__file__).parent / "gpu_info.py") - ] - try: - completed_process = subprocess.run(command, stdout=subprocess.PIPE) - for line in completed_process.stdout.read_text(): - if line.startswith("GPU_DEVICE_TYPE:"): - vendor = line.split(':')[1] - return vendor - except BaseException as e: - return None - return None - - -def get_arguments(filepath, output_filepath): - return [ - "--background", - "-noaudio", - "--factory-startup", - "--enable-autoexec", - "--debug-memory", - "--debug-exit-on-error", - filepath, - "-E", "BLENDER_EEVEE_NEXT", - "-P", - os.path.realpath(__file__), - "-o", output_filepath, - "-F", "PNG", - "-f", "1"] - - -def create_argparse(): - parser = argparse.ArgumentParser() - parser.add_argument("-blender", nargs="+") - parser.add_argument("-testdir", nargs=1) - parser.add_argument("-outdir", nargs=1) - parser.add_argument("-idiff", nargs=1) - return parser - - -def main(): - parser = create_argparse() - args = parser.parse_args() - - blender = args.blender[0] - test_dir = args.testdir[0] - idiff = args.idiff[0] - output_dir = args.outdir[0] - - gpu_device_type = get_gpu_device_type(blender) - reference_override_dir = None - if gpu_device_type == "AMD": - reference_override_dir = "eevee_next_renders/amd" - - from modules import render_report - report = render_report.Report("Eevee Next", output_dir, idiff) - report.set_pixelated(True) - report.set_reference_dir("eevee_next_renders") - report.set_reference_override_dir(reference_override_dir) - report.set_compare_engine('cycles', 'CPU') - - test_dir_name = Path(test_dir).name - if test_dir_name.startswith('image'): - report.set_fail_threshold(0.051) - - ok = report.run(test_dir, blender, get_arguments, batch=True) - - sys.exit(not ok) - - -if not inside_blender and __name__ == "__main__": - main() diff --git a/tests/python/eevee_render_tests.py b/tests/python/eevee_render_tests.py index 11327a78e9c..1ed17f90c05 100644 --- a/tests/python/eevee_render_tests.py +++ b/tests/python/eevee_render_tests.py @@ -43,13 +43,13 @@ def setup(): # Does not work in edit mode try: # Simple probe setup - bpy.ops.object.lightprobe_add(type='SPHERE', location=(0.5, 0, 1.5)) + bpy.ops.object.lightprobe_add(type='CUBEMAP', location=(0.5, 0, 1.5)) cubemap = bpy.context.selected_objects[0] cubemap.scale = (2.5, 2.5, 1.0) cubemap.data.falloff = 0 cubemap.data.clip_start = 2.4 - bpy.ops.object.lightprobe_add(type='VOLUME', location=(0, 0, 0.25)) + bpy.ops.object.lightprobe_add(type='GRID', location=(0, 0, 0.25)) grid = bpy.context.selected_objects[0] grid.scale = (1.735, 1.735, 1.735) grid.data.grid_resolution_x = 3 diff --git a/tools/check_source/check_spelling_config.py b/tools/check_source/check_spelling_config.py index d4ef2f8dfa3..af75a2b8646 100644 --- a/tools/check_source/check_spelling_config.py +++ b/tools/check_source/check_spelling_config.py @@ -100,8 +100,6 @@ dict_custom = { "defragmented", "defragmenting", "degeneracies", - "deinitialize", - "deinitializes", "deletable", "deleter", "denoised", @@ -167,7 +165,6 @@ dict_custom = { "impactful", "incrementation", "inferencing", - "initializations", "initializer", "initializers", "inlining", @@ -312,7 +309,6 @@ dict_custom = { "remesher", "rendeder", "renderable", - "renderers", "renormalize", "renormalized", "reparameterization",

    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 z zD_elTRa9P7hiC+EU{dTeMVX?!-YuzZ!JcoJ`<4)))Rq`PbHf@8T2wTX1K!$ic{jF5 z40!KZuNxjM8@|5wJX$tGQ=7)&T++5Ic<;BhHaz+{RxgZ;D5NfssoZf(~iWvYiIRH38FA%V6dm9C=02O>nNw9=;uV^Kj+Z`XR04t3dc)(7AEoPNG*9LFg!^p~9&WbQ zb;~e{JI1xXl@*iquZuW58-1N?JqDD2I_X%PC?9pN6JkLvhUXqG)KaibIguVVD%-ud zFcsEJw~f`&L1t?Y3QVH%hQPP4;f*Q0dFXiOq~hnEulQzdShrtg;lE4Q)|AD9AV~r% zU6WLpSyD0ep3S64&9*UhP;LWq&A@F*4R+HP=Y2DO)L>2f7CF@S2J}}L@iQbtES0PE zn&qk<=jVhvSQ-coC%iax1`tcm!t%Qe9#W#G?EFIm*e6DB;Qq&C9p>N-tyiwV107sRIFFmbeUsOHyeBmOcdT*95At#=P3WYoAXbY^3UQK7?(g}msiL_2t^4I z%9-Qd>oYFT8#Z<1s4Dm^x8m~pil6FT{?Z+>X=;4efx_OwVXA9JaA!q`V(Abbon7&+ zfl1dQm)Gd3rs)F4fR#i~muR_|7kJ~D+z%Wd9C2k&X+n`=CU3xrG=peUws$(KlKEYm z)rPj-Fv|)D5L!t|D05&T!cz0Is3AYAhNv#xe-WCdV?LkrYrpif{NP6)@$lI>epVqN zJu8(nymoZR>!(K?A1-jl5rU@+Ar;25wHaB!xiYPR)+R`-=AlS$mC6;5Dwxg689od8=u#Z@-mN zKps8EzIKGcX3NMHvSbT~Mx{P`!PO6bz|}_&X*So0E0R^oKznt~?Bs;9EYYsT`CI^O zvcJxWfVDX1kQfM2X?%;x;I!x?8aK4Q##&2NP7~}*g(`Ho=n*v({RPPzJtjq$P3>%$ zo>Qb*i6zOZTH}c!P2YN7;oO$|{r!^Pj>>fme5(t5udZ1Kr9pYx2HvnqHW*P33rmF^ zr1bjgu<*}380yU;j@y&mpipb%zBBpYBp6#VsRZA3)SchToQz4+NWEDR_7nA9?-tRR z{N>ZCVpe7eybsy0o+j}JRmEOe^1))k%37}6gz&v@a}@&*Wy(*>igTAX6q^eK^BLr_ zG5vjbPs0kId?3bd0TA0DT6PQkk?&k|OKu-m!8S{uP3aI#GWZy?G$v=?4&8eFjCyzk zEF09fdPB&)}FpL_-^s4BXv{kC7W(fg3Y?-EF#fLOuk>p_4e48ShMMM)!G7m)6nqU=TLGBZwI! zlvP#(l%?Bxfcit95nR1N+BQ*;gU=n;Y)8o?X1$?gh@&(I{j5fbY!76LFprUoI9c$IdQIVuDcwA&Y1EFs=$QSipvHjTBrje_+~P<9CS*>4pzS$UzmulBMS*h; z9~5iNwqKPex`V`A!H1N&-v6B#m8R`zn=Va5jp6n8ei0S$dGcnnk8p?|GF#P3QtKpr z^wuJ}`yda~!knerZBi^rFqYTa6?@C4_@YRyQ#HdOUT;ZZU!k%QZrx{$;7mKcyO5eM zRD38lr_p*NL^3yvB&0YULGUV>sGRN{ zYh?HO=I=v*#Ww4l`C7ByzVs5hP8X#G|nBZn?Wc8 zt7Zow(eDW|$e)L$H84aIiELFMIj3wV6tjQ5nc(t^%*|e z3>^y$zztP=NT9gOXBQOHf~}0m(5WAy1$(c#xoK<5#lmgyX&;3b(oDQBE5CwPQwFRZ#QHPS?Oni)FUZ=ETyc8YawXp9c+?VX6qStGWCrf~vF`j>OX z=pIb4L1T9Jdoz*=1`XJ3ok)?eIy972VcoX0Az({Gs6C5f%E4mJyqYp8igYi>-F>_t zq}6qeZzGH8G_hPlNNekWgOwe~Zekk*Hnw1S`T*w-SX_UIw*~8qHP0L6!5jNXVE}eO ziNC0(1@|88@%-tU_1aSvmMz01J6{D&Jb)sLdS^;>&rk}*A#mt(HJK4lBy_>D^C}pwMsGD zDk{hP0+|5H*i<(txdNBTIk9-ogG5+3JC75;l&3et%`5CLTYrOB$bEPKRBNL~h zDk~07k15IuixM_9tLqitee@}BTwO8Uo1)7#TvSZUf=vi%y>%soj^!tx5T8Ed^Y4G2 z>&tUq)JrI`N;4|}u0H*g=@&oX__f#I(?__rkJQZDOJ5AQJl(WbD4c`f2{BR!kBy2& zF+NcH4zs?-6&2;)0alf%57?MuAc{Q2w9$+RC>gt-DSgS)ELbX{#&nQt>YV#3EVEqV zowDTJO8MPM#aiK+_k88f9q!Jj_!#-d%QODw@{)gXwc^Mc4xFVDvxRH*(12Ie>2XBv zzBxM88;eUY!dVC$D6H0X_6eozb*yFL9Nv5AJU%MXgb|PSruS=@A*LgphIG>*SW-cY znWF^OO_O9G8QiuIrPy3^eSC<2dBGRYm%Q!Pd|Z`0oX@x@i)~ESjY;URUFoiRU@Z1_ zRz_H^Q90RDm~V%24N+L*a+iE`4dzx>z3NR*-wo#djn6I;S^CtHjH=A0liYcil;51+Yeb2f zHe0(|;pVdu%WT7|f&HVl+So&%mDjv5RBFdumTw6y~D(~HY!jtoc>t;jn zdsw%?+9^hgp$R2J99)Zv5M!cgr}&c+-#J7YG&+i_Z{X~6R2{RzB|stx7!)+_Wch0L z<8~-MX5XL5UdgC5O-Jw%9~w^H`~{|qgB;Z;8QeC~N|-H$UNFh~7(&j$?who_ThPZX zt-s!EsOy?GL^fEy^8EV*6=F>Ba*T3wbp&d+H=ABJJh?J%6h$3{^4yL-kO*C;Si37d zyLpe!D$o6}P^ zK%3W#Xbn!3loFm^(Wx0GVB{&Y0Bs1EU@#`9ZwN`++{5?;j7^XnmnWspx$QxCh<4gG zIitqmJMLwlJ}^wq$1!W;Lc`n$)9y%nMn44%+-oLz1B+Qpi0FFXG4rsKdG4h&3;rO1pK zV-g9Tp5uVfAiU=zu2>VKOp2E=D6TI^?RJ3;5q^FDDgWxp=kUf8x@)X02iOurOr9si z7S(F2Fp;8uF<2QccDPm&|Xiu?$F{Jgc zuGg%VYvQ^cWRG4W6*2qA8K%8fWT!bVS^c%$5Ot8l!`9wrt+ENHoQGqGk$+IO%ucTO zN%sbIXK5o`2jz(mG%@Cescsu7!)ppiNl`2koGnX~5g~Y=mGwUK5+1?VWkT;%>!JqV!f%UCRI99Ma8=^TY3VGF<42{L)9H^*bWm%M$7wjKE-># zwZY_lW4ntcy8pO16qNx0s5|?!&}ve-SZmQP@aF7uct-eXO-iMfmFo zfBbU6?;NagMQVB#q7;pyO+!5uyfZB7#ImxYgf{U}M1*(eYkt2g`HQx~RC6}LGtFny zUj^41`*)+WL z`#jI8XBqA3i2)i=4Om;Hy*K)-HcU<1tBV}PmY)YPhB`zFBC`-_tw|DhO$BF4w9%}h z&kC3T_l}RacY1^~Hd|snn|70cbGgZy)M9pM8i{TbI+CR~=WpfpM@BGPD@mOrncrf> zGMQ!xdtnJ(AbOu{G-nrVR%^Vb*U*@3N)lnVn6ZC!fGewX*6LcC^Gl*{aRPQy;STnA zyjgNrg{eY(afv%E6FAUE&VT$9)-RqAf^ssgn11j9)%))w|L?!yCr_UzK(w*csyzJU z6HZ>dWdDONFuivNd*|Tbe^>(>N%FK#pfSP*VI#s4Xas_F>EgQrFNzl-7=sr>kRDV} z5Ur!JLUfK;IDFygtl=tjtU{zAvi2Pt-?47nOnIYZQpV^R3bqpYdJD=yTpw`j29-Z%j*fPj*`meTq+MLS)lR)Kob= zKExon!lY@D$X1GL3?>GO%_hCJ_pE$OJa5^){;fvCD*=F`=E@MLv@JSf|9dKk_q5L8 zjBv16GcOyi>yCD_rt?cQG$=o4ni&O2=1W{XJSktx_M8Bk~&)BSU^ax&0I= z<{a(M@}yg+ssf{Cm|72hNSh8gy%ay!Zd}(bUE9%h4dvnv_dox$IeR?r$T^1cJ6{;D ze&qMg>In7pMtmK`HHGX`jy}@*mZod**78oh-Mzk|jT(+RdE20F)--m213B8R$ zwv9c_hiJ77{EdF?P*UFe<+@{&Dat!C%zijKvJ}Ob5+b@LAhmLMK#mVwc}+7(7iqQP|{> zp|Li_*OB611d|A_*5Qf*Te?IScU!eXs;Ep>&P0L+qNe6!%#_(Q5z`?Dml3BzM#YbE zyKy>xC?x-5#H2$qy|rY_3Nynq*-zhOh%1vDk&$wvDV_ftQz7ns%8ZQ~lcYbdMOJK5 zS{XwzDXFRnt|%}zUz?o8n+{S*F<>Quoh-?>t|sj-y@X$~r!x8kI9iG2r78KuPWg$Q z@yzXUQOs$p1^bH`9~@2i)#C~O_;A9{7J>_r#~aT#eL!P3OqGYq)mmzV-`ZUAxx?!e zkEh#)%b*mI-3GnV@J_Mjk3Rhpv4|8HtSK;AvBBs&HKxQBiBTcW4)H?C6TCf8$_V(` zPctRUzK7mRE>d*#F*N>dlCE(>MMqH>ui(A>EqmxrqjuCNAH%*hGEly^d$kTKw*E)@ z&O>OZUl_jpg@)OzAq0k|4tbceyk7F`)5lytIj1w4k@jg)y$htd9c>zS)POnRCpUf! zDb`d}5mWO~^Hnxs!52jNgZ&H2Q~1I10d+K71;f)$d0L-gO&H1BWxFx;rwZ{t`3%Ej_zd5@p;N z!C1q(X>;5foZ1kk-|r`5X}8M z_;!W&kr3gnspl*6$Oi|)Wh{8srhRoE_~q+sj@BEZ2+^eb&J>R0<9qDw?^9J%CX*^D zZ)bBR)s&*DSj-pf&-Yl&ro4D`LGi$%dd>9Hj{UnQG^#_4-T-LKuxZGaMY|pF_1DCE zk8^Ijr`WCUy!43-Z^{X!bK9ZBXaJ_Uxz>NNA#|xI&i2XSpOK;?Gg9-fb1gE836`wx z>}?kkB{hGFlF6)Mx;LkoPO&0vmP^i_JmbZqCu}aRiH!$MX$dAK_4dK>5hwRfneXr8 zj3sQ=bl2Aq!Bv)GI>SvTDazWK;TfN`IoZTouFhZZ!`@5#Crp&Z3l8-c6U zId+u}rPI<W%>6w`vOhK$3{K)ehP7&_jnJqjtiM2GQq;rQ z^OsEIB}bErH%?A)RfVy_uin4Mub!T8zG?V$x#DhNxpQ>Ds`mWR(2LArug8%HTH^3&pK5jS)rR+OWoRPB@OjaNipwZI!kg`#8 zRFcM+4!8-sKBA4LLOt{u^dvg1wSm$Wl(W6mzzBg&yCL{MH7S`qc#UxHE=SiZ4ld7m z@Zy3`%aVutbI!^VQ7G~vtmDEb=Vp%d%{y{dVVs*Tx8zheH~q0MT(%73L9RIpF-y@~ zsyowt1d7TgO@}66UGPfu5hM(fr?GsD+v@Kf!h4dwjqhW2K9MT7e#TCe*$%Pw=I7p> za*vNf37VGvmNSBNE=$LfUonI}kJ6yNpD-(J4s+HK4`3(&Z_xB~)Y;x{Y>9UuH{|>R zBN3V29?J_mle?HfGzPf#AvYQVi|LHADDZhvRt>}`gQrz2NZSkqZtpulcPrpqA8i$Pf%-dn&j#sDCN`*vh%I4Jzsl;wtvR6FS+%eX4Bw9 zixeuH^*CLx^Ah`^a<(1v|R@wkc6@4ayy{ako;}pcj@bc2RAqCG(RQ?6UwfkFPBA!6e- zQbBUFSo$RO7_n87yBlYBL8~y>nL-Gt_xLy}_%)CFtic$QWTtsyD3Xe+7!x(Uww4VA zkF`YIj74?CN!4;^zu~>(4L{Ru_|My#^Q#TtylnZ-vf)n~WyGbWNB{sJ07*naRALPK zF;GQiVI6N3s~n}RuT1|K7R%7DHxp*lPx$5ehQIUB$z zWT(qIoEtJIu&@?y1!j@&{CLWp)2kG3D>gMoW11A#bi4FZbGZl#&q}(1WH?CB+Vs#yvvzC0S)~ABye>6oKNHD@zKz!a_NP0?=j*>=bdq0 zokg&yCKie0Y&^ZOxapX)p z;A5az2CTIhX9qRVxRH?(B4Tb>2tGubuEse>RixP7{ypeuBC=Icj`#Yl+0JStOe!8v zU+4SJpYT?@=1tpBmI+S$^zoYGy-6bPJIkH7?-Tr*v&WvciJVN7J6!YLEbynR1Af#P zro-tYDTsaNS=CDfVLm@3_>QJt(nTfsju)3tnN$m2oA1$L`0t;sxEnhD+tUeOU0AH^ zPy&U`(7Zxe?jJBQfzx{nLhX6-=`~#dD}nv)ir?CcJP!x#`^Ya|USPaW>b@+gnauZ? z&*#H=C}`N`SVd25HcZVdpRFRUthj%4m-A=OsqQ4> z#^l$>=4ZJ-OC|ZCD7hFO7w^gl`UFvN7^-ihwDq{Mq}We(Ez?I_8rcFgQ8op1F2%XZqYJ{>$FwBsejH$Y zhM)ZKL%#gl>zMoZIQfm=D79Rjwp{MWDDXW29~3ab#P zy=NUG=Z$A2$_DVxqSkH?b0IM6TJAc>Z@uw=7DZy4^1wsN>$4(53w*t)Q`giwoHMCN z7bu-c8&3!{KFI?mcU1#@&aOdne1_!7zLgS+m=Ac}hyd%TswshbJ5?uA?Ju}@xW{SN za{pq%DjLD+4d0?}>E0#^(TvE=x|<>1`yhMirDU4p&1CUDGHFL4 zE4w(in+J^T&^=3HjiInWRV06mrbs2tv2KGbUX!0#;;W|t=9YB(b_T56IH-J9QLa6B z>LzIJMxh>bKp`dhPTnkywcDwKN-88`QWI#1)S5i8!%TMU)z+=p&Wxm~+f3QrNG#x1 zF8r(73@?^3xly~2evl!~-wCa{-tnYx}OU}kjBv|b5b37O<;1lH@05F>5tx%auR zFr7_PajtzMr1v;xxqAvJAN-;{yJFlp^O_rq(r3#jg1e7C`TTsObB-@xJYlwePH^+> zG#M~1d&8sbWRMRX^&P3~mR&I>Daws!y8ok~;3TF4_5j+6Lj9vkX>~-5twR_Ey9a&a{h{r^W zNW>L3(NGM7vdQGj5b~jA=wHPo$Mq7^fg#6+_Wy?v0&Q!Ck>mh1j4>C=xf$YI@|fmH zm1dcxu{NbWBrruPOH_7>uOWs`1kLfGK@}@z%at&uZ@2GKD%7Cr@!6x@A67KCFNbm@ zH=Fu|fSVCrAp}f@)rF7_=^;kyrcD{p&JLs5G-yjI9MuHJl(L{Si9S!SDDZ)xn&iPD zj2gy%Wa-G>!{l&8#jqjr&=&lZ3b6;=nRFa=8%`DtzkRyl@5Pq?a$WQ3)rLQPdd2_t zy^pvzn=^mol9(IBLxFm8?+7Dqizb-Quk4@km*@A15(vH~6eTuCO(ox=14*zf4HmV1 zq@--+TxoNsAHI(q5a~0<@p`^h;iPRtZ+Oe)GvPTq>+NSScy{*O5fMCD1IYKtE!-ZO zEs|!G_AxXRBu7t@SO=ymu$`frM}F|h9{=I5Kc&>9M)5wRJ+EDH{^AU+n`9GdyWtiP zlM8v>rBwDF_&(vI-)}?T%r^b+`!rQEhVNf~5wQG<@%;ScDZ1x*v^=3xSO?*0Fud?7 z*IY86p!Y|M6o@zwlO?z=6__!Oyh1E!c_ zav_;Om@+)~GNdi!F^GCzM5jwFd7!R~P53E|+XpYhnm_El>L?su(Mr z-oKl!X$F6)?~=;QfSpW{Nq&|>NJ$=c8+AI=hhngWF`^Lr4C)X7$TICWJAIBg<)7; zqE9yQZ=44i6A|vtrrg=vV>+2&GL5M70pE4Rm?TCM zYmuTzpL?c*ek|=PVUQOgC01s5l$RG*sUXayk>p4T(^m(Eyr?IM`lj|Yro$DE!n!1N zwU*MRsjbgNzEL5X?V+xhFX;`cy4W(2r%^-`z+`Va!D|A$ZMqikJE8}SWqxwR;^c@o zH#J|0kr%=9VOw+7C~Id4&f=}5iRDR9nc%u z&#L3Lm2MBfG0S^j6-hNZ3w1FqP6`yp4a9fDXrd^J>$>c-3N(G3P3~H&?&wyx#Z|A^ z%uzWyYH|!wX}dx4?3Tf|hen#eS2Id6Fe0qGmYBV*M~i*R!llntmF%MWvQ5Hw&^Cy% zNbq!+!4n#9NH9gVh%1w4B}HHEN-FGkqg~kfPE7{$4DwQI*bkBOxU<^gu@a3mp!`n&v4l@p9$TX8IC5>iBvB5|^V@5GcGLjr3BhTzE;wslS$yeqx z+oTH$U82nFE#UOELk{oWL-vngxX0kr*dT-Lr_4d;rP!77|fxDh4!XR`*JoyayQB zMldaq}H|t`O_iCn`TQlNP z3!wJabJ_c6cR-G+gN3EPiFz8nh|qNPV7qYnP;W>L31bt$wO?cLelRW@lJwl7pcQDc zw`q(79}=LpR}`7;SUKiQU@NJeVXWn4v^+27cwMkp!EyAQ&NsYuxaJQIF3=d5J$cUI ziADW6ic5_-wfSe?*e|q~-nMie{+IK&L6?{aNJH#eOi?7|3WBI7_>Nd~M0LcN)Ns;E zk+bJ~_+D>Z^D%*bm(`F++))EjWp@gGqtQHUwPu@=F+jS^F44W0*{7(mQJP2CvLRG& zC@KardRC`}fVC#`He?hnmmVrZQec$U^T|&Pt5w9gF2khK^SEBEIe+wGC_XeZA9_N+ z^uXbMI;>lM61}=4ZxS(hf+(dFbQ+Sp9p$RI%ZqplqWr2mqCfAQFqjPR^df7e0>F9EG$1$ z8UA|hS;j9=t-pp!$@%#id;9yTX*XREug+1MQ!HfAex>$W&hQ)i(4APSX_@y$87!BB z1tD8o-I&J9Hy!cLswKqSfqTGKQ{AE>6Okh;9O0%Is# z$)qZW*vIh=J?40f?LA4IV0d!z0=(xA(9Vze3sSrUC>f>c%Z$>;BMKHH5*C+UgOPD<2>s^gT zxOcdhpR;ZLfl7`^)-+8|`=%LqBkiWfS;urfOXvNZLgK3mS=V4ZS`=9-pZQq`L47(q z_L=FqiEDd{Tqd)Rg+;zoD(5m-T=US*P)@2;lot+bEN$IzeR;w4`6XQ&au7t0UelD~ zaC&-&vTzuil-RNDvdt-9%eWm!4}eIU{qVWjI_OeW5i2ySn!o|ctSE#3K}sM&B$2`pp#&n35g|eb8IX*HZ6;xyn0NrS-PN>RJydtiRd@RC zH=lF%u-1C>$69-zeZKEDX{n^Hs$2J-Z=b#QdWYxvJ#QW!a{UYclAUk)W{!UGm+?ke z+_;7+oIifdyI*~qW$03ao_rx!i*?C4cNUYkWUy~+l3K-$u*pzR+hWYYG(Z;;Afs2M zkLfRqhu{;f*!PGlQWV>4&IKuQtmyl6(C3$r2pJ|e6pHx^%@3zB1|ynYULnx;9Uf>= zj?b5TKgrg$H=7OjXA*n`4 z(%;#vO4oEkfu5eNQI(?a6QCypbNT@iBG0A_53;r}K0#BAGFO=TIxP|}Vd;kmzJ1?w z+9nU2G%bF&XYOiDY3aHIT`YIiE5hYUJ1S% zhC3lI1VY-N_ie{=-6Gndl-z##6B!2QCq35Y{gZC!iKH_1ml(H6IT){1_Gp?s8Hgxt z+tM{HQK3`AcOQNU8`EJwJ@w*5`6>V|rnL@c>voq*@=ly zKfFI}Y6=Grr!7!o_DH7Bnlk78)@Iu}mw{E=DG?i5wQd!iVVLH}v_;0``#9=kyOAdH zF*mD2BQ!uYC1lB8KKJN-qPc5o$TY?)2^{LX9BmUvS*qkO%-U42$t1JS*o^%%iD|!S z_>$ekn8|=^5}7!><3a`#4ia^fU+mNO&Nyt#4T<*&G8BQ>G+Qu?;n!6~F{%khtBXw| z`eHa|t9-vo+ODT8+dLe}GWpSa>vi-gT2-2;d}s{s*f6n$G2HfX}v{&1QT{ zT7EeSRIZxGB)}bdQaq7^-xMnges%p(P~nf39nT-UM|1svx6fb3tFVsns2BPOmYAaT zWFX1Q$ceC~%uAR8W6ETc(xl269P4IFX`tJKFBv#0N~y|w9UPd7d$r@&Rvn$WgZKqq zyXKv@-{upa{3N!p=wcT=JsQi;0d72Ch_@ow275ua0K}|1K_u}XT(Y7Jc0?V&uVlT` zDEY_Uj!#YT!RbsYRVe233E5pIN=e-+Ibw`7%^ER6RhD24i@kjoKy`M)+g-jPExH|5bd|JJV-1)^PLsP2PIvE!?L{xB>B@rF?kE-5tjpYH1FSd2WA~vMk2V zCaJQO=<`M*7SY+hi=Ehl3% z+Neg7bY0W%x1+KSSGq)EuPUqo-+3;c9&>(rM!RluIz;Zve4s81s##54)hy6*W@C?4sv9bkpz!eoRt)MZ%vZ;8Sq_)Z`R2hk8tdjqsLEpxhh%AXH-?m ztSqU^GD#9+B(z=fcPZn>ZXgg3Ma)nH40TT!r??xhjwQh;ro?HJWx!+XR@bxcJ(aUy zOone{QZN=D<2VEf6E;{NB7Uf-6`wOgH5X`UljA{^QiP%`Da(?s?`XR|`&g2HVs|#@ zlXcBUI?rL-aM*S{Iy@mX9k%b2e{=LTZsJ8W`{G}=$J`RZi%^BgORF_^+m^$k;Qd*} z)2hUqOlzM?Q-(c_Y$8c6%fm0{lZ$LB2%YHuWD@H%FtA7Xj!VAXqAbROIfmqA*CA7M zQq7ou^?cwG>;00)@A4vjfOQQ`fH8go3RCZekEtx%yM;CAqTC*Y~XVsHDklQP>13RH#aq4z9`cU<7=0P1+iHzeS`U zBVDG%S$)<|HhocVhAYE}VU)DPR?$GFIyPg-n z`8z3#Di0!Kj++}mEJJgwn3!bITQa{e-1jyOwj_I+v&F#}!FN4<-xE|5sru|OcTV4; zH?xgxWCEz#f(WJvkHm;kbM>~HPKqX3z#fAap?8L047ElictlfdW}%*Y_vYNZc?Y+D z0P{U4>I{d8#7y^J6?5Jz_gGuU%A|uZYM@Y?I0qv+KOdnvBYMkwV>ggD)tDej3L&H% zZ?S@yG>K2~_S;Xs_x=iGYw*5`Y>Hr;tK?+kN(_-W`53|b>}8AjEgrKaC*jR~JY~TmRRNl-F0}&;rd&si2)xUggS8ao?H~-xfG-;iqaBftop{Ym|HxT0$ zTR1}B2q*=T66Q-Iwy+==S|1cM^A7&f-8i&6HGJ|8o6fR&b;8rM=mTJ`N+{qSWs}E ziVH~qKO>Q7gtZB@bQIE4xQarOcl$S%p9J0K-!?tpzVi@!eZ^PSpXRVJbPC5E9EWVx z)a1)egCmESB7;yZA_WG6brr_sxHyTUa#E*Wvu29c&|;_f?E(GdjFNh5^tdrq)b{ahZWM zrrT+PI%m^Z@SzRM8(Y`_kp_n_Qg;`M$HPS11gzK}GZ(2R(7qpI0oxVV+5NVX(vv%*w0woJ=8neY>2 zB9L%0L_H@pk70;QH8^XjXLa_LC#6-hT5*1K%JSkIAM(~cZm3rVWp96k{&yxOqN0gVN+RDb#L~x}sG0)khpIdVe6yl!8mx5`^&BZ{ zdQ(ska#p9sN!o192B1XFx+D7r&@j^coNh=)MgunX91C2mR&-B~Svbp$SQ33g!|YMJ2twkO|7a!}=18&7_MD6OTKJA#sm_n`ElPsLVgd9K*u_B`@x*x^ar z@L=6@5&8sO=mX+AT#RXvA#hXS2GA)~&QM@D3Z5z|FEt&{^&O9$u!W^41SnFpo8(Pcpq_Ec*ChF$@yMR@8{ip3mgz^xmflpGkj~30>Mu#z+$*aT;7TZniF6GSP|n zUbkc8riBe8uVExT6PM+ogKL)|*H{c?jLcRIt66&SK0SWQom;mP*hbRyvxpJD zQ}Q4z_<^HCZk?WhPb!#4zTtuI@oJDV5c@T)8tPe*N!(1fhUr=NG3#$sIByaizVCW= zZob0K?g1gD!$i$+K;*_R1Nai~&P-wfYrgv2Hv1zN0s=2=J{R zewJXWl=UoMx9;C=Q%xkHF67d!l%sfTDrSNREp{`Yj8H~T9lKP(%q#5f0S7y~EM|LI zz78fPg8tf89GjYl#f+6Pgq#(eyk{{H!XKN(0RR9X07*naR5fM=flsnt5rPkC=*b4s zGQN4O8xO9r1O4!8E6PODl&{scgkc< zTRmeokxT=<*5r6UHF;~CZ3jBdsou&;PqJAva6cxnNR563QVmq)f#fd(M?xeOmXdPc zLx?yd6ej(Q7@P9E^Vu3hvKWZ7S>l-fysqoV=P{}#xj&MmvuXxIa*DPw7MZZi^UXdP zTBiYmHFnd)PwuoBBfe>9CH-1!+^7n0wn#;jNwG!Nl$2GOu0u%iPca1A-V?n~#nhl= zAR=lZMjDhi)$mq*gWc*n&xJL&nvYPmPm!}H==wugKS44FuI*}m?sNzC((mJ{UHYcY zx#25(7g6286*ZP(d~Q{;4?OC`rjVZ~yAjGTu!J%*)?Q|Y3};Tviig}3tI=nwxKz1< z4>LwK|3*)H?Aa6Hx6aP6rePWGBBr2?mJd6} zuSd`4W5=UqCjQzAxX<^Np5IeG;b8tckIbj|a6PAw$|?$;l-?tn)QM_}u{lozumUkI z5!uCHOflLP(so@hAJCKG?GPgtCxdm();Hm?2NMgfIm3zf9JTlP#``bxl}AtMvwEek zD~f1RI$chPFynh;qcoGzy|k$!WOQ&QBF7elO|r2}^_h?wKd`Ed;bOf;RH&=lFzQ(fbvjDmx0}@qOUKx6YW&9dqk20)-Q* z8`tO_9n$&0M;4JkUUh^;$=iL$7nUybRHn~iju7)bKE#wpkIpQ&EeMY!)x5w zu2eQwgUp~IBqb4&&cfR_@9_2qZ{t4du(u$drup#;O^@8VNm-O!cLjA@@MOKr5ZZ}q zF-Ah4mNW!UltIO8@V;ktv1Gnjj4I@$1WbOfezihkg%qh#6|-5!n2}AC78XVo4IVKC zW#y=6Gn`9lR9)Y4^7Mj>lQW|4MoW!os->JUl&)YtpRt(D(>>%pK|=m>hUPnO>z%`j z-BQbLO8&5TXK!5#&GBU3p_Y_ouwMyIC_VV3D}+M?C;*;e!t}X=8T!x ztaeF*@2@?4%>J)_j)PDC7C!djhrIS1UqYlxi*Qw3=#xj$oAf~nP1{j7i9Hh8e(0wl zDan2^mmkc2vKbh)`wZT@6gvS>*@E+a4Vv6vsX!gTMiH#+|0D3snA(e&Q@#Qx>!>hxK&r&na|mq)l^l1EefLd zv?1VwZfLrOl$XcV7w~}ik8HoBhkVe@rg3u64)mdzJgbfG5*)!M`3e}URcr*WW`uN& zEtzan(nmjO_9KPD4VdIKRntIOI#dN43c5bYe0`RERz=D4MZxV^WZiWx% zPb%Bn_7G_=&N)6^atRlK<5iY6)z?a%&tn? zF9##(v*2|Ts=SC?iC@zxuwA*@5Kk(wW!t7LM@Yp$UDbr%<7SSg_1W4YlLlWfZIf=s zD6~G{2T%J(b@_GL@hOV(S#zBHy-c^tll6fag@Z%!nSfz6g09!BQ1nPCijr&B4sv{t z88@1ZHT=}=JAC{3Id?A33G*4h@br)`Ha*9{YMHLZ>;^1$Dxk!gxYg)}Apyb&7t4;O z?ZJ1bU2yNkkLE4-uvyZvnbgrzF=6vwioP3iK9l`rlQ}Fv@I6i6;QKz)eudxu;I*VH z$^NQsd*3!7F-)ZHDvFp0(K3w{jtIUqcr}~%9EvkZsvARK$3@D$J=i@!XLDk;&$@6_ zzQ^`m+A`RRH|ra`Tg`<6XIbVP<7NVq zwySYlLOxNKz_!|C6Me(z5!Hb>uQAE=2B4`j0Y%C9_i@5L3s|B+(?^0pVS9`zQ&X>D zlbVoi0K;{a+=L86gCY~1N9o*f{`tg)+59(Y2q4C7xCfIaeca|TRdeVh$}9c=^ zuBA;L#G*)f&}OJpOlshZ0;vkhm0a^3I%F^V{6n zeTnPqV~X~exO{|Ne~B-B_-R-hm|IS4WKR|27KE@$YQxYG^#Wo@DvhYb=+WRt1=5ul z-z5*ZF;{1qB3ECSHruRX7`Q0$QuNpq948}rSSQ16E+u4ar17%VlaGuEc@{A~J%h%| z2Glx*r%nut!*`C29G$w9X>Ba$&6*!PJY=zMQWB1AEL^>$REoCP-=`fy&)%@rKzO05_||Zne=|JCZ}?r_Rm(jB-{PL|ynR5u`!1^+FYs>j3T;UD z%(6E;^-3S}vup+_dzI`mV#kJD#ATk6%}klFxlUQ7!zIf=s^upVbd|~VPbBMOG0d%{ zke>H07JTydoR8g`bMe7L%DZSFRKkEGXuK8;M&S;JMc z1VZ*%n=O0G@P3LK(!dTVTtRF%f9{4)kU1!_xo0jeE(oeDcIJ5CcI>&me3zEAg)EAc zlH{!Cw^m2I-t6(Z57bq`qN?&j#tfeVV-izFjpN2*2kVM7^v?2gLt;$zSbiZG-f7{V zHYNYr+_6)%h$@w)l$+oK?%r*R+Hv#tjML+W)yh+gV!8`Hwdk0Y*ZKKFm<`6)AsgI# zyl;}FubcxB-!184z$l!YpD^<+_ZA0ul)rSm;I8{}%INc$HmSPutt@yQ2AD`h4 z_IT&ylsk(#^Qz#vV!`9{6+<}m`0R%S=?gYsi!v|W(p}VdJu6o1?(Ss8SSp5$v)G*- zLeo-+$CM?6K4qy4L*BgTup$&iNmZ9rRf(~d7(C~vr<|Rhv0g5bFtmJ<>g14M(eqi& z!Tug~zGqBcplq_$)9_QKM9T?B)rjFb!$?dC?Y8Dq#cw~lMKZlmD~lo*ae z%ezPK@?ue8iaF0+dxhEg1HOFzC@JDdLm90r-hAyfzUBEB*#EX~=Oaf?`SA3dxLXi* z1UG`aV)o1#OK&Yk6c~sc@@y+*!=IIiEAHlh@XVK%X}ySF~XR!TeGvr%du_ zCQ7HREb*aG9KB+BgV9ImW6V$ka5lq0O!6puQiOu2*<|<(f4ulLe9*K!@I7x=C3RJB5S87msMw8>T`9S# z%5m@<#*}v*G;+P|*;}r;&Ium^?*XT|xt=QftOGYIaaDyYW=Wzt$;KomD21FZ`2+va zAE8|>sq30w{>T4>!_%ki9NgH*8aEVx(L1```bjEV<8YYeO=0OMt71cmjA^@0z6S;% z-9Vijb5n-KU$?i8txpjY62W9ys1H3y@4wIY{m}1YcmIGGq3qWD!@v1AxLw!8kR-!y z(u7Bi$-Xfq*M6P-^JRtPRLVc)IBfeCC%bneW5L$0mb}+&Nas7i@q5X7vUB%Zf<6^R zVmla@ysv=i)~QL1o<_G-1v(jNWUEkmN966+aHyNjxszD1df8~8TeRG9_&*dP6aShD zETW)I%d&(I4U4HSW z{wASYr9!zZ(@&I^BY3ZQ^QH7YHFsU`-2cQUkzyz$62J^iO`c394H2DYY2<-JxFUVf zJoqFpCfl~7Z+pBdjVQnU@GW+ikMMT3<>Mcs&4$PmvD*V29R`V#rr91tE6@)^*O+xy zL!%G^;$pH6+}wwH2VX8Yt#^3W&3WQxtW`L$o&)Xa)o^AC0z%n0d2}6DPw!*?cbn{A4Vvb0A!O2XppS8rE|KNZlCjj1x4mKm*1F`w3>gR-L!#=(B$bqy z(*>gDP1$hDn>>t79iAEWpfYBM8%;$^lyvCJ#;3HAxtfjV*yu1|jCm_=HgKu@eo60u zBoB?6lvXiIx{Z<1Z#rTitx3!ejiW>}=DW(8q@xUt z6KN1%GG%&vP2$W(iKw|GO&=rEbUQkgZAtpJ`Fa34HXGR??`$fc-U`gr zn4Yuz&z+5&t&+8C9X%g=a>)Gjm{yXSLbA<=7?G;v;q_bm>Stm9@RaYrdqnLXgL6ue9IB)3(B^a_k)syyc176n!WK zsK;k(jmevI#A3vzVUt{yqUk7e*)-l;x_`aYP|G01%>YCkD;$={S*%#L3(9JV?ONLR zKR|C@=SERsqUTz(;;@?KLEv^wps1#FkD)dVO0a$$+DUG1d`?hEf|8hRJF=~2Q6|IK zVXza^r}SY`XQX#%$dKUS@~qblF$SutN=XkQys&yojDe_@YInwSFU&Z5vShXF5Tkta z{Dd2g=U10C$KErq3JRMRL8@u!b<(bx&lVV0Vxy;N8;mQ!x(N!ZL)PcqkCDaRHU7Rh zUg<7)RhHZ;v$3cyXoFrry#+?r9Ib)1~EXdemZEw9c_c*E`Twzup`YSKlOw!I*R zKwa3|td?o7dc$&3Lk2jWw>0{Y;YATa&-&gOHFca>IHCBs+A-p z$hKy9o8&z8dR8-=&2dG6Mo+U|bAEQt^6ZSzCyS9W8zr2x$$oOMzo4#aoOM~v6jHpK zk#TuEyvHQ!$XJol3Svil>=u?vE?I;KDF#l4;k0D_fMapJWbfc6JC#5Wg46z*uYZ}>e*Lq|fCFIX&I^=Pi8V52yqcVEKEzTu z5&UEuvkqMIiL!NfJH8Noi^@E^o&%=2^L1+b8qJkv&Q- zxIzKQ2Vy4u4t~Wv{9)UHw8_Yhld~b@f;uwIC>K=w{Bjqf7&_n6$4JPVqoJ!3l7GAR zp5wmbX&-qIJRzpZxmD#laG=Wd7`f$>um2=^j@yn7xCfl&za9WzgcNC)>HkkJHJKj~ zSlqppUIIA|=*rbBZ9y|=T&d{*H@X2{x@bd&$XV}EjyN63>sC?z)o3E*A=qZh1o7$G3SF~#=CBZ3lXd)`KFx~_$I~UI3zX8Symx{ zvnlo`1f}aceDDN~8DJ(OQJNuwE>AwqIGEX}42EHi$z=Cr7s{LKEdL&gO)F+gjbhA@ zU~`wYH%)QAmIE9tEY8GPPj9FlI% z6B!a8vcY_eMuL=b!iFN==cvlT7F1}&I5$T4p_?=?J8aUeY=#tUM{W+jz0touIeW$d z>BJ^9$Z7|^#E|8xaiEu_?P}qQOazZ3gJF!&o4l7b-;W{#%qeeUtizb(X*W5qT65!1 zgqY><-lyF10mf%0YOH9|kYz{UjyBxwPMMuIoIJY5PrP}ZpM2Xu*+A`a7Ai^h1|tFzO(HnGe9i&=i^JJ0&!gww`zV-fjJe&}tEE;@ejo%g8QhVxr@@GcEl zs?`c#6}(rLy!B?w^RJz9=lTa2StnVkGpJLvwound=n{2Z6A90BUH;4+YAsS&LVdvb z>>K#f#jBh(mX#NlYI))vZ}g6c#aV~3CE73Xs}HfRPT!kx<2^JyyJE{^(Rk)Hx=Pk?Q1~r01dDJcVWxqp+;H|Kz3g+35|KN0) z-dFi0X*9BGE>{!+uuI?_hrqVM_2>jlw7B9?edC>M$ehIqC>3pY`2ZVH^lz4Am9 zm#viLY))NPmq5o!I{nqhU#0pchg>M}i%^B0?z!u{yEDVawEWvIOWu0)l=HUd-i4=XI7aQlL2%|6_W;3Y0zhk8JADP87z}DCzAnyV(lhA zIV;^ZP5j1(B-Vl?7Leq~-J`eP=e^UC*j_+)hBOy^>)vzBsu`fX@#qa6Hy1Fs=naSN zm9&!5zKoR&ay1y2R{c&U@(7*EVq-cCy%B;ymp9>-xQWNI2AdBr88!%+S{kAfyB31a zMNRe%>n2)enC2@fzco!Y66~!sj?$)V*psJ+yyIKm1ODz``pbOg5BxB;C~|S3X#-;f z4UvEGv;UC4@Wa24_R)Ln9y~{}Qzv;NZ2h?oXfqJb7?tmPNY|%H4U>uUBvU6(mNH-?QQXD|9?8*hgG@DU#aeA9p&{3X zd5&hAqH&pN$W4k^Ws#70I0t<3`pwK$$d0gXOOys^XkWo6Gzqw@pLSG=vyw z+a~>dUBkMlrF1 zn}~3BddkoJqkqi*{3HJrpL_ESz8ToNeH&{XosU^=o%0U#ioRr2p4gG`c@<9}Y^zT6 z5($4RhnETP?^2N~V%RZB4!&F|?|t|ocW>Wje`gQt%w*W%Q3Pi!GvNH_3G1e%ZP%c% zK3WsH^s7UkywSBye{Lzl>8hjYJrX^p+UL0!Urrl?Y;_o;=JXP{Zv?-{1Z0t8Rg7$8 zteU+~s(9bgv@JeH8ibdc3qJYi^T~IfLaRpg!juCw)aNG84SR%@X1<2WFwhBDNCXHy za|6}9rdV7<>IJ@-^VltT$IUsm#YkN)CFPn*w$Kbhu94Ix7=zNa*i%kNwrwBKTq zlEfrwagWgTo?A`J`MrA_8$&d9l%}4{YToQUU-h2f{N)wb_m40%s5R(Ji2HM5IU~q| z_x#7{Z9zeo)FL)3Lr@H!pq90tvFa+%x@93r42c0oDTBO_Xg5=vl;~o|q2fl`p1(Rv9`r_nrsyrNp7WD4TwPGl zX32*f!&c4GHObOc)pe38M<_(`>hV~NwRpcqHKmKp_9`^O`Ef&Q;C8v@->E#Y@A+cb z=L=2pcT-gPT#RG7K}49(_h_3HLf~|HoGXFItlDJ=4*x3y2X86v+NXPo(#3T3a}$x6UL4Cy`y3RPJrWnsUjFowb; z^_kB~t+@y{ExdnjxnVo-%D#iTGOT>IBKdR=iv_I>1hWMp9QY8MSiMve46&bFQee5< zNXyC+yND|VYNcPoJS&ilrf2lR_w4V^Y5W3u7M~QD$)Y_B5+#F8$Kn03DW62fVi-9#I%G?VOaRTYX^Mw`HdxBY_QsIv|AXi6 z&|Z9vN3kH75}obxT7SxS6eSMJ3%hsu{K1>d7B2OHy~h?NTTX0_Th;s#6sLMM%45rW zt~wHMF&1Cg3~WgSjraWsw$jienV^?E#+!y%H|qV|m?epwH7Sx{kj4%h51*xG+V-q{ z%Xa~P<>(0OY|1ik+jLk<&eZD?;NsiA|12@e=3H?v3dW5uqqNQO1fx9cJrDaczHoZNfh~A$K0{+**A?v6^SrItDVQ&1sOxx-$(H}tO0tQhM!`%))WpswC5I36KH^2V;n#fn!+$}bAZ7~s zuzA)g)oI7kb=@BkQoCZu3>e5l|?qL=?sGD_`oe#5^*XxjU&G# zC0Qf~TN<3D_)I4bv5I0b5nA8T_kozRQPUvEY*Gb=_^&Jn57Be-@F73)C;ucNc&v52 z`Nc2ufB%D@rQY4em?TY?4Q(uMTNw)kBZio>)MJb(S&lGLQ3u~<`Y!wyXl}?8)%@Kh zziOh#4x8QtlyfPMYWQ|FL7Rpf-DW9fBxV~8L=!UiB7(M?GeA=5(GBl)IG7dd0eusL5}tA#cO>DD=(@wcKKIY}w9_QIE zBEYI}*r=Rz4R7@wcOQE0#7EfrDc)Ml+~5zc^LF@F&Q~?6hU2cpM}uL3*GoQKO)xIW zD#56*j)te+;KXpMj&qwN?9RGGTQ(LON@7t?wr6HkeWd2sZB2mn8S*rlYC>GE^+Zg{4G^ zxMc$z9%~&$zvEWkhcNo1cK7$_Pg<7kir#ywy2iO;d_{C!N8h(t5z4wAxA_eQAClj_ zyIAqgIqdIu+EG0#2Kv(;>m zh|+f*Q6Q*rzC0(!$h=-qi1LBLuQb>A7IRAFA|i?p&^=tUwrkw=8V9)4_Rxi-tTmQun{)Hejt+#pV4J}>bgwj%w^cwgy+OW|V*0o>o)=j8YZxoHTyfpDtKoTadq-go%E zr*s9CbKJeY&y%AwZZGB-YdP=z7!!(mq8dVIG9*tjtSKzj=MzFS^Dje7M_fq0j%0nAfzXO+mVQ@S!HYU$v%tNE}+{VdJ zc{;3|!mIU)T8Ru_)xkoX?TkbHuBsR-#3nGWBe;DMx%BKI-*f^o2v2?661pat`zLb1p}0%V$waSCQZqA> zlc(M%1^fh95eL$q1};t>^91<$r$=dn+y~0S@!-7=`1zmtX^x&8vVZM>f8z(fpXXnF zm99$@l|S`!Kf`CPU!(u?#%~vz$H&&*U@zyAN!u~;l=OzE@F&aJbJ|c z`J+Ed;rj%#lQiAES(uavVS*mU^33GjR>wV^lg4os!|qi^jkkGo7{}5hG77)-E>bS&)a0&30+$z z&+$a&I26bmvg@Yk-Bt%|u0>E8UC){uL}M(6PYyYJ@)#qQgX;&}yMG^J488aC%N4)t zhkl69f8rB->eHX*)uUtF-nA61Gogg*r4n*fPp~<7%yhG`wLRJfYkj?;#L&o^A(cW0 z+41HY>=?_r_gpk9`iCFV-RL>k-J`C`QMT;8C-gnH@7>2(n|;M_lsO9ac+H|NFbG8v z@jkF@JY^wtZOh)RS6J-r6SU8pPCv;pPe1b*_koPts1e*Y$k|oK0hY;|QXeAT_ep^P zT`*343NYiKCfo#Tw(_nu!+JWO z{re`2pUav}`o=)^9UA&1!M0cY(Q%L@4pRP+IWbYlM4cpeM0!6+Np~G9`ZWZEn`{;7P5e3BsGE=I#9%xA$7U^Uf*%;N!=9 z<=q*7_Vr!fKk7NUQL|$d<2`Y`B7{JwYKk&5JIr?G{Pti{w_-wcj8vN#;Y2Vt$HN-7 zY#5qj`o@HfvEG!evrf#=U{AnrTcFDP&vetIr&yg0#T}JQq}Qz;bQ%E_CE4bZlF4&( zXU@}NPRt90ItF@UF(&eG6)8mchOZvNEI{RuN(hUJxB5@<&hh|>=`%JlN1CeAsnDoU zUD>%^+nOyEZ)cgJgITQHr%#YcN4MjWunU8bZ z#bO-fiC8+6FU_Cl_W2|Bq(vogdfIY)+_Knl+`h9wL!NDtw!zjp`o8C8z2-ZbhF!nM zXYGPWq#9LyYUYj$+d%=InXe|}Q&eao8Pz0+W%&+J%F=UqzIC@pc>AXQepjyKe%cGy@o!HyBI zS;5N&WE)jE*xTdq{D|U?VipE3O26)i^BJe7Cp2A)Eh_FDT&FCAdpGx~3QOq{wC%j@ z^0F%rt2NoiG%q-em^8SKif?;#Ua@q>K-?4=oJ;y)xXZ{@j_5bvzwmY2v?!V=K{E;8wNB{Jnrfpk_qTs*(@gL_0{?H%d zw}1G6YHyDs`_MG>%(FDhNs{n;70yIQY(ABvC|oAon^A>lqf&T}Gf7ntx{ki<`Rs#7 zeCy7fU<|z~#yP5@U|!eM&SAx+hdoOV$=jQ;5r`Z4KSQprNlF8q=$&QSnyL~*kA+mM z5eb<%K5Qh0=~>V?D4Rf`PlmM(3EmjIkC+hgCP@j^2(b@zK-+t|u0vy_@B8E-)#UN> zS-R_EYW$sn`K)GHm7Ilud+-5ITR*BJ5aEs2e}hkd@Ap2_kM)JuUgL$AUZn3kZr-`g zfBM6}pC9{)zr{EDv|)2s{97S4wPT~P(e%a`@qI_=do-j-4(AF?QDg;%-4a!6$T`J% zN^ES(;EmoBd{68HA`vMZ)|MFOvS)PTZI6J7O53&cZBOIZlubq31}H5>U4C6ZT2MpK zM6VuPv7!)Kk6$md^coBWrYUN6{TBY*U;Qy&dG%HLuH&QM_>KJIpZrNa^Lu|U z&z~K$dvGJ&%ejynlmpRwv}p-_GXVuwxY?YT0Z*doK#9FabM&EDi>r!l#=|i24}sWu zV(bxHV#*?I2rmt+V&@6#HMMrEQI6|f`nJR0=(%?7fVxgHM~#uL?Kg*8wnz-N&bDpn z`W}}HIAEl?Y&REecGK43ca>@N{T1f(d#(B@9R^^{#!dH+~z?{m(;%ja`H%)q_pp0f8^>v^8v@B7P5)p}8p z3Xd^3c&1s-{K13V3KV%xQ53{+Oxo|$d3Znhnpxsn;tAbDO(>(Ys-Uj+yKtamWZyA^ zCW8`4=V4{8;?_B&9kfK9)s-x+R62Oe6&3KXR4R=~M%)m>TMW^!abQDEtBgZ6=BITo zL<|}`)Km+9c9ubCs}nwUt8m!Rk`VZPuDtFv{u+}~Vu}K+Z<3fYdp#VgCBl1Fl?b0# zsqS?ngm5XUv(NAxf-3?N5Bi~=vqgI`dqh>3OsOAauk)jqWluodPXFYgAu?1%^g}{-JW|L;~%n~0zVlVsl^!S(i6hB{z>5NO-35+>Qnq@@FOYLj58gY_%rhy1W@?kk< z<#Sg8tQxJ`h4GTwk{x2@;QTIzAfcm(4>uASiscb@Fn3rN*xA7cS|(7824%X6k5Qf@ zr5Gko3$ILYnnDZKPa9mlLDx8fc7|RQp&aDevS1BocDt;~meGwBoKUzL>{G`xw>;@5 zch;hv;6U!#37LfzBUx~YIu0!rxfS&AX+MQ6De@6YIr_q1A4*2PK_g}OyralFXQK74KPca&KegL%4t+9Gy1#A6vo<%j< zSRf*{Cg>OfO}cCh3W<~sYX+>2!-o!mb2-wqDgQidg&d$zT!{V?)u2cUsMYG!YBl=( zK1ET`Sy}NDdm&JXFX+mm%7s|4<$8lovW3Zhhne02V=}`@L)tgoziWwh!%L|Zu_y(xte)M}O!qa|jA=Euc2sS|@JzHkoec9$^LQg4otI7gawFxEGp7nTmtYK$?J zG*~9$OIg9*o?uH~@~TNmq6(sTkP1?aHArd+(`#$=JJ4ClDGG<^_Yul+bQ4@{4Zk=j zsS84KM_7k-8jA857d3E76YHGCyo<4xmCg!dEyu~V20IPBe6G)nonQmLNsAU36XHH2 zGnCVg+mnh(=Pd zW@;L1yk+@7H)nPtq8TfMi&@Ew!9H3>NoNKb2abtxs94b&Z&0h(h@yzagYzsMI^-J` z+Eg7!8LpSHW{Y;KjubKgx_#U#%K9d<8~DY`csVgF+L3{aW?khAb}raTKJq1xrxXgXQ(_Nu^ z?;olSJ_6MED*DR@$->_ypyDx#Vugc%&NJ_+x0XU%eh-X2^bpfP^Qa?m^(MwTQlouT zkFh>uH;&P(){yR=3qE)eCd@Ga#{x@JV?+*$V z_=Z^zHF5*#pK-8%ahdX=c9TIbgWjW{aEJd>Ls73sSS_(elXg?~^t<#Q@tp;v1*Bb$ zh+>lVn4fA{hwgOQS)`-ie)t3EjP_E-hKZ?Aw212YO9AN+omEJ03qVRHCdUETzJ3?C!cyMKmG6j=A_e4C(Co5^o(b4;c+MM-MjAKs5~R7*9Yg5Lep7UjNOs!jsRD#9*hnU-SH!1$o zIcj{C>7zEH>M=4(C}c!C%XsnwzrgPE-&00RCgJMKp}Mcj)QcLLct#^@VCVezbb!U6 z;Ft_|*uHfed0=do+&K~i(rH8yCMp)PJZ0s;0tfHDjeMwiHa)wA$)h$=Z?$l791gKi z6ggS1$I70)=-vwPyd++(( z2mr}AN0z4iY+*m=-gO64M;=9Utc_FAhz3xIGJeoi8h>wWa(==-EL{kH*67N2Duh5= zhZVMxFb@2+VG8Dgl-!movY`TrQE7mb10wox3_*oM9HK#&^5H_4f46}|ANFlEEHiP{ zv{jDSKE&v*t}Plt5=+v)k8G-85S;>@8N#|9N_=*u_iyOp6iD=Y`96Az0Nw2U| z%70!%jX?1TEzzKHKYT!{*dGDFW&< zSchE+#j_YK(M2B>DP*LGL_`!xoUBo6)O@ycR$vQVdDVq>9F&3swP_~SPqJxxk+*EP zmz9Gvd}sFt{&iuBPD3&kIU3esvWz^-eCkacQ*Snjl6b(>a09t4;Y`S3@o1=EW09_M zX}Cv;Z>yx#VlbUIwsLHYkVoCfwL0W?M?8WAUy=H&EWm*qVt6PkgE7prRxE2L3#|2x zmP%=2e6zjROG&Ncn57;MIY%PFCZ2ngYelXd9qmvEQXzRjN4_X^h6Us33yZR4x|qLi zjgUx1H3e3oc_^gxX(Uoqz$>XF)(jerrM!KF?P6WU3-BOT7Y^lORJ1poVB&`En;{TL z1!;3;=)VgZ%5tzj$-0Ss6d?n<5E2{J!D-rAkGR(I@t0au z?lNoo3p7QBQa-@BFqY=(aiYjaXicwgvv{D3*1nOoR`qydYWOjeq|&cHHRs5RK84Pa zLePjCAO$Ll>GxL<#-JT6b(d%q8Dp(+YFUSM)dWKUcgL=%d6MgPXnsjyLk^*6>W~+$n;k{NAQ4JNPC}%MSmOD$##5Iy4 zV-uEL2sR7fSdy8g$U)W}t|o9=46#0j9Yu znS+N{Qr6cKc5K|pN-wK8Dp9y64t7(f8VO0HXgWu)&_pr{67#U+gn?b7R;M-IL`YBj zKQOnS#e)aKedlAf>q*2|yG<;;)hdbNDyBQn3V%-uhKle3(?Q@PCMzPlQb=Qi?Mwta zTRAk41B!c~yAPZ>K}8fY603>43kAO$Ow$Ma9}oMbOuQMkMV1nJqq%?XPVQUYk5&?e zAdkvnM2OZde@+gF@iKr|Et2TiR-Hq}GM{Di3V&QNxBkAECv z6B8WRyO&#k_(R_P(wFj^ZkJ8n9%^cexZ5XLTHx8Qcm-!%emT-PR`%@SOD}p6XT9jf zJmK}PV|v>*R1~qeXAgIN;~V_+O>ZKiPICOweqvj`Gun|InrCJ1LAGCbF^_%qb*$O3 zgR!-%F-5`B?l~U(K*cZRdOD?;FSN-W**nZq`B(*vR_U+}y zfB$!`d()d~?cT#t8`gzl(ci%P9( zi%RBJmEFoqDv2F=r{KPR7l04^)nD<<=RTJWn>Klp>fXKFa?}6vhO1x5E?ux;ah~?X zG#1D1BIUa0zktiHzM7&aFh$OV&v_0nd(P$j*}MLh%|{%;AMU=J9Veem(N9U*ts(EP zpI#Uz9??;hRi_qeNrNbgs5e_oOwVw~uAMyT+;dr3TJmc|mhsL{eS+^k;R)DY7wUCz zmaLa@U*@|MFFN-byy9(t#^HQGq+~;77q26rr{Bxg1(N7UANq1?U z2g6Ic{@w59f=e%DcH>3_uy1aTpa1k{T>Zz_a`)~B*s#39`VF(R+hbU5=yq3po@mK} zNEFTSX&Pfq7Klk|HC8p6bZ)(wH@xNTT=49R*|cQ~Q`1wVX~v$pId1;>FZl4=|CVU1 z$;|q-KFUXU)kbH2k&k@&^AuTzjAK6d&cEZKyYA*MKlDCMf867ko|&Q3UE!WP@8*WT z{~(|H`d2yql*h0&h9~U)HD({IIcpRB=6Bu8FTV6eZg~HvIJPlCt5NsfOh8G$ z)~_6z$96kB{|&F>$>*QX+I8ztQN+TbgZ$y{d-%rZKhG^c{4p~}ZY5594oKGT^Tcz{ z}Mhw`GO^nk2UXN4F^-H=1}H4)TO&X_Q9_xr5(e+NjSlwmCkL3>6YhCCEi zYD!t1+0f{&e2<6G6)Py4k|X)l1dd1fT;qC7EzPKfNro{w-8>KQ9f5W^t^^1v|DGzT zg74f*UQu58WlKw!wOFUZRIAcXkECL_p=RYENkLS(-out4V;&h7CWbxhZZM^nLkx*> zLLvf*&1!EnQt>|QY^+(G}lhBezL>mYwqS*i_?5(&(VB-slkGCOiM!@Fve08UDC8q zJ*g8n5|Shi@3EyyHFS?Ka8(nY@NJJQpdG<8>W0(|{ynMc!eN8^h(`M`ko#diWrlu- zs~QT7>}zgFpW?K}Is-;BIc9=kOjeALQbCbv%^E#8C&(2pQ4O8(1gk8OcE- zNral zqvmLc8~j;1X&}-iPp2<=6jqN!8~S9d zk_+I1IwMS3wF^lSqT(YB2bU|P*WS3HYbC^n7I{9n5A7h?#z#6zl8`}doE^l>9mbmp zk~d#uNy%b+l9kp3jovcTy2DyJ80p{lthY=QD})qG&9><+<*Y2GSmT&&Wjr@O$Wku3 zrM8wiE9j*j#Y27a%+S=}4$kw0tjo<}f_bT^3y10Ve1RAzDCIcZrB6bWke2G-{O{M66s&rVZoHJNvo**Ib*j>F#K&poC3b+KPC*$~u1^Q9k#r_zf;TsU`h#U#LUzr?M5po2R)?P8a))f>~OlY01f)m9wO!&;i9zr zxM!pLtYsC1D_q^VTfg@3Qm$ z+jtu3UFc7HpX8Ju3KFH>yw_&vA z=p8#CM0A~aXfKV{l#efo6x)Dn|NPIm_Rs&Udec?|>>xWsy^#couQJj*1lX^9%eI zIBoB4YKn$ zUE1T5JmvDsdD@j%@+T*r!T;>KgPDFG8!PSxE}mV_5oeu+&T|$I9^@Gx`3PsfvU;Z-3UwWyBVOUF+dZ}=0 zb{)rG_8d;W;!3`H-K)9v)BnQQk=sbxO`O*BfeojhMs0G!7kl^L&#Q00otYz!z-Ud8 zru1@+7LudRd;(dTqKm@IQ>~vu9RJj_iEA~wOG|X{QAz?IjiZ2?lGJ2^c9yQrf=Z$a z;6Mn$IIwzVhD{=(kcQO3THq(cJ}4=f+q1i>5Z$B1gbfZE4r?5XyZ0~$eCDG!aMd6E z5qVxl zV=QSuWqQ>NFMs{(xZvVTm|r;Pr$0mfYpn#aa}>QUcN{###iyOX7k>D2RHXdx4qfu; z)vG!G;%9Mhzt3A<^(sF2sZX(CX`V*2MGCCnxRJBYehO)xBZ1dk{bJtt@sE;a8Qt(c z0ABh(1XFehy1KwhMo6%au3HH4P;tbLtw(VC{k!<@Z-1MoUUU&gYj&J+3INMEV(V~) zu8Q;D+;kJio_Hcf;br*!exG`y!3n3H%0GPe(_Hz|mvHto&Sz)8g9Uzf%dPzJ8(vSA zrqpUR9uNF4j-#C=k`ler0pOZfUB^+!Zbt~gL-#+xL%>m{APvR&3!eIPKJm@3VS@)x z2M_Y}+O=GIi9NS7#%|{@oAJm>wrzNoh?_ zvHPBzx$)*-bKFVCQ{)BK`PT(7yMC6-uDX(EJ?GiH_9ZXpj$hu)x+As{*CLEI?7QQ3 zPCE4z`e`4fBA)ilXK>jUK24G&7_I4ayMSWHi6`)huly^gUVjE(e)F3+Jq9-gDUh_L z*!1M5QDlC7I{vxmaM`E6;n@#bllJ_3zviUJ@VuY(`POw;a_6URW-j=Pce3%KOZlU-&g9r9pY0EtQew1bY0o~kpL`PU`uInQlNd9+eznIq<#CVW zj3+*kFMQ$?T>q+9vH8hQCXQqJE6Yr+n&$YEPbM$?Wb^r#UB>la{GzvI7(>73n^MP~ zcp~rn)F*lWTi(ilec=n(dW}_U*KzV=9_!a6UHF5CF-*%jjxKH+--gwtYmxOGRavMD??bqE@S>FJ1~B zA+$fZYoS31jFf&I3jwu4`RSxmY6xH^1`(^_FjG>1l~9A&yvjo>M#T)nl6vtd0S!Zi zqZ}%Otab?LhFC9pMD2hPI5egRAg##ra-8%M&1%OAlY65rg-a2+B#t6v9Qg#ASW!zF zB-S@Kvb4Zx1KQA!B=@Q|53U|(%}mBKr~klt2iNfVeH-{eR%a}7ObNe6;yevlcY0(3 zqFSALy^f4zHKiIh5)8k5p>XaidtYL3ocF zfaOYww5{SK!!=$K5>vs{xcBCos`2T)ryhElGi`E~=l=kW({S2gYl=hSX!i7*{ z&=zu`C{QF~xL7WWUFpHOQuzb8@Lb3rjP%&4h48sab` zXc-46Mgj;&?1Yu1RuG=cVR`gUAXG%8VzlnFZ0cCsK&c#O{X{%Z>NL_Nl!Hl~F`v|{ zw2!LT57Kg+MQb}k=XXJlYpo553Bx@`F9jbgh%(r91WNj!6V70b_05)w##P#v%*aWh zno=H?9a9!vqEreC!5$G2yO>#9kUGV_hYqn+i&(R+L3=DgAZSe_L~+E*dLInoSjkwGl4(6&BZ zChr;4R2>xyqMBmWbPK&w;GCpy46}_M=j1tevYI=!L0Rk77;)W4H<#w0@D_}<#t|~2 zB{a22;9N{B6^D{K%Qht{e3Y|YvFI5IiNGxPiCw$e8_LgG3Kf!G7_x0jAL&{aufi*# zkdh+L>191yDn@69Y`IJ4sP#x3iBd?HV_c~ckrlU~WP~lH1!J{{X6)Hwz0Ai6V;!m< z)0k*twWG7NNYUy07_l(L_O_mccC#6ZWm`26h7~WRs$)0}QiP~KpFti{Kb-SPFs1CB zsLBX+&SJIq#5)8s5Nw5%-kvADZ@jd=4RzEjhmpkudw>o8A#a5f1MPQE!Fb$v(OG8a zt~=PBErN={LS9!sp#nP)#@XOscS9VH@Y=D?2YzMFkproNP9iz=N{q(-}*MkKI>Ve{l0I0#4+841$3IC;uvELS(fqKkKMpYFTIwf z`|c%A3yP9>u08N3O;b*O_N7d2-i)=@6K&%d8%LzhG5_$x9DMLWbnb6Jq5MX??|}z6 z_~3&q&Cg>2Z3lTAMo9W1rY~ZQtWirEOqUS1p;h}3(4E^uXV?AoAAE>x_Z$G<|L6@k zt%;?g7T5UN``*c95T7jKi;+j>i26f8)>~l?_{wKL!xb-jF};2t=PXJ^9N4pmtnVd@ zMN!b67~{bHy#O5AvmcY|aGhxKLX)N`*S_jCob&Yay+5wAf-#09@!rrZ?X!B_I<9}; z-|+xobdIr_dzN}!cg;1t;?>vD>v2x|sA=tEaGdoT^sgmhdlaV9=XSiek zA)a>3R=)a+Utyj1{YP<3XL*^00|!vbPpbN9%6o3OfvcW=F1vv!2|i_GDYPcfGV-F} zo!5VeJkOCzk(9nLK*X{4mxcvWJ40^WVFY1!c#N)(RSFX-AtVu&2Jro_dwjFnzF9yRjZTCYDRhvkbrabnnGx^>(zEMVGbHl%Vi9E|F zv?k4SuKL@*_0KKK{e}_XU)xXEL7HV4=lJXmAMp}z=hy{YdFGS&)OWt=8!g5V#W4%> z3mn>ifH;oP#*pVZ|NiY8nFjWC7df=F$Xy2?rn|B<6jdZhmpdHjE@7=@Y4HF%@B2O9 zzWW}w?>Lq$^?v6_MI1VK5NrHKo#zDz$;ZCPqF zG8>LMf)9M|(?}^Ov_{1-S(>ppKkp%1*3#?ux$4?$dEQ^VgXM?kd;xB)FXY2}t&4&W ze(rOXRJqsd5yvq?`G!>5>+$NhzmFR94>SQWL+0Dhb36ex{wTFbeNtD zdE#wgU6eWMVgSdhNXy|+Ys>C^h;-E2`XbJ0bcph_0jy@Ut#a2(3x%W1MIZeBYUDX2 zsm*E$k#od&I`X=rPaK|OnJH_-VakTs&a$cE0=L6xjVlbMC`kK#(te*jP06ziT@)UW z7L;+$LQxdtIw#Yq=hTGPG59k6OPeY-uKd#0)93e$d9W>*Upifvov z@!~v4Pdc!9PSNjE^!gZ`50VZ_wT82mEg?7l`t0?({`F}OiIOU!(Q42dZ&7R3D*Ah# z7R=e0TW2H-+ZMU@nBVi^O$(gVtn*;4iEY-Yw_7w@O&YBxwR)2%lIS8s>#TxYa+qJT zoRXFw`yj<)&?s`lMUkj_HX}CHk>-bFL=rl<7Y5f(RdQJ8!L%iaY^b56hU|=58*G*Z zDYj+xR9b;7EBk|xz7dUMthY=rnkGst)MIqi=0Lid%tmyr#8|L&M0s0e_2nBaaioZ(^waBb&Jm&6RB3Ml5#E29@TEup?C*}je+z#vs}B+M zx-KN;lq?Ry^oFgbh?FBO47+x9xa-b&4jxRg#*$^$^LniHanM@OtZB{|>vL%g?bZZ+M%Pqp zEyik+IHFk_M~3L=vJrJ?@c>1h6CwC^XN+FoLr_&vPtY^yxi0B^M&8L0-NM^AF+n0O z7^`VckOvqmQgR12ZjoA_Ea8SgrwD5O|56_TACHV54^5P@~Y zVJadq;yibOH>I_%(yg;R5AS(U1zE$vA+MptYSXN+%dg?+a$Vc&2TZF9c{lw2a~8%fR;J`z`3Y@TDa3;RmhO!RTE zevQ}F9^|a&C0Xr48j1rERoA*LhmX#oyIo(L_ZKiITqMZqF$Dr`eT-+C8S>nqbL01N zV==~gK(7-O?9y0=ESy6-N9r7zbM%cRvySOlab&B>ns$xxT0&$>|B_$elnTDW;42=c zM~cIOga*ER|3Xcn(GCi!D1^YtvQz1AC>>(foKzeKy#2!;;{M3OV`Inpi zhq(vt=lXa24G#jh*o>PO4^S*F5lu`GjgKK?iAlSpO9uy$FK(11ZKU!aB)(Cht*#an z3+yWiP2eQI39z1Zv zlD-3b_VCzNi}5vUm{_-tZLJm$-FF`%bh#!sZRE6TuVwLp`{`x{on1RQ_Boex^m$LK z3a)#9c{5kGT3mMIkvwyDmUpyf$+~``nD%>I`u_K0fplpZZ9{=hdEoHG!YESw=6nCe zSKjeA{Odd3!OmNM%l&uW&a1a><)R%sxaa@=UmuBwWO4s~ww`bTS8UtHKfm-P#GAH; z10Y!8b25!H7%T;ru2IYn+#O&o^=6Z&0b90jXTxzPaK!1S^Q4zt!^eK|6P|qai?G&G zuh;p(7eCL#_wVJrRWqCtM?4U2YaM%4H1pR)W5B$rgeRCo8?F(6pMo6rY zQao_qF5dr+zviv4dmaCL{fGSjuhjxE)JI43^Kub%l>&t!s=6H-nTVmtG^q{if#2VF z;{eWOEyta9s<*&*R#*f+{(%p&dUlpVYvLr~y>I^ure6h|F<46SyXL;LsRfPTNvFK)jbfW?J{ii$c6s=Dv)+dT83mog8$ z=ffXiwmhQEX%5xtZOg3j8$vbu<3{+Xtvwzy!Q{j=CmQM_v@X`DHJoH8#eYb?a!iTHN*9-}=wm7~*=J%dUDMMoGT#fq&%m z&6_#um}9Dmu!M4r8!Vs;TvQW1p6DJ2~yi{Ba zN$5%vx)MHuMpe1YVx;r$bE-uJ8LZ^(hf9^i1VZ2_EEGzOKsZ;Y9j_p>190Ae9p)-l>%{G793tmiz~A%CR|g|!%l>xGWK9D>=} zpiZ!5@5K&fvI{wQf22S}35du8$ODkAYJ?0&i5Z9PXXO1f6e(d!BZk-mA%d>Raylz3 z^txU0EcdZ&N)aasjjW&?@9p|%R{EMRigptkjfzyLk*@NfiAxe_eZKBQAcUt z2tTJp0K&P!&$@DwtUNow76r~(3L|K)bJU_f){1~29V(=p1uIC34k_bcG&-^5^(GNn zP<1+T1c$63FV!)nmo(%dmoi|2gk6?JdiWA5NZ-q5Qz$F_rBlX5mBp2*M1~nsozEj50kUTkPGkCbpje;&qF{g)d0d;u6@o@ zs_i@9FUSKP<60-zP5$R1`73X$C8yFaoIz zCr@Nt+Fan|rbdSX3Wo1-zh{`x(1?Yq_#`VU3n-Iwmx%aSHpMOJ1PA&FokDu3o^{y8 z9JNwFVX;|(YSn3PT1_pMqz3A-=Gl`yuGu2EcsApxx*#=F=NLP5pZdbigl2~?hHE$; zvN`vl!vxonE%?SL<01BWs4?r@D1qNuN1o^8SsK;~N0#-;vaA}y?4MsCUMDG1O*Ypj z+q^bJjSA|e^dzsz}jDVt=n0 zjm6UK_PnaqIj?My3Z>M*>JiT1I1gU6)>33yWxSW7x=o2=6&kZtNlwE|l)++T{q6l}<4LCV`Jp>$c`sv|-25Fvi!gHU?T|nMhVHr5@ZEqvbJcy&Wu*7T5 zeHvrH32QfS(&{zrVVU=D*hm~FSmRJ}%#+^rw;bBHn>>)v#UR3)xEAx~R-3n6`CM-N z!29^#yWh=&zy1wl+m2?{<}Dlp#7S)=PxDk@+u9AR+I|eRb{i)Zxr3sxXzS1p--L0# zXtxZMnZ{by9(g1m>U8<*-~N_&+;TH-`{_@3=|6s$Ehn8smSyxi9X|NVS8~xyu4OAw zYc^@im|5VMK-rDL)MF<0g`^Iuh`0a4hy4`O!NUAJ&v@3wUS=#{9Qf`{xA2h<{XI%X z6h+QuSG@p$m98FXEh(wkw|5^;J>S#jH`f!^0uKUT`Pa`AMG@9o*3Pc;*GYGUU8&=b zE`K()hWFpZal&;kc?mzg^L8eIjrAq~=U;dM3x^H`(*6+oEzP;7pY!S$y@2VN)&4P( zgo}<_55PtR8$-2x;;E^pnwjPqk3Et7Y5s_VLYyT0`=zJ_hnvz&VLactRm1aJH6ckr{%{tJ=v zERd&Z9WyPhb9W{_As};~9^8JkL7wES|J& zJ8ymAHNV3Zg>OHaU&L-!ocDwDb}}D`k+* zO0Px_ztXzVXhS!IcX!S`BHqe{^d1yX_+f}*{Lr1t@9C@D6>-R`|U3B4N=b}i_9Ba{Rw@^Y~ z_CcEf03ZNKL_t(ya!s$F^FZ9^_VpdsZMvIxths|1H@Y0qlKmF6lYX+I{aJa~XFu%w zE^QK{Uv~8GLvf4N4t?f!P&|&@17W&rt;H5W!4Z-gs@!}0gxCx<3Cd=VpqvPskb7=D zSV=mvLU8hy9#$AX$Z!^Ibtpb9Cd7iqCS}34=;jlM9BRb^nThBWia%r#cjpQtK;f#U zuveStYIii8HMw?@75Mxd2C$RyGA2^o%1j^YaMYbAx;wdoepWIOO|{1cy4?o zs0c|Q!nwfH5_EehMV?nvYGaF_F7s=Tsp5zIxs5`j!;qz151nzo@HKXjU^LXY7*=%; z8kK&{(L+;m7n&g^sDZ2t6Lw?6&x)#%R@J-Cd-}7L4)^c6orhNrVvRs6=+xq%3<`~Z zAqMJ_*zaBbd=Mt*&Y{D-V{C{hH)W(*nZfLQj&=1bAn8^*U@c!CNKqh=XoQNb*qt4dx8P)=4KJ^@~yXpnZ zA38*%(LgDQ);Yaim)VUQ`SbU`mxFuva^15oVJEPZ`VCSa230AGuM(7F1LbcO5GU~? z*s|s8%RBoAoed$l<%d6J9nhTJOmqD#t0&hm4t)0S-cJ-oA-q1~;;Geif;8I=0T85A zJPe!)jIY_i#HzJCUU*sm|9tCPL`tEJ;n*|I;M>4|?VV#kuztr5j4?>1_|C^~pba#) zY^5>Lran2v)P_ym4Lta}-yx+y>w?EV=W-4JSzbnxg~eJae*2>z^Gjgcs@1IBwvAQW zk6~if3{jj=OA=~jH&_lj!!00g)<{$oGEg-Jhml}HJZeSxv|gRyup>9O4L72noG_Aq z+-SFW>@zOlDFHxU|0m&*EaVl{9VvolGk3HiItTCua@lPN4K=qw_oG;b@ z{`{}riHdwzu07UbYoJ&UqgSPr{N|Vc3&7E6kQbFOZ~tD{5qiM zA2#st&>QgJ{SP37L>t2;S6x%RzOo1s!XJ*WdD+VVIBs?w$8T8A*3BDej*n4m)@iis zG{zce>1Aoc`o>2+G^a+rivtjoKbU&SRM~Yuc$hCVsd=zi2e|gqXIH*Ar4(0R`_k%h z_YeJ!Fa67>t4aB}7hO=%^uK-EuhCf^vamI8y7pR69=BdH`;@1j8$S1l&wTXbBMpYD zUV1G>Q6Qw`mYe>!`dtUZ7a`%H+woU{HikF9?)5_!T@$>l{;;D1RXqi+eclU5Y6;dF zqA23}_x=N0X*0cQhE~1F_}BzbIq5O{-Sr=&pQZ@>^S|iXm$Gx`?=ZUXQy>QZs^9(g zHUK8quR)ECaq3vYD}nF7@-=>K3D0rIn_f<*T%(3=AgL64zyD*Vf${YTV{2+m#y-2< zg@Q*#ir;+q8?=GR8ECH-r0Ztc3_P%NX9e>bpO~oLlf|76a{jX~q0k!P94jj;{L^PX z!+H{QmSX!ol0>o(`2AhJ(V~sviRYZdp?mJ7$kL%?7)hiQ-~YFNh`H4>nCJmEwzvk$X401a% z%@~%ed%xZ0Y=zl$h3PzFx=2}<^;nyB*pjX=oAp?qF0-Y#%#r;iw)U6U+*@H|+NE9O zOy()ALZh^!sr}>T){z-YX8l2;FhQ>Ee1Kwj;i1`Ls@fYlkZ$MY;lvFk)|U`<*NO;icF=u)sx*3jWlqJ!kNoqJn|4v*vI=yqsdw~v_I3g-`t+GvU*M;Dsm z85;;9C6O{h#Q}CDB-S{8Xe$a|u;)2xzfY0nUUqIx73=EEKr*USkg*R)C!Uittkb5H zYEqG@eeY(dqLC70gp(5M@KZ)(Dz$^n3on7sa%^FOFF3g0MRf>57nU?jN&7wTN5`uv z8toRX@iw)jhA@s^mhp#X&b^zKIc?(|T)+ArE{s#|G5&td%e~YNzFZaAYO9HO`HD-J znFv#D5sbc-oO-yJbUtZE2!}86j{vpG%2qIX#2Q=&#*O;kjd9SA*uJTU z&txe_@v=ITnQ)CoCZ#4FHPwOGp^aib^?B2gaP%#xvbhqJBC0ah%YZi)15dh?NaeXn zGLl~2JrvnA5TUpVb|<1JP@<`1>aLXYmwxlZ#^_@77%`ydhsN5d%G83>zL8=DgDg>f;6-Dd{Yl>RjAj12!^E@T(ERy6I8;gu-q4`O?ieKhaI8j3>hmwN4)uh`?aYYvk zG{zE=hQd1GqfnPq8n)on2}|DzimIqH2%Do^sKBDS&qt4H^sPiAA?{`eaOsSL);rmN9>4Ckw*xgycxCrM2-CFDwDnY6~48U}ORpap1E6bqR zw>XUO_iJGtk#=SLMVZ+?kg40DNty}S&H`vCNjp;XZA`8Wy`rG7B|)!T;6@M`Bhf4> z%rNYbe82=>foHe~R2<{N;^Z22R@G*yAK1%hZu|zHx$zrp0{-g5|HN~ie-&|3!x&AP zWvDpe17H3UPx$O-xca*5I40}S2)}JO@4b9)jkUf3YW)G-2IHO>rpJa&(lQ!H;!M}e z;lFbYI`OS}i z##1l31hgg!^4ebmM{nOwVKmNKl$8ATi(g_aOiBGl1|v{QiA>>Aml#H91+qQ{p7y=#x+3%!@9>8p9bEUC6f%9N?9!S0nfCC)&1+ZeyIZ z=#R{DjqwGO1D?+HBf1{amu4ebuS0$1n_s%edh!dViZ*DXP*`PAn$01<3a;O>9pdbTeKMUX&0e!7_OdYL7$d&E& zQuYHf(co++ZjnaTZ@mv)`gq81{A)@y5sX4~qJ_?yNct}RJVrwWa=EKrADu!^c6bcR zgRNYk(aOMN*li45){>0-qm4!4R7yrtD=NoM8b#oCagOAB2^}6As zG%woDYnxbUP+B!IbKixk)iJP2%*ae*$wjUtt7PnUL51X%WW`rZtfGoC{%)wYh+e#U z>Yzj$=Oklar0b3z-I2G6^X%ux7T|EK!9ut)}PQ;?7-bZziFFU0p zIlpZt&tyZ>%~O|6#(cffr6gH`sSIVAv+kyB&IwkAmM6`x^XT>6d~kC=A2FJP($k9(n6d+G z@(-kK^5X2m`7ARMLX53ZG^`%b|v zRHxu>HL^`_Ch7v(DYT3bXU&tOc&oRsti^Bfk6 zGwE*5RmX4+#+Ab#KF#t<%3vWhFnYZNH|H5`R(PRU>hpT7bd{G6z zx=4aD5AV^j3A(Yd#g4^!60I8uTPt)HP5h(tj-)@pj3@Zfm}EEz&%g5+XPUExk6v*Z zRb90;t4$Sfc%edr(g!6~C#bWjAG`;h=%yjvHF1XqsN(d|J2<{^7#*!O8+{%Bas5b( znKOos_9xNFaG#B070p>iH=B2Ta1m{HyAw`Q=gv1>O6NRULY5{BQ%!Cx)2br3F3Jl8 zT6VxYokhuR+g9rA(|}v3V`G8`&&6WWT8r%UI9L|s&T&8R;-CLHUi|Yv$8#S0c>dS# z{VrEN;^9<9!E`#|*)MnjU%%;Q{^VWn;Bwq|wF^091G$jf7RUN)=YT%YXKv=-iz1Zs zwkSf(L0lfc-S{BFpLguv%SO!hweIX?+(n6;jTPiDJBa z58Zn&!{I#AdyMlWv7@Ab(}xbVl3eH56=m|1E+L@f{TJH=atc+Wc3P=Y{JNr+GR2%h z7aKjLkKU24m(sha9*F;sRIHym#f^Xab{-Y4t&K&`9l&FNcW-S`RV6y>@v{H@A%5{0 zPv>&ySV}4uckP?`j4G`u;#RbB*=5b-$QZ*<{_?M&V>hn7-l2n8RaGO~+IRkW4|tC8 zo$iw8#EjLsiVqb0wqxhcsGbmQNW;{SlL-%{kFDwRfw|881^~3qck9;g{ zoSm#kA9;3|%ptjFg?>t^X;({lt z1iok2{`Sxz1gB24v+=zL_BY;8YYhK<(@jnBH?x3s=<3^2qj=M={szzd@gE~M1&@04 zbLdWAVh7uLkPh!GyMdF(PlPFk= zmB9dv4(p}_Hcy>ouxmHod**lVqlk?HSIyoZby}E|zJWf;uFNxD*(9z@r*~bzG*cwd zN~dRHM1lfkWycOo6}sUEFS>v~dFxv`ldINeXCsUcU}feHHF=V^+?s;$yi>Wg+BD{!O_J{x`zd8 zLqjHnng=C?7x64qvs`VMQbrlL>b}qTnRd%W;bG2NhBj2!^+vrrrk)5u+Mq@Pm$FO=Vo*%vab zFD}>xN|7=eLw^{cZ~gfpCSa)W){>Z#Jj+7Vo{R9-rd1&@#s(R1tqk%G=|menH?z)r z!pNhg`}?@=*6aoE0%xdBplL|(kpM51;(TXVo}A%GI?u7Jhw`AiiV6XyDDd9W>n}9u zdo3k-S+F%4W1M9;oFi2V?F0Ygbg3B-(xf&VY-|=}sbcT`K68tG2J;za8t!f9L1dbP zY%w>r{PQ&Bl!v`pADLz`sYAx34M&>x&?;dxT8s9WFvYd5BK4MYg6id}I-%b}Prki1{mKraUF#Hs4vpG#OcIcgq zrX#fUd`qVs92X>K48~N*oesH6LQyeUFEJY)wV|-ilchZ-(hZC24mIfG^Bg;!qWb+1`yS8y zVefSaAAI1o3|>S=N!_SJw8VSSq_(JFTXLZm6CRAStswush} zCEdd~PiYL}%0yXBSOA6ZW|%%epV7AwDRm5&tx7gdpX6}oYj**_Nqa)MZKFtrsab28NooVKbFStG^&M(gv zELsO{sPL%hHK=BSEMiHTWvqSIk}MX)A%+hx1Qk#jH`BfD)zhqAFM=Mz!d}H3NfW2n zW!x+(h_bql{UAzm3uZTEAKznX139>!#^f>2cC@Xz;qal^5RRqI##PSTH|@4$;&MMT zdzF&?z<~qjkoE^b3gIweyKR(7aKkx)hRdUS}pTHJyW>SEg!98XjQ%1M3=+6EuZ8LGy&5W&{ zRO?hX#lwI1%XTMPP1j#W`Oh|+hEgGem=vWHbMtd&75cNqf9NLG-RydCu--E&iU_HI zEJ+YLd{z3uC%0xUpr$#_hJyQk-~~VNBeByM7TK@f^tt9=&do2*Hn`;3hlAQ9mn30E z+KY1Lln}vp)0vZuMia^7 z*@=Zvie79O=tMJ&)BEt+=Y#h${&{`oiijCN6Tq~{$%+J-BuEzydNL*i)X#ewiTk6C z(agjJ?-{PIvA0f-sI#&o$kLh4)1-A!@c`Cs%uY&csw*Dp#jNsS=B|J{rYX1OhQY=b zO83tCT*3rBtfX0*HW6u}lcmcx)rL${5 z?PaYb6mA2I!P(lgKU-|pK@w8Ps0tI!pj5<7y8Q(04D?UdhvZl=*3FC^roamqC4WKk zK2`}js>v+DqntStUTZ{GJJ+3viJ9rFt0~t~hwRt-=f|~GIvd)>k%H}kIbSjt4?hlp zjnZT3z)FD;f_15}@eOP5DV(Gd?R21ya`ABuboKt;Sxmc4$rD<$(}tKvq+(!lQa!*b z6`B+dyonM`FWWe;m|%t!LCP6bB=tSwy$$zFRADr6Y_z%UZRWY?th=p0s&1o*7%x=E zw@IaNmBWdmX+YILFz*FA4PJN=83B*}thUP}5|k0eQ{s*)pW!l_-Fy>o~8h3qt6+&su% zPv_Vx90MQXOcAq*1=aq2ow$Ybm|XQBQS+zOrs1lCcdyEfC6&VTD z^@K}s_Q=}L#CwJHp}q&4^$*Z&4~>F2l8SzD(YYX<5?q97b9a*NXXPcc=wv_ZV?XOa z9K(^N5BIaMp-3N35)w5gSy&92(>e{$pn{<(Sx?gl^AodBei49&K1zn2583WkSP~^; zBx76`bYw-j(AcF><%q`Gsg`-9;66`oyC$Ncvyal#p*Q9S{BMP>~MRyt+zn# zAQ2voiqW5AdV^uKn%0U&!Rq>HjJ5Q$Axfn|6%(OlUr@_jSZuOa=M0p?$Y2?|_t1!? zg+O7~B4k$wJD8yIl9j=f$2!NcR3PUUI5rK#L@j*>B^ThEb8{U_Akw?ojE)mouO!N~3Ki`I{Ui(Zopto*1_&XTZfrtX z#&c!Iuik*=B0v?G6cuw>f=mQ1T6I;lX>N_i>|9w2_nLEIEmN^gBHHhX_lPXRjK{dq zh-_{yHc!L;?_FS)xTyTDH$Wjp8)aCZTRJgs-c=#Fj4(0v-6?(z#Eh(yV(ldN9{LvR zwm|BXe4y~&qP(CFlz1v11KDi1c_;}R1kJwD9NJX?7P<)6)6JBjBa3P;gy=wPI;BAi zy=#hj>)Oe9nkHmv5+oMJHpO}!*D{-iI16n9;)NA#oj%F+Pk161UVIUGo|6p*{NbDb zl5{#|eq|SOUNZpZtzoowiuZlx^E~^X|AnXC@INrduzT;`##=hO!FOam&8B9;RM@-k zUPN#En{>FuB6W^#D0If#ewF}^-gS4o@W%)kx2@oHgp55`S` zzrCU6d+%8WMq3;7cJFSxRd{k>ZqMEZlq7}V+rUE-1(oRt!NNtTng^?sU5ZBv-_UMn z_LT5J;XTa*?duM-@Alrqd$r%?nyMs?hx|y8TwLIi)f3$Pja#|)Nl&DT#mHqqUgjw6 zFr+irFppB$-N5L`k#=INH7~yII=*@9tt{0t;LfvPViM6p^toRTd>Tj__f*^1GO|7_ z`{py-{;T#opE`At`Gt8Lp5>h@E$iT{Q$u$=R5uUe>$5gU?3Ltp^Os`)03ZNKL_t&o z|NI_a@KgWuzb)*SfW5JhE%`2!Zxvfoz6G@iDVR(PsyGdiDEj?AA~u^AzRQT;Md@); zg}iUO5lr$w^dry4nvfeQrR2k3`V#wrqrlqg$;R_%onwA}p8v03`|Anza1K!zFgYLi z$A9FMYB+&`Z8jb^t%S(9YkP=Qtb%9A_Wb}khNjQhE za;Cskf^hAWbORXf+}%XdojHEIWr$cY)10v#m|ZQCuDYM2Ym`{5yNpR?u&2*pO+nLY z?i?tqBK+&E%}u1%2qF0FCqKmxJ@XknV%JU*QoPs51w~(FpcFDy=qzP8x5)XIJ_uh{ zXLYtpV8L0=pPWIXFkYdWwMOER7$kWl&?@=en_L0%Vq^S?N17HOV+hUv0d7G`nIB{Jc+e938`~P-?0q%=-U%M z65pe*xNd9;)xgS|(M2Px3p@lFSrOn-4y3T`j??%W0xD#rt6SA5(h!mjosbKGu%W>! zl|p!j*f#r{3eCFqEEa(vBD`mz%sJH?kl5D7QY+wUU!jQ7*&s&`KD!{J7PC}cDch*C zT6XLO#=558Q_K15HMIWRLS55Y<>L1egoCi?L=ai;k=BN5Y^ui3uY_!D6*YWP3KggM z+oZUS%1?9}1U?q@DuU-k^Ip2edS$B6G)i=kSgQ~Lh_wL_WG6xLo+N0kQBt6LJ**4B zxkJeoC(f<7_vP301>^DxJ>ol0o z_iwBw@r7C&Yy!g9AV+X6q!OS)oLD^wm$E&U*J_J)@DZLL*JzR2x2lBVihPwpeu_dW zluSty&jELmY&79p!`&1@SV+5BBN-j$^^w&6?_W9gaKPoZm z^&&K1iYmuC7tW_q=3ki-v$aMwT;lb9f$x=l9;T;wmorQQhlkAyCL%QDoI@-O$mWJ* z$4*e>6`**~aKxoa53xMY(T$vMjxt6Lbd3=6U6rH1lkt2QumgcKofv zFvs~&lqOO!NEK6Kf?Pe#P+5u@jqqg|RL-$!U2j{$dAy1#2`)4ho$!o`l4aFzk_qbl zVOr#DZjG29_A%Z!d%g3%S*|(-2+43jH5yZGjY#H)cq_12gcPBWj>?Ey4eoqfl?W#g zvFRfkwTz!p3)9%++9KaMk1unM9J-D5YKlTJQjV!NWC(OrB_%+Dr}Unp9!mI*oyg0U zG+#46Y}weXodE4Th65EIxKFZ~kM0pzkYLB{xU;f=!P!WBx>(E#G&#UQVW-?iggP(gx)+Z4XSL7T6 zp7XGWHyvgnCExkXC!2e!DvNllv~Ruy`XX#5RbH?LJmlJIgY2Yp;m-xWa`Vm6%M6vZ zJmjZd%17V!R^}>$k3cRvDOd$Az4lQU7dp}#r%!V~@Sv>Eq$*|#RTrn~r3(w7i5*ez zJ*Bm5#+#+v&^QH@vCG?4K={rSrtXg$A7Z2;LlznHF>}D+BGEB?ufiDCfj#FQXyniy zI0X!a22-A;hY(IO2GqhbcYN(@$Ov{p2rj?=dfs#Etvn3KmsU`KEJ~P;W_vG~702ucs*js$kQjIe$^hYua*;!7^U7{^r)eP}rBo>?R9?L$Z~@%a$Z zhtuuwC$YjlcJwGackjmIc<>chH9uu5Z1 zlF@V;HUc47=x0$H`d!LV>6^o{Dk_|n|_C znRO1`cW*dQ2*GgBYrf@5Y;M${EbE=?y9pf3=1@t7PZX@@{K3m#&huaTQt~|K2~T-4 z&wJvNgS1I0KK1ec1HgjP-7E=;$uv%A;PR`kXbbOc&5XQTlmmlwfGQ_pLf7#)rX3E$ zu}}O5@)f_+G*)^j4o^Hex~pRMPWRB$PuI z^Jld;;Xu-?rLN&LIAl1m)Kz#up*05=FM_OH#*JUa_#@>*AwMW93c?i83dNRp6e>iX z2DxfmjBNcBUxh;SPR)v3prej zJ5p-4p;9B?w#$G;XX;zOcNe{vLE;-ATQYjhMZAvn+U}eqj_NUG74xq}@SBS$*^k$? zw*Rorc`sCJd#N+xrSHzCV*%S=*%t+%|R#EB|7 z>tdXm1EIt8c{>0&riTXgoT;eFoXQC(6xUvU66d1*2*} zX?me65p? z9cqiK#+Ez#yE$gVKx=L|kBbUh8U5>{@q`M`ARUrOiB2_nWrB@VLMp=7otR9x!wJsy zJuXoxyZwa4RK&1Ql%A^)Qg8>3jZ|mj`hi3u@Ea%()M zmnI}>g0ppvVVf*>(Sa`t4{A8XY>jcFG0DPQD7;+&8`gV+tSwocT2|C`m@`pPsal0) z-NZM(1uZy`QsaeZ_5RyAK01XH8mC~YgAyed;Vhd*E(2bWMCCyRRIawhMJ4M@bFgj< zSM2~*r5dL~Buuy9Zc-MSqO7%Cx!8$t@iDj2(r;>%eJs>$1ov1U2z!GxWo6J~sh2WL zR7;w5?XM^{X((kmn_nY0j^|CD57g9Ir81KS{p6L2+h{d#0fbf7D|h z(|WX}~1`$+oi`{NSt$y`E&Pj8kn43 zr5J57+6tM;|MQiv#KsTZImf5g&ameJc7fWj`W;|0F3HC^t9jsXKP7^&gi?I%mRp(u z=(m95hYzEa!WhFde*WjU8Q45@5BX?9F&?va>@Y*%^6&j#ytPQJ`Q-aQz(Jr&B%9m3 zy$%=D12RZ5>Y}=)3bzuK@cNZbSO+FsW2Tj%az4`kLve2iX#gQ=&10{*DaV~*a(bO|G~wRUVRE|f+;cksqZ-^4nk*lB-@gQT zmh(LLiic3+--bQS;_?!UJC;adasFdZelj`W$_ib#zTMf>g}M(Hii+U#tV)A1F|*kF z0G5i9Q5~B{-OtU&#?#j7DK?KB;{Mgs+#2-}@4o34ilRUYL6RiA@R`pfApmXm1FwDM z@1x`7)f&g&y|pE+uk2i6_pY6+^g`U>;0@9(TpCx2T$^k+Y;o9nIjMwV%s<*y5 z6zE#>w%5Nd%G(EMrAX6003UqsKclq9SSf!($%TG|mRukX<|X zQ^e_UeK~tT5}OO}_^+=coI?wNH-`8A{o6xR;J&*t8*3EDPjcIldwBBR^VqR-2N*-5 zC7=BBKjZQTP$_ulSlqdb+oH0g7<#SzVb;gX0w`75Ormt`-1}Ipb^xG2U%wi@d=K!k4}GXfso1@5FE75~*=(+zVs37M zet$@o^^sb#@$GN&y-)mp#tk{;sUSITI&p7UQbsux_ml?1PvrF=v7fMR<_jv4!NGv=^J` zF>1;M+OUYlneeW$2&q``NbdtcPDm;Qh4*YB*pvyII%QpIjwJ(*r+rRl0}c=7IXqb4 z@L-W+{UN849&1^j&1AqxWfV%IWnf4M9}Xhe7!6k&wyX%M3mc^3VJ{C#1nXr0z1gtu zCdRO%EZAF4IVYboFpgzgvD;e)(Y7_B%~lo&22~lLhyl@wjr*;O>bJ1g7;FSS$+{Sk z67NiGD74Wx4QaPScCI6I>MVNKG_KlnMh%>Fk-uVNUULH{jC-t~DLG`P@NXN`E|PYg z^_a2>kw4R%GScH+Bv`wMN%h{%WDnPrF)2iB*apy5^!Bwyx_7wJ;3Azq#Da;Yapb+D zEDA^zrS>ZJy?!7PZ2%*O#*cj<=0ZXJ2`XF zr`~$V1H33^S&KHzuK5h&dQdAKg!HrVZo)T>`Z~(aRuxrMV2oj$OTO>n>kI~w789@4 zOkpeA0$T}*Qz_n87&NB0LSYiD4-AGXwprTuRX>*g!sX+C1~1w9t)ehiwo>W?*(*IZ3C3QctX1-Te>d{;>(oIVDtR_E~T; z%spKlX|T<@SSv+kw0N{ra2a6DF;~tv{E*JiZw4-&_M?)N#smvdj8v3O_<>-5z8U~) z4%Fg+#d8+utz=<1V{Puc79m28o(q`N^yj#WPc6rHqx`1H6>sT)Z*GR&Ze6@;!@!Jgz|^CI~4&IfHkeN+wt@nTV9aI!aTK z0LO70Sz2QCyaSv$?;P%39&l#Y0uOn{4>G*+QuO{Ep&+hoD4ZkTXYbx!;lB0`DI95$ zH>peE_-AJ_)#fq{(z7|8VuUB@^)RKuPRBT7u`XCH`)QAbxdFYXpmy<@R9*ad9O_lp zG0BSnbM+1v?SIA@*3N8%y;sT@VVN`*3)x}p$Y}M_3&Wtg8BN04BwQ0`SWDAu@O2%b z8UHFYWRY!zr+3t=X%i`E+T8ik$)g-uy$2`5{jk+j*jlyZJcaj+jAI&y(KRnD5rUzX z^dorB#ox{OtcfX=v5_s1OZ^@T!vRSzL!=?I)z$dYPtd46CS|Xg~9N??Ko!|KmI#HOa;#tpoE^m1C9|dVC za4!Y-MREO&Fa22_|CA@=Y=}F$>$cmQNf^Lwx85260?u;f!yn3gl-}O%OYQ@nlpNp} ze&-dzC)!y=YhJ9YrIOxbs)|4Thj#(+Z7g5UM|=nP)gSx6xbE?f#m3k0+5h->m?Cx^ zpK*aNjp5TD`3L~toUHM+={kpi_rLR9q=P=zJCY>fz3=*m;Qd_O0l?c{`v%f9#XHCT z3ohVouYV)&1HN_aFke4y6iT$DZKxzGZ!t|U0NkH%ad*tk{)^YXj`J_Qv^ki$ zT2hv2_5d&cjo*w-5kXOw+z5jh20gCZ> z=KIagLBir>eT;Q-j;-kwNA*X-~cP%58W@+~hP6A*5@|VzBV~pWPpZh#s^_w?x61Z)3oo}C7<<5M{ zWA?7_iNlBae~ukx8OYbx(W#<8?6YHku&uoB-R~w*3TG|D;V`T-*72{O`3!T>Pi~w; z^~2NulAn1QX_^M2a8+^7op%MFEpW&?j>h--##j9z|MIC%@YXlKCA{yd!Y7*1c=Uik zzd04Y|G&QD9jGM1Sj!V{cs7420nJn7x~tI-o!V)@OeJ*l`r#> zSHFfoeAAnF>*qhrnY-@5JBv(I3tEH-Z6f5XT7f1M3ZH*~i(S(0HGN=fbE|2BKIkD= zatipy7j9@%!>zbG!ro1MWZE>oNqi+osi}9Aq!=WUNKFyfV>THP8 z@wTxFxlZhokL$eX`g5a7D01eTrl52VVN4*v+wi}w^Wi&~DsZZvpi88a9a~T=@P)uhfsukbIAD)0S#*}6HSDn^ z=TIuQ+GGXW*x8La<|1T) z`{=+cm z!G2Z7dvcbCQm4EWRng`~yAA-bi3e;4NTSnN>RKF(H=$Y6?QBHl?VR^$Golm%uN8x# z2J1-r3B840v>!?4b7d^mpn>Uh z#CSTQaE>HR>8pgHhq0`qpP|SN)TBr=IQ?vx_9IIze zvA(&%v>1VRjEup=CZIKjs;Y3N0_hrcd6^=6- zo6(99&O~+mW>qHvxzMon@yssg22@2!xit;|YiqH#!juJNVVX$iw&@$zxGKVIoi%uC zLzJd9SX+hvFYY%|DO@q(*pcrrH5DK^kqO4m*J84UZDvI{SB;!w;vB~LX3JCvhC&t@ns{;hW}x&aFX7!Cb0QB$UoGHHNCR@$ljru#oqj2PAMr@?$O4v0?_vv26)2Ezf|?>WTL z!}pW*dR%_x6{J~8Q4|O%$$CA0`iFzmeW?1JvQz%)Z{ET8KlLe8#*mjKM>jUO`}R9I zdGrVuUUnJ#&OL{!sv7u(*alH^RWT!}TwGpeJR0%E&wh?mN00NMhhD)2mtGpOj)fpe zQ=and8<+#K!7@zF#F&a2{A#)S`s+D;@+6=CYMifbNoJ5zpXB1pE~Ba{p8nz&a_kvT=hiRYLcibR%ImKOrEt#C>-Bi| z8{WW|fvflJ4?wVvWW6rVPXT3R1CT+;&Y)kC0(al`9UlF(r%_fFWmU0y^eBgJzk~G? zC-~jx{3y$FLsXKq3p}k4?<{h@wOsm;hcFq9P*QgBtD$f-24f96(Nty0OMdh@Tn#9l zQl$x|G&2l?hJY_5`*!Z)7hm-UJmDG7vQF!u3@yf$v40BCDu-^vj5;gF1zZA zkW*P!-1yo*;r;*kZcK+nTKoK69WkYQD6Npu!?=x?9b!opJZ$G~?m2Oi_rBrv{P-{Z z5_z7pvb4Q@<$#$0jLl`JkTQI!uM*C%CZ*MJkJPV?Kp^2@yXb+2POozm-P z+%+C?*X_6Soo|1e`Go}@dEKK)(}c-%%u}E7On&&W*YjVW`F!X5ZF`kkA$;(f2@yKs zg)xntB})=~Bf3i{r9qQCOij4QSiTh{Wo~g&-@(!b;~VOWzK`IdKml9IR|SkS=Qri?|LWC zdEWEc5$!KBN!Sbg*>C&?Fa5=rQ%0J2l4w5uk$(@q_2m_kUWPXoXBFqFg!^s98(#ft ze&N@DojlK3T3Kc?9`ntwewDlLxPz5lySV1M>rh%Vnv8kji(kZzKm9Ux?BB_mLr1W- zY9)>F5NIQ`k#WgS{K7AA{29-nzpy}&7rgxM{*mYW_8r{$r7yF*XAf6A_OXGs?*+Yn zpI`amA7oFQ#JdNWpgZUI>(Q7SzxHJgee+xFId~qIKl*x#GN2fGk_O_^K=E4+i8#!NVW*D4z177jpAwK8wrqrcjUsSdpu>lfsc3_;%9g zuCsie?ZI0~&4s{yci+YLTyZ7F7!I6w9*6F^hcm0I-1O;B@qu^z4N9+|q;5)P85fK0 z0IQa`8}DcKrb0+E?@+?Crc%6!;K$kzu2huFw;t-+t0tm^v>Rd8vZ6$tj27aFe26>q zLPaa9?+|50<0Eypijr%AH6E!&RJVk~FepgkdTTAnEVNvlCjm0=$ef|?JUUL7g{&iG zV*Hz*wsdb_ z;?`<7-%4%02Op;`^*&Qy*C3JCVN&EuWTf&6p*rzBavrkF001BWNklI%Db}p zdT(~lA(f2H)?okev6&g7h{{k*)&RUn1y;3+DNbVA$)xBgKYUDO2r;MD7F6Ctsx zmG+lq0Ltk^qqIh;gh7&0CR6V2=bV44^=sDkQDp((yQ#KS7)>NU{5}6uS2+GL+ z$R<-jll2mkxrF&lFly*+%>NXFKZM!0n2suO`%)I*RL zV=97wcv|KNq3HGclnCy~haB){NOXwQou1CwTr21g6Bc&&5v7MoxE|g)tkSIXa~_!= z=gZS2J~7Q1Iyiq}5#xNrbgCm%=Z1@nCtJauD8gA5DM^53?(=~2XiV69k@y%dudAO$*0LsCd=L<$*HB+fO+ zDh@wAx{&)W!PP2P+>B#-(2?+@~FIV%_Hz7;7Yx8L?{F2CYR9(nzvn?j{53Zx34t)Ksi=LN6JU=i0- zFah6*mC{o3oo{`M%dfbC$369_5GOO0snA+e8OwPW1S$T)uDu{7gPpsPtH*iRHP`T^ zn{MJES6xL}RScGvxc13UYHn}|q=Nw;c>CLV(Tz9qxO9lfdibIYKI}-#mO`L)!Xm)@ zfJ$kcv+Xnwi5(7j$M3#^pLqGp(OQQtTCd0VJmTTp_LZ;lHQ@RffTBI;2a>JZ$w8S6uT5 zzVPYKaMd-Bq$*2{wLIwRtK&l*WQ;mdoH%-fCtPw#m=-sg=d$@t^rhrlVs(O$30r_o zGo);83r zkNPmLvy=;Zg4e#~uNV%8{MM^pMNyU%Wx=`UpU=SyFKps&JRT(!KlR)n=Yya5JQuCZ z!`jx&dl2!!Xe-nBUxaszC(~$I3B*sGYJ6;*)iEu-&h;Qt$gQ;jfD*w%N=mXUW8=&j ze(ooq&$~YOZ|npX&e@NYfwC;6;=rDrY@S%<%H?HlI&_Gol@+S0qBj_D{o@|joCb_B zq-n~>Km6}J`+3jj0AOXv^2Wpb;*dKw#vDC-h@HFl;G82#QvUw00=+#OX6Phs4oBHs z4}Ih?zyABbPuA=6)4%j`ilQLTb1u33axQ!DgJ+;e);b>hi0e2FTr?PPtC^x@$oLMQ zw`33}&r(S)=+AM_bj03+2f6R|@33Rsz;S*;Nna{3{;H!3ST7bC#m45F#`mKl*on&6_{^X&wdm9@r^( zxg0+0`g4zA;W1h}3@f7_Qqn#@)1=2EJ+`;CfuH!9l`g2&C z=l}k#-^N-)S(fbDyO%xt_B92jb&f=9{`i%@$J^iVdLDAkwHP7MUZ8~x@HH8C66>g( z=TL8sGTI5cYZOuP7gd(*x%eX9`*(lKYu@x`#*+!g7#5e8*}HEaU%vSZY^|PRu(%_@ zb*KsHqEUk+4QMmQ6483SH4bGBR!ZmUk}srNI9e!YS2flqss>NDYG7(@6iR)K zyc7-GuM13Vl5V15SSZjkaut+syv+?(_&XtPrvliGW8&7p74oqxszcW*?H^haGlB(g?)XajK@Ev9Ru8Q}pw82M> z4-HYm0Ws9T9XU3li9tlR@68)SuPi}JswAaS8X|ev8XF2DrO=5cReh|otnJ$(nJ;<4 z@iE^V?d9*sbL>!_UcC2g-CXG;c+{3c(akjP($YHbU33{|UC;ImQNJ#xLabUoolx0| zty1yOi%0C;b%x5y1}fOd;X6-@Gp30K^v+QvJ(OH!OQx((hgb`x6Kq;Y6thKsHxjN% z6L2wVOCg-YIU5h@EtQ?{F)hZVy*a*|3^?u- z7nUQIRf!V8?y<2^usN<+?j=q0LG?3CQR0PQ&^KIFY;fW3gmE^>?K-!JU7X1+(t_#@8rxYL=@~wgUe;&O>!Y1PIfL=G+0&1%ud!#(PE1}Rl%$;I zq{Dur^f1=2v^0m#O9smeaY2v`>@ooV1dII?i)UO`NErqLQN|)# zc+o_fs!WG;tH~5&47%S7=W{8kTor6#+jONOrW>^PetaIA_|x!uwoc#Asg3&)QedQH zOVxz}4#iEON0P z0`QP^wlb7ONj}MEVO*L<0-YoYkGS?a ze({AbVPkzgypAkmI2>~F_;LQnV}F3RzW-l1Po=2&VN;mbo1hQ^og_T++G}~+8~-8@ zfYX#L>oFM4@n5&x68x&bMe{4+;O#vsNw{P#NU+AG;J#>7~pKG8X3sEDr}P&kfnN zG|%2kE@lAy(hoh6&9yZKb92EHm!%;w04RiG>MavzDXfc<@Zb$}O3(w!ya*DNvc$&! zZhjXf)5(OR_aEY8|N6gq=`(NO(#tPr3Y>rLc`+^nWUo)Qe}y71Fl9-V=Ok%Lf{q6! zSUCSet^}@q+++Ezm;DU)-gOs)!GJ7HNs@##%h*_7=S_d~C!BxDB^1Eoc^3q-D^L|B zrYI?k0`Ck-Y!A~GZ5PUoCGthGLVthI-8ZrCy*rK@mP`Hw+t|^Lws;V%?@XP<_|HX2><{$kZzrc$}B}KIy;e^d@ zN7uGA+g-AH)Qi0L3Vn#|x;{}{qq3~4EC?QAS`2ovwoso`FDTfC-~7H|8edDr_Vn#uzFJT+d8?nfAQykp3tY) zb^hcD|KzuShu2oi6z7o}ii4-?__^nJwSfQdKmL#S;ZME7@BG$pu~;st>pK13szQMO z_2+($Tena7-9P<2-}c(a(d;!Z>WZdGe$-~S@$CZ|N19?l23fccVexjK3TGgp3pVK{Ml7NaF%aYHJu2~3LgQww#D~7 zecPfjvN}7-_Y$n{-r+a?r+>sh{OO;{e!(SGUE|6!O)cv(g+OYOPOny3J5nCkLwQ)H|HlmR;3o`i;_p{6(6ityk9Ii zwkr+ans1K9%e0I?MtcN~HFr)XGVjk!_PuunM{UFU9{M2#JN31hX`*tgVT+BJX_sf|u{V#ea744reG2qM;iPq)FE7W3r(|W-f)parjuI zKP3xf;03T}nckbew@r)hJFYrmwe* z2u(~8gJx(##kiN4Gc;q;vnQkEevqZd5K^I?BP^mSzSmK;G)UajXQ~L*suKu<3MPNStEyf~LnXOmygpu8&$FECH37UF$y6VoKrqlU-B>O1xjR#j38Fc1ytKNsSpLM zEpR3c6;)xky`*cmyyGf9SKs9~`x8FfEZC^SS)nd+Ty2b)qa{Z#oN@ZhlI2lF+Y2k_ zdDeA&@~G!~mJj*FGScKaXl|$Rp-0uz$B@0%(6vPVDS zyk>4mkF1pS3l-}V(Aeg+%y4S2`GN8wKYZHondfSrtx{CA*A#0!+i^x4gwY`~G_@^E zzhm3=JbrvZx7#5m?J1${z-M@1h%A>Y?mu@QA3S{snb(m%(+~s8BAsb2x9Rn{x+M0V z*!5}JtjeOQM_Kl^?QoLz&ZcX*xW3M^@`+cG_B%!pU1~@rKl-Ncan_C?>X4OQk1x*i znx9nNp=}Afmbh&Q*IUA7L)dJH*IWG6me}`*vuM-PU0kAVo4(dfHmcwwQNuXUOf_h- z`Gy=#JATavn#~38zyAhJ2y__Uwj}OJ{wL;ISI2+XY-^gN?Kgt4dHy3>S(HXz}-Z$9p zcGQanfAeFXbXt^U&A z{o2WGin_+mCk`>-uP!;?Zg_w8y+4!**k8>5{&C>`t=qWun#!iE@SsZg#@DeZzefVK zeG+*2#gDMKa|dT-@|DRXJr7;S<1c)X&jX(Z?taTFeE9m;co%r>f87^6d6FCF(Xi-uvb^_{RKx z1D=2NW4Iz^8`JkR4?f_n?G+D!SMu*4;_^O`@0D-Pe#Z9$s~4WZi)E*g>k!%OcD!|d zbtBHrA3RU5-o2fDThLve^W}@1cljrue+FkARt)FY8?Kt37{OV?$$B+TrCgc1%%Si3 z@_X<6l|QrROT+s19jtSini;d_bU&&vbe<>gKj6VZ0^A{*@wKBh%aao@gVb=!*ljN! z^QDX3-uJJ8k34^$!WA<}&$O0=5a`;5*B?IK`+N6c?>PdvyFTUB*Iwo7`kM0(KH%NU zCpYiWazRlqvZ_((+m>&%+r97ko_xOzpVzDT9$XYA`DtUM>y+OHn(Hfe+Z{z!@jw6c zPw^l8gP$FL&JP2>HM`e72CNELls3h)Y!dbTUEpDc-@OXlIfmu3NK1L_X&=I)?7i>v zYqk6&n3X__uEeI8Mn z#PbtHefUMHI){?)QQr5e@>x$o$7?8r=-F1?9CNc`UM^%J4oGr1g z#JXbkk43Oak)pFmxjFnD@Vg!D<-( z>&E7vnVr5ROZY|XAR|HCT3ZU2K8(vJJ9gJOo7xFxQNikn zUw^-%^-3X`K@|g=>vKZ%td_?(=h$v8@gdS?n*DlpLRXg5z)8EIj)5OJZMeI3c&+J+ zQ@&o^;r_~UBpc%P10KHf6?!UE3Rob(?EeIibZ2G zLSJ_+S8d>x`_J;}kG_UGO5ok1ELa?^N0pKo!-sFb%e!xW6=U6K=ZUHul_krnWYc!+ ze2;SlrE|n~o2`SeUM-R$RF$jUjy{CcbTl%vMy5%$8B`&;f$7AKX;da#Lj>jax~8x$ z<-lhsoW{V>qUQMcn9v6#|NdOH=nvMw0W>w*AzaE1a231TZuS-r_ z84^k`h9~d6$@>@I1cdipnx4CH-%o&LnQbZqP<4MBli3z6H*_=x!DV2)B@I*MGcUfP zaV}+=>GGL*ztp4-;Q(o|Vh9R-@PsaPcC&6Ocl>59^n(?Cl#b11R5E>l^JuZXBb5T` z>HCiEV#j4{xXMdeNqSSudW9{@MBzM$5g{)mq3PMQS9JNm6+mSdlt&4R726KqwisLB zmJ6J-y!+@O-{Mw$_WS|6-Hyd#!H<5|_wv~<|2ZGAB_VWlK!u^Kmz3*e_M2xPLf`YH zzo0)PwqgSO3J=%uky~{u!$z*X?CLqJ`04JXxBjr|!dGfB{z*v`|8Nz&j)TnqL zh@qqFJl)kMLBZIXQFpOfx2`_eGnosbPyCNOUj}gV^9)wDv_nPq}<5M%@r40PY)C# ztdA4i(Yh+%?^9-E^gW^L(xNR6SEWM<1~e!io?qcZ>hv8imz1T;@F>^&FI!Z-vQPvpa zuqMT2Sg})vr3~J8k8fMNkJ!>-i!v?lGl}0Mqn${*K6qlEiuLa5IvvQ1f`_{nTU3Z~ zy!`Tugc#UeZ&H-ZuH~qPI84B7nI?TviC&2sARiF=KA_u6ssSW7SOrMZOy_b}J;!e} zUA$4C|1bZ=Pw}7pw?E4#53ZpFoWPkGWbb*s0=uA|Vf%qSm=2%d%#cO`0ZqUDpx&kiB9CYfFrg ztUM`DC*10L?)BI7s`MdEHAM~Qb;T1~Qy9sVRw0C#$m@Lv)@CAqZoK5hY=XRWv|We9 zBr7hj;_5ujGBzFJ4}8G$*Er@?8#@xtZ|tp_K3`)jx4!mOst`C?6e&A) zm}KRYzVG3qwk?8Fc>q}OF{ z%97{sw&8aQ)6`rJu$+_iifJF4O)H}&g7t9rF(#cCj5RDmz{ONhM1fsVQASS{BfF}m zEz%yIy!cAxDqP``(#)hHZ~dSoynlK1UZz5JOcgIPEvo zdJSrMv{-Xh7E^(A&{sLTb8B{Q3)Tq6*?B)>cGl7xkjCAv<;mqG5V=#I&|GelM0>Ak znPAhZQ2U1U_L`;koalzCu*3-K+bhm)IYJDhwWQznY_8He?ySM6VcYb4QEu}^S~8V-tz#{zSlnzb>3xf{meuk!sW4(>ySt+6`Vm^XS|3wb$F9BR8q1HI z9`lx2^IN8Z_3<=)GzH({EZ-Zhc;)gd6z$veBIxpzSQdoXu|B=UZ@yjdxYuz=D#!PG5uxWdYwUl)Un{8?$`pBX#DP2iaW!H6VyKa<<%WNnjvpp#_FE!hK zrWnFhm>Qxe1Cu$IUrM;T%OQ2|gW`VxN3zM_KT5Uwh4F+p52CA~04k}rBV8)n~ z^nKRad9fnR|r!tZ*=PDHA3Y;tUq5(w{(fOj?jqxjWQBV{``umy}Esf(}GRCl2EoU+} zk1=OsrX=gt-U2gD=#;&tUl^KyG87krDT-vtSS`m*J{^4HIEB+J-HHc?L?eQAHDytz z%SRj!%QEX$Cg|N%T!bj#iqs6L7u3hcSR*uT$Fl5`T)JMes25yZoaZTqvRqZvRgwK) zac_av9LFVtzb`7gevSL!0i}jyr*YFWVP@(gPux>O%>UFwVP7TZsLcn%K^3H#H0|^2 zC`yY|g3HudSENm`j{^`aWsSOZDy)sOm~zc6zvNr*-6PZso@l;rMX9PJV@?}t+Sm#^ zfU=;-O{>y4iXug}h_UJ38t9D(!3b*~xZCfDq#~uTC50_>v&nHrz^c$7_zaj4SK^8S zN#R1{@@ex_+={_OaCSje)rp2m&e0Ynz@v1GI4{ zrO5z2rYJ2jS^8@o!8v+s>BP|5l1>a74ZSVsB;eKLg!!T2x=+BK3OqCOmgfr@LYD9j z#YK80Cg;?q_^q^knY<4S*Ura~C-4D$_BS$L*bn{JX?>Z;mC48t(1|TFMw$7&!rfq| zzmJTQO&4%{VLp7+Y37uZC+3hhbno}bJicvqPE*kt6mFi0GCibns4g8Q?b*|x8iFQ4 z*r-sg#vJt!dbVys(Ov<{al2t#p6>C0#^01F)&*`%ThJJj6>avBM8bHcN~(+qYVz

  • mI-P+P~vpKKB*A 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%0DK(tc`*3f=6alEsQNVaIa44Ve%`4p>5nxjK* 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�>^R}Vc9-uzC&#g3N@FXR>Q$iD9 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 z/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�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�OTC&#!%GY%DM=D?>=G=$)IHB`(G*2uRGQ@!#m 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