Initial Grease Pencil 3.0 stage #106848

Merged
Falk David merged 224 commits from filedescriptor/blender:grease-pencil-v3 into main 2023-05-30 11:14:22 +02:00
94 changed files with 5945 additions and 53 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,29 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
filedescriptor marked this conversation as resolved Outdated

Needs copyright.

Needs copyright.
* 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

View File

@ -0,0 +1,381 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
filedescriptor marked this conversation as resolved Outdated

Needs copyright.

Needs copyright.
* 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 {
filedescriptor marked this conversation as resolved Outdated

Decide on the namespace here. Should it maybe be just gp?

Decide on the namespace here. Should it maybe be just `gp`?
/**
* 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<StrokePoint> points;
Vector<uint3> triangles;

Is there a particular reason you remove copy assignment but keep copy construction defined?

Is there a particular reason you remove copy assignment but keep copy construction defined?

Just because copying should be explicit. And removing the copy assignment constructor avoids errors.

Just because copying should be explicit. And removing the copy assignment constructor avoids errors.
int mat = 0;
void clear()
{
this->points.clear_and_shrink();
this->triangles.clear_and_shrink();
this->mat = 0;
}
};
class DrawingRuntime {

Is defining these as constexpr helpful? I sort of doubt any real computation is done on these nodes at compile time. But maybe?

Is defining these as `constexpr` helpful? I sort of doubt any real computation is done on these nodes at compile time. But maybe?

The const in the const bool return type means nothing here

The `const` in the `const bool` return type means nothing here
public:
/**
* Triangle cache for all the strokes in the drawing.
*/
mutable SharedCache<Vector<uint3>> 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);

Try putting this class out of line.

Try putting this class out of line.
TreeNode(const TreeNode &other);
public:
/**
* \returns true if this node is a LayerGroup.
*/
bool is_group() const
{
return this->type == GP_LAYER_TREE_GROUP;
}
/**
Review

The _for_write() suffix doesn't seem super helpful here TBH, but I guess it doesn't hurt.

The `_for_write()` suffix doesn't seem super helpful here TBH, but I guess it doesn't hurt.
* \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();
};

exclusive

`exclusive`
/**
* A layer mask stores a reference to a layer that will mask other layers.
*/

I am a bit confused here.

Following the text, it seems it should be Frame and Drawing (not Time and Frame).

Or, the description needs to be updated (and a good argument made why the common terms Time and Frame are redefined in the grease pencil).

I am a bit confused here. Following the text, it seems it should be `Frame` and `Drawing` (not `Time` and `Frame`). Or, the description needs to be updated (and a good argument made why the common terms `Time` and `Frame` are redefined in the grease pencil).

Ah yes, Frame is the term we use for a Drawing at a particular time. I agree that this is very confusing here. I am not sure what a better term would be.

Ah yes, `Frame` is the term we use for a `Drawing` at a particular time. I agree that this is very confusing here. I am not sure what a better term would be.

I see. The GreasePencilFrame is nice and unambiguous. And within a context of GP it is not bad to stick to general rule "frame means grease pencil frame".

But when you refer to frame as a moment in time, is the best to use "scene frame". So, it will be something rhe first drawing starts at scene frame 0 and ends at scene frame 5 (exclusive) (btw exclusive not excusive)`.

Also use the same "scene frame" in diagram below. Generally time in Blender is scene frame number divided by the scene fps, and is measured in seconds.

I see. The `GreasePencilFrame` is nice and unambiguous. And within a context of GP it is not bad to stick to general rule "frame means grease pencil frame". But when you refer to frame as a moment in time, is the best to use "scene frame". So, it will be something `rhe first drawing starts at scene frame 0 and ends at scene frame 5 (exclusive)` (btw exclusive not excusive)`. Also use the same "scene frame" in diagram below. Generally time in Blender is scene frame number divided by the scene fps, and is measured in seconds.
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<int, GreasePencilFrame> frames_;

Better to return a span here maybe?

Better to return a span here maybe?
/**
* Caches a sorted vector of the keys of `frames_`.
*/
mutable SharedCache<Vector<int>> 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.

Put out of line.

Put out of line.
*/
Vector<LayerMask> masks_;
};

The function is "is_locked" and the comment says "return if it is locked". This sort of comment doesn't add anything IMO, just wastes space. If there is a comment, maybe it should use a different word besides "locked" to describe the state. Or there doesn't need to be a comment at all.

The function is "is_locked" and the comment says "return if it is locked". This sort of comment doesn't add anything IMO, just wastes space. If there is a comment, maybe it should use a different word besides "locked" to describe the state. Or there doesn't need to be a comment at all.
/**
* 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<int, GreasePencilFrame> &frames() const;
Map<int, GreasePencilFrame> &frames_for_write();
bool is_visible() const;
bool is_locked() const;
/**
filedescriptor marked this conversation as resolved Outdated

Would be good to know why this is commented out, or it should be cleaned up before merge in main.

Would be good to know why this is commented out, or it should be cleaned up before merge in main.
* 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);
/**

Put out of line.

Put out of line.
* 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);
/**

Put out of line.

Put out of line.
* Returns the sorted (start) frame numbers of the frames of this layer.
* \note This will cache the keys lazily.

Not sure what is a 'pre-order vector'? or is a typo? Like pre-ordered vector? Same below.

Not sure what is a 'pre-order vector'? or is a typo? Like `pre-ordered vector`? Same below.
*/
Span<int> 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

