diff --git a/scripts/presets/keyconfig/Blender.py b/scripts/presets/keyconfig/Blender.py index 8d1de45220f..90846b76a6b 100644 --- a/scripts/presets/keyconfig/Blender.py +++ b/scripts/presets/keyconfig/Blender.py @@ -382,6 +382,7 @@ def load(): use_alt_click_leader=kc_prefs.use_alt_click_leader, use_pie_click_drag=kc_prefs.use_pie_click_drag, use_file_single_click=kc_prefs.use_file_single_click, + experimental=prefs.experimental, use_transform_navigation=kc_prefs.use_transform_navigation, ), ) diff --git a/scripts/presets/keyconfig/keymap_data/blender_default.py b/scripts/presets/keyconfig/keymap_data/blender_default.py index d8f4a71160e..43079dc8884 100644 --- a/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -95,6 +95,8 @@ class Params: # Since this means with RMB select enabled in edit-mode for e.g. # `Ctrl-LMB` would be caught by box-select instead of add/extrude. "tool_maybe_tweak_event", + # Access to bpy.context.preferences.experimental + "experimental", # Changes some transformers modal key-map items to avoid conflicts with navigation operations "use_transform_navigation", ) @@ -124,6 +126,7 @@ class Params: use_file_single_click=False, v3d_tilde_action='VIEW', v3d_alt_mmb_drag_action='RELATIVE', + experimental=None, use_transform_navigation=False, ): from sys import platform @@ -225,6 +228,8 @@ class Params: self.tool_maybe_tweak_event = {"type": self.tool_mouse, "value": self.tool_maybe_tweak_value} self.use_transform_navigation = use_transform_navigation + self.experimental = experimental + # ------------------------------------------------------------------------------ # Constants @@ -3830,8 +3835,18 @@ def km_grease_pencil_stroke_paint_draw_brush(params): {"items": items}, ) - items.extend([ - # Draw + # Draw + if params.experimental and params.experimental.use_grease_pencil_version3: + items.extend([ + ("grease_pencil.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS'}, + {"properties": [("mode", 'NORMAL')]}), + ("grease_pencil.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True}, + {"properties": [("mode", 'INVERT')]}), + ("grease_pencil.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True}, + {"properties": [("mode", 'SMOOTH')]}), + ]) + else: + items.extend([ ("gpencil.draw", {"type": 'LEFTMOUSE', "value": 'PRESS'}, {"properties": [("mode", 'DRAW'), ("wait_for_input", False)]}), ("gpencil.draw", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True}, @@ -3842,6 +3857,9 @@ def km_grease_pencil_stroke_paint_draw_brush(params): # Erase ("gpencil.draw", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True}, {"properties": [("mode", 'ERASER'), ("wait_for_input", False)]}), + ]) + + items.extend([ # Constrain Guides Speedlines # Freehand ("gpencil.draw", {"type": 'O', "value": 'PRESS'}, None), diff --git a/scripts/startup/bl_ui/properties_grease_pencil_common.py b/scripts/startup/bl_ui/properties_grease_pencil_common.py index ff8bfa39203..57c5f28bc37 100644 --- a/scripts/startup/bl_ui/properties_grease_pencil_common.py +++ b/scripts/startup/bl_ui/properties_grease_pencil_common.py @@ -500,6 +500,8 @@ class GreasePencilMaterialsPanel: show_full_ui = (self.bl_space_type == 'PROPERTIES') is_view3d = (self.bl_space_type == 'VIEW_3D') + is_grease_pencil_version3 = context.preferences.experimental.use_grease_pencil_version3 + tool_settings = context.scene.tool_settings gpencil_paint = tool_settings.gpencil_paint brush = gpencil_paint.brush if gpencil_paint else None @@ -550,7 +552,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 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/space_toolsystem_toolbar.py b/scripts/startup/bl_ui/space_toolsystem_toolbar.py index 29a20a6e984..7e8d277a380 100644 --- a/scripts/startup/bl_ui/space_toolsystem_toolbar.py +++ b/scripts/startup/bl_ui/space_toolsystem_toolbar.py @@ -1970,6 +1970,13 @@ class _defs_gpencil_paint: @staticmethod def generate_from_brushes(context): + if context and context.preferences.experimental.use_grease_pencil_version3: + return tuple([ToolDef.from_dict(dict( + idname="builtin_brush.draw", + label="Draw", + icon="brush.gpencil_draw.draw", + data_block='DRAW', + ))]) return generate_from_enum_ex( context, idname_prefix="builtin_brush.", diff --git a/scripts/startup/bl_ui/space_userpref.py b/scripts/startup/bl_ui/space_userpref.py index 0bd6a099cb7..31c193edaa2 100644 --- a/scripts/startup/bl_ui/space_userpref.py +++ b/scripts/startup/bl_ui/space_userpref.py @@ -2398,6 +2398,7 @@ class USERPREF_PT_experimental_prototypes(ExperimentalPanel, Panel): ({"property": "use_full_frame_compositor"}, ("blender/blender/issues/88150", "#88150")), ({"property": "enable_eevee_next"}, ("blender/blender/issues/93220", "#93220")), ({"property": "enable_workbench_next"}, ("blender/blender/issues/101619", "#101619")), + ({"property": "use_grease_pencil_version3"}, ("blender/blender/projects/40", "Grease Pencil 3.0")), ({"property": "enable_overlay_next"}, ("blender/blender/issues/102179", "#102179")), ), ) diff --git a/scripts/startup/bl_ui/space_view3d.py b/scripts/startup/bl_ui/space_view3d.py index 7837df869b4..9f3982e8f6e 100644 --- a/scripts/startup/bl_ui/space_view3d.py +++ b/scripts/startup/bl_ui/space_view3d.py @@ -1981,7 +1981,7 @@ class VIEW3D_MT_paint_gpencil(Menu): layout.operator("gpencil.vertex_color_brightness_contrast", text="Brightness/Contrast") -class VIEW3D_MT_select_gpencil(Menu): +class VIEW3D_MT_select_edit_gpencil(Menu): bl_label = "Select" def draw(self, context): @@ -5414,6 +5414,13 @@ class VIEW3D_MT_edit_gpencil_showhide(Menu): layout.operator("gpencil.hide", text="Hide Inactive Layers").unselected = True +class VIEW3D_MT_edit_greasepencil(Menu): + bl_label = "Grease Pencil" + + def draw(self, _context): + pass + + class VIEW3D_MT_edit_curves(Menu): bl_label = "Curves" @@ -8163,7 +8170,7 @@ classes = ( VIEW3D_MT_edit_lattice_context_menu, VIEW3D_MT_select_edit_lattice, VIEW3D_MT_select_edit_armature, - VIEW3D_MT_select_gpencil, + VIEW3D_MT_select_edit_gpencil, VIEW3D_MT_select_paint_mask, VIEW3D_MT_select_paint_mask_vertex, VIEW3D_MT_edit_curves_select_more_less, @@ -8267,6 +8274,7 @@ classes = ( VIEW3D_MT_gpencil_simplify, VIEW3D_MT_gpencil_autoweights, VIEW3D_MT_gpencil_edit_context_menu, + VIEW3D_MT_edit_greasepencil, VIEW3D_MT_edit_curve, VIEW3D_MT_edit_curve_ctrlpoints, VIEW3D_MT_edit_curve_segments, diff --git a/source/blender/CMakeLists.txt b/source/blender/CMakeLists.txt index 4f173fdc1ac..ad97606bebc 100644 --- a/source/blender/CMakeLists.txt +++ b/source/blender/CMakeLists.txt @@ -32,6 +32,7 @@ set(SRC_DNA_INC ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_gpencil_legacy_types.h ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_gpencil_modifier_types.h ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_gpu_types.h + ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_grease_pencil_types.h ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_image_types.h ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_ipo_types.h ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_key_types.h diff --git a/source/blender/blenkernel/BKE_grease_pencil.h b/source/blender/blenkernel/BKE_grease_pencil.h new file mode 100644 index 00000000000..97f4abb18f7 --- /dev/null +++ b/source/blender/blenkernel/BKE_grease_pencil.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2023 Blender Foundation. */ + +#pragma once + +#include "DNA_grease_pencil_types.h" + +/** \file + * \ingroup bke + * \brief Low-level operations for grease pencil that cannot be defined in the C++ header yet. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +enum { + BKE_GREASEPENCIL_BATCH_DIRTY_ALL = 0, +}; + +extern void (*BKE_grease_pencil_batch_cache_dirty_tag_cb)(GreasePencil *grease_pencil, int mode); +extern void (*BKE_grease_pencil_batch_cache_free_cb)(GreasePencil *grease_pencil); + +void BKE_grease_pencil_batch_cache_dirty_tag(GreasePencil *grease_pencil, int mode); +void BKE_grease_pencil_batch_cache_free(GreasePencil *grease_pencil); + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/blenkernel/BKE_grease_pencil.hh b/source/blender/blenkernel/BKE_grease_pencil.hh new file mode 100644 index 00000000000..634ae9ea35f --- /dev/null +++ b/source/blender/blenkernel/BKE_grease_pencil.hh @@ -0,0 +1,381 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2023 Blender Foundation. */ + +#pragma once + +/** \file + * \ingroup bke + * \brief Low-level operations for grease pencil. + */ + +#include "BLI_function_ref.hh" +#include "BLI_map.hh" +#include "BLI_math_vector_types.hh" +#include "BLI_shared_cache.hh" +#include "BLI_utility_mixins.hh" + +#include "DNA_gpencil_legacy_types.h" +#include "DNA_grease_pencil_types.h" + +namespace blender::bke { + +namespace greasepencil { + +/** + * A single point for a stroke that is currently being drawn. + */ +struct StrokePoint { + float3 position; + float radius; + float opacity; + float4 color; +}; + +/** + * 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 { + public: + /** + * Triangle cache for all the strokes in the drawing. + */ + mutable SharedCache> triangles_cache; + + StrokeCache stroke_cache; +}; + +class LayerGroup; +class Layer; + +/** + * A TreeNode represents one node in the layer tree. + * It can either be a layer or a group. The node has zero children if it is a layer or zero or + more + * children if it is a group. + */ +class TreeNode : public ::GreasePencilLayerTreeNode { + public: + TreeNode(); + explicit TreeNode(GreasePencilLayerTreeNodeType type); + explicit TreeNode(GreasePencilLayerTreeNodeType type, StringRefNull name); + TreeNode(const TreeNode &other); + + public: + /** + * \returns true if this node is a LayerGroup. + */ + bool is_group() const + { + return this->type == GP_LAYER_TREE_GROUP; + } + + /** + * \returns true if this node is a Layer. + */ + bool is_layer() const + { + return this->type == GP_LAYER_TREE_LEAF; + } + + /** + * \returns this tree node as a LayerGroup. + * \note This results in undefined behavior if the node is not a LayerGroup. + */ + const LayerGroup &as_group() const; + + /** + * \returns this tree node as a Layer. + * \note This results in undefined behavior if the node is not a Layer. + */ + const Layer &as_layer() const; + + /** + * \returns this tree node as a mutable LayerGroup. + * \note This results in undefined behavior if the node is not a LayerGroup. + */ + LayerGroup &as_group_for_write(); + + /** + * \returns this tree node as a mutable Layer. + * \note This results in undefined behavior if the node is not a Layer. + */ + Layer &as_layer_for_write(); +}; + +/** + * A layer mask stores a reference to a layer that will mask other layers. + */ +class LayerMask : public ::GreasePencilLayerMask { + public: + LayerMask(); + explicit LayerMask(StringRefNull name); + LayerMask(const LayerMask &other); + ~LayerMask(); +}; + +class LayerRuntime { + public: + /** + * This Map maps a scene frame number (key) to a GreasePencilFrame. This struct holds an index + * (drawing_index) to the drawing in the GreasePencil->drawings array. The frame number indicates + * the first frame the drawing is shown. The end time is implicitly defined by the next greater + * frame number (key) in the map. If the value mapped to (index) is -1, no drawing is shown at + * this frame. + * + * \example: + * + * {0: 0, 5: 1, 10: -1, 12: 2, 16: -1} + * + * In this example there are three drawings (drawing #0, drawing #1 and drawing #2). The first + * drawing starts at frame 0 and ends at frame 5 (exclusive). The second drawing starts at + * frame 5 and ends at frame 10. Finally, the third drawing starts at frame 12 and ends at + * frame 16. + * + * | | | | | | | | | | |1|1|1|1|1|1|1| + * Scene Frame: |0|1|2|3|4|5|6|7|8|9|0|1|2|3|4|5|6|... + * Drawing: [#0 ][#1 ] [#2 ] + * + * \note If a drawing references another data-block, all of the drawings in that data-block are + * mapped sequentially to the frames (frame-by-frame). If another frame starts, the rest of the + * referenced drawings are discarded. If the frame is longer than the number of referenced + * drawings, then the last referenced drawing is held for the rest of the duration. + */ + Map frames_; + /** + * Caches a sorted vector of the keys of `frames_`. + */ + mutable SharedCache> sorted_keys_cache_; + /** + * A vector of LayerMask. This layer will be masked by the layers referenced in the masks. + * A layer can have zero or more layer masks. + */ + Vector masks_; +}; + +/** + * A layer maps drawings to scene frames. It can be thought of as one independent channel in the + * timeline. + */ +class Layer : public ::GreasePencilLayer { + public: + Layer(); + explicit Layer(StringRefNull name); + Layer(const Layer &other); + ~Layer(); + + StringRefNull name() const + { + return this->base.name; + } + + /** + * \returns the frames mapping. + */ + const Map &frames() const; + Map &frames_for_write(); + + bool is_visible() const; + bool is_locked() const; + + /** + * Inserts the frame into the layer. Fails if there exists a frame at \a frame_number already. + * \returns true on success. + */ + bool insert_frame(int frame_number, const GreasePencilFrame &frame); + bool insert_frame(int frame_number, GreasePencilFrame &&frame); + + /** + * Inserts the frame into the layer. If there exists a frame at \a frame_number already, it is + * overwritten. + * \returns true on success. + */ + bool overwrite_frame(int frame_number, const GreasePencilFrame &frame); + bool overwrite_frame(int frame_number, GreasePencilFrame &&frame); + + /** + * Returns the sorted (start) frame numbers of the frames of this layer. + * \note This will cache the keys lazily. + */ + Span sorted_keys() const; + + /** + * \returns the index of the drawing at frame \a frame or -1 if there is no drawing. + */ + int drawing_index_at(const int frame) const; + + void tag_frames_map_changed(); + + /** + * Should be called whenever the keys in the frames map have changed. E.g. when new keys were + * added, removed or updated. + */ + void tag_frames_map_keys_changed(); +}; + +class LayerGroupRuntime { + public: + /** + * CacheMutex for `nodes_cache_` and `layer_cache_`; + */ + mutable CacheMutex nodes_cache_mutex_; + /** + * Caches all the nodes of this group in a single pre-ordered vector. + */ + mutable Vector nodes_cache_; + /** + * Caches all the layers in this group in a single pre-ordered vector. + */ + mutable Vector layer_cache_; +}; + +/** + * A LayerGroup is a grouping of zero or more Layers. + */ +class LayerGroup : public ::GreasePencilLayerTreeGroup { + public: + LayerGroup(); + explicit LayerGroup(StringRefNull name); + LayerGroup(const LayerGroup &other); + ~LayerGroup(); + + public: + /** + * Adds a group at the end of this group. + */ + LayerGroup &add_group(LayerGroup *group); + LayerGroup &add_group(StringRefNull name); + + /** + * Adds a layer at the end of this group and returns it. + */ + Layer &add_layer(Layer *layer); + Layer &add_layer(StringRefNull name); + + /** + * Returns the number of direct nodes in this group. + */ + int64_t num_direct_nodes() const; + + /** + * Returns the total number of nodes in this group. + */ + int64_t num_nodes_total() const; + + /** + * Removes a child from the group by index. Does not free the memory. + * \note: Assumes the removed child is not the active layer. + */ + void remove_child(int64_t index); + + /** + * Returns a `Span` of pointers to all the `TreeNode`s in this group. + */ + Span nodes() const; + Span nodes_for_write(); + + /** + * Returns a `Span` of pointers to all the `Layer`s in this group. + */ + Span layers() const; + Span layers_for_write(); + + /** + * Returns a pointer to the layer with \a name. If no such layer was found, returns nullptr. + */ + const Layer *find_layer_by_name(StringRefNull name) const; + Layer *find_layer_by_name(StringRefNull name); + + /** + * Print the nodes. For debugging purposes. + */ + void print_nodes(StringRefNull header) const; + + private: + void ensure_nodes_cache() const; + void tag_nodes_cache_dirty() const; +}; + +namespace convert { + +void legacy_gpencil_frame_to_grease_pencil_drawing(const bGPDframe &gpf, + GreasePencilDrawing &r_drawing); +void legacy_gpencil_to_grease_pencil(Main &main, GreasePencil &grease_pencil, bGPdata &gpd); + +} // namespace convert + +} // namespace greasepencil + +class GreasePencilRuntime { + public: + /** + * Allocated and freed by the drawing code. See `DRW_grease_pencil_batch_cache_*` functions. + */ + void *batch_cache = nullptr; + + public: + GreasePencilRuntime() {} + ~GreasePencilRuntime() {} +}; + +} // namespace blender::bke + +inline blender::bke::greasepencil::TreeNode &GreasePencilLayerTreeNode::wrap() +{ + return *reinterpret_cast(this); +} +inline const blender::bke::greasepencil::TreeNode &GreasePencilLayerTreeNode::wrap() const +{ + return *reinterpret_cast(this); +} + +inline blender::bke::greasepencil::Layer &GreasePencilLayer::wrap() +{ + return *reinterpret_cast(this); +} +inline const blender::bke::greasepencil::Layer &GreasePencilLayer::wrap() const +{ + return *reinterpret_cast(this); +} + +inline blender::bke::greasepencil::LayerGroup &GreasePencilLayerTreeGroup::wrap() +{ + return *reinterpret_cast(this); +} +inline const blender::bke::greasepencil::LayerGroup &GreasePencilLayerTreeGroup::wrap() const +{ + return *reinterpret_cast(this); +} + +inline bool GreasePencil::has_active_layer() const +{ + return (this->active_layer != nullptr); +} + +struct Main; +struct Depsgraph; +struct BoundBox; +struct Scene; +struct Object; + +void *BKE_grease_pencil_add(Main *bmain, const char *name); +GreasePencil *BKE_grease_pencil_new_nomain(); +BoundBox *BKE_grease_pencil_boundbox_get(Object *ob); +void BKE_grease_pencil_data_update(struct Depsgraph *depsgraph, + struct Scene *scene, + struct Object *object); + +bool BKE_grease_pencil_references_cyclic_check(const GreasePencil *id_reference, + const GreasePencil *grease_pencil); diff --git a/source/blender/blenkernel/BKE_idtype.h b/source/blender/blenkernel/BKE_idtype.h index 02e6088e483..0e6889c3cda 100644 --- a/source/blender/blenkernel/BKE_idtype.h +++ b/source/blender/blenkernel/BKE_idtype.h @@ -273,6 +273,7 @@ extern IDTypeInfo IDType_ID_CV; extern IDTypeInfo IDType_ID_PT; extern IDTypeInfo IDType_ID_VO; extern IDTypeInfo IDType_ID_SIM; +extern IDTypeInfo IDType_ID_GP; /** Empty shell mostly, but needed for read code. */ extern IDTypeInfo IDType_ID_LINK_PLACEHOLDER; diff --git a/source/blender/blenkernel/BKE_main.h b/source/blender/blenkernel/BKE_main.h index 2bef22819ff..ffc0f754c23 100644 --- a/source/blender/blenkernel/BKE_main.h +++ b/source/blender/blenkernel/BKE_main.h @@ -211,6 +211,7 @@ typedef struct Main { ListBase paintcurves; ListBase wm; /* Singleton (exception). */ ListBase gpencils; /* Legacy Grease Pencil. */ + ListBase grease_pencils; ListBase movieclips; ListBase masks; ListBase linestyles; diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt index 3a1fc0fcdcc..e796c2adc0f 100644 --- a/source/blender/blenkernel/CMakeLists.txt +++ b/source/blender/blenkernel/CMakeLists.txt @@ -149,6 +149,8 @@ set(SRC intern/gpencil_legacy.c intern/gpencil_modifier_legacy.c intern/gpencil_update_cache_legacy.c + intern/grease_pencil_convert_legacy.cc + intern/grease_pencil.cc intern/icons.cc intern/icons_rasterize.c intern/idprop.c @@ -393,6 +395,8 @@ set(SRC BKE_gpencil_legacy.h BKE_gpencil_modifier_legacy.h BKE_gpencil_update_cache_legacy.h + BKE_grease_pencil.h + BKE_grease_pencil.hh BKE_icons.h BKE_idprop.h BKE_idprop.hh @@ -836,6 +840,7 @@ if(WITH_GTESTS) intern/lib_remap_test.cc intern/nla_test.cc intern/tracking_test.cc + intern/grease_pencil_test.cc ) set(TEST_INC ../editors/include diff --git a/source/blender/blenkernel/intern/context.cc b/source/blender/blenkernel/intern/context.cc index 79852696f6b..437ef0b27d6 100644 --- a/source/blender/blenkernel/intern/context.cc +++ b/source/blender/blenkernel/intern/context.cc @@ -1184,6 +1184,8 @@ enum eContextObjectMode CTX_data_mode_enum_ex(const Object *obedit, return CTX_MODE_EDIT_LATTICE; case OB_CURVES: return CTX_MODE_EDIT_CURVES; + case OB_GREASE_PENCIL: + return CTX_MODE_EDIT_GPENCIL; } } else { diff --git a/source/blender/blenkernel/intern/grease_pencil.cc b/source/blender/blenkernel/intern/grease_pencil.cc new file mode 100644 index 00000000000..6522b459674 --- /dev/null +++ b/source/blender/blenkernel/intern/grease_pencil.cc @@ -0,0 +1,1308 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2023 Blender Foundation. */ + +/** \file + * \ingroup bke + */ + +#include "BKE_anim_data.h" +#include "BKE_curves.hh" +#include "BKE_customdata.h" +#include "BKE_grease_pencil.h" +#include "BKE_grease_pencil.hh" +#include "BKE_idtype.h" +#include "BKE_lib_id.h" +#include "BKE_lib_query.h" +#include "BKE_object.h" + +#include "BLI_map.hh" +#include "BLI_math_vector_types.hh" +#include "BLI_memarena.h" +#include "BLI_memory_utils.hh" +#include "BLI_polyfill_2d.h" +#include "BLI_span.hh" +#include "BLI_stack.hh" +#include "BLI_string.h" + +#include "BLO_read_write.h" + +#include "BLT_translation.h" + +#include "DNA_ID.h" +#include "DNA_ID_enums.h" +#include "DNA_grease_pencil_types.h" +#include "DNA_material_types.h" + +#include "MEM_guardedalloc.h" + +using blender::float3; +using blender::Span; +using blender::uint3; +using blender::Vector; + +static void grease_pencil_init_data(ID *id) +{ + using namespace blender::bke; + + GreasePencil *grease_pencil = reinterpret_cast(id); + grease_pencil->runtime = MEM_new(__func__); + + new (&grease_pencil->root_group) greasepencil::LayerGroup(); + grease_pencil->active_layer = nullptr; +} + +static void grease_pencil_copy_data(Main * /*bmain*/, + ID *id_dst, + const ID *id_src, + const int /*flag*/) +{ + using namespace blender; + + GreasePencil *grease_pencil_dst = reinterpret_cast(id_dst); + const GreasePencil *grease_pencil_src = reinterpret_cast(id_src); + + /* Duplicate material array. */ + grease_pencil_dst->material_array = static_cast( + MEM_dupallocN(grease_pencil_src->material_array)); + + /* Duplicate drawing array. */ + grease_pencil_dst->drawing_array_size = grease_pencil_src->drawing_array_size; + grease_pencil_dst->drawing_array = MEM_cnew_array( + grease_pencil_src->drawing_array_size, __func__); + for (int i = 0; i < grease_pencil_src->drawing_array_size; 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_cnew(__func__)); + GreasePencilDrawing *dst_drawing = reinterpret_cast( + grease_pencil_dst->drawing_array[i]); + + dst_drawing->base.type = src_drawing->base.type; + dst_drawing->base.flag = src_drawing->base.flag; + + new (&dst_drawing->geometry) bke::CurvesGeometry(src_drawing->geometry.wrap()); + dst_drawing->runtime = MEM_new(__func__); + dst_drawing->runtime->triangles_cache = src_drawing->runtime->triangles_cache; + 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; + } + } + /* TODO: Update drawing user counts. */ + } + + /* Duplicate layer tree. */ + new (&grease_pencil_dst->root_group) + bke::greasepencil::LayerGroup(grease_pencil_src->root_group.wrap()); + + /* Set active layer. */ + if (grease_pencil_src->has_active_layer()) { + grease_pencil_dst->active_layer = grease_pencil_dst->find_layer_by_name( + grease_pencil_src->active_layer->wrap().name()); + } + + /* Make sure the runtime pointer exists. */ + grease_pencil_dst->runtime = MEM_new(__func__); +} + +static void grease_pencil_free_data(ID *id) +{ + GreasePencil *grease_pencil = reinterpret_cast(id); + BKE_animdata_free(&grease_pencil->id, false); + + MEM_SAFE_FREE(grease_pencil->material_array); + + grease_pencil->free_drawing_array(); + grease_pencil->root_group.wrap().~LayerGroup(); + + BKE_grease_pencil_batch_cache_free(grease_pencil); + + MEM_delete(grease_pencil->runtime); + grease_pencil->runtime = nullptr; +} + +static void grease_pencil_foreach_id(ID *id, LibraryForeachIDData *data) +{ + GreasePencil *grease_pencil = reinterpret_cast(id); + for (int i = 0; i < grease_pencil->material_array_size; i++) { + BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, grease_pencil->material_array[i], IDWALK_CB_USER); + } + for (int i = 0; i < grease_pencil->drawing_array_size; i++) { + GreasePencilDrawingBase *drawing_base = grease_pencil->drawing_array[i]; + if (drawing_base->type == GP_DRAWING_REFERENCE) { + GreasePencilDrawingReference *drawing_reference = + reinterpret_cast(drawing_base); + BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, drawing_reference->id_reference, IDWALK_CB_USER); + } + } +} + +static void grease_pencil_blend_write(BlendWriter *writer, ID *id, const void *id_address) +{ + GreasePencil *grease_pencil = reinterpret_cast(id); + + /* Write LibData */ + BLO_write_id_struct(writer, GreasePencil, id_address, &grease_pencil->id); + BKE_id_blend_write(writer, &grease_pencil->id); + + /* Write animation data. */ + if (grease_pencil->adt) { + BKE_animdata_blend_write(writer, grease_pencil->adt); + } + + /* Write drawings. */ + grease_pencil->write_drawing_array(writer); + /* Write layer tree. */ + grease_pencil->write_layer_tree(writer); + + /* Write materials. */ + BLO_write_pointer_array( + writer, grease_pencil->material_array_size, grease_pencil->material_array); +} + +static void grease_pencil_blend_read_data(BlendDataReader *reader, ID *id) +{ + using namespace blender::bke::greasepencil; + GreasePencil *grease_pencil = reinterpret_cast(id); + + /* Read animation data. */ + BLO_read_data_address(reader, &grease_pencil->adt); + BKE_animdata_blend_read_data(reader, grease_pencil->adt); + + /* Read drawings. */ + grease_pencil->read_drawing_array(reader); + /* Read layer tree. */ + grease_pencil->read_layer_tree(reader); + /* Read active layer. */ + BLO_read_data_address(reader, reinterpret_cast(&grease_pencil->active_layer)); + + /* Read materials. */ + BLO_read_pointer_array(reader, reinterpret_cast(&grease_pencil->material_array)); + + grease_pencil->runtime = MEM_new(__func__); +} + +static void grease_pencil_blend_read_lib(BlendLibReader *reader, ID *id) +{ + GreasePencil *grease_pencil = reinterpret_cast(id); + for (int i = 0; i < grease_pencil->material_array_size; i++) { + BLO_read_id_address(reader, id, &grease_pencil->material_array[i]); + } + for (int i = 0; i < grease_pencil->drawing_array_size; i++) { + GreasePencilDrawingBase *drawing_base = grease_pencil->drawing_array[i]; + if (drawing_base->type == GP_DRAWING_REFERENCE) { + GreasePencilDrawingReference *drawing_reference = + reinterpret_cast(drawing_base); + BLO_read_id_address(reader, id, &drawing_reference->id_reference); + } + } +} + +static void grease_pencil_blend_read_expand(BlendExpander *expander, ID *id) +{ + GreasePencil *grease_pencil = reinterpret_cast(id); + for (int i = 0; i < grease_pencil->material_array_size; i++) { + BLO_expand(expander, grease_pencil->material_array[i]); + } + for (int i = 0; i < grease_pencil->drawing_array_size; i++) { + GreasePencilDrawingBase *drawing_base = grease_pencil->drawing_array[i]; + if (drawing_base->type == GP_DRAWING_REFERENCE) { + GreasePencilDrawingReference *drawing_reference = + reinterpret_cast(drawing_base); + BLO_expand(expander, drawing_reference->id_reference); + } + } +} + +IDTypeInfo IDType_ID_GP = { + /*id_code*/ ID_GP, + /*id_filter*/ FILTER_ID_GP, + /*main_listbase_index*/ INDEX_ID_GP, + /*struct_size*/ sizeof(GreasePencil), + /*name*/ "GreasePencil", + /*name_plural*/ "grease_pencils_new", + /*translation_context*/ BLT_I18NCONTEXT_ID_GPENCIL, + /*flags*/ IDTYPE_FLAGS_APPEND_IS_REUSABLE, + /*asset_type_info*/ nullptr, + + /*init_data*/ grease_pencil_init_data, + /*copy_data*/ grease_pencil_copy_data, + /*free_data*/ grease_pencil_free_data, + /*make_local*/ nullptr, + /*foreach_id*/ grease_pencil_foreach_id, + /*foreach_cache*/ nullptr, + /*foreach_path*/ nullptr, + /*owner_pointer_get*/ nullptr, + + /*blend_write*/ grease_pencil_blend_write, + /*blend_read_data*/ grease_pencil_blend_read_data, + /*blend_read_lib*/ grease_pencil_blend_read_lib, + /*blend_read_expand*/ grease_pencil_blend_read_expand, + + /*blend_read_undo_preserve*/ nullptr, + + /*lib_override_apply_post*/ nullptr, +}; + +namespace blender::bke::greasepencil { + +TreeNode::TreeNode() +{ + this->next = this->prev = nullptr; + this->parent = nullptr; + + this->name = nullptr; + this->flag = 0; + this->color[0] = this->color[1] = this->color[2] = 0; +} + +TreeNode::TreeNode(GreasePencilLayerTreeNodeType type) : TreeNode() +{ + this->type = type; +} + +TreeNode::TreeNode(GreasePencilLayerTreeNodeType type, StringRefNull name) : TreeNode() +{ + this->type = type; + this->name = BLI_strdup(name.c_str()); +} + +TreeNode::TreeNode(const TreeNode &other) + : TreeNode::TreeNode(GreasePencilLayerTreeNodeType(other.type)) +{ + if (other.name) { + this->name = BLI_strdup(other.name); + } + this->flag = other.flag; + copy_v3_v3_uchar(this->color, other.color); +} + +const LayerGroup &TreeNode::as_group() const +{ + return *reinterpret_cast(this); +} + +const Layer &TreeNode::as_layer() const +{ + return *reinterpret_cast(this); +} + +LayerGroup &TreeNode::as_group_for_write() +{ + return *reinterpret_cast(this); +} + +Layer &TreeNode::as_layer_for_write() +{ + return *reinterpret_cast(this); +} + +LayerMask::LayerMask() +{ + this->layer_name = nullptr; + this->flag = 0; +} + +LayerMask::LayerMask(StringRefNull name) : LayerMask() +{ + this->layer_name = BLI_strdup(name.c_str()); +} + +LayerMask::LayerMask(const LayerMask &other) : LayerMask() +{ + if (other.layer_name) { + this->layer_name = BLI_strdup(other.layer_name); + } + this->flag = other.flag; +} + +LayerMask::~LayerMask() +{ + if (this->layer_name) { + MEM_freeN(this->layer_name); + } +} + +Layer::Layer() +{ + this->base = TreeNode(GP_LAYER_TREE_LEAF); + + this->frames_storage.size = 0; + this->frames_storage.keys = nullptr; + this->frames_storage.values = nullptr; + this->frames_storage.flag = 0; + + BLI_listbase_clear(&this->masks); + + this->runtime = MEM_new(__func__); +} + +Layer::Layer(StringRefNull name) : Layer() +{ + this->base.name = BLI_strdup(name.c_str()); +} + +Layer::Layer(const Layer &other) : Layer() +{ + this->base = TreeNode(other.base.wrap()); + + /* TODO: duplicate masks. */ + + /* Note: We do not duplicate the frame storage since it is only needed for writing. */ + + this->blend_mode = other.blend_mode; + this->opacity = other.opacity; + + this->runtime->frames_ = other.runtime->frames_; + this->runtime->sorted_keys_cache_ = other.runtime->sorted_keys_cache_; + /* TODO: what about masks cache? */ +} + +Layer::~Layer() +{ + MEM_SAFE_FREE(this->base.name); + MEM_SAFE_FREE(this->frames_storage.keys); + MEM_SAFE_FREE(this->frames_storage.values); + + LISTBASE_FOREACH_MUTABLE (GreasePencilLayerMask *, mask, &this->masks) { + MEM_SAFE_FREE(mask->layer_name); + MEM_freeN(mask); + } + + MEM_delete(this->runtime); + this->runtime = nullptr; +} + +const Map &Layer::frames() const +{ + return this->runtime->frames_; +} + +Map &Layer::frames_for_write() +{ + return this->runtime->frames_; +} + +bool Layer::is_visible() const +{ + return (this->base.flag & GP_LAYER_TREE_NODE_HIDE) == 0; +} + +bool Layer::is_locked() const +{ + return (this->base.flag & GP_LAYER_TREE_NODE_LOCKED) != 0; +} + +bool Layer::insert_frame(int frame_number, const GreasePencilFrame &frame) +{ + this->tag_frames_map_changed(); + return this->frames_for_write().add(frame_number, frame); +} + +bool Layer::insert_frame(int frame_number, GreasePencilFrame &&frame) +{ + this->tag_frames_map_changed(); + return this->frames_for_write().add(frame_number, frame); +} + +bool Layer::overwrite_frame(int frame_number, const GreasePencilFrame &frame) +{ + this->tag_frames_map_changed(); + return this->frames_for_write().add_overwrite(frame_number, frame); +} + +bool Layer::overwrite_frame(int frame_number, GreasePencilFrame &&frame) +{ + this->tag_frames_map_changed(); + return this->frames_for_write().add_overwrite(frame_number, std::move(frame)); +} + +Span Layer::sorted_keys() const +{ + this->runtime->sorted_keys_cache_.ensure([&](Vector &r_data) { + r_data.reinitialize(this->frames().size()); + int i = 0; + for (int64_t key : this->frames().keys()) { + r_data[i++] = key; + } + std::sort(r_data.begin(), r_data.end()); + }); + return this->runtime->sorted_keys_cache_.data(); +} + +int Layer::drawing_index_at(const int frame) const +{ + Span sorted_keys = this->sorted_keys(); + /* No keyframes, return no drawing. */ + if (sorted_keys.size() == 0) { + return -1; + } + /* Before the first drawing, return no drawing. */ + if (frame < sorted_keys.first()) { + return -1; + } + /* After or at the last drawing, return the last drawing. */ + if (frame >= sorted_keys.last()) { + return this->frames().lookup(sorted_keys.last()).drawing_index; + } + /* Search for the drawing. upper_bound will get the drawing just after. */ + auto it = std::upper_bound(sorted_keys.begin(), sorted_keys.end(), frame); + if (it == sorted_keys.end() || it == sorted_keys.begin()) { + return -1; + } + return this->frames().lookup(*std::prev(it)).drawing_index; +} + +void Layer::tag_frames_map_changed() +{ + this->frames_storage.flag |= GP_LAYER_FRAMES_STORAGE_DIRTY; +} + +void Layer::tag_frames_map_keys_changed() +{ + this->tag_frames_map_changed(); + this->runtime->sorted_keys_cache_.tag_dirty(); +} + +LayerGroup::LayerGroup() +{ + this->base = TreeNode(GP_LAYER_TREE_GROUP); + + BLI_listbase_clear(&this->children); + + this->runtime = MEM_new(__func__); +} + +LayerGroup::LayerGroup(StringRefNull name) : LayerGroup() +{ + this->base.name = BLI_strdup(name.c_str()); +} + +LayerGroup::LayerGroup(const LayerGroup &other) : LayerGroup() +{ + this->base = TreeNode(other.base.wrap()); + + LISTBASE_FOREACH (GreasePencilLayerTreeNode *, child, &other.children) { + switch (child->type) { + case GP_LAYER_TREE_LEAF: { + GreasePencilLayer *layer = reinterpret_cast(child); + Layer *dup_layer = new Layer(layer->wrap()); + this->add_layer(dup_layer); + break; + } + case GP_LAYER_TREE_GROUP: { + GreasePencilLayerTreeGroup *group = reinterpret_cast(child); + LayerGroup *dup_group = new LayerGroup(group->wrap()); + this->add_group(dup_group); + break; + } + } + } +} + +LayerGroup::~LayerGroup() +{ + MEM_SAFE_FREE(this->base.name); + + LISTBASE_FOREACH_MUTABLE (GreasePencilLayerTreeNode *, child, &this->children) { + switch (child->type) { + case GP_LAYER_TREE_LEAF: { + GreasePencilLayer *layer = reinterpret_cast(child); + layer->wrap().~Layer(); + break; + } + case GP_LAYER_TREE_GROUP: { + GreasePencilLayerTreeGroup *group = reinterpret_cast(child); + group->wrap().~LayerGroup(); + break; + } + } + } + + MEM_delete(this->runtime); + this->runtime = nullptr; +} + +LayerGroup &LayerGroup::add_group(LayerGroup *group) +{ + BLI_assert(group != nullptr); + BLI_addtail(&this->children, reinterpret_cast(group)); + group->base.parent = reinterpret_cast(this); + this->tag_nodes_cache_dirty(); + return *group; +} + +LayerGroup &LayerGroup::add_group(StringRefNull name) +{ + LayerGroup *new_group = new LayerGroup(name); + return this->add_group(new_group); +} + +Layer &LayerGroup::add_layer(Layer *layer) +{ + BLI_assert(layer != nullptr); + BLI_addtail(&this->children, reinterpret_cast(layer)); + layer->base.parent = reinterpret_cast(this); + this->tag_nodes_cache_dirty(); + return *layer; +} + +Layer &LayerGroup::add_layer(StringRefNull name) +{ + Layer *new_layer = new Layer(name); + return this->add_layer(new_layer); +} + +int64_t LayerGroup::num_direct_nodes() const +{ + return BLI_listbase_count(&this->children); +} + +int64_t LayerGroup::num_nodes_total() const +{ + this->ensure_nodes_cache(); + return this->runtime->nodes_cache_.size(); +} + +void LayerGroup::remove_child(int64_t index) +{ + BLI_assert(index >= 0 && index < this->num_direct_nodes()); + BLI_remlink(&this->children, BLI_findlink(&this->children, index)); + this->tag_nodes_cache_dirty(); +} + +Span LayerGroup::nodes() const +{ + this->ensure_nodes_cache(); + return this->runtime->nodes_cache_.as_span(); +} + +Span LayerGroup::nodes_for_write() +{ + this->ensure_nodes_cache(); + return this->runtime->nodes_cache_.as_span(); +} + +Span LayerGroup::layers() const +{ + this->ensure_nodes_cache(); + return this->runtime->layer_cache_.as_span(); +} + +Span LayerGroup::layers_for_write() +{ + this->ensure_nodes_cache(); + return this->runtime->layer_cache_.as_span(); +} + +const Layer *LayerGroup::find_layer_by_name(const StringRefNull name) const +{ + for (const Layer *layer : this->layers()) { + if (StringRef(layer->name()) == StringRef(name)) { + return layer; + } + } + return nullptr; +} + +Layer *LayerGroup::find_layer_by_name(const StringRefNull name) +{ + for (Layer *layer : this->layers_for_write()) { + if (StringRef(layer->name()) == StringRef(name)) { + return layer; + } + } + return nullptr; +} + +void LayerGroup::print_nodes(StringRefNull header) const +{ + std::cout << header << std::endl; + Stack> next_node; + LISTBASE_FOREACH_BACKWARD (GreasePencilLayerTreeNode *, child_, &this->children) { + TreeNode *child = reinterpret_cast(child_); + next_node.push(std::make_pair(1, child)); + } + while (!next_node.is_empty()) { + auto [indent, node] = next_node.pop(); + for (int i = 0; i < indent; i++) { + std::cout << " "; + } + if (node->is_layer()) { + std::cout << StringRefNull(node->name); + } + else if (node->is_group()) { + std::cout << StringRefNull(node->name) << ": "; + LISTBASE_FOREACH_BACKWARD (GreasePencilLayerTreeNode *, child_, &node->as_group().children) { + TreeNode *child = reinterpret_cast(child_); + next_node.push(std::make_pair(indent + 1, child)); + } + } + std::cout << std::endl; + } + std::cout << std::endl; +} + +void LayerGroup::ensure_nodes_cache() const +{ + this->runtime->nodes_cache_mutex_.ensure([&]() { + this->runtime->nodes_cache_.clear_and_shrink(); + this->runtime->layer_cache_.clear_and_shrink(); + + LISTBASE_FOREACH (GreasePencilLayerTreeNode *, child_, &this->children) { + TreeNode *node = reinterpret_cast(child_); + this->runtime->nodes_cache_.append(node); + switch (node->type) { + case GP_LAYER_TREE_LEAF: { + this->runtime->layer_cache_.append(&node->as_layer_for_write()); + break; + } + case GP_LAYER_TREE_GROUP: { + for (TreeNode *child : node->as_group_for_write().nodes_for_write()) { + this->runtime->nodes_cache_.append(child); + if (child->is_layer()) { + this->runtime->layer_cache_.append(&child->as_layer_for_write()); + } + } + break; + } + } + } + }); +} + +void LayerGroup::tag_nodes_cache_dirty() const +{ + this->runtime->nodes_cache_mutex_.tag_dirty(); + if (this->base.parent) { + this->base.parent->wrap().tag_nodes_cache_dirty(); + } +} + +} // namespace blender::bke::greasepencil + +/* ------------------------------------------------------------------- */ +/** \name Grease Pencil kernel functions + * \{ */ + +void *BKE_grease_pencil_add(Main *bmain, const char *name) +{ + GreasePencil *grease_pencil = reinterpret_cast(BKE_id_new(bmain, ID_GP, name)); + + return grease_pencil; +} + +GreasePencil *BKE_grease_pencil_new_nomain() +{ + GreasePencil *grease_pencil = reinterpret_cast( + BKE_id_new_nomain(ID_GP, nullptr)); + return grease_pencil; +} + +BoundBox *BKE_grease_pencil_boundbox_get(Object *ob) +{ + 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__); + + float3 min(FLT_MAX); + float3 max(-FLT_MAX); + + if (!grease_pencil->bounds_min_max(min, max)) { + min = float3(-1); + max = float3(1); + } + + BKE_boundbox_init_from_minmax(ob->runtime.bb, min, max); + } + + return ob->runtime.bb; +} + +void BKE_grease_pencil_data_update(struct Depsgraph * /*depsgraph*/, + struct Scene * /*scene*/, + Object *object) +{ + /* Free any evaluated data and restore original data. */ + BKE_object_free_derived_caches(object); + + GreasePencil *grease_pencil = reinterpret_cast(object->data); + /* Evaluate modifiers. */ + /* TODO: evaluate modifiers. */ + + /* Assign evaluated object. */ + /* TODO: Get eval from modifiers geometry set. */ + GreasePencil *grease_pencil_eval = reinterpret_cast( + BKE_id_copy_ex(nullptr, &grease_pencil->id, nullptr, LIB_ID_COPY_LOCALIZE)); + BKE_object_eval_assign_data(object, &grease_pencil_eval->id, true); +} + +/** \} */ + +/* ------------------------------------------------------------------- */ +/** \name Grease Pencil reference functions + * \{ */ + +static bool grease_pencil_references_cyclic_check_internal(const GreasePencil *id_reference, + const GreasePencil *grease_pencil) +{ + for (GreasePencilDrawingBase *base : grease_pencil->drawings()) { + if (base->type == GP_DRAWING_REFERENCE) { + GreasePencilDrawingReference *reference = reinterpret_cast( + base); + if (id_reference == reference->id_reference) { + return true; + } + + if (grease_pencil_references_cyclic_check_internal(id_reference, reference->id_reference)) { + return true; + } + } + } + return false; +} + +bool BKE_grease_pencil_references_cyclic_check(const GreasePencil *id_reference, + const GreasePencil *grease_pencil) +{ + return grease_pencil_references_cyclic_check_internal(id_reference, grease_pencil); +} + +/** \} */ + +/* ------------------------------------------------------------------- */ +/** \name Draw Cache + * \{ */ + +void (*BKE_grease_pencil_batch_cache_dirty_tag_cb)(GreasePencil *grease_pencil, + int mode) = nullptr; +void (*BKE_grease_pencil_batch_cache_free_cb)(GreasePencil *grease_pencil) = nullptr; + +void BKE_grease_pencil_batch_cache_dirty_tag(GreasePencil *grease_pencil, int mode) +{ + if (grease_pencil->runtime && grease_pencil->runtime->batch_cache) { + BKE_grease_pencil_batch_cache_dirty_tag_cb(grease_pencil, mode); + } +} + +void BKE_grease_pencil_batch_cache_free(GreasePencil *grease_pencil) +{ + if (grease_pencil->runtime && grease_pencil->runtime->batch_cache) { + BKE_grease_pencil_batch_cache_free_cb(grease_pencil); + } +} + +/** \} */ + +/* ------------------------------------------------------------------- */ +/** \name Grease Pencil Drawing API + * \{ */ + +blender::Span GreasePencilDrawing::triangles() const +{ + using namespace blender; + const bke::greasepencil::DrawingRuntime &runtime = *this->runtime; + runtime.triangles_cache.ensure([&](Vector &r_data) { + MemArena *pf_arena = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, __func__); + + const bke::CurvesGeometry &curves = this->geometry.wrap(); + const Span positions = curves.positions(); + const OffsetIndices points_by_curve = curves.points_by_curve(); + + int total_triangles = 0; + Array tris_offests(curves.curves_num()); + for (int curve_i : curves.curves_range()) { + IndexRange points = points_by_curve[curve_i]; + if (points.size() > 2) { + tris_offests[curve_i] = total_triangles; + total_triangles += points.size() - 2; + } + } + + r_data.resize(total_triangles); + + /* TODO: use threading. */ + for (const int curve_i : curves.curves_range()) { + const IndexRange points = points_by_curve[curve_i]; + + if (points.size() < 3) { + continue; + } + + const int num_trinagles = points.size() - 2; + MutableSpan r_tris = r_data.as_mutable_span().slice(tris_offests[curve_i], + num_trinagles); + + float(*projverts)[2] = static_cast( + BLI_memarena_alloc(pf_arena, sizeof(*projverts) * size_t(points.size()))); + + /* TODO: calculate axis_mat properly. */ + float3x3 axis_mat; + axis_dominant_v3_to_m3(axis_mat.ptr(), float3(0.0f, -1.0f, 0.0f)); + + for (const int i : IndexRange(points.size())) { + mul_v2_m3v3(projverts[i], axis_mat.ptr(), positions[points[i]]); + } + + BLI_polyfill_calc_arena( + projverts, points.size(), 0, reinterpret_cast(r_tris.data()), pf_arena); + BLI_memarena_clear(pf_arena); + } + + BLI_memarena_free(pf_arena); + }); + + return this->runtime->triangles_cache.data().as_span(); +} + +void GreasePencilDrawing::tag_positions_changed() +{ + this->geometry.wrap().tag_positions_changed(); + this->runtime->triangles_cache.tag_dirty(); +} + +bool GreasePencilDrawing::has_stroke_buffer() const +{ + return this->runtime->stroke_cache.points.size() > 0; +} + +blender::Span GreasePencilDrawing::stroke_buffer() const +{ + return this->runtime->stroke_cache.points.as_span(); +} + +/** \} */ + +/* ------------------------------------------------------------------- */ +/** \name Grease Pencil data-block API + * \{ */ + +template static void grow_array(T **array, int *size, const int add_size) +{ + BLI_assert(add_size > 0); + const int new_array_size = *size + add_size; + T *new_array = reinterpret_cast(MEM_cnew_array(new_array_size, __func__)); + + blender::uninitialized_relocate_n(*array, *size, new_array); + + *array = new_array; + *size = new_array_size; +} +template static void shrink_array(T **array, int *size, const int shrink_size) +{ + BLI_assert(shrink_size > 0); + const int new_array_size = *size - shrink_size; + T *new_array = reinterpret_cast(MEM_cnew_array(new_array_size, __func__)); + + blender::uninitialized_move_n(*array, new_array_size, new_array); + MEM_freeN(*array); + + *array = new_array; + *size = new_array_size; +} + +blender::Span GreasePencil::drawings() const +{ + return blender::Span{this->drawing_array, this->drawing_array_size}; +} + +blender::MutableSpan GreasePencil::drawings_for_write() +{ + return blender::MutableSpan{this->drawing_array, + this->drawing_array_size}; +} + +void GreasePencil::add_empty_drawings(const int add_size) +{ + using namespace blender; + BLI_assert(add_size > 0); + const int prev_size = this->drawings().size(); + grow_array(&this->drawing_array, &this->drawing_array_size, add_size); + MutableSpan new_drawings = this->drawings_for_write().drop_front( + prev_size); + for (const int i : new_drawings.index_range()) { + new_drawings[i] = reinterpret_cast( + MEM_new(__func__)); + GreasePencilDrawing *drawing = reinterpret_cast(new_drawings[i]); + new (&drawing->geometry) bke::CurvesGeometry(); + drawing->runtime = MEM_new(__func__); + } + + /* TODO: Update drawing user counts. */ +} + +void GreasePencil::remove_drawing(const int index_to_remove) +{ + using namespace blender::bke::greasepencil; + /* In order to not change the indices of the drawings, we do the following to the drawing to be + * removed: + * - If the drawing (A) is not the last one: + * 1.1) Find any frames in the layers that reference the last drawing (B) and point them to + * A's index. + * 1.2) Swap drawing A with drawing B. + * 2) Destroy A and shrink the array by one. + * 3) Remove any frames in the layers that reference the A's index. + */ + BLI_assert(this->drawing_array_size > 0); + BLI_assert(index_to_remove >= 0 && index_to_remove < this->drawing_array_size); + + /* Move the drawing that should be removed to the last index. */ + const int last_drawing_index = this->drawing_array_size - 1; + if (index_to_remove != last_drawing_index) { + for (Layer *layer : this->layers_for_write()) { + blender::Map &frames = layer->frames_for_write(); + for (auto [key, value] : frames.items()) { + if (value.drawing_index == last_drawing_index) { + value.drawing_index = index_to_remove; + } + else if (value.drawing_index == index_to_remove) { + value.drawing_index = last_drawing_index; + } + } + } + std::swap(this->drawings_for_write()[index_to_remove], + this->drawings_for_write()[last_drawing_index]); + } + + /* Delete the last drawing. */ + GreasePencilDrawingBase *drawing_base_to_remove = this->drawings_for_write()[last_drawing_index]; + switch (drawing_base_to_remove->type) { + case GP_DRAWING: { + GreasePencilDrawing *drawing_to_remove = reinterpret_cast( + drawing_base_to_remove); + drawing_to_remove->geometry.wrap().~CurvesGeometry(); + MEM_delete(drawing_to_remove->runtime); + drawing_to_remove->runtime = nullptr; + MEM_freeN(drawing_to_remove); + break; + } + case GP_DRAWING_REFERENCE: { + GreasePencilDrawingReference *drawing_reference_to_remove = + reinterpret_cast(drawing_base_to_remove); + MEM_freeN(drawing_reference_to_remove); + break; + } + } + + /* Remove any frame that points to the last drawing. */ + for (Layer *layer : this->layers_for_write()) { + blender::Map &frames = layer->frames_for_write(); + int64_t frames_removed = frames.remove_if([last_drawing_index](auto item) { + return item.value.drawing_index == last_drawing_index; + }); + if (frames_removed > 0) { + layer->tag_frames_map_keys_changed(); + } + } + + /* Shrink drawing array. */ + shrink_array(&this->drawing_array, &this->drawing_array_size, 1); +} + +void GreasePencil::foreach_visible_drawing( + int frame, blender::FunctionRef function) +{ + using namespace blender::bke::greasepencil; + + blender::Span drawings = this->drawings(); + for (const Layer *layer : this->layers()) { + if (!layer->is_visible()) { + continue; + } + 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(*drawing); + } + else if (drawing_base->type == GP_DRAWING_REFERENCE) { + /* TODO */ + } + } +} + +bool GreasePencil::bounds_min_max(float3 &min, float3 &max) const +{ + bool found = false; + /* FIXME: this should somehow go through the visible drawings. We don't have access to the + * scene time here, so we probably need to cache the visible drawing for each layer somehow. */ + for (int i = 0; i < this->drawing_array_size; i++) { + GreasePencilDrawingBase *drawing_base = this->drawing_array[i]; + switch (drawing_base->type) { + case GP_DRAWING: { + GreasePencilDrawing *drawing = reinterpret_cast(drawing_base); + const blender::bke::CurvesGeometry &curves = drawing->geometry.wrap(); + + if (curves.bounds_min_max(min, max)) { + found = true; + } + break; + } + case GP_DRAWING_REFERENCE: { + /* TODO: Calculate the bounding box of the reference drawing. */ + break; + } + } + } + + return found; +} + +blender::Span GreasePencil::layers() const +{ + BLI_assert(this->runtime != nullptr); + return this->root_group.wrap().layers(); +} + +blender::Span GreasePencil::layers_for_write() +{ + BLI_assert(this->runtime != nullptr); + return this->root_group.wrap().layers_for_write(); +} + +blender::bke::greasepencil::Layer &GreasePencil::add_layer( + blender::bke::greasepencil::LayerGroup &group, const blender::StringRefNull name) +{ + using namespace blender; + StringRefNull checked_name; + /* TODO: Check for name collisions and resolve them. */ + return group.add_layer(name); +} + +const blender::bke::greasepencil::Layer *GreasePencil::find_layer_by_name( + const blender::StringRefNull name) const +{ + return this->root_group.wrap().find_layer_by_name(name); +} + +blender::bke::greasepencil::Layer *GreasePencil::find_layer_by_name( + const blender::StringRefNull name) +{ + return this->root_group.wrap().find_layer_by_name(name); +} + +void GreasePencil::print_layer_tree() +{ + using namespace blender::bke::greasepencil; + this->root_group.wrap().print_nodes("Layer Tree:"); +} + +/** \} */ + +/* ------------------------------------------------------------------- */ +/** \name Drawing array read/write functions + * \{ */ + +void GreasePencil::read_drawing_array(BlendDataReader *reader) +{ + BLO_read_pointer_array(reader, reinterpret_cast(&this->drawing_array)); + for (int i = 0; i < this->drawing_array_size; i++) { + BLO_read_data_address(reader, &this->drawing_array[i]); + GreasePencilDrawingBase *drawing_base = this->drawing_array[i]; + switch (drawing_base->type) { + case GP_DRAWING: { + GreasePencilDrawing *drawing = reinterpret_cast(drawing_base); + drawing->geometry.wrap().blend_read(*reader); + /* Initialize runtime data. */ + drawing->runtime = MEM_new(__func__); + break; + } + case GP_DRAWING_REFERENCE: { + GreasePencilDrawingReference *drawing_reference = + reinterpret_cast(drawing_base); + BLO_read_data_address(reader, &drawing_reference->id_reference); + break; + } + } + } +} + +void GreasePencil::write_drawing_array(BlendWriter *writer) +{ + BLO_write_pointer_array(writer, this->drawing_array_size, this->drawing_array); + for (int i = 0; i < this->drawing_array_size; i++) { + GreasePencilDrawingBase *drawing_base = this->drawing_array[i]; + switch (drawing_base->type) { + case GP_DRAWING: { + GreasePencilDrawing *drawing = reinterpret_cast(drawing_base); + BLO_write_struct(writer, GreasePencilDrawing, drawing); + drawing->geometry.wrap().blend_write(*writer, this->id); + break; + } + case GP_DRAWING_REFERENCE: { + GreasePencilDrawingReference *drawing_reference = + reinterpret_cast(drawing_base); + BLO_write_struct(writer, GreasePencilDrawingReference, drawing_reference); + break; + } + } + } +} + +void GreasePencil::free_drawing_array() +{ + if (this->drawing_array == nullptr || this->drawing_array_size == 0) { + return; + } + for (int i = 0; i < this->drawing_array_size; i++) { + GreasePencilDrawingBase *drawing_base = this->drawing_array[i]; + switch (drawing_base->type) { + case GP_DRAWING: { + GreasePencilDrawing *drawing = reinterpret_cast(drawing_base); + drawing->geometry.wrap().~CurvesGeometry(); + MEM_delete(drawing->runtime); + drawing->runtime = nullptr; + MEM_freeN(drawing); + break; + } + case GP_DRAWING_REFERENCE: { + GreasePencilDrawingReference *drawing_reference = + reinterpret_cast(drawing_base); + MEM_freeN(drawing_reference); + break; + } + } + } + MEM_freeN(this->drawing_array); + this->drawing_array = nullptr; + this->drawing_array_size = 0; +} + +/** \} */ + +/* ------------------------------------------------------------------- */ +/** \name Layer tree read/write functions + * \{ */ + +static void read_layer(BlendDataReader *reader, GreasePencilLayer *node, GreasePencilLayerTreeGroup *parent) +{ + BLO_read_data_address(reader, &node->base.name); + node->base.parent = parent; + + /* Read frames storage. */ + BLO_read_int32_array(reader, node->frames_storage.size, &node->frames_storage.keys); + BLO_read_data_address(reader, &node->frames_storage.values); + + /* Re-create frames data in runtime map. */ + node->wrap().runtime = MEM_new(__func__); + for (int i = 0; i < node->frames_storage.size; i++) { + node->wrap().frames_for_write().add(node->frames_storage.keys[i], + node->frames_storage.values[i]); + } + + /* Read layer masks. */ + BLO_read_list(reader, &node->masks); + LISTBASE_FOREACH (GreasePencilLayerMask *, mask, &node->masks) { + BLO_read_data_address(reader, &mask->layer_name); + } +} + +static void read_layer_tree_group(BlendDataReader *reader, GreasePencilLayerTreeGroup *node, GreasePencilLayerTreeGroup *parent) +{ + BLO_read_data_address(reader, &node->base.name); + node->base.parent = parent; + /* Read list of children. */ + BLO_read_list(reader, &node->children); + LISTBASE_FOREACH (GreasePencilLayerTreeNode *, child, &node->children) { + switch (child->type) { + case GP_LAYER_TREE_LEAF: { + GreasePencilLayer *layer = reinterpret_cast(child); + read_layer(reader, layer, node); + break; + } + case GP_LAYER_TREE_GROUP: { + GreasePencilLayerTreeGroup *group = reinterpret_cast(child); + read_layer_tree_group(reader, group, node); + break; + } + } + } + + node->wrap().runtime = MEM_new(__func__); +} + +void GreasePencil::read_layer_tree(BlendDataReader *reader) +{ + read_layer_tree_group(reader, &this->root_group, nullptr); +} + +static void write_layer(BlendWriter *writer, GreasePencilLayer *node) +{ + using namespace blender::bke::greasepencil; + + BLO_write_struct(writer, GreasePencilLayer, node); + BLO_write_string(writer, node->base.name); + + /* Re-create the frames storage only if it was tagged dirty. */ + if ((node->frames_storage.flag & GP_LAYER_FRAMES_STORAGE_DIRTY) != 0) { + MEM_SAFE_FREE(node->frames_storage.keys); + MEM_SAFE_FREE(node->frames_storage.values); + + const Layer &layer = node->wrap(); + node->frames_storage.size = layer.frames().size(); + node->frames_storage.keys = MEM_cnew_array(node->frames_storage.size, __func__); + node->frames_storage.values = MEM_cnew_array(node->frames_storage.size, + __func__); + const Span sorted_keys = layer.sorted_keys(); + for (const int i : sorted_keys.index_range()) { + node->frames_storage.keys[i] = sorted_keys[i]; + node->frames_storage.values[i] = layer.frames().lookup(sorted_keys[i]); + } + + /* Reset the flag. */ + node->frames_storage.flag &= ~GP_LAYER_FRAMES_STORAGE_DIRTY; + } + + BLO_write_int32_array(writer, node->frames_storage.size, node->frames_storage.keys); + BLO_write_struct_array( + writer, GreasePencilFrame, node->frames_storage.size, node->frames_storage.values); + + BLO_write_struct_list(writer, GreasePencilLayerMask, &node->masks); + LISTBASE_FOREACH (GreasePencilLayerMask *, mask, &node->masks) { + BLO_write_string(writer, mask->layer_name); + } +} + +static void write_layer_tree_group(BlendWriter *writer, GreasePencilLayerTreeGroup *node) +{ + BLO_write_struct(writer, GreasePencilLayerTreeGroup, node); + BLO_write_string(writer, node->base.name); + LISTBASE_FOREACH (GreasePencilLayerTreeNode *, child, &node->children) { + switch (child->type) { + case GP_LAYER_TREE_LEAF: { + GreasePencilLayer *layer = reinterpret_cast(child); + write_layer(writer, layer); + break; + } + case GP_LAYER_TREE_GROUP: { + GreasePencilLayerTreeGroup *group = reinterpret_cast(child); + write_layer_tree_group(writer, group); + break; + } + } + } +} + +void GreasePencil::write_layer_tree(BlendWriter *writer) +{ + write_layer_tree_group(writer, &this->root_group); +} + +/** \} */ diff --git a/source/blender/blenkernel/intern/grease_pencil_convert_legacy.cc b/source/blender/blenkernel/intern/grease_pencil_convert_legacy.cc new file mode 100644 index 00000000000..52a3ad208cc --- /dev/null +++ b/source/blender/blenkernel/intern/grease_pencil_convert_legacy.cc @@ -0,0 +1,262 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2023 Blender Foundation. */ + +/** \file + * \ingroup bke + */ + +#include "BKE_curves.hh" +#include "BKE_grease_pencil.hh" +#include "BKE_material.h" + +#include "BLI_color.hh" +#include "BLI_listbase.h" +#include "BLI_math_vector_types.hh" +#include "BLI_vector.hh" + +#include "DNA_gpencil_legacy_types.h" +#include "DNA_grease_pencil_types.h" + +namespace blender::bke::greasepencil::convert { + +void legacy_gpencil_frame_to_grease_pencil_drawing(const bGPDframe &gpf, + GreasePencilDrawing &r_drawing) +{ + /* Construct an empty CurvesGeometry in-place. */ + new (&r_drawing.geometry) CurvesGeometry(); + r_drawing.base.type = GP_DRAWING; + r_drawing.runtime = MEM_new(__func__); + + /* Get the number of points, number of strokes and the offsets for each stroke. */ + Vector offsets; + offsets.append(0); + int num_strokes = 0; + int num_points = 0; + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf.strokes) { + num_points += gps->totpoints; + offsets.append(num_points); + num_strokes++; + } + + /* Resize the CurvesGeometry. */ + CurvesGeometry &curves = r_drawing.geometry.wrap(); + curves.resize(num_points, num_strokes); + if (num_strokes > 0) { + curves.offsets_for_write().copy_from(offsets); + } + OffsetIndices points_by_curve = curves.points_by_curve(); + MutableAttributeAccessor attributes = curves.attributes_for_write(); + + /* All strokes are poly curves. */ + curves.fill_curve_types(CURVE_TYPE_POLY); + + /* Point Attributes. */ + MutableSpan positions = curves.positions_for_write(); + SpanAttributeWriter radii = attributes.lookup_or_add_for_write_span( + "radius", ATTR_DOMAIN_POINT); + SpanAttributeWriter opacities = attributes.lookup_or_add_for_write_span( + "opacity", ATTR_DOMAIN_POINT); + SpanAttributeWriter delta_times = attributes.lookup_or_add_for_write_span( + "delta_time", ATTR_DOMAIN_POINT); + SpanAttributeWriter rotations = attributes.lookup_or_add_for_write_span( + "rotation", ATTR_DOMAIN_POINT); + SpanAttributeWriter vertex_colors = + attributes.lookup_or_add_for_write_span("vertex_color", ATTR_DOMAIN_POINT); + SpanAttributeWriter selection = attributes.lookup_or_add_for_write_span( + ".selection", ATTR_DOMAIN_POINT); + + /* Curve Attributes. */ + SpanAttributeWriter stroke_cyclic = attributes.lookup_or_add_for_write_span( + "cyclic", ATTR_DOMAIN_CURVE); + /* TODO: This should be a `double` attribute. */ + SpanAttributeWriter stroke_init_times = attributes.lookup_or_add_for_write_span( + "init_time", ATTR_DOMAIN_CURVE); + SpanAttributeWriter stroke_start_caps = attributes.lookup_or_add_for_write_span( + "start_cap", ATTR_DOMAIN_CURVE); + SpanAttributeWriter stroke_end_caps = attributes.lookup_or_add_for_write_span( + "end_cap", ATTR_DOMAIN_CURVE); + SpanAttributeWriter stroke_hardnesses = attributes.lookup_or_add_for_write_span( + "hardness", ATTR_DOMAIN_CURVE); + SpanAttributeWriter stroke_point_aspect_ratios = + attributes.lookup_or_add_for_write_span("point_aspect_ratio", ATTR_DOMAIN_CURVE); + SpanAttributeWriter stroke_fill_translations = + attributes.lookup_or_add_for_write_span("fill_translation", ATTR_DOMAIN_CURVE); + SpanAttributeWriter stroke_fill_rotations = + attributes.lookup_or_add_for_write_span("fill_rotation", ATTR_DOMAIN_CURVE); + SpanAttributeWriter stroke_fill_scales = attributes.lookup_or_add_for_write_span( + "fill_scale", ATTR_DOMAIN_CURVE); + SpanAttributeWriter stroke_fill_colors = + attributes.lookup_or_add_for_write_span("fill_color", ATTR_DOMAIN_CURVE); + SpanAttributeWriter stroke_materials = attributes.lookup_or_add_for_write_span( + "material_index", ATTR_DOMAIN_CURVE); + + int stroke_i = 0; + LISTBASE_FOREACH_INDEX (bGPDstroke *, gps, &gpf.strokes, stroke_i) { + /* TODO: check if gps->editcurve is not nullptr and parse bezier curve instead. */ + + /* Write curve attributes. */ + stroke_cyclic.span[stroke_i] = (gps->flag & GP_STROKE_CYCLIC) != 0; + /* TODO: This should be a `double` attribute. */ + stroke_init_times.span[stroke_i] = static_cast(gps->inittime); + stroke_start_caps.span[stroke_i] = static_cast(gps->caps[0]); + stroke_end_caps.span[stroke_i] = static_cast(gps->caps[1]); + stroke_hardnesses.span[stroke_i] = gps->hardeness; + stroke_point_aspect_ratios.span[stroke_i] = gps->aspect_ratio[0] / + max_ff(gps->aspect_ratio[1], 1e-8); + stroke_fill_translations.span[stroke_i] = float2(gps->uv_translation); + stroke_fill_rotations.span[stroke_i] = gps->uv_rotation; + stroke_fill_scales.span[stroke_i] = float2(gps->uv_scale); + stroke_fill_colors.span[stroke_i] = ColorGeometry4f(gps->vert_color_fill); + stroke_materials.span[stroke_i] = gps->mat_nr; + + /* Write point attributes. */ + IndexRange stroke_points_range = points_by_curve[stroke_i]; + if (stroke_points_range.size() == 0) { + continue; + } + + Span stroke_points{gps->points, gps->totpoints}; + MutableSpan stroke_positions = positions.slice(stroke_points_range); + MutableSpan stroke_radii = radii.span.slice(stroke_points_range); + MutableSpan stroke_opacities = opacities.span.slice(stroke_points_range); + MutableSpan stroke_deltatimes = delta_times.span.slice(stroke_points_range); + MutableSpan stroke_rotations = rotations.span.slice(stroke_points_range); + MutableSpan stroke_vertex_colors = vertex_colors.span.slice( + stroke_points_range); + MutableSpan stroke_selections = selection.span.slice(stroke_points_range); + + /* Do first point. */ + const bGPDspoint &first_pt = stroke_points.first(); + stroke_positions.first() = float3(first_pt.x, first_pt.y, first_pt.z); + /* 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; + stroke_vertex_colors.first() = ColorGeometry4f(first_pt.vert_color); + stroke_selections.first() = (first_pt.flag & GP_SPOINT_SELECT) != 0; + + /* Do the rest of the points. */ + for (const int i : stroke_points.index_range().drop_back(1)) { + const int point_i = i + 1; + 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); + /* 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; + stroke_vertex_colors[point_i] = ColorGeometry4f(pt.vert_color); + stroke_selections[point_i] = (pt.flag & GP_SPOINT_SELECT) != 0; + } + } + + radii.finish(); + opacities.finish(); + delta_times.finish(); + rotations.finish(); + vertex_colors.finish(); + selection.finish(); + + stroke_cyclic.finish(); + stroke_init_times.finish(); + stroke_start_caps.finish(); + stroke_end_caps.finish(); + stroke_hardnesses.finish(); + stroke_point_aspect_ratios.finish(); + stroke_fill_translations.finish(); + stroke_fill_rotations.finish(); + stroke_fill_scales.finish(); + stroke_fill_colors.finish(); + stroke_materials.finish(); +} + +void legacy_gpencil_to_grease_pencil(Main &bmain, GreasePencil &grease_pencil, bGPdata &gpd) +{ + using namespace blender::bke::greasepencil; + + int num_layers = 0; + int num_drawings = 0; + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd.layers) { + num_drawings += BLI_listbase_count(&gpl->frames); + num_layers++; + } + + grease_pencil.drawing_array_size = num_drawings; + grease_pencil.drawing_array = reinterpret_cast( + MEM_cnew_array(num_drawings, __func__)); + + int i = 0, layer_idx = 0; + LayerGroup &root_group = grease_pencil.root_group.wrap(); + LISTBASE_FOREACH_INDEX (bGPDlayer *, gpl, &gpd.layers, layer_idx) { + /* Create a new layer. */ + Layer &new_layer = grease_pencil.add_layer( + root_group, StringRefNull(gpl->info, BLI_strnlen(gpl->info, 128))); + + /* Flags. */ + SET_FLAG_FROM_TEST(new_layer.base.flag, (gpl->flag & GP_LAYER_HIDE), GP_LAYER_TREE_NODE_HIDE); + SET_FLAG_FROM_TEST( + new_layer.base.flag, (gpl->flag & GP_LAYER_LOCKED), GP_LAYER_TREE_NODE_LOCKED); + SET_FLAG_FROM_TEST( + new_layer.base.flag, (gpl->flag & GP_LAYER_SELECT), GP_LAYER_TREE_NODE_SELECT); + SET_FLAG_FROM_TEST( + new_layer.base.flag, (gpl->flag & GP_LAYER_FRAMELOCK), GP_LAYER_TREE_NODE_MUTE); + SET_FLAG_FROM_TEST( + new_layer.base.flag, (gpl->flag & GP_LAYER_USE_LIGHTS), GP_LAYER_TREE_NODE_USE_LIGHTS); + SET_FLAG_FROM_TEST(new_layer.base.flag, + (gpl->onion_flag & GP_LAYER_ONIONSKIN), + GP_LAYER_TREE_NODE_USE_ONION_SKINNING); + + new_layer.blend_mode = static_cast(gpl->blend_mode); + + /* Convert the layer masks. */ + LISTBASE_FOREACH (bGPDlayer_Mask *, mask, &gpl->mask_layers) { + LayerMask *new_mask = new LayerMask(mask->name); + new_mask->flag = mask->flag; + BLI_addtail(&new_layer.masks, new_mask); + } + new_layer.opacity = gpl->opacity; + + LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { + grease_pencil.drawing_array[i] = reinterpret_cast( + MEM_new(__func__)); + GreasePencilDrawing &drawing = *reinterpret_cast( + grease_pencil.drawing_array[i]); + + /* Convert the frame to a drawing. */ + legacy_gpencil_frame_to_grease_pencil_drawing(*gpf, drawing); + + GreasePencilFrame new_frame; + new_frame.drawing_index = i; + new_frame.type = gpf->key_type; + SET_FLAG_FROM_TEST(new_frame.flag, (gpf->flag & GP_FRAME_SELECT), GP_FRAME_SELECTED); + new_layer.insert_frame(gpf->framenum, std::move(new_frame)); + i++; + } + + if ((gpl->flag & GP_LAYER_ACTIVE) != 0) { + grease_pencil.active_layer = static_cast(&new_layer); + } + + /* TODO: Update drawing user counts. */ + } + + /* Convert the onion skinning settings. */ + grease_pencil.onion_skinning_settings.opacity = gpd.onion_factor; + grease_pencil.onion_skinning_settings.mode = gpd.onion_mode; + if (gpd.onion_keytype == -1) { + grease_pencil.onion_skinning_settings.filter = GREASE_PENCIL_ONION_SKINNING_FILTER_ALL; + } + else { + grease_pencil.onion_skinning_settings.filter = (1 << gpd.onion_keytype); + } + grease_pencil.onion_skinning_settings.num_frames_before = gpd.gstep; + grease_pencil.onion_skinning_settings.num_frames_after = gpd.gstep_next; + copy_v3_v3(grease_pencil.onion_skinning_settings.color_before, gpd.gcolor_prev); + copy_v3_v3(grease_pencil.onion_skinning_settings.color_after, gpd.gcolor_next); + + BKE_id_materials_copy(&bmain, &gpd.id, &grease_pencil.id); +} + +} // namespace blender::bke::greasepencil::convert \ No newline at end of file diff --git a/source/blender/blenkernel/intern/grease_pencil_test.cc b/source/blender/blenkernel/intern/grease_pencil_test.cc new file mode 100644 index 00000000000..674a8299f2f --- /dev/null +++ b/source/blender/blenkernel/intern/grease_pencil_test.cc @@ -0,0 +1,181 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2023 Blender Foundation. */ + +#include "testing/testing.h" + +#include "BKE_curves.hh" +#include "BKE_grease_pencil.hh" +#include "BKE_idtype.h" +#include "BKE_lib_id.h" +#include "BKE_main.h" + +using namespace blender::bke::greasepencil; + +namespace blender::bke::greasepencil::tests { + +/* --------------------------------------------------------------------------------------------- */ +/* Grease Pencil ID Tests. */ + +/* Note: Using a struct with constructor and destructor instead of a fixture here, to have all the + * tests in the same group (`greasepencil`). */ +struct GreasePencilIDTestContext { + Main *bmain = nullptr; + + GreasePencilIDTestContext() + { + BKE_idtype_init(); + bmain = BKE_main_new(); + } + ~GreasePencilIDTestContext() + { + BKE_main_free(bmain); + } +}; + +TEST(greasepencil, create_grease_pencil_id) +{ + GreasePencilIDTestContext ctx; + + GreasePencil &grease_pencil = *static_cast(BKE_id_new(ctx.bmain, ID_GP, "GP")); + EXPECT_EQ(grease_pencil.drawings().size(), 0); + EXPECT_EQ(grease_pencil.root_group.wrap().num_nodes_total(), 0); +} + +/* --------------------------------------------------------------------------------------------- */ +/* Drawing Array Tests. */ + +TEST(greasepencil, add_empty_drawings) +{ + GreasePencilIDTestContext ctx; + GreasePencil &grease_pencil = *static_cast(BKE_id_new(ctx.bmain, ID_GP, "GP")); + grease_pencil.add_empty_drawings(3); + EXPECT_EQ(grease_pencil.drawings().size(), 3); +} + +TEST(greasepencil, remove_drawing) +{ + GreasePencilIDTestContext ctx; + GreasePencil &grease_pencil = *static_cast(BKE_id_new(ctx.bmain, ID_GP, "GP")); + grease_pencil.add_empty_drawings(3); + + GreasePencilDrawing *drawing = reinterpret_cast( + grease_pencil.drawings_for_write()[1]); + drawing->geometry.wrap().resize(0, 10); + + Layer &layer1 = grease_pencil.root_group.wrap().add_layer("Layer1"); + Layer &layer2 = grease_pencil.root_group.wrap().add_layer("Layer2"); + + layer1.insert_frame(0, GreasePencilFrame{0}); + layer1.insert_frame(10, GreasePencilFrame{1}); + layer1.insert_frame(20, GreasePencilFrame{2}); + layer1.tag_frames_map_keys_changed(); + + layer2.insert_frame(0, GreasePencilFrame{1}); + layer2.tag_frames_map_keys_changed(); + + grease_pencil.remove_drawing(1); + EXPECT_EQ(grease_pencil.drawings().size(), 2); + + static int expected_frames_size[] = {2, 0}; + static int expected_frames_pairs_layer0[][2] = {{0, 0}, {20, 1}}; + + Span layers = grease_pencil.layers(); + EXPECT_EQ(layers[0]->frames().size(), expected_frames_size[0]); + EXPECT_EQ(layers[1]->frames().size(), expected_frames_size[1]); + EXPECT_EQ(layers[0]->frames().lookup(expected_frames_pairs_layer0[0][0]).drawing_index, + expected_frames_pairs_layer0[0][1]); + EXPECT_EQ(layers[0]->frames().lookup(expected_frames_pairs_layer0[1][0]).drawing_index, + expected_frames_pairs_layer0[1][1]); +} + +TEST(greasepencil, overwrite_frame) +{ + Layer layer1("Layer1"); + + layer1.insert_frame(0, GreasePencilFrame{0}); + layer1.tag_frames_map_keys_changed(); + + EXPECT_EQ(layer1.frames().lookup(0).drawing_index, 0); + + layer1.overwrite_frame(0, GreasePencilFrame{42}); + EXPECT_EQ(layer1.frames().lookup(0).drawing_index, 42); +} + +/* --------------------------------------------------------------------------------------------- */ +/* Layer Tree Tests. */ + +TEST(greasepencil, layer_tree_empty) +{ + LayerGroup root; +} + +TEST(greasepencil, layer_tree_build_simple) +{ + LayerGroup root; + + LayerGroup &group = root.add_group("Group1"); + group.add_layer("Layer1"); + group.add_layer("Layer2"); +} + +struct GreasePencilLayerTreeExample { + StringRefNull names[7] = {"Group1", "Layer1", "Layer2", "Group2", "Layer3", "Layer4", "Layer5"}; + const bool is_layer[7] = {false, true, true, false, true, true, true}; + LayerGroup root; + + GreasePencilLayerTreeExample() + { + LayerGroup &group = root.add_group(names[0]); + group.add_layer(names[1]); + group.add_layer(names[2]); + + LayerGroup &group2 = group.add_group(names[3]); + group2.add_layer(names[4]); + group2.add_layer(names[5]); + + root.add_layer(names[6]); + } +}; + +TEST(greasepencil, layer_tree_pre_order_iteration) +{ + GreasePencilLayerTreeExample ex; + + Span children = ex.root.nodes(); + for (const int i : children.index_range()) { + const TreeNode &child = *children[i]; + EXPECT_STREQ(child.name, ex.names[i].data()); + } +} + +TEST(greasepencil, layer_tree_pre_order_iteration2) +{ + GreasePencilLayerTreeExample ex; + + Span layers = ex.root.layers(); + char name[64]; + for (const int i : layers.index_range()) { + const Layer &layer = *layers[i]; + snprintf(name, 64, "%s%d", "Layer", i + 1); + EXPECT_STREQ(layer.name().data(), name); + } +} + +TEST(greasepencil, layer_tree_total_size) +{ + GreasePencilLayerTreeExample ex; + EXPECT_EQ(ex.root.num_nodes_total(), 7); +} + +TEST(greasepencil, layer_tree_node_types) +{ + GreasePencilLayerTreeExample ex; + Span children = ex.root.nodes(); + for (const int i : children.index_range()) { + const TreeNode &child = *children[i]; + EXPECT_EQ(child.is_layer(), ex.is_layer[i]); + EXPECT_EQ(child.is_group(), !ex.is_layer[i]); + } +} + +} // namespace blender::bke::greasepencil::tests \ No newline at end of file diff --git a/source/blender/blenkernel/intern/idtype.c b/source/blender/blenkernel/intern/idtype.c index 501588fc55e..e8d21e2c453 100644 --- a/source/blender/blenkernel/intern/idtype.c +++ b/source/blender/blenkernel/intern/idtype.c @@ -95,6 +95,7 @@ static void id_type_init(void) INIT_TYPE(ID_PT); INIT_TYPE(ID_VO); INIT_TYPE(ID_SIM); + INIT_TYPE(ID_GP); /* Special naughty boy... */ BLI_assert(IDType_ID_LINK_PLACEHOLDER.main_listbase_index == INDEX_ID_NULL); @@ -222,6 +223,7 @@ uint64_t BKE_idtype_idcode_to_idfilter(const short idcode) CASE_IDFILTER(CF); CASE_IDFILTER(CU_LEGACY); CASE_IDFILTER(GD_LEGACY); + CASE_IDFILTER(GP); CASE_IDFILTER(GR); CASE_IDFILTER(CV); CASE_IDFILTER(IM); @@ -280,6 +282,7 @@ short BKE_idtype_idcode_from_idfilter(const uint64_t idfilter) CASE_IDFILTER(CF); CASE_IDFILTER(CU_LEGACY); CASE_IDFILTER(GD_LEGACY); + CASE_IDFILTER(GP); CASE_IDFILTER(GR); CASE_IDFILTER(CV); CASE_IDFILTER(IM); @@ -336,6 +339,7 @@ int BKE_idtype_idcode_to_index(const short idcode) CASE_IDINDEX(CF); CASE_IDINDEX(CU_LEGACY); CASE_IDINDEX(GD_LEGACY); + CASE_IDINDEX(GP); CASE_IDINDEX(GR); CASE_IDINDEX(CV); CASE_IDINDEX(IM); @@ -395,6 +399,7 @@ short BKE_idtype_idcode_from_index(const int index) CASE_IDCODE(CF); CASE_IDCODE(CU_LEGACY); CASE_IDCODE(GD_LEGACY); + CASE_IDCODE(GP); CASE_IDCODE(GR); CASE_IDCODE(CV); CASE_IDCODE(IM); diff --git a/source/blender/blenkernel/intern/lib_query.c b/source/blender/blenkernel/intern/lib_query.c index c5825f0e8f8..3ebcf242aba 100644 --- a/source/blender/blenkernel/intern/lib_query.c +++ b/source/blender/blenkernel/intern/lib_query.c @@ -464,6 +464,8 @@ uint64_t BKE_library_id_can_use_filter_id(const ID *owner_id, const bool include return FILTER_ID_IM; case ID_GD_LEGACY: return FILTER_ID_MA; + case ID_GP: + return FILTER_ID_GP | FILTER_ID_MA; case ID_WS: return FILTER_ID_SCE; case ID_CV: diff --git a/source/blender/blenkernel/intern/main.c b/source/blender/blenkernel/intern/main.c index e5b3944613b..d55229cc784 100644 --- a/source/blender/blenkernel/intern/main.c +++ b/source/blender/blenkernel/intern/main.c @@ -628,6 +628,8 @@ ListBase *which_libbase(Main *bmain, short type) return &(bmain->wm); case ID_GD_LEGACY: return &(bmain->gpencils); + case ID_GP: + return &(bmain->grease_pencils); case ID_MC: return &(bmain->movieclips); case ID_MSK: @@ -671,6 +673,7 @@ int set_listbasepointers(Main *bmain, ListBase *lb[/*INDEX_ID_MAX*/]) /* Referenced by nodes, objects, view, scene etc, before to free after. */ lb[INDEX_ID_GD_LEGACY] = &(bmain->gpencils); + lb[INDEX_ID_GP] = &(bmain->grease_pencils); lb[INDEX_ID_NT] = &(bmain->nodetrees); lb[INDEX_ID_IM] = &(bmain->images); diff --git a/source/blender/blenkernel/intern/material.cc b/source/blender/blenkernel/intern/material.cc index 16602787e8d..2537e11d1dd 100644 --- a/source/blender/blenkernel/intern/material.cc +++ b/source/blender/blenkernel/intern/material.cc @@ -24,6 +24,7 @@ #include "DNA_customdata_types.h" #include "DNA_defaults.h" #include "DNA_gpencil_legacy_types.h" +#include "DNA_grease_pencil_types.h" #include "DNA_material_types.h" #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" @@ -375,6 +376,10 @@ Material ***BKE_object_material_array_p(Object *ob) Volume *volume = static_cast(ob->data); return &(volume->mat); } + if (ob->type == OB_GREASE_PENCIL) { + GreasePencil *grease_pencil = static_cast(ob->data); + return &(grease_pencil->material_array); + } return nullptr; } @@ -408,6 +413,10 @@ short *BKE_object_material_len_p(Object *ob) Volume *volume = static_cast(ob->data); return &(volume->totcol); } + if (ob->type == OB_GREASE_PENCIL) { + GreasePencil *grease_pencil = static_cast(ob->data); + return &(grease_pencil->material_array_size); + } return nullptr; } @@ -431,6 +440,8 @@ Material ***BKE_id_material_array_p(ID *id) return &(((PointCloud *)id)->mat); case ID_VO: return &(((Volume *)id)->mat); + case ID_GP: + return &(((GreasePencil *)id)->material_array); default: break; } @@ -457,6 +468,8 @@ short *BKE_id_material_len_p(ID *id) return &(((PointCloud *)id)->totcol); case ID_VO: return &(((Volume *)id)->totcol); + case ID_GP: + return &(((GreasePencil *)id)->material_array_size); default: break; } diff --git a/source/blender/blenkernel/intern/object.cc b/source/blender/blenkernel/intern/object.cc index 7245d5b26a0..2ecbf5b91a3 100644 --- a/source/blender/blenkernel/intern/object.cc +++ b/source/blender/blenkernel/intern/object.cc @@ -27,6 +27,7 @@ #include "DNA_fluid_types.h" #include "DNA_gpencil_legacy_types.h" #include "DNA_gpencil_modifier_types.h" +#include "DNA_grease_pencil_types.h" #include "DNA_key_types.h" #include "DNA_lattice_types.h" #include "DNA_light_types.h" @@ -89,6 +90,7 @@ #include "BKE_gpencil_geom_legacy.h" #include "BKE_gpencil_legacy.h" #include "BKE_gpencil_modifier_legacy.h" +#include "BKE_grease_pencil.hh" #include "BKE_icons.h" #include "BKE_idprop.h" #include "BKE_idtype.h" @@ -1467,7 +1469,8 @@ bool BKE_object_supports_modifiers(const Object *ob) OB_FONT, OB_LATTICE, OB_POINTCLOUD, - OB_VOLUME); + OB_VOLUME, + OB_GREASE_PENCIL); } bool BKE_object_support_modifier_type_check(const Object *ob, int modifier_type) @@ -1970,6 +1973,8 @@ bool BKE_object_is_in_editmode(const Object *ob) case OB_CURVES: /* Curves object has no edit mode data. */ return ob->mode == OB_MODE_EDIT; + case OB_GREASE_PENCIL: + return ob->mode == OB_MODE_EDIT; default: return false; } @@ -1997,6 +2002,7 @@ bool BKE_object_data_is_in_editmode(const Object *ob, const ID *id) case ID_AR: return ((const bArmature *)id)->edbo != nullptr; case ID_CV: + case ID_GP: if (ob) { return BKE_object_is_in_editmode(ob); } @@ -2052,6 +2058,10 @@ char *BKE_object_data_editmode_flush_ptr_get(struct ID *id) /* Curves have no edit mode data. */ return nullptr; } + case ID_GP: { + /* Grease Pencil has no edit mode data. */ + return nullptr; + } default: BLI_assert_unreachable(); return nullptr; @@ -2195,6 +2205,8 @@ static const char *get_obdata_defname(int type) return DATA_("GPencil"); case OB_LIGHTPROBE: return DATA_("LightProbe"); + case OB_GREASE_PENCIL: + return DATA_("GreasePencil"); default: CLOG_ERROR(&LOG, "Internal error, bad type: %d", type); return CTX_DATA_(BLT_I18NCONTEXT_ID_ID, "Empty"); @@ -2264,6 +2276,8 @@ void *BKE_object_obdata_add_from_type(Main *bmain, int type, const char *name) return BKE_pointcloud_add_default(bmain, name); case OB_VOLUME: return BKE_volume_add(bmain, name); + case OB_GREASE_PENCIL: + return BKE_grease_pencil_add(bmain, name); case OB_EMPTY: return nullptr; default: @@ -2302,6 +2316,8 @@ int BKE_object_obdata_to_type(const ID *id) return OB_POINTCLOUD; case ID_VO: return OB_VOLUME; + case ID_GP: + return OB_GREASE_PENCIL; default: return -1; } @@ -3813,6 +3829,8 @@ const BoundBox *BKE_object_boundbox_get(Object *ob) case OB_VOLUME: bb = BKE_volume_boundbox_get(ob); break; + case OB_GREASE_PENCIL: + bb = BKE_grease_pencil_boundbox_get(ob); default: break; } @@ -3997,7 +4015,6 @@ void BKE_object_minmax(Object *ob, float r_min[3], float r_max[3], const bool us changed = true; break; } - case OB_POINTCLOUD: { const BoundBox bb = *BKE_pointcloud_boundbox_get(ob); BKE_boundbox_minmax(&bb, ob->object_to_world, r_min, r_max); @@ -4010,6 +4027,12 @@ void BKE_object_minmax(Object *ob, float r_min[3], float r_max[3], const bool us changed = true; break; } + case OB_GREASE_PENCIL: { + const BoundBox bb = *BKE_grease_pencil_boundbox_get(ob); + BKE_boundbox_minmax(&bb, ob->object_to_world, r_min, r_max); + changed = true; + break; + } } if (changed == false) { @@ -5185,7 +5208,8 @@ bool BKE_object_supports_material_slots(struct Object *ob) OB_CURVES, OB_POINTCLOUD, OB_VOLUME, - OB_GPENCIL_LEGACY); + OB_GPENCIL_LEGACY, + OB_GREASE_PENCIL); } /** \} */ diff --git a/source/blender/blenkernel/intern/object_update.cc b/source/blender/blenkernel/intern/object_update.cc index e4a14b9afe4..738c4c80b34 100644 --- a/source/blender/blenkernel/intern/object_update.cc +++ b/source/blender/blenkernel/intern/object_update.cc @@ -31,6 +31,8 @@ #include "BKE_effect.h" #include "BKE_gpencil_legacy.h" #include "BKE_gpencil_modifier_legacy.h" +#include "BKE_grease_pencil.h" +#include "BKE_grease_pencil.hh" #include "BKE_image.h" #include "BKE_key.h" #include "BKE_lattice.h" @@ -199,6 +201,9 @@ void BKE_object_handle_data_update(Depsgraph *depsgraph, Scene *scene, Object *o case OB_VOLUME: BKE_volume_data_update(depsgraph, scene, ob); break; + case OB_GREASE_PENCIL: + BKE_grease_pencil_data_update(depsgraph, scene, ob); + break; } /* particles */ @@ -319,6 +324,10 @@ void BKE_object_batch_cache_dirty_tag(Object *ob) case OB_VOLUME: BKE_volume_batch_cache_dirty_tag((struct Volume *)ob->data, BKE_VOLUME_BATCH_DIRTY_ALL); break; + case OB_GREASE_PENCIL: + BKE_grease_pencil_batch_cache_dirty_tag((struct GreasePencil *)ob->data, + BKE_GREASEPENCIL_BATCH_DIRTY_ALL); + break; default: break; } diff --git a/source/blender/blenloader/intern/readfile.cc b/source/blender/blenloader/intern/readfile.cc index cab023265a6..08fc9ebdc6b 100644 --- a/source/blender/blenloader/intern/readfile.cc +++ b/source/blender/blenloader/intern/readfile.cc @@ -2902,6 +2902,8 @@ static const char *dataname(short id_code) return "Data from VO"; case ID_SIM: return "Data from SIM"; + case ID_GP: + return "Data from GP"; } return "Data from Lib Block"; } diff --git a/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc b/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc index 706823b5db7..e20cb4f2b98 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc +++ b/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc @@ -63,6 +63,7 @@ #include "BKE_fcurve_driver.h" #include "BKE_gpencil_legacy.h" #include "BKE_gpencil_modifier_legacy.h" +#include "BKE_grease_pencil.hh" #include "BKE_idprop.h" #include "BKE_idtype.h" #include "BKE_image.h" @@ -618,6 +619,7 @@ void DepsgraphNodeBuilder::build_id(ID *id) case ID_CV: case ID_PT: case ID_VO: + case ID_GP: build_object_data_geometry_datablock(id); break; case ID_SPK: @@ -968,6 +970,7 @@ void DepsgraphNodeBuilder::build_object_data(Object *object) case OB_CURVES: case OB_POINTCLOUD: case OB_VOLUME: + case OB_GREASE_PENCIL: build_object_data_geometry(object); break; case OB_ARMATURE: @@ -1740,6 +1743,11 @@ void DepsgraphNodeBuilder::build_object_data_geometry_datablock(ID *obdata) op_node->set_as_entry(); break; } + case ID_GP: { + op_node = add_operation_node(obdata, NodeType::GEOMETRY, OperationCode::GEOMETRY_EVAL); + op_node->set_as_entry(); + break; + } default: BLI_assert_msg(0, "Should not happen"); break; diff --git a/source/blender/depsgraph/intern/builder/deg_builder_relations.cc b/source/blender/depsgraph/intern/builder/deg_builder_relations.cc index 3f596905517..56d7af26053 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_relations.cc +++ b/source/blender/depsgraph/intern/builder/deg_builder_relations.cc @@ -564,6 +564,7 @@ void DepsgraphRelationBuilder::build_id(ID *id) case ID_PT: case ID_VO: case ID_GD_LEGACY: + case ID_GP: build_object_data_geometry_datablock(id); break; case ID_SPK: @@ -972,7 +973,8 @@ void DepsgraphRelationBuilder::build_object_data(Object *object) case OB_GPENCIL_LEGACY: case OB_CURVES: case OB_POINTCLOUD: - case OB_VOLUME: { + case OB_VOLUME: + case OB_GREASE_PENCIL: { build_object_data_geometry(object); /* TODO(sergey): Only for until we support granular * update of curves. */ @@ -2653,6 +2655,8 @@ void DepsgraphRelationBuilder::build_object_data_geometry_datablock(ID *obdata) } break; } + case ID_GP: + break; default: BLI_assert_msg(0, "Should not happen"); break; diff --git a/source/blender/depsgraph/intern/depsgraph_tag.cc b/source/blender/depsgraph/intern/depsgraph_tag.cc index c3770cb5e8f..b5d48d4fca6 100644 --- a/source/blender/depsgraph/intern/depsgraph_tag.cc +++ b/source/blender/depsgraph/intern/depsgraph_tag.cc @@ -593,6 +593,7 @@ NodeType geometry_tag_to_component(const ID *id) case OB_CURVES: case OB_POINTCLOUD: case OB_VOLUME: + case OB_GREASE_PENCIL: return NodeType::GEOMETRY; case OB_ARMATURE: return NodeType::EVAL_POSE; @@ -619,6 +620,8 @@ NodeType geometry_tag_to_component(const ID *id) return NodeType::PARAMETERS; case ID_MSK: return NodeType::PARAMETERS; + case ID_GP: + return NodeType::GEOMETRY; default: break; } diff --git a/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_object.cc b/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_object.cc index 9a674f18c06..74e0c2366ac 100644 --- a/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_object.cc +++ b/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_object.cc @@ -114,7 +114,7 @@ void ObjectRuntimeBackup::restore_to_object(Object *object) } } } - else if (ELEM(object->type, OB_CURVES, OB_POINTCLOUD, OB_VOLUME)) { + else if (ELEM(object->type, OB_CURVES, OB_POINTCLOUD, OB_VOLUME, OB_GREASE_PENCIL)) { if (object->id.recalc & ID_RECALC_GEOMETRY) { /* Free evaluated caches. */ object->data = data_orig; diff --git a/source/blender/draw/CMakeLists.txt b/source/blender/draw/CMakeLists.txt index 6edd63698b2..c1cd30b209d 100644 --- a/source/blender/draw/CMakeLists.txt +++ b/source/blender/draw/CMakeLists.txt @@ -72,7 +72,8 @@ set(SRC intern/draw_attributes.cc intern/draw_cache_impl_curve.cc intern/draw_cache_impl_curves.cc - intern/draw_cache_impl_gpencil.cc + intern/draw_cache_impl_gpencil_legacy.cc + intern/draw_cache_impl_grease_pencil.cc intern/draw_cache_impl_lattice.c intern/draw_cache_impl_mesh.cc intern/draw_cache_impl_particles.c @@ -186,9 +187,11 @@ set(SRC engines/gpencil/gpencil_cache_utils.c engines/gpencil/gpencil_draw_data.c engines/gpencil/gpencil_engine.c + engines/gpencil/gpencil_engine.cc engines/gpencil/gpencil_engine.h engines/gpencil/gpencil_render.c engines/gpencil/gpencil_shader.c + engines/gpencil/gpencil_shader.cc engines/gpencil/gpencil_shader_fx.c engines/select/select_draw_utils.c engines/select/select_engine.c diff --git a/source/blender/draw/engines/gpencil/gpencil_antialiasing.hh b/source/blender/draw/engines/gpencil/gpencil_antialiasing.hh new file mode 100644 index 00000000000..5262d2e3461 --- /dev/null +++ b/source/blender/draw/engines/gpencil/gpencil_antialiasing.hh @@ -0,0 +1,157 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2023 Blender Foundation. */ + +/** \file + * \ingroup draw + */ + +#pragma once + +#include "BKE_gpencil_legacy.h" +#include "BKE_image.h" +#include "DRW_gpu_wrapper.hh" +#include "DRW_render.h" + +#include "draw_manager.hh" +#include "draw_pass.hh" + +#include "gpencil_shader.hh" + +#include "BLI_smaa_textures.h" + +namespace blender::draw::greasepencil { + +using namespace draw; + +/** Final anti-aliasing post processing and compositing on top of render. */ +class AntiAliasing { + private: + ShaderModule &shaders_; + + Texture smaa_search_tx_ = {"smaa_search", + GPU_R8, + GPU_TEXTURE_USAGE_SHADER_READ, + int2(SEARCHTEX_WIDTH, SEARCHTEX_HEIGHT)}; + Texture smaa_area_tx_ = { + "smaa_area", GPU_RG8, GPU_TEXTURE_USAGE_SHADER_READ, int2(AREATEX_WIDTH, AREATEX_HEIGHT)}; + + TextureFromPool edge_detect_tx_ = {"edge_detect_tx"}; + Framebuffer edge_detect_fb_ = {"edge_detect_fb"}; + PassSimple edge_detect_ps_ = {"edge_detect_ps"}; + + TextureFromPool blend_weight_tx_ = {"blend_weight_tx"}; + Framebuffer blend_weight_fb_ = {"blend_weight_fb"}; + PassSimple blend_weight_ps_ = {"blend_weight_ps"}; + + Framebuffer output_fb_ = {"output_fb"}; + PassSimple resolve_ps_ = {"resolve_ps"}; + + bool draw_wireframe_ = false; + float luma_weight_ = 1.0f; + bool anti_aliasing_enabled_ = true; + + public: + AntiAliasing(ShaderModule &shaders) : shaders_(shaders) + { + GPU_texture_update(smaa_search_tx_, GPU_DATA_UBYTE, searchTexBytes); + GPU_texture_update(smaa_area_tx_, GPU_DATA_UBYTE, areaTexBytes); + + GPU_texture_filter_mode(smaa_search_tx_, true); + GPU_texture_filter_mode(smaa_area_tx_, true); + } + + void init(const View3D *v3d, const Scene *scene) + { + if (v3d) { + draw_wireframe_ = (v3d->shading.type == OB_WIRE); + } + + luma_weight_ = scene->grease_pencil_settings.smaa_threshold; + anti_aliasing_enabled_ = true; // GPENCIL_SIMPLIFY_AA(scene); + } + + void begin_sync(TextureFromPool &color_tx, TextureFromPool &reveal_tx) + { + /* TODO(fclem): No global access. */ + const float *size = DRW_viewport_size_get(); + const float *sizeinv = DRW_viewport_invert_size_get(); + const float4 metrics = {sizeinv[0], sizeinv[1], size[0], size[1]}; + + anti_aliasing_pass(color_tx, reveal_tx, metrics); + + /* Resolve pass. */ + PassSimple &pass = resolve_ps_; + pass.init(); + pass.framebuffer_set(&output_fb_); + pass.state_set(DRW_STATE_WRITE_COLOR | DRW_STATE_BLEND_CUSTOM); + pass.shader_set(shaders_.static_shader_get(ANTIALIASING_RESOLVE)); + /** \note use color_tx as dummy if AA is diabled. */ + pass.bind_texture("blendTex", anti_aliasing_enabled_ ? &blend_weight_tx_ : &color_tx); + pass.bind_texture("colorTex", &color_tx); + pass.bind_texture("revealTex", &reveal_tx); + pass.push_constant("doAntiAliasing", anti_aliasing_enabled_); + pass.push_constant("onlyAlpha", draw_wireframe_); + pass.push_constant("viewportMetrics", metrics); + pass.draw_procedural(GPU_PRIM_TRIS, 1, 3); + } + + void draw(Manager &manager, GPUTexture *dst_color_tx) + { + int2 render_size = {GPU_texture_width(dst_color_tx), GPU_texture_height(dst_color_tx)}; + + DRW_stats_group_start("Anti-Aliasing"); + + if (anti_aliasing_enabled_) { + edge_detect_tx_.acquire(render_size, GPU_RG8); + edge_detect_fb_.ensure(GPU_ATTACHMENT_NONE, GPU_ATTACHMENT_TEXTURE(edge_detect_tx_)); + manager.submit(edge_detect_ps_); + + blend_weight_tx_.acquire(render_size, GPU_RGBA8); + blend_weight_fb_.ensure(GPU_ATTACHMENT_NONE, GPU_ATTACHMENT_TEXTURE(blend_weight_tx_)); + manager.submit(blend_weight_ps_); + edge_detect_tx_.release(); + } + + output_fb_.ensure(GPU_ATTACHMENT_NONE, GPU_ATTACHMENT_TEXTURE(dst_color_tx)); + manager.submit(resolve_ps_); + blend_weight_tx_.release(); + + DRW_stats_group_end(); + } + + private: + void anti_aliasing_pass(TextureFromPool &color_tx, + TextureFromPool &reveal_tx, + const float4 metrics) + { + if (!anti_aliasing_enabled_) { + return; + } + + /* Stage 1: Edge detection. */ + edge_detect_ps_.init(); + edge_detect_ps_.framebuffer_set(&edge_detect_fb_); + edge_detect_ps_.state_set(DRW_STATE_WRITE_COLOR); + edge_detect_ps_.shader_set(shaders_.static_shader_get(ANTIALIASING_EDGE_DETECT)); + edge_detect_ps_.bind_texture("colorTex", &color_tx); + edge_detect_ps_.bind_texture("revealTex", &reveal_tx); + edge_detect_ps_.push_constant("viewportMetrics", metrics); + edge_detect_ps_.push_constant("lumaWeight", luma_weight_); + edge_detect_ps_.clear_color(float4(0.0f)); + edge_detect_ps_.draw_procedural(GPU_PRIM_TRIS, 1, 3); + + /* Stage 2: Blend Weight/Coord. */ + blend_weight_ps_.init(); + blend_weight_ps_.framebuffer_set(&blend_weight_fb_); + blend_weight_ps_.state_set(DRW_STATE_WRITE_COLOR); + blend_weight_ps_.shader_set(shaders_.static_shader_get(ANTIALIASING_BLEND_WEIGHT)); + blend_weight_ps_.bind_texture("edgesTex", &edge_detect_tx_); + blend_weight_ps_.bind_texture("areaTex", smaa_area_tx_); + blend_weight_ps_.bind_texture("searchTex", smaa_search_tx_); + blend_weight_ps_.push_constant("viewportMetrics", metrics); + blend_weight_ps_.clear_color(float4(0.0f)); + blend_weight_ps_.draw_procedural(GPU_PRIM_TRIS, 1, 3); + } +}; + +} // namespace blender::draw::greasepencil \ No newline at end of file diff --git a/source/blender/draw/engines/gpencil/gpencil_defines.h b/source/blender/draw/engines/gpencil/gpencil_defines.h index 6eb7bd23e4e..2d709750a76 100644 --- a/source/blender/draw/engines/gpencil/gpencil_defines.h +++ b/source/blender/draw/engines/gpencil/gpencil_defines.h @@ -6,3 +6,16 @@ /* High bits are used to pass material ID to fragment shader. */ #define GPENCIl_MATID_SHIFT 16u + +/* Textures */ +#define GPENCIL_SCENE_DEPTH_TEX_SLOT 2 +#define GPENCIL_MASK_TEX_SLOT 3 +#define GPENCIL_FILL_TEX_SLOT 4 +#define GPENCIL_STROKE_TEX_SLOT 5 +/* SSBOs */ +#define GPENCIL_OBJECT_SLOT 0 +#define GPENCIL_LAYER_SLOT 1 +#define GPENCIL_MATERIAL_SLOT 2 +#define GPENCIL_LIGHT_SLOT 3 +/* UBOs */ +#define GPENCIL_SCENE_SLOT 2 diff --git a/source/blender/draw/engines/gpencil/gpencil_draw_data.c b/source/blender/draw/engines/gpencil/gpencil_draw_data.c index 6c99f27d0b8..114172f9de2 100644 --- a/source/blender/draw/engines/gpencil/gpencil_draw_data.c +++ b/source/blender/draw/engines/gpencil/gpencil_draw_data.c @@ -233,6 +233,13 @@ GPENCIL_MaterialPool *gpencil_material_pool_create(GPENCIL_PrivateData *pd, Obje mat_data->flag |= GP_FILL_HOLDOUT; } + if (gp_style->flag & GP_MATERIAL_STROKE_SHOW) { + mat_data->flag |= GP_SHOW_STROKE; + } + if (gp_style->flag & GP_MATERIAL_FILL_SHOW) { + mat_data->flag |= GP_SHOW_FILL; + } + gp_style = gpencil_viewport_material_overrides(pd, ob, color_type, gp_style, lighting_mode); /* Dots or Squares rotation. */ diff --git a/source/blender/draw/engines/gpencil/gpencil_engine.c b/source/blender/draw/engines/gpencil/gpencil_engine.c index 03c4c05b5b5..41c7cd2811b 100644 --- a/source/blender/draw/engines/gpencil/gpencil_engine.c +++ b/source/blender/draw/engines/gpencil/gpencil_engine.c @@ -397,7 +397,7 @@ static void gpencil_sbuffer_cache_populate(gpIterPopulateData *iter) * Remember, sbuffer stroke indices start from 0. So we add last index to avoid * masking issues. */ iter->grp = DRW_shgroup_create_sub(iter->grp); - DRW_shgroup_uniform_block(iter->grp, "materials", iter->ubo_mat); + DRW_shgroup_uniform_block(iter->grp, "gp_materials", iter->ubo_mat); DRW_shgroup_uniform_float_copy(iter->grp, "gpStrokeIndexOffset", iter->stroke_index_last); const DRWContextState *ctx = DRW_context_state_get(); @@ -445,8 +445,8 @@ static void gpencil_layer_cache_populate(bGPDlayer *gpl, /* Iterator dependent uniforms. */ DRWShadingGroup *grp = iter->grp = tgp_layer->base_shgrp; - DRW_shgroup_uniform_block(grp, "lights", iter->ubo_lights); - DRW_shgroup_uniform_block(grp, "materials", iter->ubo_mat); + DRW_shgroup_uniform_block(grp, "gp_lights", iter->ubo_lights); + DRW_shgroup_uniform_block(grp, "gp_materials", iter->ubo_mat); DRW_shgroup_uniform_texture(grp, "gpFillTexture", iter->tex_fill); DRW_shgroup_uniform_texture(grp, "gpStrokeTexture", iter->tex_stroke); DRW_shgroup_uniform_int_copy(grp, "gpMaterialOffset", iter->mat_ofs); @@ -493,7 +493,7 @@ static void gpencil_stroke_cache_populate(bGPDlayer *gpl, iter->grp = DRW_shgroup_create_sub(iter->grp); if (iter->ubo_mat != ubo_mat) { - DRW_shgroup_uniform_block(iter->grp, "materials", ubo_mat); + DRW_shgroup_uniform_block(iter->grp, "gp_materials", ubo_mat); iter->ubo_mat = ubo_mat; } if (tex_fill) { diff --git a/source/blender/draw/engines/gpencil/gpencil_engine.cc b/source/blender/draw/engines/gpencil/gpencil_engine.cc new file mode 100644 index 00000000000..c6f52b83c56 --- /dev/null +++ b/source/blender/draw/engines/gpencil/gpencil_engine.cc @@ -0,0 +1,324 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2023 Blender Foundation. */ + +/** \file + * \ingroup draw + */ + +#include "BKE_gpencil_modifier_legacy.h" +#include "BLI_listbase_wrapper.hh" +#include "DEG_depsgraph_query.h" +#include "DNA_shader_fx_types.h" +#include "DRW_engine.h" +#include "DRW_render.h" +#include "ED_screen.h" +#include "ED_view3d.h" +#include "GPU_capabilities.h" +#include "IMB_imbuf_types.h" + +#include "draw_manager.hh" +#include "draw_pass.hh" + +#define GP_LIGHT +#include "gpencil_antialiasing.hh" +#include "gpencil_defines.h" +#include "gpencil_engine.h" +#include "gpencil_layer.hh" +#include "gpencil_light.hh" +#include "gpencil_material.hh" +#include "gpencil_object.hh" +#include "gpencil_shader.hh" +#include "gpencil_shader_shared.h" +#include "gpencil_vfx.hh" + +namespace blender::draw::greasepencil { + +using namespace draw; + +class Instance { + private: + ShaderModule &shaders; + LayerModule layers; + MaterialModule materials; + ObjectModule objects; + LightModule lights; + VfxModule vfx; + AntiAliasing anti_aliasing; + + /** Contains all gpencil objects in the scene as well as their effect sub-passes. */ + PassSortable main_ps_ = {"gp_main_ps"}; + + /** Contains all composited GPencil object. */ + TextureFromPool depth_tx_ = {"gp_depth_tx"}; + TextureFromPool color_tx_ = {"gp_color_tx"}; + TextureFromPool reveal_tx_ = {"gp_reveal_tx"}; + Framebuffer main_fb_ = {"gp_main_fb"}; + + /** Texture format for all intermediate buffers. */ + eGPUTextureFormat texture_format_ = GPU_RGBA16F; + + UniformBuffer scene_buf_; + + /** Dummy textures. */ + static constexpr float dummy_px_[4] = {1.0f, 0.0f, 1.0f, 1.0f}; + Texture dummy_depth_tx_ = {"dummy_depth", + GPU_DEPTH_COMPONENT32F, + GPU_TEXTURE_USAGE_SHADER_READ, + int2(1), + (float *)dummy_px_}; + Texture dummy_color_tx_ = { + "dummy_color", GPU_RGBA16F, GPU_TEXTURE_USAGE_SHADER_READ, int2(1), (float *)dummy_px_}; + + /** Scene depth used for manual depth testing. Default to dummy depth to skip depth test. */ + GPUTexture *scene_depth_tx_ = dummy_depth_tx_; + + /** Context. */ + Depsgraph *depsgraph_ = nullptr; + Object *camera_ = nullptr; + + /** \note Needs not to be temporary variable since it is dereferenced later. */ + std::array clear_colors_ = {float4(0.0f, 0.0f, 0.0f, 0.0f), + float4(1.0f, 1.0f, 1.0f, 1.0f)}; + + public: + Instance() + : shaders(*ShaderModule::module_get()), + objects(layers, materials, shaders), + vfx(shaders), + anti_aliasing(shaders){}; + + void init(Depsgraph *depsgraph, const View3D *v3d, const RegionView3D *rv3d) + { + depsgraph_ = depsgraph; + const Scene *scene = DEG_get_evaluated_scene(depsgraph_); + + const bool is_viewport = (v3d != nullptr); + + if (is_viewport) { + /* Use lower precision for viewport. */ + texture_format_ = GPU_R11F_G11F_B10F; + camera_ = (rv3d->persp == RV3D_CAMOB) ? v3d->camera : nullptr; + } + + objects.init(v3d, scene); + lights.init(v3d); + /* TODO(fclem): Vfx. */ + // vfx.init(use_vfx_, camera_, rv3d); + anti_aliasing.init(v3d, scene); + } + + void begin_sync(Manager & /* manager */) + { + /* TODO(fclem): Remove global draw manager access. */ + View main_view("GPencil_MainView", DRW_view_default_get()); + + objects.begin_sync(depsgraph_, main_view); + layers.begin_sync(); + materials.begin_sync(); + lights.begin_sync(depsgraph_); + + main_ps_.init(); + PassMain::Sub &sub = main_ps_.sub("InitSubpass", -FLT_MAX); + sub.framebuffer_set(&main_fb_); + sub.clear_multi(clear_colors_); + /* TODO(fclem): Textures. */ + sub.bind_texture(GPENCIL_SCENE_DEPTH_TEX_SLOT, &dummy_depth_tx_); + sub.bind_texture(GPENCIL_MASK_TEX_SLOT, &dummy_color_tx_); + sub.bind_texture(GPENCIL_FILL_TEX_SLOT, &dummy_color_tx_); + sub.bind_texture(GPENCIL_STROKE_TEX_SLOT, &dummy_color_tx_); + sub.bind_ubo(GPENCIL_SCENE_SLOT, &scene_buf_); + objects.bind_resources(sub); + layers.bind_resources(sub); + materials.bind_resources(sub); + lights.bind_resources(sub); + + anti_aliasing.begin_sync(color_tx_, reveal_tx_); + } + + void object_sync(Manager &manager, ObjectRef &object_ref) + { + switch (object_ref.object->type) { + case OB_GREASE_PENCIL: + objects.sync_grease_pencil(manager, object_ref, main_fb_, main_ps_); + break; + case OB_LAMP: + lights.sync(object_ref); + break; + default: + break; + } + } + + void end_sync(Manager & /* manager */) + { + objects.end_sync(); + layers.end_sync(); + materials.end_sync(); + lights.end_sync(); + } + + void draw_viewport(Manager &manager, + View &view, + GPUTexture *dst_depth_tx, + GPUTexture *dst_color_tx) + { + if (!objects.scene_has_visible_gpencil_object()) { + return; + } + + int2 render_size = {GPU_texture_width(dst_depth_tx), GPU_texture_height(dst_depth_tx)}; + + depth_tx_.acquire(render_size, GPU_DEPTH24_STENCIL8); + color_tx_.acquire(render_size, texture_format_); + reveal_tx_.acquire(render_size, texture_format_); + main_fb_.ensure(GPU_ATTACHMENT_TEXTURE(depth_tx_), + GPU_ATTACHMENT_TEXTURE(color_tx_), + GPU_ATTACHMENT_TEXTURE(reveal_tx_)); + + scene_buf_.render_size = float2(render_size); + scene_buf_.push_update(); + + objects.acquire_temporary_buffers(render_size, texture_format_); + + manager.submit(main_ps_, view); + + objects.release_temporary_buffers(); + + anti_aliasing.draw(manager, dst_color_tx); + + depth_tx_.release(); + color_tx_.release(); + reveal_tx_.release(); + } +}; + +} // namespace blender::draw::greasepencil + +/* -------------------------------------------------------------------- */ +/** \name Interface with legacy C DRW manager + * \{ */ + +using namespace blender; + +struct GPENCIL_NEXT_Data { + DrawEngineType *engine_type; + DRWViewportEmptyList *fbl; + DRWViewportEmptyList *txl; + DRWViewportEmptyList *psl; + DRWViewportEmptyList *stl; + draw::greasepencil::Instance *instance; + + char info[GPU_INFO_SIZE]; +}; + +static void gpencil_engine_init(void *vedata) +{ + /* TODO(fclem): Remove once it is minimum required. */ + if (!GPU_shader_storage_buffer_objects_support()) { + return; + } + + GPENCIL_NEXT_Data *ved = reinterpret_cast(vedata); + if (ved->instance == nullptr) { + ved->instance = new draw::greasepencil::Instance(); + } + + const DRWContextState *ctx_state = DRW_context_state_get(); + + ved->instance->init(ctx_state->depsgraph, ctx_state->v3d, ctx_state->rv3d); +} + +static void gpencil_draw_scene(void *vedata) +{ + GPENCIL_NEXT_Data *ved = reinterpret_cast(vedata); + if (!GPU_shader_storage_buffer_objects_support()) { + STRNCPY(ved->info, "Error: No shader storage buffer support"); + return; + } + if (DRW_state_is_select() || DRW_state_is_depth()) { + return; + } + DefaultTextureList *dtxl = DRW_viewport_texture_list_get(); + const DRWView *default_view = DRW_view_default_get(); + draw::Manager *manager = DRW_manager_get(); + draw::View view("DefaultView", default_view); + ved->instance->draw_viewport(*manager, view, dtxl->depth, dtxl->color); +} + +static void gpencil_cache_init(void *vedata) +{ + if (!GPU_shader_storage_buffer_objects_support()) { + return; + } + draw::Manager *manager = DRW_manager_get(); + reinterpret_cast(vedata)->instance->begin_sync(*manager); +} + +static void gpencil_cache_populate(void *vedata, Object *object) +{ + if (!GPU_shader_storage_buffer_objects_support()) { + return; + } + draw::Manager *manager = DRW_manager_get(); + + draw::ObjectRef ref; + ref.object = object; + ref.dupli_object = DRW_object_get_dupli(object); + ref.dupli_parent = DRW_object_get_dupli_parent(object); + + reinterpret_cast(vedata)->instance->object_sync(*manager, ref); +} + +static void gpencil_cache_finish(void *vedata) +{ + if (!GPU_shader_storage_buffer_objects_support()) { + return; + } + draw::Manager *manager = DRW_manager_get(); + reinterpret_cast(vedata)->instance->end_sync(*manager); +} + +static void gpencil_instance_free(void *instance) +{ + if (!GPU_shader_storage_buffer_objects_support()) { + return; + } + delete reinterpret_cast(instance); +} + +static void gpencil_engine_free() +{ + blender::draw::greasepencil::ShaderModule::module_free(); +} + +static void gpencil_render_to_image(void * /*vedata*/, + struct RenderEngine * /*engine*/, + struct RenderLayer * /*layer*/, + const struct rcti * /*rect*/) +{ +} + +extern "C" { + +static const DrawEngineDataSize gpencil_data_size = DRW_VIEWPORT_DATA_SIZE(GPENCIL_NEXT_Data); + +DrawEngineType draw_engine_gpencil_next_type = { + nullptr, + nullptr, + N_("Gpencil"), + &gpencil_data_size, + &gpencil_engine_init, + &gpencil_engine_free, + &gpencil_instance_free, + &gpencil_cache_init, + &gpencil_cache_populate, + &gpencil_cache_finish, + &gpencil_draw_scene, + nullptr, + nullptr, + &gpencil_render_to_image, + nullptr, +}; +} + +/** \} */ diff --git a/source/blender/draw/engines/gpencil/gpencil_engine.h b/source/blender/draw/engines/gpencil/gpencil_engine.h index 712f5d7d51a..fb5ada9e815 100644 --- a/source/blender/draw/engines/gpencil/gpencil_engine.h +++ b/source/blender/draw/engines/gpencil/gpencil_engine.h @@ -25,6 +25,7 @@ extern "C" { #include "gpencil_shader_shared.h" extern DrawEngineType draw_engine_gpencil_type; +extern DrawEngineType draw_engine_gpencil_next_type; struct GPENCIL_Data; struct GPENCIL_StorageList; diff --git a/source/blender/draw/engines/gpencil/gpencil_layer.hh b/source/blender/draw/engines/gpencil/gpencil_layer.hh new file mode 100644 index 00000000000..63fe75840b9 --- /dev/null +++ b/source/blender/draw/engines/gpencil/gpencil_layer.hh @@ -0,0 +1,65 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2023 Blender Foundation. */ + +/** \file + * \ingroup draw + */ + +#pragma once + +#include "BKE_grease_pencil.hh" +#include "DRW_gpu_wrapper.hh" +#include "DRW_render.h" + +#include "draw_manager.hh" +#include "draw_pass.hh" + +namespace blender::draw::greasepencil { + +using namespace draw; + +class LayerModule { + private: + /** Contains all Objects in the scene. Indexed by gpObject.layer_offset + layer_id. */ + StorageVectorBuffer layers_buf_ = "gp_layers_buf"; + + public: + void begin_sync() + { + layers_buf_.clear(); + } + + void sync(const Object * /*object*/, + const bke::greasepencil::Layer & /*layer*/, + bool &do_layer_blending) + { + /* TODO(fclem): All of this is placeholder. */ + gpLayer gp_layer; + gp_layer.vertex_color_opacity = 0.0f; + gp_layer.opacity = 1.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; + + layers_buf_.append(gp_layer); + + do_layer_blending = false; + } + + void end_sync() + { + layers_buf_.push_update(); + } + + void bind_resources(PassMain::Sub &sub) + { + sub.bind_ssbo(GPENCIL_LAYER_SLOT, &layers_buf_); + } + + uint object_offset_get() const + { + return layers_buf_.size(); + } +}; + +} // namespace blender::draw::greasepencil diff --git a/source/blender/draw/engines/gpencil/gpencil_light.hh b/source/blender/draw/engines/gpencil/gpencil_light.hh new file mode 100644 index 00000000000..8877dce5b77 --- /dev/null +++ b/source/blender/draw/engines/gpencil/gpencil_light.hh @@ -0,0 +1,130 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2022 Blender Foundation. */ + +/** \file + * \ingroup draw + */ + +#pragma once + +#include "BKE_gpencil_legacy.h" +#include "BKE_image.h" +#include "DRW_gpu_wrapper.hh" +#include "DRW_render.h" + +#include "draw_manager.hh" +#include "draw_pass.hh" + +namespace blender::draw::greasepencil { + +using namespace draw; + +class LightModule { + private: + /** Contains all lights in the scene. */ + StorageVectorBuffer lights_buf_ = "gp_lights_buf"; + + float studiolight_intensity_ = 1.0f; + bool use_scene_lights_ = true; + bool use_scene_world_ = true; + + public: + void init(const View3D *v3d) + { + if (v3d != nullptr) { + use_scene_lights_ = V3D_USES_SCENE_LIGHTS(v3d); + use_scene_world_ = V3D_USES_SCENE_WORLD(v3d); + studiolight_intensity_ = v3d->shading.studiolight_intensity; + } + } + + void begin_sync(Depsgraph *depsgraph) + { + lights_buf_.clear(); + + World *world = DEG_get_evaluated_scene(depsgraph)->world; + if (world != nullptr && use_scene_world_) { + ambient_sync(float3(world->horr, world->horg, world->horb)); + } + else { + ambient_sync(float3(studiolight_intensity_)); + } + } + + void sync(ObjectRef &object_ref) + { + if (!use_scene_lights_) { + return; + } + const Object *ob = object_ref.object; + const Light *la = static_cast(ob->data); + + float light_power; + if (la->type == LA_AREA) { + light_power = 1.0f / (4.0f * M_PI); + } + else if (ELEM(la->type, LA_SPOT, LA_LOCAL)) { + light_power = 1.0f / (4.0f * M_PI * M_PI); + } + else { + light_power = 1.0f / M_PI; + } + + gpLight light; + float4x4 &mat = *reinterpret_cast(&light.right); + switch (la->type) { + case LA_SPOT: + light.type = GP_LIGHT_TYPE_SPOT; + light.spot_size = cosf(la->spotsize * 0.5f); + light.spot_blend = (1.0f - light.spot_size) * la->spotblend; + mat = float4x4(ob->world_to_object); + break; + case LA_AREA: + /* Simulate area lights using a spot light. */ + light.type = GP_LIGHT_TYPE_SPOT; + light.spot_size = cosf(M_PI_2); + light.spot_blend = (1.0f - light.spot_size) * 1.0f; + normalize_m4_m4(mat.ptr(), ob->object_to_world); + invert_m4(mat.ptr()); + break; + case LA_SUN: + light.forward = math::normalize(float3(ob->object_to_world[2])); + light.type = GP_LIGHT_TYPE_SUN; + break; + default: + light.type = GP_LIGHT_TYPE_POINT; + break; + } + light.position = float3(object_ref.object->object_to_world[3]); + light.color = float3(la->r, la->g, la->b) * (la->energy * light_power); + + lights_buf_.append(light); + } + + void end_sync() + { + /* Tag light list end. */ + gpLight light; + light.color[0] = -1.0f; + lights_buf_.append(light); + + lights_buf_.push_update(); + } + + void bind_resources(PassMain::Sub &sub) + { + sub.bind_ssbo(GPENCIL_LIGHT_SLOT, &lights_buf_); + } + + private: + void ambient_sync(float3 color) + { + gpLight light; + light.type = GP_LIGHT_TYPE_AMBIENT; + light.color = color; + + lights_buf_.append(light); + } +}; + +} // namespace blender::draw::greasepencil diff --git a/source/blender/draw/engines/gpencil/gpencil_material.hh b/source/blender/draw/engines/gpencil/gpencil_material.hh new file mode 100644 index 00000000000..4e3cc4647e9 --- /dev/null +++ b/source/blender/draw/engines/gpencil/gpencil_material.hh @@ -0,0 +1,321 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2022 Blender Foundation. */ + +/** \file + * \ingroup draw + */ + +#pragma once + +#include "BKE_gpencil_legacy.h" +#include "BKE_image.h" +#include "DRW_gpu_wrapper.hh" +#include "DRW_render.h" + +#include "draw_manager.hh" +#include "draw_pass.hh" + +namespace blender::draw::greasepencil { + +using namespace draw; + +class MaterialModule { + private: + /** Contains all materials in the scene. Indexed by gpObject.material_offset + mat_id. */ + StorageVectorBuffer materials_buf_ = "gp_materials_buf"; + /** List of all the texture used. */ + Vector texture_pool_; + + int v3d_color_type_ = -1; + int v3d_lighting_mode_ = V3D_LIGHTING_STUDIO; + float v3d_xray_alpha_ = 1.0f; + float3 v3d_single_color_ = {1.0f, 1.0f, 1.0f}; + + public: + void init(const View3D *v3d) + { + if (v3d != nullptr) { + const bool shading_mode_supports_xray = (v3d->shading.type <= OB_SOLID); + v3d_color_type_ = (v3d->shading.type == OB_SOLID) ? v3d->shading.color_type : -1; + v3d_lighting_mode_ = v3d->shading.light; + v3d_xray_alpha_ = (shading_mode_supports_xray && XRAY_ENABLED(v3d)) ? XRAY_ALPHA(v3d) : 1.0f; + v3d_single_color_ = float3(v3d->shading.single_color); + } + } + + void begin_sync() + { + materials_buf_.clear(); + texture_pool_.clear(); + } + + void sync(const Object *object, const int mat_slot, bool &do_mat_holdout) + { + const MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings((Object *)object, + mat_slot + 1); + + MaterialGPencilStyle gp_style_override; + + gp_style = material_override(object, &gp_style_override, gp_style); + + /* Material with holdout. */ + if (gp_style->flag & GP_MATERIAL_IS_STROKE_HOLDOUT) { + do_mat_holdout = true; + } + if (gp_style->flag & GP_MATERIAL_IS_FILL_HOLDOUT) { + do_mat_holdout = true; + } + + materials_buf_.append(material_sync(gp_style)); + } + + void end_sync() + { + materials_buf_.push_update(); + } + + void bind_resources(PassMain::Sub &sub) + { + sub.bind_ssbo(GPENCIL_MATERIAL_SLOT, &materials_buf_); + } + + uint object_offset_get() const + { + return materials_buf_.size(); + } + + private: + /* Returns the correct flag for this texture. */ + gpMaterialFlag texture_sync(::Image *image, gpMaterialFlag use_flag, gpMaterialFlag premul_flag) + { + ImBuf *ibuf; + ImageUser iuser = {nullptr}; + GPUTexture *gpu_tex = nullptr; + void *lock; + bool premul = false; + + if (image == nullptr) { + texture_pool_.append(nullptr); + return GP_FLAG_NONE; + } + + ibuf = BKE_image_acquire_ibuf(image, &iuser, &lock); + + if (ibuf != nullptr) { + gpu_tex = BKE_image_get_gpu_texture(image, &iuser, ibuf); + premul = (image->alpha_mode == IMA_ALPHA_PREMUL) != 0; + } + BKE_image_release_ibuf(image, ibuf, lock); + + texture_pool_.append(gpu_tex); + + return gpMaterialFlag(use_flag | (premul ? premul_flag : GP_FLAG_NONE)); + } + + void uv_transform_sync(const float ofs[2], + const float scale[2], + const float rotation, + float r_rot_scale[2][2], + float r_offset[2]) + { + /* OPTI this could use 3x2 matrices and reduce the number of operations drastically. */ + float mat[4][4]; + unit_m4(mat); + /* Offset to center. */ + translate_m4(mat, 0.5f, 0.5f, 0.0f); + /* Reversed order. */ + float3 tmp = {1.0f / scale[0], 1.0f / scale[1], 0.0}; + rescale_m4(mat, tmp); + rotate_m4(mat, 'Z', -rotation); + translate_m4(mat, ofs[0], ofs[1], 0.0f); + /* Convert to 3x2 */ + copy_v2_v2(r_rot_scale[0], mat[0]); + copy_v2_v2(r_rot_scale[1], mat[1]); + copy_v2_v2(r_offset, mat[3]); + } + + /* Amend object fill color in order to avoid completely flat look. */ + void material_shade_color(float color[3]) + { + if (v3d_lighting_mode_ == V3D_LIGHTING_FLAT) { + return; + } + /* This is scene referred color, not gamma corrected and not per perceptual. + * So we lower the threshold a bit. (1.0 / 3.0) */ + if (color[0] + color[1] + color[2] > 1.1) { + add_v3_fl(color, -0.25f); + } + else { + add_v3_fl(color, 0.15f); + } + CLAMP3(color, 0.0f, 1.0f); + } + + const MaterialGPencilStyle *material_override(const Object *object, + MaterialGPencilStyle *gp_style_override, + const MaterialGPencilStyle *gp_style) + { + switch (v3d_color_type_) { + case V3D_SHADING_MATERIAL_COLOR: + case V3D_SHADING_RANDOM_COLOR: + /* Random uses a random color per layer and this is done using the layer tint. + * A simple color by object, like meshes, is not practical in grease pencil. */ + copy_v4_v4(gp_style_override->stroke_rgba, gp_style->stroke_rgba); + copy_v4_v4(gp_style_override->fill_rgba, gp_style->fill_rgba); + gp_style = gp_style_override; + gp_style_override->stroke_style = GP_MATERIAL_STROKE_STYLE_SOLID; + gp_style_override->fill_style = GP_MATERIAL_FILL_STYLE_SOLID; + break; + case V3D_SHADING_TEXTURE_COLOR: + *gp_style_override = blender::dna::shallow_copy(*gp_style); + gp_style = gp_style_override; + if ((gp_style_override->stroke_style == GP_MATERIAL_STROKE_STYLE_TEXTURE) && + (gp_style_override->sima)) + { + copy_v4_fl(gp_style_override->stroke_rgba, 1.0f); + gp_style_override->mix_stroke_factor = 0.0f; + } + + if ((gp_style_override->fill_style == GP_MATERIAL_FILL_STYLE_TEXTURE) && + (gp_style_override->ima)) { + copy_v4_fl(gp_style_override->fill_rgba, 1.0f); + gp_style_override->mix_factor = 0.0f; + } + else if (gp_style_override->fill_style == GP_MATERIAL_FILL_STYLE_GRADIENT) { + /* gp_style_override->fill_rgba is needed for correct gradient. */ + gp_style_override->mix_factor = 0.0f; + } + break; + case V3D_SHADING_SINGLE_COLOR: + gp_style = gp_style_override; + gp_style_override->stroke_style = GP_MATERIAL_STROKE_STYLE_SOLID; + gp_style_override->fill_style = GP_MATERIAL_FILL_STYLE_SOLID; + copy_v3_v3(gp_style_override->fill_rgba, v3d_single_color_); + gp_style_override->fill_rgba[3] = 1.0f; + copy_v4_v4(gp_style_override->stroke_rgba, gp_style_override->fill_rgba); + material_shade_color(gp_style_override->fill_rgba); + break; + case V3D_SHADING_OBJECT_COLOR: + gp_style = gp_style_override; + gp_style_override->stroke_style = GP_MATERIAL_STROKE_STYLE_SOLID; + gp_style_override->fill_style = GP_MATERIAL_FILL_STYLE_SOLID; + copy_v4_v4(gp_style_override->fill_rgba, object->color); + copy_v4_v4(gp_style_override->stroke_rgba, object->color); + material_shade_color(gp_style_override->fill_rgba); + break; + case V3D_SHADING_VERTEX_COLOR: + gp_style = gp_style_override; + gp_style_override->stroke_style = GP_MATERIAL_STROKE_STYLE_SOLID; + gp_style_override->fill_style = GP_MATERIAL_FILL_STYLE_SOLID; + copy_v4_fl(gp_style_override->fill_rgba, 1.0f); + copy_v4_fl(gp_style_override->stroke_rgba, 1.0f); + break; + default: + break; + } + return gp_style; + } + + gpMaterial material_sync(const MaterialGPencilStyle *gp_style) + { + gpMaterial material; + material.flag = 0; + + /* Dots/Square alignment. */ + if (gp_style->mode != GP_MATERIAL_MODE_LINE) { + switch (gp_style->alignment_mode) { + case GP_MATERIAL_FOLLOW_PATH: + material.flag = GP_STROKE_ALIGNMENT_STROKE; + break; + case GP_MATERIAL_FOLLOW_OBJ: + material.flag = GP_STROKE_ALIGNMENT_OBJECT; + break; + case GP_MATERIAL_FOLLOW_FIXED: + default: + material.flag = GP_STROKE_ALIGNMENT_FIXED; + break; + } + if (gp_style->mode == GP_MATERIAL_MODE_DOT) { + material.flag |= GP_STROKE_DOTS; + } + } + + /* Overlap. */ + if ((gp_style->mode != GP_MATERIAL_MODE_LINE) || + (gp_style->flag & GP_MATERIAL_DISABLE_STENCIL)) { + material.flag |= GP_STROKE_OVERLAP; + } + + /* Material with holdout. */ + if (gp_style->flag & GP_MATERIAL_IS_STROKE_HOLDOUT) { + material.flag |= GP_STROKE_HOLDOUT; + } + if (gp_style->flag & GP_MATERIAL_IS_FILL_HOLDOUT) { + material.flag |= GP_FILL_HOLDOUT; + } + + /* Dots or Squares rotation. */ + material.alignment_rot[0] = cosf(gp_style->alignment_rotation); + material.alignment_rot[1] = sinf(gp_style->alignment_rotation); + + if (gp_style->flag & GP_MATERIAL_STROKE_SHOW) { + material.flag |= GP_SHOW_STROKE; + } + if (gp_style->flag & GP_MATERIAL_FILL_SHOW) { + material.flag |= GP_SHOW_FILL; + } + + /* Stroke Style */ + if ((gp_style->stroke_style == GP_MATERIAL_STROKE_STYLE_TEXTURE) && (gp_style->sima)) { + material.flag |= texture_sync( + gp_style->sima, GP_STROKE_TEXTURE_USE, GP_STROKE_TEXTURE_PREMUL); + copy_v4_v4(material.stroke_color, gp_style->stroke_rgba); + material.stroke_texture_mix = 1.0f - gp_style->mix_stroke_factor; + material.stroke_u_scale = 500.0f / gp_style->texture_pixsize; + } + else /* if (gp_style->stroke_style == GP_MATERIAL_STROKE_STYLE_SOLID) */ { + texture_sync(nullptr, GP_FLAG_NONE, GP_FLAG_NONE); + material.flag &= ~GP_STROKE_TEXTURE_USE; + copy_v4_v4(material.stroke_color, gp_style->stroke_rgba); + material.stroke_texture_mix = 0.0f; + } + + /* Fill Style */ + if ((gp_style->fill_style == GP_MATERIAL_FILL_STYLE_TEXTURE) && (gp_style->ima)) { + material.flag |= texture_sync(gp_style->ima, GP_FILL_TEXTURE_USE, GP_FILL_TEXTURE_PREMUL); + material.flag |= (gp_style->flag & GP_MATERIAL_TEX_CLAMP) ? GP_FILL_TEXTURE_CLIP : 0; + uv_transform_sync(gp_style->texture_offset, + gp_style->texture_scale, + gp_style->texture_angle, + (float(*)[2]) & material.fill_uv_rot_scale[0], + material.fill_uv_offset); + copy_v4_v4(material.fill_color, gp_style->fill_rgba); + material.fill_texture_mix = 1.0f - gp_style->mix_factor; + } + else if (gp_style->fill_style == GP_MATERIAL_FILL_STYLE_GRADIENT) { + texture_sync(nullptr, GP_FLAG_NONE, GP_FLAG_NONE); + bool use_radial = (gp_style->gradient_type == GP_MATERIAL_GRADIENT_RADIAL); + material.flag |= GP_FILL_GRADIENT_USE; + material.flag |= use_radial ? GP_FILL_GRADIENT_RADIAL : 0; + uv_transform_sync(gp_style->texture_offset, + gp_style->texture_scale, + gp_style->texture_angle, + (float(*)[2]) & material.fill_uv_rot_scale[0], + material.fill_uv_offset); + copy_v4_v4(material.fill_color, gp_style->fill_rgba); + copy_v4_v4(material.fill_mix_color, gp_style->mix_rgba); + material.fill_texture_mix = 1.0f - gp_style->mix_factor; + if (gp_style->flag & GP_MATERIAL_FLIP_FILL) { + swap_v4_v4(material.fill_color, material.fill_mix_color); + } + } + else /* if (gp_style->fill_style == GP_MATERIAL_FILL_STYLE_SOLID) */ { + texture_sync(nullptr, GP_FLAG_NONE, GP_FLAG_NONE); + copy_v4_v4(material.fill_color, gp_style->fill_rgba); + material.fill_texture_mix = 0.0f; + } + return material; + } +}; + +} // namespace blender::draw::greasepencil diff --git a/source/blender/draw/engines/gpencil/gpencil_object.hh b/source/blender/draw/engines/gpencil/gpencil_object.hh new file mode 100644 index 00000000000..7cff6cb25f7 --- /dev/null +++ b/source/blender/draw/engines/gpencil/gpencil_object.hh @@ -0,0 +1,251 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2022 Blender Foundation. */ + +/** \file + * \ingroup draw + */ + +#pragma once + +#include "BKE_grease_pencil.hh" +#include "BKE_image.h" +#include "DRW_gpu_wrapper.hh" +#include "DRW_render.h" + +#include "draw_manager.hh" +#include "draw_pass.hh" + +#include "gpencil_layer.hh" +#include "gpencil_material.hh" +#include "gpencil_shader.hh" + +namespace blender::draw::greasepencil { + +using namespace draw; + +class ObjectModule { + private: + LayerModule &layers_; + MaterialModule &materials_; + ShaderModule &shaders_; + + /** Contains all Objects in the scene. Indexed by drw_ResourceID. */ + StorageArrayBuffer objects_buf_ = "gp_objects_buf"; + + /** Contains all gpencil objects in the scene as well as their effect sub-passes. */ + PassSortable main_ps_ = {"gp_main_ps"}; + + /** Contains all composited GPencil layers from one object if is uses VFX. */ + TextureFromPool object_color_tx_ = {"gp_color_object_tx"}; + TextureFromPool object_reveal_tx_ = {"gp_reveal_object_tx"}; + Framebuffer object_fb_ = {"gp_object_fb"}; + bool is_object_fb_needed_ = false; + + /** Contains all strokes from one layer if is uses blending. (also used as target for VFX) */ + TextureFromPool layer_color_tx_ = {"gp_color_layer_tx"}; + TextureFromPool layer_reveal_tx_ = {"gp_reveal_layer_tx"}; + Framebuffer layer_fb_ = {"gp_layer_fb"}; + bool is_layer_fb_needed_ = false; + + bool use_onion_ = true; + bool use_stroke_fill_ = true; + bool use_vfx_ = true; + bool is_render_ = true; + /** Forward vector used to sort gpencil objects. */ + float3 camera_forward_; + /** Scene current frame. */ + float current_frame_ = 0; + + /** \note Needs not to be temporary variable since it is dereferenced later. */ + std::array clear_colors_ = {float4(0.0f, 0.0f, 0.0f, 0.0f), + float4(1.0f, 1.0f, 1.0f, 1.0f)}; + + public: + ObjectModule(LayerModule &layers, MaterialModule &materials, ShaderModule &shaders) + : layers_(layers), materials_(materials), shaders_(shaders){}; + + void init(const View3D *v3d, const Scene *scene) + { + const bool is_viewport = (v3d != nullptr); + + if (is_viewport) { + /* TODO(fclem): Avoid access to global DRW. */ + const struct bContext *evil_C = DRW_context_state_get()->evil_C; + const bool playing = (evil_C != nullptr) ? + ED_screen_animation_playing(CTX_wm_manager(evil_C)) != nullptr : + false; + const bool hide_overlay = ((v3d->flag2 & V3D_HIDE_OVERLAYS) != 0); + const bool show_onion = ((v3d->gp_flag & V3D_GP_SHOW_ONION_SKIN) != 0); + use_onion_ = show_onion && !hide_overlay && !playing; + use_stroke_fill_ = GPENCIL_SIMPLIFY_FILL(scene, playing); + use_vfx_ = GPENCIL_SIMPLIFY_FX(scene, playing); + is_render_ = false; + } + else { + use_stroke_fill_ = GPENCIL_SIMPLIFY_FILL(scene, false); + use_vfx_ = GPENCIL_SIMPLIFY_FX(scene, false); + } + } + + void begin_sync(Depsgraph *depsgraph, const View &main_view) + { + camera_forward_ = float3(main_view.viewinv()[2]); + current_frame_ = DEG_get_ctime(depsgraph); + + is_object_fb_needed_ = false; + is_layer_fb_needed_ = false; + + /* TODO(fclem): Shrink buffer. */ + // objects_buf_.shrink(); + } + + void sync_grease_pencil(Manager &manager, + ObjectRef &object_ref, + Framebuffer &main_fb, + PassSortable &main_ps) + { + using namespace blender::bke::greasepencil; + + Object *object = object_ref.object; + GreasePencil &grease_pencil = *static_cast(object->data); + + if (grease_pencil.drawings().is_empty()) { + return; + } + + const bool is_stroke_order_3d = false; /* TODO */ + bool do_material_holdout = false; + bool do_layer_blending = false; + bool object_has_vfx = false; // TODO: vfx.object_has_vfx(gpd); + + uint material_offset = materials_.object_offset_get(); + for (auto i : IndexRange(BKE_object_material_count_eval(object))) { + materials_.sync(object, i, do_material_holdout); + } + + uint layer_offset = layers_.object_offset_get(); + for (const Layer *layer : grease_pencil.layers()) { + layers_.sync(object, *layer, do_layer_blending); + } + + /* Order rendering using camera Z distance. */ + float3 position = float3(object->object_to_world[3]); + float camera_z = math::dot(position, camera_forward_); + + PassMain::Sub &object_subpass = main_ps.sub("GPObject", camera_z); + object_subpass.framebuffer_set((object_has_vfx) ? &object_fb_ : &main_fb); + object_subpass.clear_depth(is_stroke_order_3d ? 1.0f : 0.0f); + if (object_has_vfx) { + object_subpass.clear_multi(clear_colors_); + } + + DRWState state = DRW_STATE_WRITE_COLOR | DRW_STATE_WRITE_DEPTH | DRW_STATE_BLEND_ALPHA_PREMUL; + /* For 2D mode, we render all strokes with uniform depth (increasing with stroke id). */ + state |= (is_stroke_order_3d) ? DRW_STATE_DEPTH_LESS_EQUAL : DRW_STATE_DEPTH_GREATER; + /* Always write stencil. Only used as optimization for blending. */ + state |= DRW_STATE_WRITE_STENCIL | DRW_STATE_STENCIL_ALWAYS; + + object_subpass.state_set(state); + object_subpass.shader_set(shaders_.static_shader_get(GREASE_PENCIL)); + + GPUVertBuf *position_tx = DRW_cache_grease_pencil_position_buffer_get(object, current_frame_); + GPUVertBuf *color_tx = DRW_cache_grease_pencil_color_buffer_get(object, current_frame_); + GPUBatch *geom = DRW_cache_grease_pencil_get(object, current_frame_); + + /* TODO(fclem): Pass per frame object matrix here. */ + ResourceHandle handle = manager.resource_handle(object_ref); + gpObject &ob = objects_buf_.get_or_resize(handle.resource_index()); + ob.is_shadeless = false; + ob.stroke_order3d = false; + ob.tint = float4(1.0); // frame_tint_get(gpd, frame.gpf, current_frame_); + ob.layer_offset = layer_offset; + ob.material_offset = material_offset; + + if (do_layer_blending) { + /* TODO: Do layer blending. */ + // for (const LayerData &layer : frame.layers) { + // UNUSED_VARS(layer); + // if (has_blending(layer)) { + // object_subpass.framebuffer_set(*vfx_fb.current()); + // } + + /* TODO(fclem): Only draw subrange of geometry for this layer. */ + object_subpass.draw(geom, handle); + + /* TODO: Do layer blending. */ + // if (has_blending(layer)) { + // layer_blend_sync(object_ref, object_subpass); + // } + // } + } + else { + /* Fast path. */ + object_subpass.bind_texture("gp_pos_tx", position_tx); + object_subpass.bind_texture("gp_col_tx", color_tx); + object_subpass.draw(geom, handle); + } + + /* TODO: Do object VFX. */ +#if 0 + if (object_has_vfx) { + VfxContext vfx_ctx(object_subpass, + layer_fb_, + object_fb_, + object_color_tx_, + layer_color_tx_, + object_reveal_tx_, + layer_reveal_tx_, + is_render_); + + /* \note Update this boolean as the actual number of vfx drawn might differ. */ + object_has_vfx = vfx.object_sync(main_fb_, object_ref, vfx_ctx, do_material_holdout); + + if (object_has_vfx || do_layer_blending) { + is_layer_fb_needed_ = true; + } + } +#endif + } + + void end_sync() + { + objects_buf_.push_update(); + } + + void bind_resources(PassMain::Sub &sub) + { + sub.bind_ssbo(GPENCIL_OBJECT_SLOT, &objects_buf_); + } + + void acquire_temporary_buffers(int2 render_size, eGPUTextureFormat format) + { + object_color_tx_.acquire(render_size, format); + object_reveal_tx_.acquire(render_size, format); + object_fb_.ensure(GPU_ATTACHMENT_NONE, + GPU_ATTACHMENT_TEXTURE(object_color_tx_), + GPU_ATTACHMENT_TEXTURE(object_reveal_tx_)); + if (is_layer_fb_needed_) { + layer_color_tx_.acquire(render_size, format); + layer_reveal_tx_.acquire(render_size, format); + layer_fb_.ensure(GPU_ATTACHMENT_NONE, + GPU_ATTACHMENT_TEXTURE(layer_color_tx_), + GPU_ATTACHMENT_TEXTURE(layer_reveal_tx_)); + } + } + + void release_temporary_buffers() + { + object_color_tx_.release(); + object_reveal_tx_.release(); + + layer_color_tx_.release(); + layer_reveal_tx_.release(); + } + + bool scene_has_visible_gpencil_object() const + { + return objects_buf_.size() > 0; + } +}; + +} // namespace blender::draw::greasepencil diff --git a/source/blender/draw/engines/gpencil/gpencil_shader.cc b/source/blender/draw/engines/gpencil/gpencil_shader.cc new file mode 100644 index 00000000000..83de0a9d72e --- /dev/null +++ b/source/blender/draw/engines/gpencil/gpencil_shader.cc @@ -0,0 +1,116 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2023 Blender Foundation. */ + +/** \file + * \ingroup draw + */ + +#include "gpencil_shader.hh" + +namespace blender::draw::greasepencil { + +ShaderModule *ShaderModule::g_shader_module = nullptr; + +ShaderModule *ShaderModule::module_get() +{ + if (g_shader_module == nullptr) { + /* TODO(@fclem) thread-safety. */ + g_shader_module = new ShaderModule(); + } + return g_shader_module; +} + +void ShaderModule::module_free() +{ + if (g_shader_module != nullptr) { + /* TODO(@fclem) thread-safety. */ + delete g_shader_module; + g_shader_module = nullptr; + } +} + +ShaderModule::ShaderModule() +{ + for (GPUShader *&shader : shaders_) { + shader = nullptr; + } + +#ifdef DEBUG + /* Ensure all shader are described. */ + for (auto i : IndexRange(MAX_SHADER_TYPE)) { + const char *name = static_shader_create_info_name_get(eShaderType(i)); + if (name == nullptr) { + std::cerr << "GPencil: Missing case for eShaderType(" << i + << ") in static_shader_create_info_name_get()." << std::endl; + BLI_assert(0); + } + const GPUShaderCreateInfo *create_info = GPU_shader_create_info_get(name); + BLI_assert_msg(create_info != nullptr, "GPencil: Missing create info for static shader."); + } +#endif +} + +ShaderModule::~ShaderModule() +{ + for (GPUShader *&shader : shaders_) { + DRW_SHADER_FREE_SAFE(shader); + } +} + +const char *ShaderModule::static_shader_create_info_name_get(eShaderType shader_type) +{ + switch (shader_type) { + case ANTIALIASING_EDGE_DETECT: + return "gpencil_antialiasing_stage_0"; + case ANTIALIASING_BLEND_WEIGHT: + return "gpencil_antialiasing_stage_1"; + case ANTIALIASING_RESOLVE: + return "gpencil_antialiasing_stage_2"; + case GREASE_PENCIL: + return "gpencil_geometry_next"; + case LAYER_BLEND: + return "gpencil_layer_blend"; + case DEPTH_MERGE: + return "gpencil_depth_merge"; + case MASK_INVERT: + return "gpencil_mask_invert"; + case FX_COMPOSITE: + return "gpencil_fx_composite"; + case FX_COLORIZE: + return "gpencil_fx_colorize"; + case FX_BLUR: + return "gpencil_fx_blur"; + case FX_GLOW: + return "gpencil_fx_glow"; + case FX_PIXEL: + return "gpencil_fx_pixelize"; + case FX_RIM: + return "gpencil_fx_rim"; + case FX_SHADOW: + return "gpencil_fx_shadow"; + case FX_TRANSFORM: + return "gpencil_fx_transform"; + /* To avoid compiler warning about missing case. */ + case MAX_SHADER_TYPE: + return ""; + } + return ""; +} + +GPUShader *ShaderModule::static_shader_get(eShaderType shader_type) +{ + if (shaders_[shader_type] == nullptr) { + const char *shader_name = static_shader_create_info_name_get(shader_type); + + shaders_[shader_type] = GPU_shader_create_from_info_name(shader_name); + + if (shaders_[shader_type] == nullptr) { + std::cerr << "GPencil: error: Could not compile static shader \"" << shader_name << "\"" + << std::endl; + } + BLI_assert(shaders_[shader_type] != nullptr); + } + return shaders_[shader_type]; +} + +} // namespace blender::draw::greasepencil \ No newline at end of file diff --git a/source/blender/draw/engines/gpencil/gpencil_shader.hh b/source/blender/draw/engines/gpencil/gpencil_shader.hh new file mode 100644 index 00000000000..0ec007bcb4f --- /dev/null +++ b/source/blender/draw/engines/gpencil/gpencil_shader.hh @@ -0,0 +1,65 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2023 Blender Foundation. */ + +/** \file + * \ingroup draw + */ + +#pragma once + +#include "DRW_render.h" + +namespace blender::draw::greasepencil { + +enum eShaderType { + /* SMAA antialiasing */ + ANTIALIASING_EDGE_DETECT = 0, + ANTIALIASING_BLEND_WEIGHT, + ANTIALIASING_RESOLVE, + /* GPencil Object rendering */ + GREASE_PENCIL, + /* All layer blend types in one shader! */ + LAYER_BLEND, + /* Merge the final object depth to the depth buffer. */ + DEPTH_MERGE, + /* Invert the content of the mask buffer. */ + MASK_INVERT, + /* Final Compositing over rendered background. */ + FX_COMPOSITE, + /* Effects. */ + FX_COLORIZE, + FX_BLUR, + FX_GLOW, + FX_PIXEL, + FX_RIM, + FX_SHADOW, + FX_TRANSFORM, + + MAX_SHADER_TYPE, +}; + +/** + * Shader module. shared between instances. + */ +class ShaderModule { + private: + std::array shaders_; + + /** Shared shader module across all engine instances. */ + static ShaderModule *g_shader_module; + + public: + ShaderModule(); + ~ShaderModule(); + + GPUShader *static_shader_get(eShaderType shader_type); + + /** Only to be used by Instance constructor. */ + static ShaderModule *module_get(); + static void module_free(); + + private: + const char *static_shader_create_info_name_get(eShaderType shader_type); +}; + +} // namespace blender::draw::greasepencil \ No newline at end of file diff --git a/source/blender/draw/engines/gpencil/gpencil_shader_shared.h b/source/blender/draw/engines/gpencil/gpencil_shader_shared.h index 3f0f73e7c13..9357d31f5e1 100644 --- a/source/blender/draw/engines/gpencil/gpencil_shader_shared.h +++ b/source/blender/draw/engines/gpencil/gpencil_shader_shared.h @@ -1,11 +1,17 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2023 Blender Foundation. */ #ifndef GPU_SHADER +# pragma once + # include "GPU_shader_shared_utils.h" # ifndef __cplusplus +typedef struct gpScene gpScene; typedef struct gpMaterial gpMaterial; typedef struct gpLight gpLight; +typedef struct gpObject gpObject; +typedef struct gpLayer gpLayer; typedef enum gpMaterialFlag gpMaterialFlag; # ifdef GP_LIGHT typedef enum gpLightType gpLightType; @@ -14,6 +20,7 @@ typedef enum gpLightType gpLightType; #endif enum gpMaterialFlag { + GP_FLAG_NONE = 0u, GP_STROKE_ALIGNMENT_STROKE = 1u, GP_STROKE_ALIGNMENT_OBJECT = 2u, GP_STROKE_ALIGNMENT_FIXED = 3u, @@ -30,6 +37,8 @@ enum gpMaterialFlag { GP_FILL_TEXTURE_CLIP = (1u << 12u), GP_FILL_GRADIENT_USE = (1u << 13u), GP_FILL_GRADIENT_RADIAL = (1u << 14u), + GP_SHOW_STROKE = (1u << 15u), + GP_SHOW_FILL = (1u << 16u), GP_FILL_FLAGS = (GP_FILL_TEXTURE_USE | GP_FILL_TEXTURE_PREMUL | GP_FILL_TEXTURE_CLIP | GP_FILL_GRADIENT_USE | GP_FILL_GRADIENT_RADIAL | GP_FILL_HOLDOUT), }; @@ -50,6 +59,12 @@ enum gpLightType { # define gpLightType uint #endif +struct gpScene { + float2 render_size; + float2 _pad0; +}; +BLI_STATIC_ASSERT_ALIGN(gpScene, 16) + struct gpMaterial { float4 stroke_color; float4 fill_color; @@ -116,6 +131,38 @@ struct gpLight { BLI_STATIC_ASSERT_ALIGN(gpLight, 16) #endif +struct gpObject { + /** Wether or not to apply lighting to the GPencil object. */ + bool1 is_shadeless; + /** Switch between 2d and 3D stroke order. */ + bool1 stroke_order3d; + /** Offset inside the layer buffer to the first layer data of this object. */ + uint layer_offset; + /** Offset inside the material buffer to the first material data of this object. */ + uint material_offset; + /** Color to multiply to the final mixed color. */ + float4 tint; + /** Color to multiply to the final mixed color. */ + float3 normal; + + float _pad0; +}; +BLI_STATIC_ASSERT_ALIGN(gpObject, 16) + +struct gpLayer { + /** Amount of vertex color to blend with actual material color. */ + float vertex_color_opacity; + /** Thickness change of all the strokes. */ + float thickness_offset; + /** Thickness change of all the strokes. */ + float opacity; + /** Offset to apply to stroke index to be able to insert a currently drawn stroke in between. */ + float stroke_index_offset; + /** Color to multiply to the final mixed color. */ + float4 tint; +}; +BLI_STATIC_ASSERT_ALIGN(gpLayer, 16) + #ifndef GPU_SHADER # undef gpMaterialFlag # undef gpLightType diff --git a/source/blender/draw/engines/gpencil/gpencil_vfx.hh b/source/blender/draw/engines/gpencil/gpencil_vfx.hh new file mode 100644 index 00000000000..c4011618997 --- /dev/null +++ b/source/blender/draw/engines/gpencil/gpencil_vfx.hh @@ -0,0 +1,335 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2017 Blender Foundation. */ + +/** \file + * \ingroup draw + */ +#include "BKE_camera.h" +#include "BLI_listbase_wrapper.hh" +#include "DNA_camera_types.h" +#include "DNA_gpencil_legacy_types.h" +#include "DNA_shader_fx_types.h" + +#include "draw_manager.hh" +#include "draw_pass.hh" + +#include "gpencil_engine.h" +#include "gpencil_shader.hh" +#include "gpencil_shader_shared.h" + +namespace blender::draw::greasepencil { + +using namespace draw; + +struct VfxContext { + PassMain::Sub *object_subpass; + SwapChain vfx_fb; + SwapChain color_tx; + SwapChain reveal_tx; + bool is_viewport; + + VfxContext(PassMain::Sub &object_subpass_, + Framebuffer &layer_fb, + Framebuffer &object_fb, + TextureFromPool &object_color_tx, + TextureFromPool &layer_color_tx, + TextureFromPool &object_reveal_tx, + TextureFromPool &layer_reveal_tx, + bool is_render_) + { + object_subpass = &object_subpass_; + /* These may not be allocated yet, use address of future pointer. */ + vfx_fb.current() = &layer_fb; + vfx_fb.next() = &object_fb; + + color_tx.current() = &object_color_tx; + color_tx.next() = &layer_color_tx; + reveal_tx.current() = &object_reveal_tx; + reveal_tx.next() = &layer_reveal_tx; + + is_viewport = (is_render_ == false); + } + + PassMain::Sub &create_vfx_pass(const char *name, GPUShader *shader) + { + PassMain::Sub &sub = object_subpass->sub(name); + sub.framebuffer_set(vfx_fb.current()); + sub.shader_set(shader); + sub.bind_texture("colorBuf", color_tx.current()); + sub.bind_texture("revealBuf", reveal_tx.current()); + + vfx_fb.swap(); + color_tx.swap(); + reveal_tx.swap(); + + return sub; + } + + /* Verify if the given fx is active. */ + bool effect_is_active(const bGPdata *gpd, const ShaderFxData *fx) + { + if (fx == NULL) { + return false; + } + + if (gpd == NULL) { + return false; + } + + bool is_edit = GPENCIL_ANY_EDIT_MODE(gpd); + if (((fx->mode & eShaderFxMode_Editmode) == 0) && (is_edit) && (is_viewport)) { + return false; + } + + if (((fx->mode & eShaderFxMode_Realtime) && (is_viewport == true)) || + ((fx->mode & eShaderFxMode_Render) && (is_viewport == false))) + { + return true; + } + + return false; + } +}; + +class VfxModule { + private: + ShaderModule &shaders; + /* Global switch for all vfx. */ + bool vfx_enabled_ = false; + /* Global switch for all Depth Of Field blur. */ + bool dof_enabled_ = false; + /* Pseudo depth of field parameter. Used to scale blur radius. */ + float dof_parameters_[2]; + + public: + VfxModule(ShaderModule &shaders_) : shaders(shaders_){}; + + void init(bool enable, const Object *camera_object, const RegionView3D *rv3d) + { + vfx_enabled_ = enable; + + const Camera *camera = (camera_object != nullptr) ? + static_cast(camera_object->data) : + nullptr; + + /* Pseudo DOF setup. */ + if (camera && (camera->dof.flag & CAM_DOF_ENABLED)) { + const float *vp_size = DRW_viewport_size_get(); + float fstop = camera->dof.aperture_fstop; + float sensor = BKE_camera_sensor_size( + camera->sensor_fit, camera->sensor_x, camera->sensor_y); + float focus_dist = BKE_camera_object_dof_distance(camera_object); + float focal_len = camera->lens; + + const float scale_camera = 0.001f; + /* We want radius here for the aperture number. */ + float aperture = 0.5f * scale_camera * focal_len / fstop; + float focal_len_scaled = scale_camera * focal_len; + float sensor_scaled = scale_camera * sensor; + + if (rv3d != nullptr) { + sensor_scaled *= rv3d->viewcamtexcofac[0]; + } + + dof_parameters_[1] = aperture * fabsf(focal_len_scaled / (focus_dist - focal_len_scaled)); + dof_parameters_[1] *= vp_size[0] / sensor_scaled; + dof_parameters_[0] = -focus_dist * dof_parameters_[1]; + } + else { + /* Disable DoF blur scaling. Produce Circle of Confusion of 0 pixel. */ + dof_parameters_[0] = dof_parameters_[1] = 0.0f; + } + } + + /* Return true if any vfx is needed */ + bool object_sync(Framebuffer &main_fb, + ObjectRef &object_ref, + VfxContext &vfx_ctx, + bool do_material_holdout) + { + Object *object = object_ref.object; + bGPdata *gpd = (bGPdata *)object->data; + + int vfx_count = 0; + + if (vfx_enabled_) { + for (const ShaderFxData *fx : ListBaseWrapper(&object->shader_fx)) { + if (!vfx_ctx.effect_is_active(gpd, fx)) { + continue; + } + switch (fx->type) { + case eShaderFxType_Blur: + vfx_count += vfx_blur(*(const BlurShaderFxData *)fx, object, vfx_ctx); + break; + case eShaderFxType_Colorize: + vfx_count += vfx_colorize(*(const ColorizeShaderFxData *)fx, object, vfx_ctx); + break; + case eShaderFxType_Flip: + vfx_count += vfx_flip(*(const FlipShaderFxData *)fx, object, vfx_ctx); + break; + case eShaderFxType_Pixel: + vfx_count += vfx_pixelize(*(const PixelShaderFxData *)fx, object, vfx_ctx); + break; + case eShaderFxType_Rim: + vfx_count += vfx_rim(*(const RimShaderFxData *)fx, object, vfx_ctx); + break; + case eShaderFxType_Shadow: + vfx_count += vfx_shadow(*(const ShadowShaderFxData *)fx, object, vfx_ctx); + break; + case eShaderFxType_Glow: + vfx_count += vfx_glow(*(const GlowShaderFxData *)fx, object, vfx_ctx); + break; + case eShaderFxType_Swirl: + vfx_count += vfx_swirl(*(const SwirlShaderFxData *)fx, object, vfx_ctx); + break; + case eShaderFxType_Wave: + vfx_count += vfx_wave(*(const WaveShaderFxData *)fx, object, vfx_ctx); + break; + default: + break; + } + } + } + + if (do_material_holdout) { + vfx_count += 1; + } + + if (vfx_count > 0) { + /* We need an extra pass to combine result to main buffer. */ + merge_sync(main_fb, vfx_ctx); + } + + return vfx_count > 0; + } + + private: + int vfx_blur(const BlurShaderFxData &fx, const Object *object, VfxContext &vfx_ctx) + { + if ((fx.flag & FX_BLUR_DOF_MODE) && !dof_enabled_) { + /* No blur outside camera view (or when DOF is disabled on the camera). */ + return 0; + } + + float winmat[4][4], persmat[4][4]; + float2 blur_size = {fx.radius[0], fx.radius[1]}; + + /* TODO(fclem): Replace by draw::View. */ + DRW_view_persmat_get(nullptr, persmat, false); + const float w = fabsf(mul_project_m4_v3_zfac(persmat, object->object_to_world[3])); + + if (fx.flag & FX_BLUR_DOF_MODE) { + /* Compute circle of confusion size. */ + float coc = (dof_parameters_[0] / -w) - dof_parameters_[1]; + blur_size = float2(fabsf(coc)); + } + else { + /* Modify by distance to camera and object scale. */ + /* TODO(fclem): Replace by draw::View. */ + DRW_view_winmat_get(nullptr, winmat, false); + /* TODO(fclem): Replace by this->render_size. */ + const float *vp_size = DRW_viewport_size_get(); + + float world_pixel_scale = 1.0f / GPENCIL_PIXEL_FACTOR; + float scale = mat4_to_scale(object->object_to_world); + float distance_factor = world_pixel_scale * scale * winmat[1][1] * vp_size[1] / w; + blur_size *= distance_factor; + } + + if ((fx.samples == 0.0f) || (blur_size[0] == 0.0f && blur_size[1] == 0.0f)) { + return 0; + } + + GPUShader *sh = shaders.static_shader_get(eShaderType::FX_BLUR); + + const float rot_sin = sin(fx.rotation); + const float rot_cos = cos(fx.rotation); + + if (blur_size[0] > 0.0f) { + PassMain::Sub &sub = vfx_ctx.create_vfx_pass("Fx Blur H", sh); + sub.state_set(DRW_STATE_WRITE_COLOR); + sub.push_constant("offset", float2(blur_size[0] * rot_cos, blur_size[0] * rot_sin)); + sub.push_constant("sampCount", max_ii(1, min_ii(fx.samples, blur_size[0]))); + sub.draw_procedural(GPU_PRIM_TRIS, 1, 3); + } + if (blur_size[1] > 0.0f) { + PassMain::Sub &sub = vfx_ctx.create_vfx_pass("Fx Blur V", sh); + sub.state_set(DRW_STATE_WRITE_COLOR); + sub.push_constant("offset", float2(-blur_size[1] * rot_sin, blur_size[1] * rot_cos)); + sub.push_constant("sampCount", max_ii(1, min_ii(fx.samples, blur_size[1]))); + sub.draw_procedural(GPU_PRIM_TRIS, 1, 3); + } + + /* Return number of passes. */ + return int(blur_size[0] > 0.0f) + int(blur_size[1] > 0.0f); + } + + int vfx_colorize(const ColorizeShaderFxData &fx, const Object *object, VfxContext &vfx_ctx) + { + UNUSED_VARS(fx, object, vfx_ctx); + return 0; + } + + int vfx_flip(const FlipShaderFxData &fx, const Object *object, VfxContext &vfx_ctx) + { + UNUSED_VARS(fx, object, vfx_ctx); + return 0; + } + + int vfx_pixelize(const PixelShaderFxData &fx, const Object *object, VfxContext &vfx_ctx) + { + UNUSED_VARS(fx, object, vfx_ctx); + return 0; + } + + int vfx_rim(const RimShaderFxData &fx, const Object *object, VfxContext &vfx_ctx) + { + UNUSED_VARS(fx, object, vfx_ctx); + return 0; + } + + int vfx_shadow(const ShadowShaderFxData &fx, const Object *object, VfxContext &vfx_ctx) + { + UNUSED_VARS(fx, object, vfx_ctx); + return 0; + } + + int vfx_glow(const GlowShaderFxData &fx, const Object *object, VfxContext &vfx_ctx) + { + UNUSED_VARS(fx, object, vfx_ctx); + return 0; + } + + int vfx_swirl(const SwirlShaderFxData &fx, const Object *object, VfxContext &vfx_ctx) + { + UNUSED_VARS(fx, object, vfx_ctx); + return 0; + } + + int vfx_wave(const WaveShaderFxData &fx, const Object *object, VfxContext &vfx_ctx) + { + UNUSED_VARS(fx, object, vfx_ctx); + return 0; + } + + void merge_sync(Framebuffer &main_fb, VfxContext &vfx_ctx) + { + PassMain::Sub &sub = vfx_ctx.object_subpass->sub("GPencil Object Composite"); + sub.framebuffer_set(&main_fb); + + sub.shader_set(shaders.static_shader_get(FX_COMPOSITE)); + sub.bind_texture("colorBuf", vfx_ctx.color_tx.current()); + sub.bind_texture("revealBuf", vfx_ctx.reveal_tx.current()); + + sub.state_set(DRW_STATE_WRITE_COLOR | DRW_STATE_BLEND_MUL); + sub.push_constant("isFirstPass", true); + sub.draw_procedural(GPU_PRIM_TRIS, 1, 3); + /* We cannot do custom blending on multi-target frame-buffers. + * Workaround by doing 2 passes. */ + sub.state_set(DRW_STATE_WRITE_COLOR, DRW_STATE_BLEND_ADD_FULL); + sub.push_constant("isFirstPass", false); + sub.draw_procedural(GPU_PRIM_TRIS, 1, 3); + } +}; + +} // namespace blender::draw::greasepencil diff --git a/source/blender/draw/engines/gpencil/shaders/gpencil_frag.glsl b/source/blender/draw/engines/gpencil/shaders/gpencil_frag.glsl index 0aeae6a729c..caf65b76893 100644 --- a/source/blender/draw/engines/gpencil/shaders/gpencil_frag.glsl +++ b/source/blender/draw/engines/gpencil/shaders/gpencil_frag.glsl @@ -15,19 +15,19 @@ vec3 gpencil_lighting(void) { vec3 light_accum = vec3(0.0); for (int i = 0; i < GPENCIL_LIGHT_BUFFER_LEN; i++) { - if (lights[i]._color.x == -1.0) { + if (gp_lights[i]._color.x == -1.0) { break; } - vec3 L = lights[i]._position - gp_interp.pos; + vec3 L = gp_lights[i]._position - gp_interp.pos; float vis = 1.0; - gpLightType type = floatBitsToUint(lights[i]._type); + gpLightType type = floatBitsToUint(gp_lights[i]._type); /* Spot Attenuation. */ if (type == GP_LIGHT_TYPE_SPOT) { - mat3 rot_scale = mat3(lights[i]._right, lights[i]._up, lights[i]._forward); + 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 - lights[i]._spot_size) / lights[i]._spot_blend); + 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); } @@ -37,7 +37,7 @@ vec3 gpencil_lighting(void) vis /= L_len_sqr; } else { - L = lights[i]._forward; + L = gp_lights[i]._forward; L_len_sqr = 1.0; } /* Lambertian falloff */ @@ -45,7 +45,7 @@ vec3 gpencil_lighting(void) L /= sqrt(L_len_sqr); vis *= clamp(dot(gpNormal, L), 0.0, 1.0); } - light_accum += vis * lights[i]._color; + light_accum += vis * gp_lights[i]._color; } /* Clamp to avoid NaNs. */ return clamp(light_accum, 0.0, 1e10); @@ -68,7 +68,7 @@ void main() bool radial = flag_test(gp_interp.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.mat_flag >> GPENCIl_MATID_SHIFT; - col = mix(materials[matid].fill_color, materials[matid].fill_mix_color, fac); + col = mix(gp_materials[matid].fill_color, gp_materials[matid].fill_mix_color, fac); } else /* SOLID */ { col = vec4(1.0); diff --git a/source/blender/draw/engines/gpencil/shaders/gpencil_vert.glsl b/source/blender/draw/engines/gpencil/shaders/gpencil_vert.glsl index 2e7544cea29..bae45753c08 100644 --- a/source/blender/draw/engines/gpencil/shaders/gpencil_vert.glsl +++ b/source/blender/draw/engines/gpencil/shaders/gpencil_vert.glsl @@ -27,12 +27,16 @@ void gpencil_color_output(vec4 stroke_col, vec4 vert_col, float vert_strength, f void main() { +#ifdef GPENCIL_NEXT + PASS_RESOURCE_ID +#endif + float vert_strength; vec4 vert_color; vec3 vert_N; ivec4 ma1 = floatBitsToInt(texelFetch(gp_pos_tx, gpencil_stroke_point_id() * 3 + 1)); - gpMaterial gp_mat = materials[ma1.x + gpMaterialOffset]; + gpMaterial gp_mat = gp_materials[ma1.x + gpMaterialOffset]; gpMaterialFlag gp_flag = floatBitsToUint(gp_mat._flag); gl_Position = gpencil_vertex(vec4(viewportSize, 1.0 / viewportSize), 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 19fe29c6d38..26b2b243351 100644 --- a/source/blender/draw/engines/gpencil/shaders/infos/gpencil_info.hh +++ b/source/blender/draw/engines/gpencil/shaders/infos/gpencil_info.hh @@ -2,6 +2,8 @@ #include "gpu_shader_create_info.hh" +#include "gpencil_defines.h" + /* -------------------------------------------------------------------- */ /** \name GPencil Object rendering * \{ */ @@ -26,8 +28,8 @@ GPU_SHADER_CREATE_INFO(gpencil_geometry) .sampler(3, ImageType::FLOAT_2D, "gpStrokeTexture") .sampler(4, ImageType::DEPTH_2D, "gpSceneDepthTexture") .sampler(5, ImageType::FLOAT_2D, "gpMaskTexture") - .uniform_buf(4, "gpMaterial", "materials[GPENCIL_MATERIAL_BUFFER_LEN]", Frequency::BATCH) - .uniform_buf(3, "gpLight", "lights[GPENCIL_LIGHT_BUFFER_LEN]", Frequency::BATCH) + .uniform_buf(4, "gpMaterial", "gp_materials[GPENCIL_MATERIAL_BUFFER_LEN]", Frequency::BATCH) + .uniform_buf(3, "gpLight", "gp_lights[GPENCIL_LIGHT_BUFFER_LEN]", Frequency::BATCH) .push_constant(Type::VEC2, "viewportSize") /* Per Object */ .push_constant(Type::VEC3, "gpNormal") @@ -46,6 +48,39 @@ GPU_SHADER_CREATE_INFO(gpencil_geometry) .depth_write(DepthWrite::ANY) .additional_info("draw_gpencil"); +GPU_SHADER_CREATE_INFO(gpencil_geometry_next) + .do_static_compilation(true) + .define("GP_LIGHT") + .typedef_source("gpencil_defines.h") + .sampler(GPENCIL_SCENE_DEPTH_TEX_SLOT, ImageType::DEPTH_2D, "gpSceneDepthTexture") + .sampler(GPENCIL_MASK_TEX_SLOT, ImageType::FLOAT_2D, "gpMaskTexture") + .sampler(GPENCIL_FILL_TEX_SLOT, ImageType::FLOAT_2D, "gpFillTexture") + .sampler(GPENCIL_STROKE_TEX_SLOT, ImageType::FLOAT_2D, "gpStrokeTexture") + .storage_buf(GPENCIL_OBJECT_SLOT, Qualifier::READ, "gpObject", "gp_object[]") + .storage_buf(GPENCIL_LAYER_SLOT, Qualifier::READ, "gpLayer", "gp_layer[]") + .storage_buf(GPENCIL_MATERIAL_SLOT, Qualifier::READ, "gpMaterial", "gp_materials[]") + .storage_buf(GPENCIL_LIGHT_SLOT, Qualifier::READ, "gpLight", "gp_lights[]") + .uniform_buf(GPENCIL_SCENE_SLOT, "gpScene", "gp_scene") + /* Per Scene */ + .define("viewportSize", "gp_scene.render_size") + /* Per Object */ + .define("gpNormal", "gp_object[resource_id].normal") + .define("gpStrokeOrder3d", "gp_object[resource_id].stroke_order3d") + .define("gpMaterialOffset", "gp_object[resource_id].material_offset") + /* Per Layer */ + .define("layer_id", "gp_object[resource_id].layer_offset") /* TODO */ + .define("gpVertexColorOpacity", "gp_layer[layer_id].vertex_color_opacity") + .define("gpLayerTint", "gp_layer[layer_id].tint") + .define("gpLayerOpacity", "gp_layer[layer_id].opacity") + .define("gpStrokeIndexOffset", "gp_layer[layer_id].stroke_index_offset") + .fragment_out(0, Type::VEC4, "fragColor") + .fragment_out(1, Type::VEC4, "revealColor") + .vertex_out(gpencil_geometry_iface) + .vertex_source("gpencil_vert.glsl") + .fragment_source("gpencil_frag.glsl") + .additional_info("draw_gpencil_new") + .depth_write(DepthWrite::ANY); + /** \} */ /* -------------------------------------------------------------------- */ diff --git a/source/blender/draw/engines/overlay/overlay_engine.cc b/source/blender/draw/engines/overlay/overlay_engine.cc index b78bed0f2fa..b37df646f36 100644 --- a/source/blender/draw/engines/overlay/overlay_engine.cc +++ b/source/blender/draw/engines/overlay/overlay_engine.cc @@ -340,7 +340,8 @@ static void OVERLAY_cache_populate(void *vedata, Object *ob) OB_GPENCIL_LEGACY, OB_CURVES, OB_POINTCLOUD, - OB_VOLUME); + OB_VOLUME, + OB_GREASE_PENCIL); const bool draw_surface = (ob->dt >= OB_WIRE) && (renderable || (ob->dt == OB_WIRE)); const bool draw_facing = draw_surface && (pd->overlay.flag & V3D_OVERLAY_FACE_ORIENTATION) && !is_select; diff --git a/source/blender/draw/intern/draw_cache.c b/source/blender/draw/intern/draw_cache.c index 4240126e35f..44dcb9f2fbb 100644 --- a/source/blender/draw/intern/draw_cache.c +++ b/source/blender/draw/intern/draw_cache.c @@ -6,6 +6,7 @@ #include "DNA_curve_types.h" #include "DNA_curves_types.h" +#include "DNA_grease_pencil_types.h" #include "DNA_lattice_types.h" #include "DNA_mesh_types.h" #include "DNA_meta_types.h" @@ -3298,6 +3299,8 @@ void drw_batch_cache_validate(Object *ob) case OB_VOLUME: DRW_volume_batch_cache_validate((Volume *)ob->data); break; + case OB_GREASE_PENCIL: + DRW_grease_pencil_batch_cache_validate((GreasePencil *)ob->data); default: break; } diff --git a/source/blender/draw/intern/draw_cache.h b/source/blender/draw/intern/draw_cache.h index d1f09d235b1..226e326fbbb 100644 --- a/source/blender/draw/intern/draw_cache.h +++ b/source/blender/draw/intern/draw_cache.h @@ -253,7 +253,7 @@ DRWVolumeGrid *DRW_volume_batch_cache_get_grid(struct Volume *volume, struct GPUBatch *DRW_cache_volume_face_wireframe_get(struct Object *ob); struct GPUBatch *DRW_cache_volume_selection_surface_get(struct Object *ob); -/* GPencil */ +/* GPencil (legacy) */ struct GPUBatch *DRW_cache_gpencil_get(struct Object *ob, int cfra); struct GPUVertBuf *DRW_cache_gpencil_position_buffer_get(struct Object *ob, int cfra); @@ -276,6 +276,12 @@ struct bGPDstroke *DRW_cache_gpencil_sbuffer_stroke_data_get(struct Object *ob); */ void DRW_cache_gpencil_sbuffer_clear(struct Object *ob); +/* Grease Pencil */ + +struct GPUBatch *DRW_cache_grease_pencil_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); + #ifdef __cplusplus } #endif diff --git a/source/blender/draw/intern/draw_cache_impl.h b/source/blender/draw/intern/draw_cache_impl.h index 8cc53b48af8..5aa58017ba4 100644 --- a/source/blender/draw/intern/draw_cache_impl.h +++ b/source/blender/draw/intern/draw_cache_impl.h @@ -22,6 +22,7 @@ struct Mesh; struct PointCloud; struct Volume; struct bGPdata; +struct GreasePencil; #include "BKE_mesh_types.h" @@ -63,6 +64,10 @@ void DRW_volume_batch_cache_dirty_tag(struct Volume *volume, int mode); void DRW_volume_batch_cache_validate(struct Volume *volume); void DRW_volume_batch_cache_free(struct Volume *volume); +void DRW_grease_pencil_batch_cache_dirty_tag(struct GreasePencil *grase_pencil, int mode); +void DRW_grease_pencil_batch_cache_validate(struct GreasePencil *grase_pencil); +void DRW_grease_pencil_batch_cache_free(struct GreasePencil *grase_pencil); + /** \} */ /* -------------------------------------------------------------------- */ diff --git a/source/blender/draw/intern/draw_cache_impl_gpencil.cc b/source/blender/draw/intern/draw_cache_impl_gpencil_legacy.cc similarity index 100% rename from source/blender/draw/intern/draw_cache_impl_gpencil.cc rename to source/blender/draw/intern/draw_cache_impl_gpencil_legacy.cc diff --git a/source/blender/draw/intern/draw_cache_impl_grease_pencil.cc b/source/blender/draw/intern/draw_cache_impl_grease_pencil.cc new file mode 100644 index 00000000000..333ae107a7a --- /dev/null +++ b/source/blender/draw/intern/draw_cache_impl_grease_pencil.cc @@ -0,0 +1,523 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2023 Blender Foundation. */ + +/** \file + * \ingroup draw + * + * \brief Grease Pencil API for render engines + */ + +#include "BKE_curves.hh" +#include "BKE_grease_pencil.h" +#include "BKE_grease_pencil.hh" + +#include "BLI_task.hh" + +#include "DNA_grease_pencil_types.h" + +#include "DRW_engine.h" +#include "DRW_render.h" + +#include "GPU_batch.h" + +#include "draw_cache_impl.h" + +#include "../engines/gpencil/gpencil_defines.h" +#include "../engines/gpencil/gpencil_shader_shared.h" + +namespace blender::draw { + +struct GreasePencilBatchCache { + /** Instancing Data */ + GPUVertBuf *vbo; + GPUVertBuf *vbo_col; + /** Indices in material order, then stroke order with fill first. */ + GPUIndexBuf *ibo; + /** Batches */ + GPUBatch *geom_batch; + + /** Cache is dirty. */ + bool is_dirty; + /** Last cached frame. */ + int cache_frame; +}; + +/* -------------------------------------------------------------------- */ +/** \name Vertex Formats + * \{ */ + +/* MUST match the format below. */ +struct GreasePencilStrokeVert { + /** Position and radius packed in the same attribute. */ + float pos[3], radius; + /** Material Index, Stroke Index, Point Index, Packed aspect + hardness + rotation. */ + int32_t mat, stroke_id, point_id, packed_asp_hard_rot; + /** UV and opacity packed in the same attribute. */ + float uv_fill[2], u_stroke, opacity; +}; + +static GPUVertFormat *grease_pencil_stroke_format() +{ + static GPUVertFormat format = {0}; + if (format.attr_len == 0) { + GPU_vertformat_attr_add(&format, "pos", GPU_COMP_F32, 4, GPU_FETCH_FLOAT); + GPU_vertformat_attr_add(&format, "ma", GPU_COMP_I32, 4, GPU_FETCH_INT); + GPU_vertformat_attr_add(&format, "uv", GPU_COMP_F32, 4, GPU_FETCH_FLOAT); + } + return &format; +} + +/* MUST match the format below. */ +struct GreasePencilColorVert { + float vcol[4]; /* Vertex color */ + float fcol[4]; /* Fill color */ +}; + +static GPUVertFormat *grease_pencil_color_format() +{ + static GPUVertFormat format = {0}; + if (format.attr_len == 0) { + GPU_vertformat_attr_add(&format, "col", GPU_COMP_F32, 4, GPU_FETCH_FLOAT); + GPU_vertformat_attr_add(&format, "fcol", GPU_COMP_F32, 4, GPU_FETCH_FLOAT); + } + return &format; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Internal Utilities + * \{ */ + +static bool grease_pencil_batch_cache_valid(const GreasePencil &grease_pencil, int cfra) +{ + BLI_assert(grease_pencil.runtime != nullptr); + const GreasePencilBatchCache *cache = static_cast( + grease_pencil.runtime->batch_cache); + return (cache && cache->is_dirty == false && cache->cache_frame == cfra); +} + +static GreasePencilBatchCache *grease_pencil_batch_cache_init(GreasePencil &grease_pencil, + int cfra) +{ + BLI_assert(grease_pencil.runtime != nullptr); + GreasePencilBatchCache *cache = static_cast( + grease_pencil.runtime->batch_cache); + if (cache == nullptr) { + cache = MEM_new(__func__); + grease_pencil.runtime->batch_cache = cache; + } + else { + *cache = {}; + } + + cache->is_dirty = false; + cache->cache_frame = cfra; + + return cache; +} + +static void grease_pencil_batch_cache_clear(GreasePencil &grease_pencil) +{ + BLI_assert(grease_pencil.runtime != nullptr); + GreasePencilBatchCache *cache = static_cast( + grease_pencil.runtime->batch_cache); + if (cache == nullptr) { + return; + } + + GPU_BATCH_DISCARD_SAFE(cache->geom_batch); + GPU_VERTBUF_DISCARD_SAFE(cache->vbo); + GPU_VERTBUF_DISCARD_SAFE(cache->vbo_col); + GPU_INDEXBUF_DISCARD_SAFE(cache->ibo); + + cache->is_dirty = true; +} + +static GreasePencilBatchCache *grease_pencil_batch_cache_get(GreasePencil &grease_pencil, int cfra) +{ + BLI_assert(grease_pencil.runtime != nullptr); + GreasePencilBatchCache *cache = static_cast( + grease_pencil.runtime->batch_cache); + if (!grease_pencil_batch_cache_valid(grease_pencil, cfra)) { + grease_pencil_batch_cache_clear(grease_pencil); + return grease_pencil_batch_cache_init(grease_pencil, cfra); + } + + return cache; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Vertex Buffers + * \{ */ + +BLI_INLINE int32_t pack_rotation_aspect_hardness(float rot, float asp, float hard) +{ + int32_t packed = 0; + /* Aspect uses 9 bits */ + float asp_normalized = (asp > 1.0f) ? (1.0f / asp) : asp; + packed |= int32_t(unit_float_to_uchar_clamp(asp_normalized)); + /* Store if inversed in the 9th bit. */ + if (asp > 1.0f) { + packed |= 1 << 8; + } + /* Rotation uses 9 bits */ + /* Rotation are in [-90°..90°] range, so we can encode the sign of the angle + the cosine + * because the cosine will always be positive. */ + packed |= int32_t(unit_float_to_uchar_clamp(cosf(rot))) << 9; + /* Store sine sign in 9th bit. */ + if (rot < 0.0f) { + packed |= 1 << 17; + } + /* Hardness uses 8 bits */ + packed |= int32_t(unit_float_to_uchar_clamp(hard)) << 18; + return packed; +} + +static void grease_pencil_batches_ensure(GreasePencil &grease_pencil, int cfra) +{ + BLI_assert(grease_pencil.runtime != nullptr); + GreasePencilBatchCache *cache = static_cast( + grease_pencil.runtime->batch_cache); + + if (cache->vbo != nullptr) { + return; + } + + /* Should be discarded together. */ + BLI_assert(cache->vbo == nullptr && cache->ibo == nullptr); + BLI_assert(cache->geom_batch == nullptr); + + /* Get the visible drawings. */ + Vector drawings; + grease_pencil.foreach_visible_drawing( + cfra, [&](GreasePencilDrawing &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 verticies and triangles. */ + int total_points_num = 0; + int total_triangles_num = 0; + int v_offset = 0; + Vector> verts_start_offsets_per_visible_drawing; + Vector> tris_start_offsets_per_visible_drawing; + for (const int drawing_i : drawings.index_range()) { + const GreasePencilDrawing &drawing = *drawings[drawing_i]; + const bke::CurvesGeometry &curves = drawing.geometry.wrap(); + const OffsetIndices points_by_curve = curves.points_by_curve(); + const VArray cyclic = curves.cyclic(); + + int verts_start_offsets_size = curves.curves_num(); + int tris_start_offsets_size = curves.curves_num(); + if (drawing.has_stroke_buffer()) { + verts_start_offsets_size++; + /* TODO: triangles for stroke buffer. */ + // tris_start_offsets_size++; + } + Array verts_start_offsets(verts_start_offsets_size); + Array tris_start_offsets(tris_start_offsets_size); + + /* Calculate the vertex and triangle offsets for all the curves. */ + int t_offset = 0; + int num_cyclic = 0; + for (const int curve_i : curves.curves_range()) { + IndexRange points = points_by_curve[curve_i]; + const bool is_cyclic = cyclic[curve_i]; + + if (is_cyclic) { + num_cyclic++; + } + + tris_start_offsets[curve_i] = t_offset; + if (points.size() >= 3) { + t_offset += points.size() - 2; + } + + verts_start_offsets[curve_i] = v_offset; + v_offset += 1 + points.size() + (is_cyclic ? 1 : 0) + 1; + } + + /* One vertex is stored before and after as padding. Cyclic strokes have one extra + * vertex.*/ + total_points_num += curves.points_num() + num_cyclic + curves.curves_num() * 2; + total_triangles_num += (curves.points_num() + num_cyclic) * 2; + total_triangles_num += drawing.triangles().size(); + + if (drawing.has_stroke_buffer()) { + const int num_buffer_points = drawing.stroke_buffer().size(); + total_points_num += 1 + num_buffer_points + 1; + total_triangles_num += num_buffer_points * 2; + verts_start_offsets[curves.curves_range().size()] = v_offset; + /* TODO: triangles for stroke buffer. */ + v_offset += 1 + num_buffer_points + 1; + } + + verts_start_offsets_per_visible_drawing.append(std::move(verts_start_offsets)); + tris_start_offsets_per_visible_drawing.append(std::move(tris_start_offsets)); + } + + GPUUsageType vbo_flag = GPU_USAGE_STATIC | GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY; + /* Create VBOs. */ + GPUVertFormat *format = grease_pencil_stroke_format(); + GPUVertFormat *format_col = grease_pencil_color_format(); + cache->vbo = GPU_vertbuf_create_with_format_ex(format, vbo_flag); + cache->vbo_col = GPU_vertbuf_create_with_format_ex(format_col, vbo_flag); + /* Add extra space at the end of the buffer because of quad load. */ + GPU_vertbuf_data_alloc(cache->vbo, total_points_num + 2); + GPU_vertbuf_data_alloc(cache->vbo_col, total_points_num + 2); + + GPUIndexBufBuilder ibo; + MutableSpan verts = { + static_cast(GPU_vertbuf_get_data(cache->vbo)), + GPU_vertbuf_get_vertex_len(cache->vbo)}; + MutableSpan cols = { + static_cast(GPU_vertbuf_get_data(cache->vbo_col)), + GPU_vertbuf_get_vertex_len(cache->vbo_col)}; + /* Create IBO. */ + GPU_indexbuf_init(&ibo, GPU_PRIM_TRIS, total_triangles_num, 0xFFFFFFFFu); + + /* Fill buffers with data. */ + for (const int drawing_i : drawings.index_range()) { + const GreasePencilDrawing &drawing = *drawings[drawing_i]; + const bke::CurvesGeometry &curves = drawing.geometry.wrap(); + const bke::AttributeAccessor attributes = curves.attributes(); + const OffsetIndices points_by_curve = curves.points_by_curve(); + const Span positions = curves.positions(); + const VArray cyclic = curves.cyclic(); + const VArray radii = *attributes.lookup_or_default( + "radius", ATTR_DOMAIN_POINT, 1.0f); + const VArray opacities = *attributes.lookup_or_default( + "opacity", ATTR_DOMAIN_POINT, 1.0f); + const VArray start_caps = *attributes.lookup_or_default( + "start_cap", ATTR_DOMAIN_CURVE, 0); + const VArray end_caps = *attributes.lookup_or_default( + "end_cap", ATTR_DOMAIN_CURVE, 0); + const VArray materials = *attributes.lookup_or_default( + "material_index", ATTR_DOMAIN_CURVE, -1); + const Span triangles = drawing.triangles(); + const Span verts_start_offsets = verts_start_offsets_per_visible_drawing[drawing_i]; + const Span tris_start_offsets = tris_start_offsets_per_visible_drawing[drawing_i]; + + auto populate_point = [&](IndexRange verts_range, + int curve_i, + int8_t start_cap, + int8_t end_cap, + int point_i, + int idx, + GreasePencilStrokeVert &s_vert, + GreasePencilColorVert &c_vert) { + copy_v3_v3(s_vert.pos, positions[point_i]); + s_vert.radius = radii[point_i] * ((end_cap == GP_STROKE_CAP_TYPE_ROUND) ? 1.0f : -1.0f); + s_vert.opacity = opacities[point_i] * + ((start_cap == GP_STROKE_CAP_TYPE_ROUND) ? 1.0f : -1.0f); + s_vert.point_id = verts_range[idx]; + s_vert.stroke_id = verts_range.first(); + s_vert.mat = materials[curve_i] % GPENCIL_MATERIAL_BUFFER_LEN; + + /* TODO: Populate rotation, aspect and hardness. */ + s_vert.packed_asp_hard_rot = pack_rotation_aspect_hardness(0.0f, 1.0f, 1.0f); + /* TODO: Populate stroke UVs. */ + s_vert.u_stroke = 0; + /* TODO: Populate fill UVs. */ + s_vert.uv_fill[0] = s_vert.uv_fill[1] = 0; + + /* 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; + 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); + }; + + threading::parallel_for(curves.curves_range(), 512, [&](IndexRange range) { + for (const int curve_i : range) { + IndexRange points = points_by_curve[curve_i]; + const bool is_cyclic = cyclic[curve_i]; + const int verts_start_offset = verts_start_offsets[curve_i]; + const int tris_start_offset = tris_start_offsets[curve_i]; + const int num_verts = 1 + points.size() + (is_cyclic ? 1 : 0) + 1; + IndexRange verts_range = IndexRange(verts_start_offset, num_verts); + MutableSpan verts_slice = verts.slice(verts_range); + MutableSpan cols_slice = cols.slice(verts_range); + + /* First vertex is not drawn. */ + verts_slice.first().mat = -1; + + /* If the stroke has more than 2 points, add the triangle indices to the index buffer. */ + if (points.size() >= 3) { + const Span tris_slice = triangles.slice(tris_start_offset, points.size() - 2); + for (const uint3 tri : tris_slice) { + GPU_indexbuf_add_tri_verts(&ibo, + (verts_range[1] + tri.x) << GP_VERTEX_ID_SHIFT, + (verts_range[1] + tri.y) << GP_VERTEX_ID_SHIFT, + (verts_range[1] + tri.z) << GP_VERTEX_ID_SHIFT); + } + } + + /* Write all the point attributes to the vertex buffers. Create a quad for each point. */ + for (const int i : IndexRange(points.size())) { + const int idx = i + 1; + populate_point(verts_range, + curve_i, + start_caps[curve_i], + end_caps[curve_i], + points[i], + idx, + verts_slice[idx], + cols_slice[idx]); + } + + if (is_cyclic) { + const int idx = points.size() + 1; + populate_point(verts_range, + curve_i, + start_caps[curve_i], + end_caps[curve_i], + points[0], + idx, + verts_slice[idx], + cols_slice[idx]); + } + + /* Last vertex is not drawn. */ + verts_slice.last().mat = -1; + } + }); + + if (drawing.has_stroke_buffer()) { + Span points = drawing.stroke_buffer(); + const int verts_start_offset = verts_start_offsets.last(); + 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 = drawing.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. */ + verts[total_points_num + 0].mat = -1; + verts[total_points_num + 1].mat = -1; + /* Also mark first vert as invalid. */ + verts[0].mat = -1; + + /* Finish the IBO. */ + cache->ibo = GPU_indexbuf_build(&ibo); + /* Create the batches */ + cache->geom_batch = GPU_batch_create(GPU_PRIM_TRIS, cache->vbo, cache->ibo); + /* Allow creation of buffer texture. */ + GPU_vertbuf_use(cache->vbo); + GPU_vertbuf_use(cache->vbo_col); + + cache->is_dirty = false; +} + +/** \} */ + +} // namespace blender::draw + +void DRW_grease_pencil_batch_cache_dirty_tag(GreasePencil *grease_pencil, int mode) +{ + using namespace blender::draw; + BLI_assert(grease_pencil->runtime != nullptr); + GreasePencilBatchCache *cache = static_cast( + grease_pencil->runtime->batch_cache); + if (cache == nullptr) { + return; + } + switch (mode) { + case BKE_GREASEPENCIL_BATCH_DIRTY_ALL: + cache->is_dirty = true; + break; + default: + BLI_assert_unreachable(); + } +} + +void DRW_grease_pencil_batch_cache_validate(GreasePencil *grease_pencil) +{ + using namespace blender::draw; + BLI_assert(grease_pencil->runtime != nullptr); + /* TODO: pass correct frame here? */ + if (!grease_pencil_batch_cache_valid(*grease_pencil, 0)) { + grease_pencil_batch_cache_clear(*grease_pencil); + /* TODO: pass correct frame here? */ + grease_pencil_batch_cache_init(*grease_pencil, 0); + } +} + +void DRW_grease_pencil_batch_cache_free(GreasePencil *grease_pencil) +{ + using namespace blender::draw; + grease_pencil_batch_cache_clear(*grease_pencil); + MEM_delete(static_cast(grease_pencil->runtime->batch_cache)); + grease_pencil->runtime->batch_cache = nullptr; +} + +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, cfra); + grease_pencil_batches_ensure(grease_pencil, cfra); + + return cache->geom_batch; +} + +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, cfra); + grease_pencil_batches_ensure(grease_pencil, cfra); + + return cache->vbo; +} + +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, cfra); + grease_pencil_batches_ensure(grease_pencil, cfra); + + return cache->vbo_col; +} diff --git a/source/blender/draw/intern/draw_manager.c b/source/blender/draw/intern/draw_manager.c index 2406b7651a2..3f0088512b0 100644 --- a/source/blender/draw/intern/draw_manager.c +++ b/source/blender/draw/intern/draw_manager.c @@ -25,6 +25,7 @@ #include "BKE_editmesh.h" #include "BKE_global.h" #include "BKE_gpencil_legacy.h" +#include "BKE_grease_pencil.h" #include "BKE_lattice.h" #include "BKE_main.h" #include "BKE_mball.h" @@ -1286,7 +1287,8 @@ static void drw_engines_enable(ViewLayer *UNUSED(view_layer), drw_engines_enable_from_engine(engine_type, drawtype); if (gpencil_engine_needed && ((drawtype >= OB_SOLID) || !use_xray)) { - use_drw_engine(&draw_engine_gpencil_type); + use_drw_engine(((U.experimental.use_grease_pencil_version3) ? &draw_engine_gpencil_next_type : + &draw_engine_gpencil_type)); } if (is_compositor_enabled()) { @@ -1316,6 +1318,12 @@ static void drw_engines_data_validate(void) * For slow exact check use `DRW_render_check_grease_pencil` */ static bool drw_gpencil_engine_needed(Depsgraph *depsgraph, View3D *v3d) { + if (U.experimental.use_grease_pencil_version3) { + const bool exclude_gpencil_rendering = v3d ? (v3d->object_type_exclude_viewport & + (1 << OB_GREASE_PENCIL)) != 0 : + false; + return (!exclude_gpencil_rendering) && DEG_id_type_any_exists(depsgraph, ID_GP); + } const bool exclude_gpencil_rendering = v3d ? (v3d->object_type_exclude_viewport & (1 << OB_GPENCIL_LEGACY)) != 0 : false; @@ -1880,7 +1888,8 @@ bool DRW_render_check_grease_pencil(Depsgraph *depsgraph) deg_iter_settings.depsgraph = depsgraph; deg_iter_settings.flags = DEG_OBJECT_ITER_FOR_RENDER_ENGINE_FLAGS; DEG_OBJECT_ITER_BEGIN (°_iter_settings, ob) { - if (ob->type == OB_GPENCIL_LEGACY) { + if (ob->type == OB_GPENCIL_LEGACY || + (U.experimental.use_grease_pencil_version3 && ob->type == OB_GREASE_PENCIL)) { if (DRW_object_visibility_in_active_context(ob) & OB_VISIBLE_SELF) { return true; } @@ -1895,10 +1904,13 @@ static void DRW_render_gpencil_to_image(RenderEngine *engine, struct RenderLayer *render_layer, const rcti *rect) { - if (draw_engine_gpencil_type.render_to_image) { + DrawEngineType *draw_engine = U.experimental.use_grease_pencil_version3 ? + &draw_engine_gpencil_next_type : + &draw_engine_gpencil_type; + if (draw_engine->render_to_image) { ViewportEngineData *gpdata = DRW_view_data_engine_data_get_ensure(DST.view_data_active, - &draw_engine_gpencil_type); - draw_engine_gpencil_type.render_to_image(gpdata, engine, render_layer, rect); + draw_engine); + draw_engine->render_to_image(gpdata, engine, render_layer, rect); } } @@ -2470,7 +2482,9 @@ void DRW_draw_select_loop(struct Depsgraph *depsgraph, else if (!draw_surface) { /* grease pencil selection */ if (drw_gpencil_engine_needed(depsgraph, v3d)) { - use_drw_engine(&draw_engine_gpencil_type); + use_drw_engine(((U.experimental.use_grease_pencil_version3) ? + &draw_engine_gpencil_next_type : + &draw_engine_gpencil_type)); } drw_engines_enable_overlays(); @@ -2480,7 +2494,9 @@ void DRW_draw_select_loop(struct Depsgraph *depsgraph, drw_engines_enable_basic(); /* grease pencil selection */ if (drw_gpencil_engine_needed(depsgraph, v3d)) { - use_drw_engine(&draw_engine_gpencil_type); + use_drw_engine(((U.experimental.use_grease_pencil_version3) ? + &draw_engine_gpencil_next_type : + &draw_engine_gpencil_type)); } drw_engines_enable_overlays(); @@ -2652,7 +2668,8 @@ void DRW_draw_depth_loop(struct Depsgraph *depsgraph, drw_manager_init(&DST, viewport, NULL); if (use_gpencil) { - use_drw_engine(&draw_engine_gpencil_type); + use_drw_engine(((U.experimental.use_grease_pencil_version3) ? &draw_engine_gpencil_next_type : + &draw_engine_gpencil_type)); } if (use_basic) { drw_engines_enable_basic(); @@ -3045,6 +3062,7 @@ void DRW_engines_register(void) RE_engines_register(&DRW_engine_viewport_workbench_type); DRW_engine_register(&draw_engine_gpencil_type); + DRW_engine_register(&draw_engine_gpencil_next_type); DRW_engine_register(&draw_engine_overlay_type); DRW_engine_register(&draw_engine_overlay_next_type); @@ -3085,6 +3103,9 @@ void DRW_engines_register(void) BKE_volume_batch_cache_dirty_tag_cb = DRW_volume_batch_cache_dirty_tag; BKE_volume_batch_cache_free_cb = DRW_volume_batch_cache_free; + BKE_grease_pencil_batch_cache_dirty_tag_cb = DRW_grease_pencil_batch_cache_dirty_tag; + BKE_grease_pencil_batch_cache_free_cb = DRW_grease_pencil_batch_cache_free; + BKE_subsurf_modifier_free_gpu_cache_cb = DRW_subdiv_cache_free; } } diff --git a/source/blender/draw/intern/shaders/common_gpencil_lib.glsl b/source/blender/draw/intern/shaders/common_gpencil_lib.glsl index f48c355835a..a171dd4bec2 100644 --- a/source/blender/draw/intern/shaders/common_gpencil_lib.glsl +++ b/source/blender/draw/intern/shaders/common_gpencil_lib.glsl @@ -178,6 +178,13 @@ vec4 gpencil_vertex(vec4 viewport_size, vec4 out_ndc; if (gpencil_is_stroke_vertex()) { + bool show_stroke = flag_test(material_flags, GP_SHOW_STROKE); + if (!show_stroke) { + /* We set the vertex at the camera origin to generate 0 fragments. */ + out_ndc = vec4(0.0, 0.0, -3e36, 0.0); + return out_ndc; + } + bool is_dot = flag_test(material_flags, GP_STROKE_ALIGNMENT); bool is_squares = !flag_test(material_flags, GP_STROKE_DOTS); @@ -330,6 +337,14 @@ vec4 gpencil_vertex(vec4 viewport_size, } else { /* Fill vertex. */ + + bool show_fill = flag_test(material_flags, GP_SHOW_FILL); + if (!show_fill) { + /* We set the vertex at the camera origin to generate 0 fragments. */ + out_ndc = vec4(0.0, 0.0, -3e36, 0.0); + return out_ndc; + } + out_P = transform_point(ModelMatrix, pos1.xyz); out_ndc = point_world_to_ndc(out_P); out_uv = uv1.xy; diff --git a/source/blender/draw/intern/shaders/draw_view_info.hh b/source/blender/draw/intern/shaders/draw_view_info.hh index 0341e9d90be..dbd276ea583 100644 --- a/source/blender/draw/intern/shaders/draw_view_info.hh +++ b/source/blender/draw/intern/shaders/draw_view_info.hh @@ -142,6 +142,22 @@ GPU_SHADER_CREATE_INFO(draw_gpencil) .push_constant(Type::FLOAT, "gpThicknessOffset") .additional_info("draw_modelmat", "draw_object_infos"); +GPU_SHADER_CREATE_INFO(draw_gpencil_new) + .typedef_source("gpencil_shader_shared.h") + .define("DRW_GPENCIL_INFO") + .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("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", + "draw_object_infos_new"); + /** \} */ /* -------------------------------------------------------------------- */ diff --git a/source/blender/editors/animation/anim_channels_defines.c b/source/blender/editors/animation/anim_channels_defines.c index 5c0b6cee136..ca11989a64e 100644 --- a/source/blender/editors/animation/anim_channels_defines.c +++ b/source/blender/editors/animation/anim_channels_defines.c @@ -697,6 +697,8 @@ static int acf_object_icon(bAnimListElem *ale) return ICON_OUTLINER_OB_EMPTY; case OB_GPENCIL_LEGACY: return ICON_OUTLINER_OB_GREASEPENCIL; + case OB_GREASE_PENCIL: + return ICON_OUTLINER_OB_GREASEPENCIL; default: return ICON_OBJECT_DATA; } diff --git a/source/blender/editors/gpencil_legacy/gpencil_ops.c b/source/blender/editors/gpencil_legacy/gpencil_ops.c index 2db2cab7d0b..8cae1655bf1 100644 --- a/source/blender/editors/gpencil_legacy/gpencil_ops.c +++ b/source/blender/editors/gpencil_legacy/gpencil_ops.c @@ -138,6 +138,18 @@ static bool gpencil_stroke_weightmode_poll_with_tool(bContext *C, const char gpe /* Poll callback for stroke painting (draw brush) */ static bool gpencil_stroke_paintmode_draw_poll(bContext *C) { + if (U.experimental.use_grease_pencil_version3) { + Object *object = CTX_data_active_object(C); + if (object == NULL || object->type != OB_GREASE_PENCIL) { + return false; + } + ToolSettings *ts = CTX_data_tool_settings(C); + if (!ts || !ts->gp_paint) { + return false; + } + const Brush *brush = BKE_paint_brush_for_read(&ts->gp_paint->paint); + return WM_toolsystem_active_tool_is_brush(C) && brush->gpencil_tool == GPAINT_TOOL_DRAW; + } return gpencil_stroke_paintmode_poll_with_tool(C, GPAINT_TOOL_DRAW); } diff --git a/source/blender/editors/include/ED_grease_pencil_draw.h b/source/blender/editors/include/ED_grease_pencil_draw.h new file mode 100644 index 00000000000..185fd266720 --- /dev/null +++ b/source/blender/editors/include/ED_grease_pencil_draw.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2023 Blender Foundation. */ + +/** \file + * \ingroup editors + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +void ED_operatortypes_grease_pencil_draw(void); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/source/blender/editors/interface/interface_icons.cc b/source/blender/editors/interface/interface_icons.cc index c56564531e2..49bd4fcce21 100644 --- a/source/blender/editors/interface/interface_icons.cc +++ b/source/blender/editors/interface/interface_icons.cc @@ -2459,6 +2459,8 @@ int UI_icon_from_idcode(const int idcode) case ID_SIM: /* TODO: Use correct icon. */ return ICON_PHYSICS; + case ID_GP: + return ICON_OUTLINER_DATA_GREASEPENCIL; /* No icons for these ID-types. */ case ID_LI: diff --git a/source/blender/editors/interface/interface_templates.cc b/source/blender/editors/interface/interface_templates.cc index 8560c3eddc3..1813e340e0a 100644 --- a/source/blender/editors/interface/interface_templates.cc +++ b/source/blender/editors/interface/interface_templates.cc @@ -1108,7 +1108,7 @@ static const char *template_id_browse_tip(const StructRNA *type) case ID_PA: return N_("Browse Particle Settings to be linked"); case ID_GD_LEGACY: - return N_("Browse Grease Pencil Data to be linked"); + return N_("Browse Grease Pencil (legacy) Data to be linked"); case ID_MC: return N_("Browse Movie Clip to be linked"); case ID_MSK: @@ -1131,6 +1131,8 @@ static const char *template_id_browse_tip(const StructRNA *type) return N_("Browse Volume Data to be linked"); case ID_SIM: return N_("Browse Simulation to be linked"); + case ID_GP: + return N_("Browse Grease Pencil Data to be linked"); /* Use generic text. */ case ID_LI: diff --git a/source/blender/editors/object/CMakeLists.txt b/source/blender/editors/object/CMakeLists.txt index 4d5aa4eba0a..a22d187c0d3 100644 --- a/source/blender/editors/object/CMakeLists.txt +++ b/source/blender/editors/object/CMakeLists.txt @@ -80,6 +80,7 @@ endif() if(WITH_EXPERIMENTAL_FEATURES) add_definitions(-DWITH_SIMULATION_DATABLOCK) add_definitions(-DWITH_POINT_CLOUD) + add_definitions(-DWITH_GREASE_PENCIL_V3) endif() blender_add_lib(bf_editor_object "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") diff --git a/source/blender/editors/object/object_add.cc b/source/blender/editors/object/object_add.cc index 2cacdab3f5b..3516502d8e6 100644 --- a/source/blender/editors/object/object_add.cc +++ b/source/blender/editors/object/object_add.cc @@ -59,7 +59,9 @@ #include "BKE_geometry_set.hh" #include "BKE_gpencil_curve_legacy.h" #include "BKE_gpencil_geom_legacy.h" +#include "BKE_gpencil_legacy.h" #include "BKE_gpencil_modifier_legacy.h" +#include "BKE_grease_pencil.hh" #include "BKE_key.h" #include "BKE_lattice.h" #include "BKE_layer.h" @@ -2788,8 +2790,16 @@ static const EnumPropertyItem convert_target_items[] = { {OB_GPENCIL_LEGACY, "GPENCIL", ICON_OUTLINER_OB_GREASEPENCIL, +#ifdef WITH_GREASE_PENCIL_V3 + "Grease Pencil (legacy)", +#else "Grease Pencil", +#endif +#ifdef WITH_GREASE_PENCIL_V3 + "Grease Pencil (legacy) from Curve or Mesh objects"}, +#else "Grease Pencil from Curve or Mesh objects"}, +#endif #ifdef WITH_POINT_CLOUD {OB_POINTCLOUD, "POINTCLOUD", @@ -2798,6 +2808,13 @@ static const EnumPropertyItem convert_target_items[] = { "Point Cloud from Mesh objects"}, #endif {OB_CURVES, "CURVES", ICON_OUTLINER_OB_CURVES, "Curves", "Curves from evaluated curve data"}, +#ifdef WITH_GREASE_PENCIL_V3 + {OB_GREASE_PENCIL, + "GREASEPENCIL", + ICON_OUTLINER_OB_GREASEPENCIL, + "Grease Pencil", + "Grease Pencil from Grease Pencil (legacy)"}, +#endif {0, nullptr, 0, nullptr, nullptr}, }; @@ -3086,6 +3103,30 @@ static int object_convert_exec(bContext *C, wmOperator *op) } ob_gpencil->actcol = actcol; } + else if (U.experimental.use_grease_pencil_version3 && ob->type == OB_GPENCIL_LEGACY && + target == OB_GREASE_PENCIL) { + ob->flag |= OB_DONE; + + bGPdata *gpd = static_cast(ob->data); + + if (keep_original) { + BLI_assert_unreachable(); + } + else { + newob = ob; + } + + GreasePencil *new_grease_pencil = static_cast( + BKE_id_new(bmain, ID_GP, newob->id.name + 2)); + newob->data = new_grease_pencil; + newob->type = OB_GREASE_PENCIL; + + bke::greasepencil::convert::legacy_gpencil_to_grease_pencil( + *bmain, *new_grease_pencil, *gpd); + + BKE_object_free_derived_caches(newob); + BKE_object_free_modifiers(newob, 0); + } else if (target == OB_CURVES) { ob->flag |= OB_DONE; diff --git a/source/blender/editors/object/object_edit.cc b/source/blender/editors/object/object_edit.cc index 2e79d4d59ef..a4a1b401ce4 100644 --- a/source/blender/editors/object/object_edit.cc +++ b/source/blender/editors/object/object_edit.cc @@ -686,8 +686,8 @@ static bool ED_object_editmode_load_free_ex(Main *bmain, ED_mball_editmball_free(obedit); } } - else if (obedit->type == OB_CURVES) { - /* Curves don't have specific edit mode data, so pass. */ + else if (ELEM(obedit->type, OB_CURVES, OB_GREASE_PENCIL)) { + /* Object doesn't have specific edit mode data, so pass. */ } else { return false; @@ -878,6 +878,10 @@ bool ED_object_editmode_enter_ex(Main *bmain, Scene *scene, Object *ob, int flag ok = true; WM_main_add_notifier(NC_SCENE | ND_MODE | NS_EDITMODE_CURVES, scene); } + else if (ob->type == OB_GREASE_PENCIL) { + ok = true; + WM_main_add_notifier(NC_SCENE | ND_MODE | NS_EDITMODE_GREASE_PENCIL, scene); + } if (ok) { DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); diff --git a/source/blender/editors/object/object_modes.cc b/source/blender/editors/object/object_modes.cc index 66ecf468f70..f440e51ad52 100644 --- a/source/blender/editors/object/object_modes.cc +++ b/source/blender/editors/object/object_modes.cc @@ -82,6 +82,11 @@ static const char *object_mode_op_string(eObjectMode mode) if (mode == OB_MODE_EDIT_GPENCIL) { return "GPENCIL_OT_editmode_toggle"; } + if (U.experimental.use_grease_pencil_version3) { + if (mode == OB_MODE_PAINT_GPENCIL) { + return "GREASE_PENCIL_OT_draw_mode_toggle"; + } + } if (mode == OB_MODE_PAINT_GPENCIL) { return "GPENCIL_OT_paintmode_toggle"; } @@ -147,6 +152,11 @@ bool ED_object_mode_compat_test(const Object *ob, eObjectMode mode) return true; } break; + case OB_GREASE_PENCIL: + if (mode & (OB_MODE_EDIT | OB_MODE_PAINT_GPENCIL)) { + return true; + } + break; } return false; diff --git a/source/blender/editors/object/object_relations.c b/source/blender/editors/object/object_relations.c index 765d5617bd5..628a3ecdc4d 100644 --- a/source/blender/editors/object/object_relations.c +++ b/source/blender/editors/object/object_relations.c @@ -1916,6 +1916,11 @@ static void single_obdata_users( ob->data, BKE_id_copy_ex(bmain, ob->data, NULL, LIB_ID_COPY_DEFAULT | LIB_ID_COPY_ACTIONS)); break; + case OB_GREASE_PENCIL: + ob->data = ID_NEW_SET( + ob->data, + BKE_id_copy_ex(bmain, ob->data, NULL, LIB_ID_COPY_DEFAULT | LIB_ID_COPY_ACTIONS)); + break; default: printf("ERROR %s: can't copy %s\n", __func__, id->name); BLI_assert_msg(0, "This should never happen."); diff --git a/source/blender/editors/render/render_opengl.cc b/source/blender/editors/render/render_opengl.cc index 58f29fd5f82..dfb6fbfad26 100644 --- a/source/blender/editors/render/render_opengl.cc +++ b/source/blender/editors/render/render_opengl.cc @@ -636,6 +636,9 @@ static int gather_frames_to_render_for_id(LibraryIDLinkCallbackData *cb_data) * system that requires specific handling here. */ gather_frames_to_render_for_grease_pencil(oglrender, (bGPdata *)id); break; + case ID_GP: + /* TODO: gather frames. */ + break; } AnimData *adt = BKE_animdata_from_id(id); diff --git a/source/blender/editors/sculpt_paint/CMakeLists.txt b/source/blender/editors/sculpt_paint/CMakeLists.txt index 84761c2f2b3..743b0831d4e 100644 --- a/source/blender/editors/sculpt_paint/CMakeLists.txt +++ b/source/blender/editors/sculpt_paint/CMakeLists.txt @@ -41,6 +41,8 @@ set(SRC curves_sculpt_slide.cc curves_sculpt_smooth.cc curves_sculpt_snake_hook.cc + grease_pencil_draw_ops.cc + grease_pencil_paint.cc paint_canvas.cc paint_cursor.cc paint_curve.cc @@ -86,6 +88,7 @@ set(SRC sculpt_uv.cc curves_sculpt_intern.hh + grease_pencil_intern.hh paint_intern.hh sculpt_intern.hh ) diff --git a/source/blender/editors/sculpt_paint/grease_pencil_draw_ops.cc b/source/blender/editors/sculpt_paint/grease_pencil_draw_ops.cc new file mode 100644 index 00000000000..601ce7a83aa --- /dev/null +++ b/source/blender/editors/sculpt_paint/grease_pencil_draw_ops.cc @@ -0,0 +1,265 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2023 Blender Foundation. */ + +#include "BKE_context.h" +#include "BKE_grease_pencil.hh" + +#include "DEG_depsgraph_query.h" + +#include "DNA_brush_types.h" +#include "DNA_grease_pencil_types.h" + +#include "ED_grease_pencil_draw.h" +#include "ED_object.h" +#include "ED_screen.h" + +#include "RNA_access.h" +#include "RNA_define.h" +#include "RNA_enum_types.h" + +#include "WM_api.h" +#include "WM_message.h" +#include "WM_toolsystem.h" + +#include "grease_pencil_intern.hh" +#include "paint_intern.hh" + +namespace blender::ed::sculpt_paint { + +/* -------------------------------------------------------------------- */ +/** \name Brush Stroke Operator + * \{ */ + +static bool start_brush_operation(bContext &C, + wmOperator & /*op*/, + PaintStroke *paint_stroke, + const StrokeExtension & /*stroke_start*/) +{ + // const BrushStrokeMode mode = static_cast(RNA_enum_get(op.ptr, "mode")); + + const Scene &scene = *CTX_data_scene(&C); + const GpPaint &gp_paint = *scene.toolsettings->gp_paint; + const Brush &brush = *BKE_paint_brush_for_read(&gp_paint.paint); + GreasePencilStrokeOperation *operation = nullptr; + switch (brush.gpencil_tool) { + case GPAINT_TOOL_DRAW: + /* FIXME: Somehow store the unique_ptr in the PaintStroke. */ + operation = greasepencil::new_paint_operation().release(); + break; + } + + if (operation) { + paint_stroke_set_mode_data(paint_stroke, operation); + return true; + } + return false; +} + +static bool stroke_get_location(bContext * /*C*/, + float out[3], + const float mouse[2], + bool /*force_original*/) +{ + out[0] = mouse[0]; + out[1] = mouse[1]; + out[2] = 0; + return true; +} + +static bool stroke_test_start(bContext *C, struct wmOperator *op, const float mouse[2]) +{ + PaintStroke *paint_stroke = static_cast(op->customdata); + + StrokeExtension stroke_extension; + stroke_extension.mouse_position = float2(mouse); + stroke_extension.pressure = 0.0f; + stroke_extension.is_first = true; + + if (!start_brush_operation(*C, *op, paint_stroke, stroke_extension)) { + return false; + } + + return true; +} + +static void stroke_update_step(bContext *C, + wmOperator * /*op*/, + PaintStroke *stroke, + PointerRNA *stroke_element) +{ + GreasePencilStrokeOperation *operation = static_cast( + paint_stroke_mode_data(stroke)); + + StrokeExtension stroke_extension; + RNA_float_get_array(stroke_element, "mouse", stroke_extension.mouse_position); + stroke_extension.pressure = RNA_float_get(stroke_element, "pressure"); + stroke_extension.is_first = false; + + if (operation) { + operation->on_stroke_extended(*C, stroke_extension); + } +} + +static void stroke_redraw(const bContext *C, PaintStroke * /*stroke*/, bool /*final*/) +{ + ED_region_tag_redraw(CTX_wm_region(C)); +} + +static void stroke_done(const bContext *C, PaintStroke *stroke) +{ + GreasePencilStrokeOperation *operation = static_cast( + paint_stroke_mode_data(stroke)); + operation->on_stroke_done(*C); +} + +static int grease_pencil_stroke_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + 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; + } + + op->customdata = paint_stroke_new(C, + op, + stroke_get_location, + stroke_test_start, + stroke_update_step, + stroke_redraw, + stroke_done, + event->type); + + const int return_value = op->type->modal(C, op, event); + if (return_value == OPERATOR_FINISHED) { + return OPERATOR_FINISHED; + } + + WM_event_add_modal_handler(C, op); + return OPERATOR_RUNNING_MODAL; +} + +static int grease_pencil_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + return paint_stroke_modal(C, op, event, reinterpret_cast(&op->customdata)); +} + +static void grease_pencil_stroke_cancel(bContext *C, wmOperator *op) +{ + paint_stroke_cancel(C, op, static_cast(op->customdata)); +} + +static void GREASE_PENCIL_OT_brush_stroke(struct wmOperatorType *ot) +{ + ot->name = "Stroke Curves Sculpt"; + ot->idname = "GREASE_PENCIL_OT_brush_stroke"; + ot->description = "Sculpt curves using a brush"; + + ot->invoke = grease_pencil_stroke_invoke; + ot->modal = grease_pencil_stroke_modal; + ot->cancel = grease_pencil_stroke_cancel; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + paint_stroke_operator_properties(ot); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Toggle Draw Mode + * \{ */ + +static bool grease_pencil_poll(bContext *C) +{ + Object *object = CTX_data_active_object(C); + if (object == nullptr || object->type != OB_GREASE_PENCIL) { + return false; + } + return true; +} + +static void grease_pencil_draw_mode_enter(bContext *C) +{ + Scene *scene = CTX_data_scene(C); + wmMsgBus *mbus = CTX_wm_message_bus(C); + + Object *ob = CTX_data_active_object(C); + GpPaint *grease_pencil_paint = scene->toolsettings->gp_paint; + BKE_paint_ensure(scene->toolsettings, (Paint **)&grease_pencil_paint); + + ob->mode = OB_MODE_PAINT_GPENCIL; + + /* TODO: Setup cursor color. BKE_paint_init() could be used, but creates an additional brush. */ + /* TODO: Call ED_paint_cursor_start(...) */ + + paint_init_pivot(ob, scene); + + /* Necessary to change the object mode on the evaluated object. */ + DEG_id_tag_update(&ob->id, ID_RECALC_COPY_ON_WRITE); + WM_msg_publish_rna_prop(mbus, &ob->id, ob, Object, mode); + WM_event_add_notifier(C, NC_SCENE | ND_MODE, nullptr); +} + +static void grease_pencil_draw_mode_exit(bContext *C) +{ + Object *ob = CTX_data_active_object(C); + ob->mode = OB_MODE_OBJECT; +} + +static int grease_pencil_draw_mode_toggle_exec(bContext *C, wmOperator *op) +{ + Object *ob = CTX_data_active_object(C); + wmMsgBus *mbus = CTX_wm_message_bus(C); + + const bool is_mode_set = ob->mode == OB_MODE_PAINT_GPENCIL; + + if (is_mode_set) { + if (!ED_object_mode_compat_set(C, ob, OB_MODE_PAINT_GPENCIL, op->reports)) { + return OPERATOR_CANCELLED; + } + } + + if (is_mode_set) { + grease_pencil_draw_mode_exit(C); + } + else { + grease_pencil_draw_mode_enter(C); + } + + WM_toolsystem_update_from_context_view3d(C); + + /* Necessary to change the object mode on the evaluated object. */ + DEG_id_tag_update(&ob->id, ID_RECALC_COPY_ON_WRITE); + WM_msg_publish_rna_prop(mbus, &ob->id, ob, Object, mode); + WM_event_add_notifier(C, NC_SCENE | ND_MODE, nullptr); + return OPERATOR_FINISHED; +} + +static void GREASE_PENCIL_OT_draw_mode_toggle(wmOperatorType *ot) +{ + ot->name = "Grease Pencil Draw Mode Toggle"; + ot->idname = "GREASE_PENCIL_OT_draw_mode_toggle"; + ot->description = "Enter/Exit draw mode for grease pencil"; + + ot->exec = grease_pencil_draw_mode_toggle_exec; + ot->poll = grease_pencil_poll; + + ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER; +} + +/** \} */ + +} // namespace blender::ed::sculpt_paint + +/* -------------------------------------------------------------------- */ +/** \name Registration + * \{ */ + +void ED_operatortypes_grease_pencil_draw() +{ + using namespace blender::ed::sculpt_paint; + WM_operatortype_append(GREASE_PENCIL_OT_brush_stroke); + WM_operatortype_append(GREASE_PENCIL_OT_draw_mode_toggle); +} + +/** \} */ \ No newline at end of file diff --git a/source/blender/editors/sculpt_paint/grease_pencil_intern.hh b/source/blender/editors/sculpt_paint/grease_pencil_intern.hh new file mode 100644 index 00000000000..2fb3053c086 --- /dev/null +++ b/source/blender/editors/sculpt_paint/grease_pencil_intern.hh @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2023 Blender Foundation. */ + +#pragma once + +#include "paint_intern.hh" + +#include "BLI_math_vector.hh" + +namespace blender::ed::sculpt_paint { + +struct StrokeExtension { + bool is_first; + float2 mouse_position; + float pressure; +}; + +class GreasePencilStrokeOperation { + public: + virtual ~GreasePencilStrokeOperation() = default; + virtual void on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension) = 0; + virtual void on_stroke_done(const bContext &C) = 0; +}; + +namespace greasepencil { + +std::unique_ptr new_paint_operation(); + +} // namespace greasepencil + +} // namespace blender::ed::sculpt_paint \ No newline at end of file diff --git a/source/blender/editors/sculpt_paint/grease_pencil_paint.cc b/source/blender/editors/sculpt_paint/grease_pencil_paint.cc new file mode 100644 index 00000000000..275e210aacd --- /dev/null +++ b/source/blender/editors/sculpt_paint/grease_pencil_paint.cc @@ -0,0 +1,176 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2023 Blender Foundation. */ + +#include "BKE_context.h" +#include "BKE_curves.hh" +#include "BKE_grease_pencil.h" +#include "BKE_grease_pencil.hh" +#include "BKE_scene.h" + +#include "DEG_depsgraph_query.h" + +#include "ED_view3d.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "grease_pencil_intern.hh" + +namespace blender::ed::sculpt_paint::greasepencil { + +class PaintOperation : public GreasePencilStrokeOperation { + + public: + ~PaintOperation() override {} + + void on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension) override; + void on_stroke_done(const bContext &C) override; +}; + +/** + * Utility class that actually executes the update when the stroke is updated. That's useful + * because it avoids passing a very large number of parameters between functions. + */ +struct PaintOperationExecutor { + + PaintOperationExecutor(const bContext & /*C*/) {} + + void execute(PaintOperation & /*self*/, + const bContext &C, + const StrokeExtension &stroke_extension) + { + using namespace blender::bke; + Depsgraph *depsgraph = CTX_data_depsgraph_pointer(&C); + Scene *scene = CTX_data_scene(&C); + ARegion *region = CTX_wm_region(&C); + Object *obact = CTX_data_active_object(&C); + Object *ob_eval = DEG_get_evaluated_object(depsgraph, obact); + + /** + * 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); + if (!grease_pencil.has_active_layer()) { + /* TODO: create a new layer. */ + BLI_assert_unreachable(); + // grease_pencil.runtime->set_active_layer_index(0); + } + const bke::greasepencil::Layer &active_layer = grease_pencil.active_layer->wrap(); + int index = active_layer.drawing_index_at(scene->r.cfra); + BLI_assert(index != -1); + + GreasePencilDrawing &drawing = *reinterpret_cast( + grease_pencil.drawings()[index]); + + float4 plane{0.0f, -1.0f, 0.0f, 0.0f}; + float3 proj_pos; + ED_view3d_win_to_3d_on_plane(region, plane, stroke_extension.mouse_position, false, proj_pos); + + bke::greasepencil::StrokePoint new_point{ + proj_pos, stroke_extension.pressure * 100.0f, 1.0f, float4(1.0f)}; + + drawing.runtime->stroke_cache.points.append(std::move(new_point)); + + BKE_grease_pencil_batch_cache_dirty_tag(&grease_pencil, BKE_GREASEPENCIL_BATCH_DIRTY_ALL); + } +}; + +void PaintOperation::on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension) +{ + PaintOperationExecutor executor{C}; + executor.execute(*this, C, stroke_extension); +} + +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 *obact = CTX_data_active_object(&C); + Object *ob_eval = DEG_get_evaluated_object(depsgraph, obact); + + 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() && grease_pencil_eval.has_active_layer()); + const bke::greasepencil::Layer &active_layer_orig = grease_pencil_orig.active_layer->wrap(); + const bke::greasepencil::Layer &active_layer_eval = grease_pencil_eval.active_layer->wrap(); + int index_orig = active_layer_orig.drawing_index_at(scene->r.cfra); + int index_eval = active_layer_eval.drawing_index_at(scene->r.cfra); + BLI_assert(index_orig != -1 && index_eval != -1); + + GreasePencilDrawing &drawing_orig = *reinterpret_cast( + grease_pencil_orig.drawings()[index_orig]); + GreasePencilDrawing &drawing_eval = *reinterpret_cast( + grease_pencil_eval.drawings()[index_eval]); + + const Span stroke_points = drawing_eval.stroke_buffer(); + CurvesGeometry &curves = drawing_orig.geometry.wrap(); + + 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(); + SpanAttributeWriter radii = attributes.lookup_for_write_span("radius"); + SpanAttributeWriter opacities = attributes.lookup_for_write_span("opacity"); + 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.span[point_i] = point.radius; + opacities.span[point_i] = point.opacity; + } + + /* Set material index attribute. */ + int material_index = 0; + SpanAttributeWriter materials = attributes.lookup_for_write_span("material_index"); + 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; + }); + + drawing_eval.runtime->stroke_cache.clear(); + drawing_orig.tag_positions_changed(); + + radii.finish(); + opacities.finish(); + 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() +{ + return std::make_unique(); +} + +} // namespace blender::ed::sculpt_paint::greasepencil \ No newline at end of file diff --git a/source/blender/editors/sculpt_paint/paint_image.cc b/source/blender/editors/sculpt_paint/paint_image.cc index 72fb2e8274d..37df1b661b8 100644 --- a/source/blender/editors/sculpt_paint/paint_image.cc +++ b/source/blender/editors/sculpt_paint/paint_image.cc @@ -814,6 +814,12 @@ static void paint_init_pivot_curves(Object *ob, float location[3]) 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); +} + void paint_init_pivot(Object *ob, Scene *scene) { UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings; @@ -826,6 +832,9 @@ void paint_init_pivot(Object *ob, Scene *scene) case OB_CURVES: paint_init_pivot_curves(ob, location); break; + case OB_GREASE_PENCIL: + paint_init_pivot_grease_pencil(ob, location); + break; default: BLI_assert_unreachable(); ups->last_stroke_valid = false; diff --git a/source/blender/editors/space_api/spacetypes.c b/source/blender/editors/space_api/spacetypes.c index 56aa0104329..6e8bd697ec2 100644 --- a/source/blender/editors/space_api/spacetypes.c +++ b/source/blender/editors/space_api/spacetypes.c @@ -34,6 +34,7 @@ #include "ED_geometry.h" #include "ED_gizmo_library.h" #include "ED_gpencil_legacy.h" +#include "ED_grease_pencil_draw.h" #include "ED_lattice.h" #include "ED_markers.h" #include "ED_mask.h" @@ -91,6 +92,7 @@ void ED_spacetypes_init(void) ED_operatortypes_anim(); ED_operatortypes_animchannels(); ED_operatortypes_asset(); + ED_operatortypes_grease_pencil_draw(); ED_operatortypes_gpencil(); ED_operatortypes_object(); ED_operatortypes_lattice(); diff --git a/source/blender/editors/space_info/info_stats.cc b/source/blender/editors/space_info/info_stats.cc index 19dbd60820f..8d3b1eab313 100644 --- a/source/blender/editors/space_info/info_stats.cc +++ b/source/blender/editors/space_info/info_stats.cc @@ -186,7 +186,8 @@ static void stats_object(Object *ob, } case OB_CURVES: case OB_POINTCLOUD: - case OB_VOLUME: { + case OB_VOLUME: + case OB_GREASE_PENCIL: { break; } } diff --git a/source/blender/editors/space_outliner/outliner_draw.cc b/source/blender/editors/space_outliner/outliner_draw.cc index 009835ef83f..4fb50579d42 100644 --- a/source/blender/editors/space_outliner/outliner_draw.cc +++ b/source/blender/editors/space_outliner/outliner_draw.cc @@ -2368,6 +2368,8 @@ static BIFIconID tree_element_get_icon_from_id(const ID *id) } case OB_GPENCIL_LEGACY: return ICON_OUTLINER_OB_GREASEPENCIL; + case OB_GREASE_PENCIL: + return ICON_OUTLINER_OB_GREASEPENCIL; } return ICON_NONE; diff --git a/source/blender/editors/space_outliner/outliner_intern.hh b/source/blender/editors/space_outliner/outliner_intern.hh index a8a5002761b..2662fd3a708 100644 --- a/source/blender/editors/space_outliner/outliner_intern.hh +++ b/source/blender/editors/space_outliner/outliner_intern.hh @@ -133,7 +133,8 @@ struct TreeElementIcon { ID_CV, \ ID_PT, \ ID_VO, \ - ID_SIM) || /* Only in 'blendfile' mode ... :/ */ \ + ID_SIM, \ + ID_GP) || /* Only in 'blendfile' mode ... :/ */ \ ELEM(GS((_id)->name), \ ID_SCR, \ ID_WM, \ diff --git a/source/blender/editors/space_outliner/outliner_tools.cc b/source/blender/editors/space_outliner/outliner_tools.cc index 656a06f09bb..411574634a4 100644 --- a/source/blender/editors/space_outliner/outliner_tools.cc +++ b/source/blender/editors/space_outliner/outliner_tools.cc @@ -165,6 +165,7 @@ static void get_element_operation_type( case ID_PT: case ID_VO: case ID_SIM: + case ID_GP: is_standard_id = true; break; case ID_WM: diff --git a/source/blender/editors/space_outliner/tree/tree_element_id.cc b/source/blender/editors/space_outliner/tree/tree_element_id.cc index d4f6865fb75..668d0966bc6 100644 --- a/source/blender/editors/space_outliner/tree/tree_element_id.cc +++ b/source/blender/editors/space_outliner/tree/tree_element_id.cc @@ -74,6 +74,7 @@ std::unique_ptr TreeElementID::createFromID(TreeElement &legacy_t case ID_PAL: case ID_PC: case ID_CF: + case ID_GP: return std::make_unique(legacy_te, id); case ID_IP: BLI_assert_unreachable(); diff --git a/source/blender/editors/util/CMakeLists.txt b/source/blender/editors/util/CMakeLists.txt index 90f81030669..35e79e1e792 100644 --- a/source/blender/editors/util/CMakeLists.txt +++ b/source/blender/editors/util/CMakeLists.txt @@ -53,6 +53,7 @@ set(SRC ../include/ED_gizmo_library.h ../include/ED_gizmo_utils.h ../include/ED_gpencil_legacy.h + ../include/ED_grease_pencil_draw.h ../include/ED_image.h ../include/ED_info.h ../include/ED_keyframes_draw.h diff --git a/source/blender/gpu/CMakeLists.txt b/source/blender/gpu/CMakeLists.txt index 5e2ac715a7e..cdc676c6f5f 100644 --- a/source/blender/gpu/CMakeLists.txt +++ b/source/blender/gpu/CMakeLists.txt @@ -29,6 +29,7 @@ set(INC # For *_info.hh includes. ../compositor/realtime_compositor ../draw/engines/eevee_next + ../draw/engines/gpencil ../draw/engines/select ../draw/engines/workbench ../draw/intern diff --git a/source/blender/io/usd/intern/usd_hierarchy_iterator.cc b/source/blender/io/usd/intern/usd_hierarchy_iterator.cc index 2a8cddb30ab..2b0f978a5e5 100644 --- a/source/blender/io/usd/intern/usd_hierarchy_iterator.cc +++ b/source/blender/io/usd/intern/usd_hierarchy_iterator.cc @@ -129,6 +129,7 @@ AbstractHierarchyWriter *USDHierarchyIterator::create_data_writer(const Hierarch case OB_LATTICE: case OB_ARMATURE: case OB_GPENCIL_LEGACY: + case OB_GREASE_PENCIL: case OB_POINTCLOUD: return nullptr; case OB_TYPE_MAX: diff --git a/source/blender/makesdna/DNA_ID.h b/source/blender/makesdna/DNA_ID.h index 0fee0f962e1..dbbca6aadcb 100644 --- a/source/blender/makesdna/DNA_ID.h +++ b/source/blender/makesdna/DNA_ID.h @@ -1151,6 +1151,7 @@ typedef enum IDRecalcFlag { #define FILTER_ID_SCR (1ULL << 37) #define FILTER_ID_WM (1ULL << 38) #define FILTER_ID_LI (1ULL << 39) +#define FILTER_ID_GP (1ULL << 40) #define FILTER_ID_ALL \ (FILTER_ID_AC | FILTER_ID_AR | FILTER_ID_BR | FILTER_ID_CA | FILTER_ID_CU_LEGACY | \ @@ -1159,7 +1160,7 @@ typedef enum IDRecalcFlag { FILTER_ID_NT | FILTER_ID_OB | FILTER_ID_PA | FILTER_ID_PAL | FILTER_ID_PC | FILTER_ID_SCE | \ FILTER_ID_SPK | FILTER_ID_SO | FILTER_ID_TE | FILTER_ID_TXT | FILTER_ID_VF | FILTER_ID_WO | \ FILTER_ID_CF | FILTER_ID_WS | FILTER_ID_LP | FILTER_ID_CV | FILTER_ID_PT | FILTER_ID_VO | \ - FILTER_ID_SIM | FILTER_ID_KE | FILTER_ID_SCR | FILTER_ID_WM | FILTER_ID_LI) + FILTER_ID_SIM | FILTER_ID_KE | FILTER_ID_SCR | FILTER_ID_WM | FILTER_ID_LI | FILTER_ID_GP) /** * This enum defines the index assigned to each type of IDs in the array returned by @@ -1250,6 +1251,7 @@ enum { INDEX_ID_CA, INDEX_ID_SPK, INDEX_ID_LP, + INDEX_ID_GP, /* Collection and object types. */ INDEX_ID_OB, diff --git a/source/blender/makesdna/DNA_ID_enums.h b/source/blender/makesdna/DNA_ID_enums.h index d632c062a1a..e45ce35ac32 100644 --- a/source/blender/makesdna/DNA_ID_enums.h +++ b/source/blender/makesdna/DNA_ID_enums.h @@ -82,6 +82,7 @@ typedef enum ID_Type { ID_PT = MAKE_ID2('P', 'T'), /* PointCloud */ ID_VO = MAKE_ID2('V', 'O'), /* Volume */ ID_SIM = MAKE_ID2('S', 'I'), /* Simulation (geometry node groups) */ + ID_GP = MAKE_ID2('G', 'P'), /* Grease Pencil */ } ID_Type; /* Only used as 'placeholder' in .blend files for directly linked data-blocks. */ diff --git a/source/blender/makesdna/DNA_grease_pencil_types.h b/source/blender/makesdna/DNA_grease_pencil_types.h new file mode 100644 index 00000000000..7a5e3fe1487 --- /dev/null +++ b/source/blender/makesdna/DNA_grease_pencil_types.h @@ -0,0 +1,466 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2023 Blender Foundation. */ + +/** \file + * \ingroup DNA + */ + +#pragma once + +#include "DNA_ID.h" +#include "DNA_curves_types.h" +#include "DNA_listBase.h" + +#ifdef __cplusplus +# include "BLI_function_ref.hh" +# include "BLI_map.hh" +# include "BLI_math_vector_types.hh" +# include "BLI_span.hh" +namespace blender::bke { +class GreasePencilRuntime; +class GreasePencilDrawingRuntime; +namespace greasepencil { +class DrawingRuntime; +class TreeNode; +class Layer; +class LayerRuntime; +class LayerGroup; +class LayerGroupRuntime; +struct StrokePoint; +} // namespace greasepencil +} // namespace blender::bke +using GreasePencilRuntimeHandle = blender::bke::GreasePencilRuntime; +using GreasePencilDrawingRuntimeHandle = blender::bke::greasepencil::DrawingRuntime; +using GreasePencilLayerRuntimeHandle = blender::bke::greasepencil::LayerRuntime; +using GreasePencilLayerGroupRuntimeHandle = blender::bke::greasepencil::LayerGroupRuntime; +#else +typedef struct GreasePencilRuntimeHandle GreasePencilRuntimeHandle; +typedef struct GreasePencilDrawingRuntimeHandle GreasePencilDrawingRuntimeHandle; +typedef struct GreasePencilLayerRuntimeHandle GreasePencilLayerRuntimeHandle; +typedef struct GreasePencilLayerGroupRuntimeHandle GreasePencilLayerGroupRuntimeHandle; +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +struct GreasePencil; +struct BlendDataReader; +struct BlendWriter; +struct Object; + +typedef enum GreasePencilStrokeCapType { + GP_STROKE_CAP_TYPE_ROUND = 0, + GP_STROKE_CAP_TYPE_FLAT = 1, + /* Keep last. */ + GP_STROKE_CAP_TYPE_MAX, +} GreasePencilStrokeCapType; + +/** + * Type of drawing data. + * If `GP_DRAWING` the node is a `GreasePencilDrawing`, + * if `GP_DRAWING_REFERENCE` the node is a `GreasePencilDrawingReference`. + */ +typedef enum GreasePencilDrawingType { + GP_DRAWING = 0, + GP_DRAWING_REFERENCE = 1, +} GreasePencilDrawingType; + +/** + * Flag for drawings and drawing references. #GreasePencilDrawingBase.flag + */ +typedef enum GreasePencilDrawingBaseFlag { + /* TODO */ + GreasePencilDrawingBaseFlag_TODO +} GreasePencilDrawingBaseFlag; + +/** + * Base class for drawings and drawing references (drawings from other objects). + */ +typedef struct GreasePencilDrawingBase { + /** + * One of `GreasePencilDrawingType`. + * Indicates if this is an actual drawing or a drawing referenced from another object. + */ + int8_t type; + char _pad[3]; + /** + * Flag. Used to set e.g. the selection status. See `GreasePencilDrawingBaseFlag`. + */ + uint32_t flag; +} GreasePencilDrawingBase; + +/** + * A grease pencil drawing is a set of strokes. The data is stored using the `CurvesGeometry` data + * structure and the custom attributes within it. + */ +typedef struct GreasePencilDrawing { + GreasePencilDrawingBase base; + /** + * The stroke data for this drawing. + */ + CurvesGeometry geometry; + /** + * Runtime data on the drawing. + */ + GreasePencilDrawingRuntimeHandle *runtime; +#ifdef __cplusplus + /** + * The triangles for all the fills in the geometry. + */ + blender::Span triangles() const; + void tag_positions_changed(); + /** + * A buffer for a single stroke while drawing. + */ + blender::Span stroke_buffer() const; + bool has_stroke_buffer() const; +#endif +} GreasePencilDrawing; + +typedef struct GreasePencilDrawingReference { + GreasePencilDrawingBase base; + /** + * A reference to another GreasePencil data-block. + * If the data-block has multiple drawings, this drawing references all of them sequentially. + * See the note in `GreasePencilLayer->frames()` for a detailed explanation of this. + */ + struct GreasePencil *id_reference; +} GreasePencilDrawingReference; + +/** + * Flag for grease pencil frames. #GreasePencilFrame.flag + */ +typedef enum GreasePencilFrameFlag { + GP_FRAME_SELECTED = (1 << 0), +} GreasePencilFrameFlag; + +/** + * A GreasePencilFrame is a single keyframe in the timeline. + * It references a drawing by index into the drawing array. + */ +typedef struct GreasePencilFrame { + /** + * Index into the GreasePencil->drawings array. + */ + int drawing_index; + /** + * Flag. Used to set e.g. the selection. + */ + uint32_t flag; + /** + * Keyframe type. See `eBezTriple_KeyframeType`. + */ + int8_t type; + char _pad[3]; +} GreasePencilFrame; + +typedef enum GreasePencilLayerFramesMapStorageFlag { + GP_LAYER_FRAMES_STORAGE_DIRTY = (1 << 0), +} GreasePencilLayerFramesMapStorageFlag; + +/** + * Storage for the Map in `blender::bke::greasepencil::Layer`. + * See the description there for more detail. + */ +typedef struct GreasePencilLayerFramesMapStorage { + /* Array of `frames` keys (sorted in ascending order). */ + int *keys; + /* Array of `frames` values (order matches the keys array). */ + GreasePencilFrame *values; + /* Size of the map (number of key-value pairs). */ + int size; + /* Flag for the status of the storage. */ + int flag; +} GreasePencilLayerFramesMapStorage; + +/** + * Flag for layer masks. #GreasePencilLayerMask.flag + */ +typedef enum GreasePencilLayerMaskFlag { + GP_LAYER_MASK_HIDE = (1 << 0), + GP_LAYER_MASK_INVERT = (1 << 1), +} GreasePencilLayerMaskFlag; + +/** + * A grease pencil layer mask stores the name of a layer that is the mask. + */ +typedef struct GreasePencilLayerMask { + struct GreasePencilLayerMask *next, *prev; + /** + * The name of the layer that is the mask. + */ + char *layer_name; + /** + * Layer mask flag. See `GreasePencilLayerMaskFlag`. + */ + uint16_t flag; + char _pad[6]; +} GreasePencilLayerMask; + +/** + * Layer blending modes. #GreasePencilLayer.blend_mode + */ +typedef enum GreasePencilLayerBlendMode { + GP_LAYER_BLEND_NONE = 0, + GP_LAYER_BLEND_HARDLIGHT = 1, + GP_LAYER_BLEND_ADD = 2, + GP_LAYER_BLEND_SUBTRACT = 3, + GP_LAYER_BLEND_MULTIPLY = 4, + GP_LAYER_BLEND_DIVIDE = 5, +} GreasePencilLayerBlendMode; + +/** + * Type of layer node. + * If `GP_LAYER_TREE_LEAF` the node is a `GreasePencilLayerTreeLeaf`, + * if `GP_LAYER_TREE_GROUP` the node is a `GreasePencilLayerTreeGroup`. + */ +typedef enum GreasePencilLayerTreeNodeType { + GP_LAYER_TREE_LEAF = 0, + GP_LAYER_TREE_GROUP = 1, +} GreasePencilLayerTreeNodeType; + +/** + * Flags for layer tree nodes. #GreasePencilLayerTreeNode.flag + */ +typedef enum GreasePencilLayerTreeNodeFlag { + GP_LAYER_TREE_NODE_HIDE = (1 << 0), + GP_LAYER_TREE_NODE_LOCKED = (1 << 1), + GP_LAYER_TREE_NODE_SELECT = (1 << 2), + GP_LAYER_TREE_NODE_MUTE = (1 << 3), + GP_LAYER_TREE_NODE_USE_LIGHTS = (1 << 4), + GP_LAYER_TREE_NODE_USE_ONION_SKINNING = (1 << 5), +} GreasePencilLayerTreeNodeFlag; + +struct GreasePencilLayerTreeGroup; +typedef struct GreasePencilLayerTreeNode { + /* ListBase pointers. */ + struct GreasePencilLayerTreeNode *next, *prev; + /* Parent pointer. Can be null. */ + struct GreasePencilLayerTreeGroup *parent; + /** + * Name of the layer/group. Dynamic length. + */ + char *name; + /** + * One of `GreasePencilLayerTreeNodeType`. + * Indicates the type of struct this element is. + */ + int8_t type; + /** + * Color tag. + */ + uint8_t color[3]; + /** + * Flag. Used to set e.g. the selection, visibility, ... status. + * See `GreasePencilLayerTreeNodeFlag`. + */ + uint32_t flag; +#ifdef __cplusplus + blender::bke::greasepencil::TreeNode &wrap(); + const blender::bke::greasepencil::TreeNode &wrap() const; +#endif +} GreasePencilLayerTreeNode; + +/** + * A grease pencil layer is a collection of drawings mapped to a specific time on the timeline. + */ +typedef struct GreasePencilLayer { + GreasePencilLayerTreeNode base; + /* Only used for storage in the .blend file. */ + GreasePencilLayerFramesMapStorage frames_storage; + /** + * Layer blend mode. See `GreasePencilLayerBlendMode`. + */ + int8_t blend_mode; + char _pad[3]; + /** + * Opacity of the layer. + */ + float opacity; + /** + * List of `GreasePencilLayerMask`. + */ + ListBase masks; + /** + * Runtime struct pointer. + */ + GreasePencilLayerRuntimeHandle *runtime; +#ifdef __cplusplus + blender::bke::greasepencil::Layer &wrap(); + const blender::bke::greasepencil::Layer &wrap() const; +#endif +} GreasePencilLayer; + +typedef struct GreasePencilLayerTreeGroup { + GreasePencilLayerTreeNode base; + /** + * List of `GreasePencilLayerTreeNode`. + */ + ListBase children; + /** + * Runtime struct pointer. + */ + GreasePencilLayerGroupRuntimeHandle *runtime; +#ifdef __cplusplus + blender::bke::greasepencil::LayerGroup &wrap(); + const blender::bke::greasepencil::LayerGroup &wrap() const; +#endif +} GreasePencilLayerTreeGroup; + +/** + * Flag for the grease pencil data-block. #GreasePencil.flag + */ +typedef enum GreasePencilFlag { + /* TODO */ + GreasePencilFlag_TODO +} GreasePencilFlag; + +/** + * Onion skinning mode. #GreasePencilOnionSkinningSettings.mode + */ +typedef enum GreasePencilOnionSkinningMode { + GP_ONION_SKINNING_MODE_ABSOLUTE = 0, + GP_ONION_SKINNING_MODE_RELATIVE = 1, + GP_ONION_SKINNING_MODE_SELECTED = 2, +} GreasePencilOnionSkinningMode; + +/** + * Flag for filtering the onion skinning per keyframe type. + * #GreasePencilOnionSkinningSettings.filter + * \note needs to match order of `eBezTriple_KeyframeType`. + */ +typedef enum GreasePencilOnionSkinningFilter { + GP_ONION_SKINNING_FILTER_KEYTYPE_KEYFRAME = (1 << 0), + GP_ONION_SKINNING_FILTER_KEYTYPE_EXTREME = (1 << 1), + GP_ONION_SKINNING_FILTER_KEYTYPE_BREAKDOWN = (1 << 2), + GP_ONION_SKINNING_FILTER_KEYTYPE_JITTER = (1 << 3), + GP_ONION_SKINNING_FILTER_KEYTYPE_MOVEHOLD = (1 << 4), +} GreasePencilOnionSkinningFilter; + +#define GREASE_PENCIL_ONION_SKINNING_FILTER_ALL \ + (GP_ONION_SKINNING_FILTER_KEYTYPE_KEYFRAME | GP_ONION_SKINNING_FILTER_KEYTYPE_EXTREME | \ + GP_ONION_SKINNING_FILTER_KEYTYPE_BREAKDOWN | GP_ONION_SKINNING_FILTER_KEYTYPE_JITTER | \ + GP_ONION_SKINNING_FILTER_KEYTYPE_MOVEHOLD) + +/** + * Per data-block Grease Pencil onion skinning settings. + */ +typedef struct GreasePencilOnionSkinningSettings { + /** + * Opacity for the ghost frames. + */ + float opacity; + /** + * Onion skinning mode. See `GreasePencilOnionSkinningMode`. + */ + int8_t mode; + /** + * Onion skinning filtering flag. See `GreasePencilOnionSkinningFilter`. + */ + uint8_t filter; + char _pad[2]; + /** + * Number of ghost frames shown before. + */ + int16_t num_frames_before; + /** + * Number of ghost frames shown after. + */ + int16_t num_frames_after; + /** + * Color of the ghost frames before. + */ + float color_before[3]; + /** + * Color of the ghost frames after. + */ + float color_after[3]; + char _pad2[4]; +} GreasePencilOnionSkinningSettings; + +/** + * The grease pencil data-block. + */ +typedef struct GreasePencil { + ID id; + /** Animation data. */ + struct AnimData *adt; + + /** + * An array of pointers to drawings. The drawing can own its data or reference it from another + * data-block. Note that the order of this array is arbitrary. The mapping of drawings to frames + * is done by the layers. See the `Layer` class in `BKE_grease_pencil.hh`. + */ + GreasePencilDrawingBase **drawing_array; + int drawing_array_size; + char _pad[4]; + + /* Root group of the layer tree. */ + GreasePencilLayerTreeGroup root_group; + + /** + * Pointer to the active layer. Can be NULL. + * This pointer does not own the data. + */ + GreasePencilLayer *active_layer; + + /** + * An array of materials. + */ + struct Material **material_array; + short material_array_size; + char _pad2[2]; + /** + * Global flag on the data-block. + */ + uint32_t flag; + /** + * Onion skinning settings. + */ + GreasePencilOnionSkinningSettings onion_skinning_settings; + /** + * Runtime struct pointer. + */ + GreasePencilRuntimeHandle *runtime; +#ifdef __cplusplus + /* GreasePencilDrawingBase array functions. */ + void read_drawing_array(BlendDataReader *reader); + void write_drawing_array(BlendWriter *writer); + void free_drawing_array(); + + /* Layer tree read/write functions. */ + void read_layer_tree(BlendDataReader *reader); + void write_layer_tree(BlendWriter *writer); + + /* Drawings read/write access. */ + blender::Span drawings() const; + blender::MutableSpan drawings_for_write(); + + /* Layers read/write access. */ + blender::Span layers() const; + blender::Span layers_for_write(); + + bool has_active_layer() const; + blender::bke::greasepencil::Layer &add_layer(blender::bke::greasepencil::LayerGroup &group, + blender::StringRefNull name); + + const blender::bke::greasepencil::Layer *find_layer_by_name(blender::StringRefNull name) const; + blender::bke::greasepencil::Layer *find_layer_by_name(blender::StringRefNull name); + + void add_empty_drawings(int add_size); + void remove_drawing(int index); + + void foreach_visible_drawing(int frame, + blender::FunctionRef function); + + bool bounds_min_max(blender::float3 &min, blender::float3 &max) const; + + /* For debugging purposes. */ + void print_layer_tree(); +#endif +} GreasePencil; + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/makesdna/DNA_object_types.h b/source/blender/makesdna/DNA_object_types.h index d0142280778..eea6b224a2d 100644 --- a/source/blender/makesdna/DNA_object_types.h +++ b/source/blender/makesdna/DNA_object_types.h @@ -572,7 +572,6 @@ typedef enum ObjectType { OB_ARMATURE = 25, - /** Grease Pencil object used in 3D view but not used for annotation in 2D. */ OB_GPENCIL_LEGACY = 26, OB_CURVES = 27, @@ -581,6 +580,8 @@ typedef enum ObjectType { OB_VOLUME = 29, + OB_GREASE_PENCIL = 30, + /* Keep last. */ OB_TYPE_MAX, } ObjectType; @@ -588,7 +589,7 @@ typedef enum ObjectType { /* check if the object type supports materials */ #define OB_TYPE_SUPPORT_MATERIAL(_type) \ (((_type) >= OB_MESH && (_type) <= OB_MBALL) || \ - ((_type) >= OB_GPENCIL_LEGACY && (_type) <= OB_VOLUME)) + ((_type) >= OB_GPENCIL_LEGACY && (_type) <= OB_GREASE_PENCIL)) /** Does the object have some render-able geometry (unlike empties, cameras, etc.). True for * #OB_CURVES_LEGACY, since these often evaluate to objects with geometry. */ #define OB_TYPE_IS_GEOMETRY(_type) \ @@ -601,7 +602,8 @@ typedef enum ObjectType { OB_CURVES_LEGACY, \ OB_CURVES, \ OB_POINTCLOUD, \ - OB_VOLUME)) + OB_VOLUME, \ + OB_GREASE_PENCIL)) #define OB_TYPE_SUPPORT_VGROUP(_type) (ELEM(_type, OB_MESH, OB_LATTICE, OB_GPENCIL_LEGACY)) #define OB_TYPE_SUPPORT_EDITMODE(_type) \ (ELEM(_type, \ @@ -612,13 +614,14 @@ typedef enum ObjectType { OB_MBALL, \ OB_LATTICE, \ OB_ARMATURE, \ - OB_CURVES)) + OB_CURVES, \ + OB_GREASE_PENCIL)) #define OB_TYPE_SUPPORT_PARVERT(_type) \ (ELEM(_type, OB_MESH, OB_SURF, OB_CURVES_LEGACY, OB_LATTICE)) /** Matches #OB_TYPE_SUPPORT_EDITMODE. */ #define OB_DATA_SUPPORT_EDITMODE(_type) \ - (ELEM(_type, ID_ME, ID_CU_LEGACY, ID_MB, ID_LT, ID_AR, ID_CV)) + (ELEM(_type, ID_ME, ID_CU_LEGACY, ID_MB, ID_LT, ID_AR, ID_CV, ID_GP)) /* is this ID type used as object data */ #define OB_DATA_SUPPORT_ID(_id_type) \ @@ -635,7 +638,8 @@ typedef enum ObjectType { ID_AR, \ ID_CV, \ ID_PT, \ - ID_VO)) + ID_VO, \ + ID_GP)) #define OB_DATA_SUPPORT_ID_CASE \ ID_ME: \ @@ -650,7 +654,8 @@ typedef enum ObjectType { case ID_AR: \ case ID_CV: \ case ID_PT: \ - case ID_VO + case ID_VO: \ + case ID_GP /** #Object.partype: first 4 bits: type. */ enum { diff --git a/source/blender/makesdna/DNA_userdef_types.h b/source/blender/makesdna/DNA_userdef_types.h index 4e1f90a4667..03aeccd08b9 100644 --- a/source/blender/makesdna/DNA_userdef_types.h +++ b/source/blender/makesdna/DNA_userdef_types.h @@ -675,10 +675,11 @@ typedef struct UserDef_Experimental { char use_override_templates; char enable_eevee_next; char use_sculpt_texture_paint; + char use_grease_pencil_version3; char enable_overlay_next; char enable_workbench_next; char use_new_volume_nodes; - char _pad[5]; + char _pad[4]; /** `makesdna` does not allow empty structs. */ } UserDef_Experimental; diff --git a/source/blender/makesrna/intern/CMakeLists.txt b/source/blender/makesrna/intern/CMakeLists.txt index 04b3b1389ed..ad10bb0dcc9 100644 --- a/source/blender/makesrna/intern/CMakeLists.txt +++ b/source/blender/makesrna/intern/CMakeLists.txt @@ -90,8 +90,10 @@ set(DEFSRC if(WITH_EXPERIMENTAL_FEATURES) add_definitions(-DWITH_SIMULATION_DATABLOCK) + add_definitions(-DWITH_GREASE_PENCIL_V3) list(APPEND DEFSRC rna_simulation.c + rna_grease_pencil.c ) endif() diff --git a/source/blender/makesrna/intern/makesrna.c b/source/blender/makesrna/intern/makesrna.c index 97d5c3e488a..4e7a57e4cf6 100644 --- a/source/blender/makesrna/intern/makesrna.c +++ b/source/blender/makesrna/intern/makesrna.c @@ -4523,6 +4523,7 @@ static RNAProcessItem PROCESS_ITEMS[] = { {"rna_dynamicpaint.c", NULL, RNA_def_dynamic_paint}, {"rna_fcurve.c", "rna_fcurve_api.c", RNA_def_fcurve}, {"rna_gpencil_legacy.c", NULL, RNA_def_gpencil}, + {"rna_grease_pencil.c", NULL, RNA_def_grease_pencil}, {"rna_curves.c", NULL, RNA_def_curves}, {"rna_image.c", "rna_image_api.c", RNA_def_image}, {"rna_key.c", NULL, RNA_def_key}, diff --git a/source/blender/makesrna/intern/rna_ID.c b/source/blender/makesrna/intern/rna_ID.c index 9771c701a8b..14fe9787f3e 100644 --- a/source/blender/makesrna/intern/rna_ID.c +++ b/source/blender/makesrna/intern/rna_ID.c @@ -40,7 +40,8 @@ const EnumPropertyItem rna_enum_id_type_items[] = { {ID_CU_LEGACY, "CURVE", ICON_CURVE_DATA, "Curve", ""}, {ID_CV, "CURVES", ICON_CURVES_DATA, "Curves", ""}, {ID_VF, "FONT", ICON_FONT_DATA, "Font", ""}, - {ID_GD_LEGACY, "GREASEPENCIL", ICON_GREASEPENCIL, "Grease Pencil", ""}, + {ID_GD_LEGACY, "GREASEPENCIL", ICON_GREASEPENCIL, "Grease Pencil (legacy)", ""}, + {ID_GP, "GREASEPENCIL_V3", ICON_GREASEPENCIL, "Grease Pencil", ""}, {ID_IM, "IMAGE", ICON_IMAGE_DATA, "Image", ""}, {ID_KE, "KEY", ICON_SHAPEKEY_DATA, "Key", ""}, {ID_LT, "LATTICE", ICON_LATTICE_DATA, "Lattice", ""}, @@ -484,6 +485,9 @@ StructRNA *ID_code_to_RNA_type(short idcode) return &RNA_Curve; case ID_GD_LEGACY: return &RNA_GreasePencil; + case ID_GP: + return &RNA_GreasePencilv3; + break; case ID_GR: return &RNA_Collection; case ID_CV: diff --git a/source/blender/makesrna/intern/rna_grease_pencil.c b/source/blender/makesrna/intern/rna_grease_pencil.c new file mode 100644 index 00000000000..cac59a9d460 --- /dev/null +++ b/source/blender/makesrna/intern/rna_grease_pencil.c @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2023 Blender Foundation. */ + +/** \file + * \ingroup RNA + */ + +#include "DNA_grease_pencil_types.h" + +#include "RNA_access.h" +#include "RNA_define.h" +#include "RNA_enum_types.h" + +#include "rna_internal.h" + +#ifdef RNA_RUNTIME + +#else + +static void rna_def_grease_pencil_data(BlenderRNA *brna) +{ + StructRNA *srna; + + srna = RNA_def_struct(brna, "GreasePencilv3", "ID"); + RNA_def_struct_sdna(srna, "GreasePencil"); + RNA_def_struct_ui_text(srna, "Grease Pencil", "Grease Pencil data-block"); + RNA_def_struct_ui_icon(srna, ICON_OUTLINER_DATA_GREASEPENCIL); + + /* Animation Data */ + rna_def_animdata_common(srna); +} + +void RNA_def_grease_pencil(BlenderRNA *brna) +{ + rna_def_grease_pencil_data(brna); +} + +#endif diff --git a/source/blender/makesrna/intern/rna_internal.h b/source/blender/makesrna/intern/rna_internal.h index a7daa810660..fa9f262b303 100644 --- a/source/blender/makesrna/intern/rna_internal.h +++ b/source/blender/makesrna/intern/rna_internal.h @@ -156,6 +156,7 @@ void RNA_def_depsgraph(struct BlenderRNA *brna); void RNA_def_dynamic_paint(struct BlenderRNA *brna); void RNA_def_fcurve(struct BlenderRNA *brna); void RNA_def_gpencil(struct BlenderRNA *brna); +void RNA_def_grease_pencil(struct BlenderRNA *brna); void RNA_def_greasepencil_modifier(struct BlenderRNA *brna); void RNA_def_shader_fx(struct BlenderRNA *brna); void RNA_def_curves(struct BlenderRNA *brna); @@ -502,6 +503,7 @@ void RNA_def_main_actions(BlenderRNA *brna, PropertyRNA *cprop); void RNA_def_main_particles(BlenderRNA *brna, PropertyRNA *cprop); void RNA_def_main_palettes(BlenderRNA *brna, PropertyRNA *cprop); void RNA_def_main_gpencil_legacy(BlenderRNA *brna, PropertyRNA *cprop); +void RNA_def_main_grease_pencil(BlenderRNA *brna, PropertyRNA *cprop); void RNA_def_main_movieclips(BlenderRNA *brna, PropertyRNA *cprop); void RNA_def_main_masks(BlenderRNA *brna, PropertyRNA *cprop); void RNA_def_main_linestyles(BlenderRNA *brna, PropertyRNA *cprop); diff --git a/source/blender/makesrna/intern/rna_main.c b/source/blender/makesrna/intern/rna_main.c index c4a2f70029a..49d3c388f69 100644 --- a/source/blender/makesrna/intern/rna_main.c +++ b/source/blender/makesrna/intern/rna_main.c @@ -96,6 +96,7 @@ RNA_MAIN_LISTBASE_FUNCS_DEF(collections) RNA_MAIN_LISTBASE_FUNCS_DEF(curves) RNA_MAIN_LISTBASE_FUNCS_DEF(fonts) RNA_MAIN_LISTBASE_FUNCS_DEF(gpencils) +RNA_MAIN_LISTBASE_FUNCS_DEF(grease_pencils) RNA_MAIN_LISTBASE_FUNCS_DEF(hair_curves) RNA_MAIN_LISTBASE_FUNCS_DEF(images) RNA_MAIN_LISTBASE_FUNCS_DEF(lattices) @@ -332,9 +333,20 @@ void RNA_def_main(BlenderRNA *brna) {"grease_pencils", "GreasePencil", "rna_Main_gpencils_begin", +# ifdef WITH_GREASE_PENCIL_V3 "Grease Pencil (legacy)", "Grease Pencil (legacy) data-blocks", +# else + "Grease Pencil", + "Grease Pencil data-blocks", +# endif RNA_def_main_gpencil_legacy}, + {"grease_pencils_v3", + "GreasePencilv3", + "rna_Main_grease_pencils_begin", + "Grease Pencil", + "Grease Pencil data-blocks", + RNA_def_main_grease_pencil}, {"movieclips", "MovieClip", "rna_Main_movieclips_begin", diff --git a/source/blender/makesrna/intern/rna_main_api.c b/source/blender/makesrna/intern/rna_main_api.c index 7cefc919401..06e1adeb9d2 100644 --- a/source/blender/makesrna/intern/rna_main_api.c +++ b/source/blender/makesrna/intern/rna_main_api.c @@ -2058,6 +2058,16 @@ void RNA_def_main_gpencil_legacy(BlenderRNA *brna, PropertyRNA *cprop) func, "do_ui_user", true, "", "Make sure interface does not reference this grease pencil"); } +void RNA_def_main_grease_pencil(BlenderRNA *brna, PropertyRNA *cprop) +{ + StructRNA *srna; + + RNA_def_property_srna(cprop, "BlendDataGreasePencilsV3"); + srna = RNA_def_struct(brna, "BlendDataGreasePencilsV3", NULL); + RNA_def_struct_sdna(srna, "Main"); + RNA_def_struct_ui_text(srna, "Main Grease Pencils", "Collection of grease pencils"); +} + void RNA_def_main_movieclips(BlenderRNA *brna, PropertyRNA *cprop) { StructRNA *srna; diff --git a/source/blender/makesrna/intern/rna_object.c b/source/blender/makesrna/intern/rna_object.c index 8cd4068ff39..7786f5da29a 100644 --- a/source/blender/makesrna/intern/rna_object.c +++ b/source/blender/makesrna/intern/rna_object.c @@ -254,7 +254,8 @@ const EnumPropertyItem rna_enum_object_type_items[] = { {OB_CURVES, "CURVES", ICON_OUTLINER_OB_CURVES, "Hair Curves", ""}, {OB_POINTCLOUD, "POINTCLOUD", ICON_OUTLINER_OB_POINTCLOUD, "Point Cloud", ""}, {OB_VOLUME, "VOLUME", ICON_OUTLINER_OB_VOLUME, "Volume", ""}, - {OB_GPENCIL_LEGACY, "GPENCIL", ICON_OUTLINER_OB_GREASEPENCIL, "Grease Pencil", ""}, + {OB_GPENCIL_LEGACY, "GPENCIL", ICON_OUTLINER_OB_GREASEPENCIL, "Grease Pencil (legacy)", ""}, + {OB_GREASE_PENCIL, "GREASEPENCIL", ICON_OUTLINER_OB_GREASEPENCIL, "Grease Pencil", ""}, RNA_ENUM_ITEM_SEPR, {OB_ARMATURE, "ARMATURE", ICON_OUTLINER_OB_ARMATURE, "Armature", ""}, {OB_LATTICE, "LATTICE", ICON_OUTLINER_OB_LATTICE, "Lattice", ""}, @@ -602,6 +603,8 @@ static StructRNA *rna_Object_data_typef(PointerRNA *ptr) return &RNA_LightProbe; case OB_GPENCIL_LEGACY: return &RNA_GreasePencil; + case OB_GREASE_PENCIL: + return &RNA_GreasePencilv3; case OB_CURVES: return &RNA_Curves; case OB_POINTCLOUD: diff --git a/source/blender/makesrna/intern/rna_userdef.c b/source/blender/makesrna/intern/rna_userdef.c index 73485103a55..6c6892e6d3d 100644 --- a/source/blender/makesrna/intern/rna_userdef.c +++ b/source/blender/makesrna/intern/rna_userdef.c @@ -6681,6 +6681,10 @@ static void rna_def_userdef_experimental(BlenderRNA *brna) RNA_def_property_boolean_sdna(prop, NULL, "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, NULL, "use_grease_pencil_version3", 1); + RNA_def_property_ui_text(prop, "Grease Pencil 3.0", "Enable the new grease pencil 3.0 codebase"); + prop = RNA_def_property(srna, "enable_workbench_next", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "enable_workbench_next", 1); RNA_def_property_ui_text(prop, diff --git a/source/blender/windowmanager/WM_types.h b/source/blender/windowmanager/WM_types.h index a0d5bca5257..07539bc1195 100644 --- a/source/blender/windowmanager/WM_types.h +++ b/source/blender/windowmanager/WM_types.h @@ -513,6 +513,7 @@ typedef struct wmNotifier { #define NS_MODE_POSE (9 << 8) #define NS_MODE_PARTICLE (10 << 8) #define NS_EDITMODE_CURVES (11 << 8) +#define NS_EDITMODE_GREASE_PENCIL (12 << 8) /* subtype 3d view editing */ #define NS_VIEW3D_GPU (16 << 8)