Returning an element added to a vector by reference is quite dangerous since they're invalidated by further additions, we usually don't do that. Either returning an index or not having these wrapper functions seems like the best choices IMO

Returning an element added to a vector by reference is quite dangerous since they're invalidated by further additions, we usually don't do that. Either returning an index or not having these wrapper functions seems like the best choices IMO

From quick look it seems that it should actually be emplace_layer() or something like this.
And for that it is typically very handy to return reference. And not only that, since C++17 it is expected behavior for vector type containers as well.

From quick look it seems that it should actually be `emplace_layer()` or something like this. And for that it is typically very handy to return reference. And not only that, since C++17 it is expected behavior for vector type containers as well.

I think emplace_layer would be a better name indeed.

I think `emplace_layer` would be a better name indeed.
* 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<TreeNode *> nodes_cache_;
/**
* Caches all the layers in this group in a single pre-ordered vector.
*/
mutable Vector<Layer *> layer_cache_;
};
/**
* A LayerGroup is a grouping of zero or more Layers.
*/
class LayerGroup : public ::GreasePencilLayerTreeGroup {
public:
LayerGroup();
filedescriptor marked this conversation as resolved Outdated

Since the map will have to change to store more than just an index as the value, make sure to update the comment too.

Since the map will have to change to store more than just an index as the value, make sure to update the comment too.
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;

Not sure why the greasepencil namespace ends up here? Also means that e.g. the fairly generically-named StrokePoint struct is in the fairly generic blender::bke namespace, which does not sounds great to me? not to mention 'C-style' namespace in names like GreasePencilDrawingRuntime.

Not sure why the `greasepencil` namespace ends up here? Also means that e.g. the fairly generically-named `StrokePoint` struct is in the fairly generic `blender::bke` namespace, which does not sounds great to me? not to mention 'C-style' namespace in names like `GreasePencilDrawingRuntime`.
/**
* 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<const TreeNode *> nodes() const;
Span<TreeNode *> nodes_for_write();
/**
* Returns a `Span` of pointers to all the `Layer`s in this group.
*/
Span<const Layer *> layers() const;
Span<Layer *> layers_for_write();
/**
* Returns a pointer to the layer with \a name. If no such layer was found, returns nullptr.

= {} doesn't change anything here since Vector has a default constructor. It can be removed I think. Same with below

`= {}` doesn't change anything here since `Vector` has a default constructor. It can be removed I think. Same with below
*/

Put out of line.

Put out of line.
const Layer *find_layer_by_name(StringRefNull name) const;
Layer *find_layer_by_name(StringRefNull name);
/**
* Print the nodes. For debugging purposes.
*/

Looks like this should be private maybe? It has a _ suffix.

Looks like this should be private maybe? It has a `_` suffix.
void print_nodes(StringRefNull header) const;
private:
void ensure_nodes_cache() const;
void tag_nodes_cache_dirty() const;
};

Put out of line.

Put out of line.
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

Put out of line.

Put out of line.
class GreasePencilRuntime {
public:
/**
filedescriptor marked this conversation as resolved Outdated

Same remark as above about commented out code.

Same remark as above about commented out code.
* Allocated and freed by the drawing code. See `DRW_grease_pencil_batch_cache_*` functions.
*/
void *batch_cache = nullptr;

These two functions should never have to be called outside of the ID callbacks in grease_pencil.cc; IMO they make more sense as static functions there. Probably better to keep that storage for DNA thing as localized as possible.

These two functions should never have to be called outside of the `ID` callbacks in `grease_pencil.cc`; IMO they make more sense as static functions there. Probably better to keep that storage for DNA thing as localized as possible.
public:
GreasePencilRuntime() {}
~GreasePencilRuntime() {}
};

Member variables come before functions https://wiki.blender.org/wiki/Style_Guide/C_Cpp#Class_Layout

Member variables come before functions https://wiki.blender.org/wiki/Style_Guide/C_Cpp#Class_Layout
} // namespace blender::bke
inline blender::bke::greasepencil::TreeNode &GreasePencilLayerTreeNode::wrap()
{
return *reinterpret_cast<blender::bke::greasepencil::TreeNode *>(this);
}
inline const blender::bke::greasepencil::TreeNode &GreasePencilLayerTreeNode::wrap() const
{
return *reinterpret_cast<const blender::bke::greasepencil::TreeNode *>(this);
}
inline blender::bke::greasepencil::Layer &GreasePencilLayer::wrap()
{
return *reinterpret_cast<blender::bke::greasepencil::Layer *>(this);
}

Put out of line.

Put out of line.
inline const blender::bke::greasepencil::Layer &GreasePencilLayer::wrap() const
{
return *reinterpret_cast<const blender::bke::greasepencil::Layer *>(this);
}
inline blender::bke::greasepencil::LayerGroup &GreasePencilLayerTreeGroup::wrap()
{
return *reinterpret_cast<blender::bke::greasepencil::LayerGroup *>(this);
}
inline const blender::bke::greasepencil::LayerGroup &GreasePencilLayerTreeGroup::wrap() const
{
return *reinterpret_cast<const blender::bke::greasepencil::LayerGroup *>(this);

This needs to be implemented.

This needs to be implemented.
}
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);

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -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"
filedescriptor marked this conversation as resolved Outdated

legacy_gpencil_frame_to_grease_pencil_drawing

`legacy_gpencil_frame_to_grease_pencil_drawing`
namespace blender::bke::greasepencil::convert {
void legacy_gpencil_frame_to_grease_pencil_drawing(const bGPDframe &gpf,

Order return argument last, make the gpf argument const

Order return argument last, make the `gpf` argument const
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<bke::greasepencil::DrawingRuntime>(__func__);
/* Get the number of points, number of strokes and the offsets for each stroke. */
Vector<int> 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<int> 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<float3> positions = curves.positions_for_write();
SpanAttributeWriter<float> radii = attributes.lookup_or_add_for_write_span<float>(
"radius", ATTR_DOMAIN_POINT);
SpanAttributeWriter<float> opacities = attributes.lookup_or_add_for_write_span<float>(
"opacity", ATTR_DOMAIN_POINT);
SpanAttributeWriter<float> delta_times = attributes.lookup_or_add_for_write_span<float>(
"delta_time", ATTR_DOMAIN_POINT);
SpanAttributeWriter<float> rotations = attributes.lookup_or_add_for_write_span<float>(
"rotation", ATTR_DOMAIN_POINT);
SpanAttributeWriter<ColorGeometry4f> vertex_colors =
attributes.lookup_or_add_for_write_span<ColorGeometry4f>("vertex_color", ATTR_DOMAIN_POINT);
SpanAttributeWriter<bool> selection = attributes.lookup_or_add_for_write_span<bool>(
".selection", ATTR_DOMAIN_POINT);
/* Curve Attributes. */
SpanAttributeWriter<bool> stroke_cyclic = attributes.lookup_or_add_for_write_span<bool>(
"cyclic", ATTR_DOMAIN_CURVE);
/* TODO: This should be a `double` attribute. */
SpanAttributeWriter<float> stroke_init_times = attributes.lookup_or_add_for_write_span<float>(
"init_time", ATTR_DOMAIN_CURVE);
SpanAttributeWriter<int8_t> stroke_start_caps = attributes.lookup_or_add_for_write_span<int8_t>(
"start_cap", ATTR_DOMAIN_CURVE);
SpanAttributeWriter<int8_t> stroke_end_caps = attributes.lookup_or_add_for_write_span<int8_t>(
"end_cap", ATTR_DOMAIN_CURVE);
SpanAttributeWriter<float> stroke_hardnesses = attributes.lookup_or_add_for_write_span<float>(
"hardness", ATTR_DOMAIN_CURVE);
SpanAttributeWriter<float> stroke_point_aspect_ratios =
attributes.lookup_or_add_for_write_span<float>("point_aspect_ratio", ATTR_DOMAIN_CURVE);
SpanAttributeWriter<float2> stroke_fill_translations =
attributes.lookup_or_add_for_write_span<float2>("fill_translation", ATTR_DOMAIN_CURVE);
SpanAttributeWriter<float> stroke_fill_rotations =
attributes.lookup_or_add_for_write_span<float>("fill_rotation", ATTR_DOMAIN_CURVE);
SpanAttributeWriter<float2> stroke_fill_scales = attributes.lookup_or_add_for_write_span<float2>(
"fill_scale", ATTR_DOMAIN_CURVE);
SpanAttributeWriter<ColorGeometry4f> stroke_fill_colors =
attributes.lookup_or_add_for_write_span<ColorGeometry4f>("fill_color", ATTR_DOMAIN_CURVE);
SpanAttributeWriter<int> stroke_materials = attributes.lookup_or_add_for_write_span<int>(
"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<float>(gps->inittime);
stroke_start_caps.span[stroke_i] = static_cast<int8_t>(gps->caps[0]);
stroke_end_caps.span[stroke_i] = static_cast<int8_t>(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<bGPDspoint> stroke_points{gps->points, gps->totpoints};
MutableSpan<float3> stroke_positions = positions.slice(stroke_points_range);
MutableSpan<float> stroke_radii = radii.span.slice(stroke_points_range);
MutableSpan<float> stroke_opacities = opacities.span.slice(stroke_points_range);
MutableSpan<float> stroke_deltatimes = delta_times.span.slice(stroke_points_range);
MutableSpan<float> stroke_rotations = rotations.span.slice(stroke_points_range);
MutableSpan<ColorGeometry4f> stroke_vertex_colors = vertex_colors.span.slice(
stroke_points_range);
MutableSpan<bool> 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;
}
}

I thought we had some RAII helper which does finish() at the end of the scope.
Or maybe it was just an idea to have one? Maybe Jacques of Hans remember better :)

I thought we had some RAII helper which does ` finish()` at the end of the scope. Or maybe it was just an idea to have one? Maybe Jacques of Hans remember better :)

Right now regular we do this manually, but true, this could be better to use BLI_SCOPED_DEFER.

Right now regular we do this manually, but true, this could be better to use `BLI_SCOPED_DEFER`.

We don't really want to do non-trivial work in a class's destructor. It's also helpful to make this explicit, since sometimes it matters when it happens.

We don't really want to do non-trivial work in a class's destructor. It's also helpful to make this explicit, since sometimes it matters when it happens.

I don't really understand it, and can't say I'm sold on the motivation.
But in any case, at this point the discussion does not affect the PR, so lets consider it resolved here, and any followup we can do in the chat instead.

I don't really understand it, and can't say I'm sold on the motivation. But in any case, at this point the discussion does not affect the PR, so lets consider it resolved here, and any followup we can do in the chat instead.
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<GreasePencilDrawingBase **>(
MEM_cnew_array<GreasePencilDrawing *>(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<int8_t>(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<GreasePencilDrawingBase *>(
MEM_new<GreasePencilDrawing>(__func__));
GreasePencilDrawing &drawing = *reinterpret_cast<GreasePencilDrawing *>(
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<GreasePencilLayer *>(&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

View File

@ -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
Use text fixtures: http://google.github.io/googletest/primer.html#same-data-multiple-tests

I used fixtures before, but decided to use this approach instead, because all the tests are then under the same greasepencil namespace. I didn't find a way to do this with fixtures, unless I name the class literally greasepencil.

I used fixtures before, but decided to use this approach instead, because all the tests are then under the same `greasepencil` namespace. I didn't find a way to do this with fixtures, unless I name the class literally `greasepencil`.

You can't do that either. It is at a very least discouraged to use the same name for fixture and "regular" test.

Typically the "namespace" defines context you're testing, and it is perfectly fine to have multiple of such contexts per file (euclidean_resection_test.cc). But with the monolithic nature of BKE/BLI tests there is no good way to achieve the same behavior.

I would love to improve the monolithic state of tests at some point (as it is really in a way every time I work on test), but that is outside of the scope of this patch.

Would be nice to add a comment on top of the GreasePencilIDTestContext which briefly summarizes your choice of this approach. Basically, so if someone else stumbles on this code and winders "why not fixtures" they have an answer.

You can't do that either. It is at a very least discouraged to use the same name for fixture and "regular" test. Typically the "namespace" defines context you're testing, and it is perfectly fine to have multiple of such contexts per file (`euclidean_resection_test.cc`). But with the monolithic nature of BKE/BLI tests there is no good way to achieve the same behavior. I would love to improve the monolithic state of tests at some point (as it is really in a way every time I work on test), but that is outside of the scope of this patch. Would be nice to add a comment on top of the `GreasePencilIDTestContext` which briefly summarizes your choice of this approach. Basically, so if someone else stumbles on this code and winders "why not fixtures" they have an answer.
* 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<GreasePencil *>(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);
filedescriptor marked this conversation as resolved Outdated

Add tests for load_layer_tree_from_storage and save_layer_tree_to_storage.

Add tests for `load_layer_tree_from_storage` and `save_layer_tree_to_storage`.
}
/* --------------------------------------------------------------------------------------------- */
/* Drawing Array Tests. */
TEST(greasepencil, add_empty_drawings)
{
GreasePencilIDTestContext ctx;
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(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<GreasePencil *>(BKE_id_new(ctx.bmain, ID_GP, "GP"));
grease_pencil.add_empty_drawings(3);
GreasePencilDrawing *drawing = reinterpret_cast<GreasePencilDrawing *>(
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<const Layer *> 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<const TreeNode *> 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<const Layer *> 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<const TreeNode *> 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

View File

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

View File

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

View File

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

View File

@ -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<Volume *>(ob->data);
return &(volume->mat);
}
if (ob->type == OB_GREASE_PENCIL) {
GreasePencil *grease_pencil = static_cast<GreasePencil *>(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<Volume *>(ob->data);
return &(volume->totcol);
}
if (ob->type == OB_GREASE_PENCIL) {
GreasePencil *grease_pencil = static_cast<GreasePencil *>(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;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,157 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2023 Blender Foundation. */
filedescriptor marked this conversation as resolved Outdated

2023

2023
/** \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 {

It looks like this should be in the blender::draw:: namespace first, given the file path. Since greasepencil isn't a top-level module, a blender::greasepencil namespace probably doesn't make sense in general.

Same with elsewhere in this diff.

It looks like this should be in the `blender::draw::` namespace first, given the file path. Since `greasepencil` isn't a top-level module, a `blender::greasepencil` namespace probably doesn't make sense in general. Same with elsewhere in this diff.
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);

I would totally split the code into smaller functions. I do not see what are we winning from such inlined code.

I would totally split the code into smaller functions. I do not see what are we winning from such inlined code.
/* 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_);

Why this extra check is needed?

Why this extra check is needed?
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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,324 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2023 Blender Foundation. */
filedescriptor marked this conversation as resolved Outdated

2023

2023
/** \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
Review

What is this about?

What is this about?
#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<gpScene> 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<float4, 2> 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 {
Review

This struct name is interesting... I guess it's temporary though :P

This struct name is interesting... I guess it's temporary though :P
Review

Yes it is :)

Yes it is :)
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<GPENCIL_NEXT_Data *>(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<GPENCIL_NEXT_Data *>(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<GPENCIL_NEXT_Data *>(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<GPENCIL_NEXT_Data *>(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<GPENCIL_NEXT_Data *>(vedata)->instance->end_sync(*manager);
}
static void gpencil_instance_free(void *instance)
{
if (!GPU_shader_storage_buffer_objects_support()) {
return;
}
delete reinterpret_cast<draw::greasepencil::Instance *>(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*/)
{
}
Review

Commented argument names instead of this macro here

Commented argument names instead of this macro here
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,
};
}
/** \} */

View File

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

View File

@ -0,0 +1,65 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2023 Blender Foundation. */

Check everywhere else :)

2023. Check everywhere else :)
/** \file
* \ingroup draw
*/
#pragma once
#include "BKE_grease_pencil.hh"
#include "DRW_gpu_wrapper.hh"
filedescriptor marked this conversation as resolved
Review

I am not sure all of those are needed here.
I.e. I do not see any BKE_image API used in this header. Might be missing something.

I am not sure all of those are needed here. I.e. I do not see any `BKE_image` API used in this header. Might be missing something.
#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<gpLayer> 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)
{

In C++ use void sync(const Object * /*object*/, const bke::greasepencil::Layer & /*layer*/, bool &do_layer_blending)

In C++ use `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

View File

@ -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<gpLight> 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<Light *>(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<float4x4 *>(&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

View File

@ -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<gpMaterial> materials_buf_ = "gp_materials_buf";
/** List of all the texture used. */
Vector<GPUTexture *> 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

View File

@ -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<gpObject> 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<float4, 2> 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<GreasePencil *>(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);

Typically dead code is discouraged.
Not sure what is it part of, so can't really give strong suggestions.

Typically dead code is discouraged. Not sure what is it part of, so can't really give strong suggestions.
// 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
Review

Add a comment what is this about, or remove.

Add a comment what is this about, or remove.
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

Same as above.

Same as above.

View File

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

<< std::endl to put new line and flush the output buffer.

Also, why not to assert the details in the static_shader_create_info_name_get() ? It is a bit weird to print an error about mistake in some other code.

P.S. I think we should implement Glog style of checkers: DCHECK_NE(name, nullptr) << "GPencil: Missing case for eShaderType(" << i << ") in static_shader_create_info_name_get()."

`<< std::endl` to put new line and flush the output buffer. Also, why not to assert the details in the `static_shader_create_info_name_get()` ? It is a bit weird to print an error about mistake in some other code. P.S. I think we should implement Glog style of checkers: `DCHECK_NE(name, nullptr) << "GPencil: Missing case for eShaderType(" << i << ") in static_shader_create_info_name_get()."`
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 << "\""

Pick a side between fprintf and std::cerr ;)

Pick a side between `fprintf` and `std::cerr` ;)
<< std::endl;
}
BLI_assert(shaders_[shader_type] != nullptr);
}
return shaders_[shader_type];
}
} // namespace blender::draw::greasepencil

View File

@ -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<GPUShader *, MAX_SHADER_TYPE> 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

View File

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

View File

@ -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<GPUFrameBuffer **, 2> vfx_fb;
SwapChain<GPUTexture **, 2> color_tx;
SwapChain<GPUTexture **, 2> 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<const Camera *>(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<const ShaderFxData>(&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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Can those be renamed to DRW_cache_gpencil_legacy_* ?

Can those be renamed to `DRW_cache_gpencil_legacy_*` ?
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

View File

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

View File

@ -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. */
filedescriptor marked this conversation as resolved Outdated

Update this comment. Out of date.

Update this comment. Out of date.
GPUIndexBuf *ibo;
/** Batches */
GPUBatch *geom_batch;
/** Cache is dirty. */
bool is_dirty;
/** Last cached frame. */
filedescriptor marked this conversation as resolved
Review

Remove while unused?

Remove while unused?
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<GreasePencilBatchCache *>(
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<GreasePencilBatchCache *>(
grease_pencil.runtime->batch_cache);
if (cache == nullptr) {
cache = MEM_new<GreasePencilBatchCache>(__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<GreasePencilBatchCache *>(
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<GreasePencilBatchCache *>(
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<GreasePencilBatchCache *>(
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<const GreasePencilDrawing *> 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

Looks like this can be Vector<Array<int>>, which is better since they don't need to be resized anyway

Looks like this can be `Vector<Array<int>>`, which is better since they don't need to be resized anyway
* offsets into the curves for the verticies and triangles. */
int total_points_num = 0;

What about building a vector of the drawings once, to avoid putting most of the code inside the lambda here and the need to call "foreach_visible_drawing" twice:

  Vector<const GreasePencilDrawing *> drawings;
  grease_pencil.foreach_visible_drawing(
      cfra, [&](GreasePencilDrawing &drawing) { drawings.append(&drawing); });

...

  Array<Array<int>> verts_start_offsets_per_visible_drawing;
  Array<Array<int>> tris_start_offsets_per_visible_drawing;

  for (const int drawing_i : drawings.index_range()) {
    const GreasePencilDrawing &drawing = *drawings[drawing_i];

...

  /* Fill buffers with data. */
  for (const int drawing_i : drawings.index_range()) {
    const GreasePencilDrawing &drawing = *drawings[drawing_i];

What about building a vector of the drawings once, to avoid putting most of the code inside the lambda here and the need to call "foreach_visible_drawing" twice: ``` Vector<const GreasePencilDrawing *> drawings; grease_pencil.foreach_visible_drawing( cfra, [&](GreasePencilDrawing &drawing) { drawings.append(&drawing); }); ... Array<Array<int>> verts_start_offsets_per_visible_drawing; Array<Array<int>> tris_start_offsets_per_visible_drawing; for (const int drawing_i : drawings.index_range()) { const GreasePencilDrawing &drawing = *drawings[drawing_i]; ... /* Fill buffers with data. */ for (const int drawing_i : drawings.index_range()) { const GreasePencilDrawing &drawing = *drawings[drawing_i]; ```
int total_triangles_num = 0;
int v_offset = 0;
Vector<Array<int>> verts_start_offsets_per_visible_drawing;
Vector<Array<int>> 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<int> points_by_curve = curves.points_by_curve();

More specific/helpful variable names than t and v would be nice here

More specific/helpful variable names than `t` and `v` would be nice here
const VArray<bool> 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<int> verts_start_offsets(verts_start_offsets_size);
Array<int> 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()) {
Review

Not clear what's going on here-- 1 + ... + 1?

Not clear what's going on here-- `1 + ... + 1`?
Review

Looks like my comment about this got lost somehow. There is one extra vertex before and after each stroke. It's written in this way to make it more clear where that padding is added.

Looks like my comment about this got lost somehow. There is one extra vertex before and after each stroke. It's written in this way to make it more clear where that padding is added.
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<GreasePencilStrokeVert> verts = {
static_cast<GreasePencilStrokeVert *>(GPU_vertbuf_get_data(cache->vbo)),
GPU_vertbuf_get_vertex_len(cache->vbo)};
MutableSpan<GreasePencilColorVert> cols = {

Use the * overload rather than adding .varray at the end

Use the `*` overload rather than adding `.varray` at the end
static_cast<GreasePencilColorVert *>(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<int> points_by_curve = curves.points_by_curve();
const Span<float3> positions = curves.positions();

These .as_span() calls shouldn't be necessary

These `.as_span()` calls shouldn't be necessary
const VArray<bool> cyclic = curves.cyclic();
const VArray<float> radii = *attributes.lookup_or_default<float>(
"radius", ATTR_DOMAIN_POINT, 1.0f);
const VArray<float> opacities = *attributes.lookup_or_default<float>(
"opacity", ATTR_DOMAIN_POINT, 1.0f);
const VArray<int8_t> start_caps = *attributes.lookup_or_default<int8_t>(
"start_cap", ATTR_DOMAIN_CURVE, 0);
const VArray<int8_t> end_caps = *attributes.lookup_or_default<int8_t>(
"end_cap", ATTR_DOMAIN_CURVE, 0);
const VArray<int> materials = *attributes.lookup_or_default<int>(
"material_index", ATTR_DOMAIN_CURVE, -1);
const Span<uint3> triangles = drawing.triangles();
const Span<int> verts_start_offsets = verts_start_offsets_per_visible_drawing[drawing_i];
const Span<int> 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,

Add details about TODO. Something that helps others to pick up a task, or to understand that specific case is not expected to be yet working.

Add details about TODO. Something that helps others to pick up a task, or to understand that specific case is not expected to be yet working.
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<GreasePencilStrokeVert> verts_slice = verts.slice(verts_range);
MutableSpan<GreasePencilColorVert> 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<uint3> 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<bke::greasepencil::StrokePoint> 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<GreasePencilStrokeVert> verts_slice = verts.slice(verts_range);
MutableSpan<GreasePencilColorVert> 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<GreasePencilBatchCache *>(
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<GreasePencilBatchCache *>(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<GreasePencil *>(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<GreasePencil *>(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<GreasePencil *>(ob->data);
GreasePencilBatchCache *cache = grease_pencil_batch_cache_get(grease_pencil, cfra);
grease_pencil_batches_ensure(grease_pencil, cfra);
return cache->vbo_col;
}

View File

@ -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 (&deg_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;
}
}

View File

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

View File

@ -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");
/** \} */
/* -------------------------------------------------------------------- */

View File

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

View File

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

View File

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

View File

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

View File

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

Shouldn't this change also be conditioned by the #ifdef WITH_GREASE_PENCIL_V3?

Shouldn't this change also be conditioned by the `#ifdef WITH_GREASE_PENCIL_V3`?
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:

View File

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

View File

@ -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<bGPdata *>(ob->data);
if (keep_original) {
BLI_assert_unreachable();
}
else {
newob = ob;
}
GreasePencil *new_grease_pencil = static_cast<GreasePencil *>(
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;

View File

@ -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. */
}

This could be added to the existing OB_CURVES check with ELEM

This could be added to the existing `OB_CURVES` check with `ELEM`
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);

View File

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

View File

@ -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.");

View File

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

View File

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

View File

@ -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<BrushStrokeMode>(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;
}
Review

I am not really sure what the goal is here. If the goal is to somehow ensure all cases are handled it is much better to not use default and let compiler tell you where cases are missing at the compile time.

I am not really sure what the goal is here. If the goal is to somehow ensure all cases are handled it is much better to not use `default` and let compiler tell you where cases are missing at the compile time.
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<PaintStroke *>(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<GreasePencilStrokeOperation *>(
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<GreasePencilStrokeOperation *>(
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,
filedescriptor marked this conversation as resolved Outdated

Is the explicit cast to void* needed?

Is the explicit cast to `void*` needed?
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;
filedescriptor marked this conversation as resolved
Review

const int

`const int`
}
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<PaintStroke **>(&op->customdata));
}
static void grease_pencil_stroke_cancel(bContext *C, wmOperator *op)
{
paint_stroke_cancel(C, op, static_cast<PaintStroke *>(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(...) */

What's this commented out code is about?

What's this commented out code is about?
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);
}
/** \} */

View File

@ -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<GreasePencilStrokeOperation> new_paint_operation();
} // namespace greasepencil
} // namespace blender::ed::sculpt_paint

View File

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

It seems a bit weird to modifier the evaluated object here. Is that purposeful? Or am I missing something?

It seems a bit weird to modifier the evaluated object here. Is that purposeful? Or am I missing something?
Review

Yes, this is done on purpose so that the extra copy from orig to eval for rendering is not needed every update. Once drawing is done, the stroke is copied from the eval buffer to orig.

Yes, this is done on purpose so that the extra copy from orig to eval for rendering is not needed every update. Once drawing is done, the stroke is copied from the eval buffer to orig.
Review

Okay, makes sense. This definitely deserves a comment, since typically changing the evaluated data isn't a good idea.

Okay, makes sense. This definitely deserves a comment, since typically changing the evaluated data isn't a good idea.
/**
* 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<GreasePencil *>(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<GreasePencilDrawing *>(
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<GreasePencil *>(obact->data);
GreasePencil &grease_pencil_eval = *static_cast<GreasePencil *>(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<GreasePencilDrawing *>(
grease_pencil_orig.drawings()[index_orig]);
GreasePencilDrawing &drawing_eval = *reinterpret_cast<GreasePencilDrawing *>(
grease_pencil_eval.drawings()[index_eval]);
const Span<bke::greasepencil::StrokePoint> 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();
filedescriptor marked this conversation as resolved Outdated

offset_indices::OffsetIndices -> OffsetIndices

`offset_indices::OffsetIndices` -> `OffsetIndices`
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<int> 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<float3> positions = curves.positions_for_write();
SpanAttributeWriter<float> radii = attributes.lookup_for_write_span<float>("radius");
SpanAttributeWriter<float> opacities = attributes.lookup_for_write_span<float>("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<int> materials = attributes.lookup_for_write_span<int>("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<std::string> 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<GreasePencilStrokeOperation> new_paint_operation()
{
return std::make_unique<PaintOperation>();
}
} // namespace blender::ed::sculpt_paint::greasepencil

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -74,6 +74,7 @@ std::unique_ptr<TreeElementID> TreeElementID::createFromID(TreeElement &legacy_t
case ID_PAL:
case ID_PC:
case ID_CF:
case ID_GP:
return std::make_unique<TreeElementID>(legacy_te, id);
case ID_IP:
BLI_assert_unreachable();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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;
/**
filedescriptor marked this conversation as resolved Outdated

Move below runtime data pointer.

Move below runtime data pointer.
* 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;

I wonder if this user count could be runtime data? Or if not, maybe it's worth mentioning why it isn't in a comment.

I wonder if this user count could be runtime data? Or if not, maybe it's worth mentioning why it isn't in a comment.

I thought about this, but I don't think it can be runtime data. Since there will be keyframe instances, we need to make the user count is saved.

I thought about this, but I don't think it can be runtime data. Since there will be keyframe instances, we need to make the user count is saved.

The connection between keyframe instances and saving the user count isn't super clear to me. Maybe a comment about what the user count is used for here would help?

The connection between keyframe instances and saving the user count isn't super clear to me. Maybe a comment about what the user count is used for here would help?

Maybe it's still better to have this as runtime data. If there is ever a bug where this doesn't get counted correctly, at least a file read could fix it. It should be possible to read this as zero, and increment it for every user?

This is easy to change afterwards though.

Maybe it's still better to have this as runtime data. If there is ever a bug where this doesn't get counted correctly, at least a file read could fix it. It should be possible to read this as zero, and increment it for every user? This is easy to change afterwards though.

I see, re-creating the user count when reading could work indeed.

I see, re-creating the user count when reading could work indeed.
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<blender::uint3> triangles() const;
void tag_positions_changed();

What about a single function that returned std::optional rather than a separate "has_" function?

What about a single function that returned `std::optional` rather than a separate "has_" function?
/**

Looks like stroke_buffer() can be const

Looks like `stroke_buffer()` can be `const`
* A buffer for a single stroke while drawing.
*/
blender::Span<blender::bke::greasepencil::StrokePoint> 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;

Pretty sure null-terminated goes without saying for char * pointers in DNA, I don't think it's worth mentioning here

Pretty sure null-terminated goes without saying for `char *` pointers in DNA, I don't think it's worth mentioning here

Not sure about dynamic names for strings. It is not something typically used in the DNA.

Not sure about dynamic names for strings. It is not something typically used in the DNA.

It's been done more recently I think. It's properly integrated with RNA now too, to avoid that boilerplate. It's nice not to have to worry about choosing a future-proof length.

It's been done more recently I think. It's properly integrated with RNA now too, to avoid that boilerplate. It's nice not to have to worry about choosing a future-proof length.
/**
* 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;
/**

I wonder if you'd consider making this an array rather than a ListBase in DNA. Then the memory could be reused when the file is loaded, fewer small allocations would be needed, and the C++ type wouldn't have to have next and prev pointers even during runtime.

I wonder if you'd consider making this an array rather than a `ListBase` in DNA. Then the memory could be reused when the file is loaded, fewer small allocations would be needed, and the C++ type wouldn't have to have `next` and `prev` pointers even during runtime.
* Color tag.
*/
uint8_t color[3];
/**
* Flag. Used to set e.g. the selection, visibility, ... status.

typo transform

typo `transform`
* 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.

Grammar: it's data -> its data

Grammar: `it's data` -> `its data`
*/
struct Material **material_array;
short material_array_size;
char _pad2[2];
/**
* Global flag on the data-block.
*/
uint32_t flag;

Best to follow the style guidelines and put all methods at the bottom.
I think that's best since you can easily see what's actually stored in the class without digging through a bunch of methods.

Best to follow the style guidelines and put all methods at the bottom. I think that's best since you can easily see what's actually stored in the class without digging through a bunch of methods.
/**
* 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<GreasePencilDrawingBase *> drawings() const;
blender::MutableSpan<GreasePencilDrawingBase *> drawings_for_write();
/* Layers read/write access. */
blender::Span<const blender::bke::greasepencil::Layer *> layers() const;
blender::Span<blender::bke::greasepencil::Layer *> 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);

It probably shouldn't be possible to get a span of non-const pointers from a const GreasePencil:

Span<const GreasePencilDrawingBase *>

It probably shouldn't be possible to get a span of non-const pointers from a const `GreasePencil`: `Span<const GreasePencilDrawingBase *>`
void add_empty_drawings(int add_size);
void remove_drawing(int index);

This sort of API function (dealing with one index at a time) can be a bit dangerous since it can easily lead to quadratic behavior if it's called many times. I wonder if a remove_drawings(IndexMask drawings_to_remove) function would work a bit better

This sort of API function (dealing with one index at a time) can be a bit dangerous since it can easily lead to quadratic behavior if it's called many times. I wonder if a `remove_drawings(IndexMask drawings_to_remove)` function would work a bit better
void foreach_visible_drawing(int frame,
blender::FunctionRef<void(GreasePencilDrawing &)> function);
bool bounds_min_max(blender::float3 &min, blender::float3 &max) const;
/* For debugging purposes. */
void print_layer_tree();
#endif
} GreasePencil;
#ifdef __cplusplus
}
#endif

View File

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

View File

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

View File

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

View File

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

This should not be ifdef'd, RNA should be available, even if the new type is hidden/disabled from user PoV.

This should not be ifdef'd, RNA should be available, even if the new type is hidden/disabled from user PoV.
{"rna_curves.c", NULL, RNA_def_curves},
{"rna_image.c", "rna_image_api.c", RNA_def_image},
{"rna_key.c", NULL, RNA_def_key},

View File

@ -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)", ""},

This change should also be conditioned to #ifdef WITH_GREASE_PENCIL_V3 ?

This change should also be conditioned to `#ifdef WITH_GREASE_PENCIL_V3` ?

Should not be ifdef'd, and missing entry for the new GP RNA type

Should not be ifdef'd, and missing entry for the new GP RNA type
{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;

Should not be ifdef'd

Should not be ifdef'd
case ID_CV:

View File

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

View File

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

View File

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

View File

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

View File

@ -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)", ""},

This change should also be conditioned to #ifdef WITH_GREASE_PENCIL_V3 ?

This change should also be conditioned to `#ifdef WITH_GREASE_PENCIL_V3` ?
{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:

View File

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

View File

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