GPencil 3.0: Initial commit

Adds the initial stage for the grease pencil 3.0 project.

This patch includes:
* New ID and new object type.
* New DNA structures.
* New drawing engine for grease pencil (gpencil-next).
* Tests for the new grease pencil data-type.
* A few operators for conversion, switching modes and (simple) drawing.

Exposed to the user:
* An experimental option to switch to the new grease pencil.
   * This will switch the grease pencil render engine to gpencil-next which can only render the new object type.
     Current grease pencil objects will no longer render.
   * Changing this option currently requires a restart of blender (for the keymap to update).
* A conversion setting in the `Object` > `Convert To` operator.
* A drawing operator in `Draw Mode`.

Pull Request: https://projects.blender.org/blender/blender/pulls/106848
This commit is contained in:
Falk David
2023-05-30 11:14:16 +02:00
committed by Falk David
parent 0d38fa2422
commit 3aaacd6e30
94 changed files with 5945 additions and 53 deletions
+1
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,
),
)
@@ -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),
@@ -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
@@ -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.",
+1
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")),
),
)
+10 -2
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,
+1
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
@@ -0,0 +1,29 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2023 Blender Foundation. */
#pragma once
#include "DNA_grease_pencil_types.h"
/** \file
* \ingroup bke
* \brief Low-level operations for grease pencil that cannot be defined in the C++ header yet.
*/
#ifdef __cplusplus
extern "C" {
#endif
enum {
BKE_GREASEPENCIL_BATCH_DIRTY_ALL = 0,
};
extern void (*BKE_grease_pencil_batch_cache_dirty_tag_cb)(GreasePencil *grease_pencil, int mode);
extern void (*BKE_grease_pencil_batch_cache_free_cb)(GreasePencil *grease_pencil);
void BKE_grease_pencil_batch_cache_dirty_tag(GreasePencil *grease_pencil, int mode);
void BKE_grease_pencil_batch_cache_free(GreasePencil *grease_pencil);
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,381 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2023 Blender Foundation. */
#pragma once
/** \file
* \ingroup bke
* \brief Low-level operations for grease pencil.
*/
#include "BLI_function_ref.hh"
#include "BLI_map.hh"
#include "BLI_math_vector_types.hh"
#include "BLI_shared_cache.hh"
#include "BLI_utility_mixins.hh"
#include "DNA_gpencil_legacy_types.h"
#include "DNA_grease_pencil_types.h"
namespace blender::bke {
namespace greasepencil {
/**
* A single point for a stroke that is currently being drawn.
*/
struct StrokePoint {
float3 position;
float radius;
float opacity;
float4 color;
};
/**
* Stroke cache for a stroke that is currently being drawn.
*/
struct StrokeCache {
Vector<StrokePoint> points;
Vector<uint3> triangles;
int mat = 0;
void clear()
{
this->points.clear_and_shrink();
this->triangles.clear_and_shrink();
this->mat = 0;
}
};
class DrawingRuntime {
public:
/**
* Triangle cache for all the strokes in the drawing.
*/
mutable SharedCache<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);
TreeNode(const TreeNode &other);
public:
/**
* \returns true if this node is a LayerGroup.
*/
bool is_group() const
{
return this->type == GP_LAYER_TREE_GROUP;
}
/**
* \returns true if this node is a Layer.
*/
bool is_layer() const
{
return this->type == GP_LAYER_TREE_LEAF;
}
/**
* \returns this tree node as a LayerGroup.
* \note This results in undefined behavior if the node is not a LayerGroup.
*/
const LayerGroup &as_group() const;
/**
* \returns this tree node as a Layer.
* \note This results in undefined behavior if the node is not a Layer.
*/
const Layer &as_layer() const;
/**
* \returns this tree node as a mutable LayerGroup.
* \note This results in undefined behavior if the node is not a LayerGroup.
*/
LayerGroup &as_group_for_write();
/**
* \returns this tree node as a mutable Layer.
* \note This results in undefined behavior if the node is not a Layer.
*/
Layer &as_layer_for_write();
};
/**
* A layer mask stores a reference to a layer that will mask other layers.
*/
class LayerMask : public ::GreasePencilLayerMask {
public:
LayerMask();
explicit LayerMask(StringRefNull name);
LayerMask(const LayerMask &other);
~LayerMask();
};
class LayerRuntime {
public:
/**
* This Map maps a scene frame number (key) to a GreasePencilFrame. This struct holds an index
* (drawing_index) to the drawing in the GreasePencil->drawings array. The frame number indicates
* the first frame the drawing is shown. The end time is implicitly defined by the next greater
* frame number (key) in the map. If the value mapped to (index) is -1, no drawing is shown at
* this frame.
*
* \example:
*
* {0: 0, 5: 1, 10: -1, 12: 2, 16: -1}
*
* In this example there are three drawings (drawing #0, drawing #1 and drawing #2). The first
* drawing starts at frame 0 and ends at frame 5 (exclusive). The second drawing starts at
* frame 5 and ends at frame 10. Finally, the third drawing starts at frame 12 and ends at
* frame 16.
*
* | | | | | | | | | | |1|1|1|1|1|1|1|
* Scene Frame: |0|1|2|3|4|5|6|7|8|9|0|1|2|3|4|5|6|...
* Drawing: [#0 ][#1 ] [#2 ]
*
* \note If a drawing references another data-block, all of the drawings in that data-block are
* mapped sequentially to the frames (frame-by-frame). If another frame starts, the rest of the
* referenced drawings are discarded. If the frame is longer than the number of referenced
* drawings, then the last referenced drawing is held for the rest of the duration.
*/
Map<int, GreasePencilFrame> frames_;
/**
* 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.
*/
Vector<LayerMask> masks_;
};
/**
* A layer maps drawings to scene frames. It can be thought of as one independent channel in the
* timeline.
*/
class Layer : public ::GreasePencilLayer {
public:
Layer();
explicit Layer(StringRefNull name);
Layer(const Layer &other);
~Layer();
StringRefNull name() const
{
return this->base.name;
}
/**
* \returns the frames mapping.
*/
const Map<int, GreasePencilFrame> &frames() const;
Map<int, GreasePencilFrame> &frames_for_write();
bool is_visible() const;
bool is_locked() const;
/**
* Inserts the frame into the layer. Fails if there exists a frame at \a frame_number already.
* \returns true on success.
*/
bool insert_frame(int frame_number, const GreasePencilFrame &frame);
bool insert_frame(int frame_number, GreasePencilFrame &&frame);
/**
* Inserts the frame into the layer. If there exists a frame at \a frame_number already, it is
* overwritten.
* \returns true on success.
*/
bool overwrite_frame(int frame_number, const GreasePencilFrame &frame);
bool overwrite_frame(int frame_number, GreasePencilFrame &&frame);
/**
* Returns the sorted (start) frame numbers of the frames of this layer.
* \note This will cache the keys lazily.
*/
Span<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
* 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();
explicit LayerGroup(StringRefNull name);
LayerGroup(const LayerGroup &other);
~LayerGroup();
public:
/**
* Adds a group at the end of this group.
*/
LayerGroup &add_group(LayerGroup *group);
LayerGroup &add_group(StringRefNull name);
/**
* Adds a layer at the end of this group and returns it.
*/
Layer &add_layer(Layer *layer);
Layer &add_layer(StringRefNull name);
/**
* Returns the number of direct nodes in this group.
*/
int64_t num_direct_nodes() const;
/**
* Returns the total number of nodes in this group.
*/
int64_t num_nodes_total() const;
/**
* Removes a child from the group by index. Does not free the memory.
* \note: Assumes the removed child is not the active layer.
*/
void remove_child(int64_t index);
/**
* Returns a `Span` of pointers to all the `TreeNode`s in this group.
*/
Span<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.
*/
const Layer *find_layer_by_name(StringRefNull name) const;
Layer *find_layer_by_name(StringRefNull name);
/**
* Print the nodes. For debugging purposes.
*/
void print_nodes(StringRefNull header) const;
private:
void ensure_nodes_cache() const;
void tag_nodes_cache_dirty() const;
};
namespace convert {
void legacy_gpencil_frame_to_grease_pencil_drawing(const bGPDframe &gpf,
GreasePencilDrawing &r_drawing);
void legacy_gpencil_to_grease_pencil(Main &main, GreasePencil &grease_pencil, bGPdata &gpd);
} // namespace convert
} // namespace greasepencil
class GreasePencilRuntime {
public:
/**
* Allocated and freed by the drawing code. See `DRW_grease_pencil_batch_cache_*` functions.
*/
void *batch_cache = nullptr;
public:
GreasePencilRuntime() {}
~GreasePencilRuntime() {}
};
} // namespace blender::bke
inline blender::bke::greasepencil::TreeNode &GreasePencilLayerTreeNode::wrap()
{
return *reinterpret_cast<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);
}
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);
}
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);
+1
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;
+1
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;
+5
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
@@ -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
@@ -0,0 +1,262 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2023 Blender Foundation. */
/** \file
* \ingroup bke
*/
#include "BKE_curves.hh"
#include "BKE_grease_pencil.hh"
#include "BKE_material.h"
#include "BLI_color.hh"
#include "BLI_listbase.h"
#include "BLI_math_vector_types.hh"
#include "BLI_vector.hh"
#include "DNA_gpencil_legacy_types.h"
#include "DNA_grease_pencil_types.h"
namespace blender::bke::greasepencil::convert {
void legacy_gpencil_frame_to_grease_pencil_drawing(const bGPDframe &gpf,
GreasePencilDrawing &r_drawing)
{
/* Construct an empty CurvesGeometry in-place. */
new (&r_drawing.geometry) CurvesGeometry();
r_drawing.base.type = GP_DRAWING;
r_drawing.runtime = MEM_new<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;
}
}
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
@@ -0,0 +1,181 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2023 Blender Foundation. */
#include "testing/testing.h"
#include "BKE_curves.hh"
#include "BKE_grease_pencil.hh"
#include "BKE_idtype.h"
#include "BKE_lib_id.h"
#include "BKE_main.h"
using namespace blender::bke::greasepencil;
namespace blender::bke::greasepencil::tests {
/* --------------------------------------------------------------------------------------------- */
/* Grease Pencil ID Tests. */
/* Note: Using a struct with constructor and destructor instead of a fixture here, to have all the
* tests in the same group (`greasepencil`). */
struct GreasePencilIDTestContext {
Main *bmain = nullptr;
GreasePencilIDTestContext()
{
BKE_idtype_init();
bmain = BKE_main_new();
}
~GreasePencilIDTestContext()
{
BKE_main_free(bmain);
}
};
TEST(greasepencil, create_grease_pencil_id)
{
GreasePencilIDTestContext ctx;
GreasePencil &grease_pencil = *static_cast<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);
}
/* --------------------------------------------------------------------------------------------- */
/* 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
@@ -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);
@@ -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:
+3
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);
@@ -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;
}
+27 -3
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);
}
/** \} */
@@ -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;
}
@@ -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";
}
@@ -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;
@@ -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;
@@ -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;
}
@@ -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;
+4 -1
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
@@ -0,0 +1,157 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2023 Blender Foundation. */
/** \file
* \ingroup draw
*/
#pragma once
#include "BKE_gpencil_legacy.h"
#include "BKE_image.h"
#include "DRW_gpu_wrapper.hh"
#include "DRW_render.h"
#include "draw_manager.hh"
#include "draw_pass.hh"
#include "gpencil_shader.hh"
#include "BLI_smaa_textures.h"
namespace blender::draw::greasepencil {
using namespace draw;
/** Final anti-aliasing post processing and compositing on top of render. */
class AntiAliasing {
private:
ShaderModule &shaders_;
Texture smaa_search_tx_ = {"smaa_search",
GPU_R8,
GPU_TEXTURE_USAGE_SHADER_READ,
int2(SEARCHTEX_WIDTH, SEARCHTEX_HEIGHT)};
Texture smaa_area_tx_ = {
"smaa_area", GPU_RG8, GPU_TEXTURE_USAGE_SHADER_READ, int2(AREATEX_WIDTH, AREATEX_HEIGHT)};
TextureFromPool edge_detect_tx_ = {"edge_detect_tx"};
Framebuffer edge_detect_fb_ = {"edge_detect_fb"};
PassSimple edge_detect_ps_ = {"edge_detect_ps"};
TextureFromPool blend_weight_tx_ = {"blend_weight_tx"};
Framebuffer blend_weight_fb_ = {"blend_weight_fb"};
PassSimple blend_weight_ps_ = {"blend_weight_ps"};
Framebuffer output_fb_ = {"output_fb"};
PassSimple resolve_ps_ = {"resolve_ps"};
bool draw_wireframe_ = false;
float luma_weight_ = 1.0f;
bool anti_aliasing_enabled_ = true;
public:
AntiAliasing(ShaderModule &shaders) : shaders_(shaders)
{
GPU_texture_update(smaa_search_tx_, GPU_DATA_UBYTE, searchTexBytes);
GPU_texture_update(smaa_area_tx_, GPU_DATA_UBYTE, areaTexBytes);
GPU_texture_filter_mode(smaa_search_tx_, true);
GPU_texture_filter_mode(smaa_area_tx_, true);
}
void init(const View3D *v3d, const Scene *scene)
{
if (v3d) {
draw_wireframe_ = (v3d->shading.type == OB_WIRE);
}
luma_weight_ = scene->grease_pencil_settings.smaa_threshold;
anti_aliasing_enabled_ = true; // GPENCIL_SIMPLIFY_AA(scene);
}
void begin_sync(TextureFromPool &color_tx, TextureFromPool &reveal_tx)
{
/* TODO(fclem): No global access. */
const float *size = DRW_viewport_size_get();
const float *sizeinv = DRW_viewport_invert_size_get();
const float4 metrics = {sizeinv[0], sizeinv[1], size[0], size[1]};
anti_aliasing_pass(color_tx, reveal_tx, metrics);
/* Resolve pass. */
PassSimple &pass = resolve_ps_;
pass.init();
pass.framebuffer_set(&output_fb_);
pass.state_set(DRW_STATE_WRITE_COLOR | DRW_STATE_BLEND_CUSTOM);
pass.shader_set(shaders_.static_shader_get(ANTIALIASING_RESOLVE));
/** \note use color_tx as dummy if AA is diabled. */
pass.bind_texture("blendTex", anti_aliasing_enabled_ ? &blend_weight_tx_ : &color_tx);
pass.bind_texture("colorTex", &color_tx);
pass.bind_texture("revealTex", &reveal_tx);
pass.push_constant("doAntiAliasing", anti_aliasing_enabled_);
pass.push_constant("onlyAlpha", draw_wireframe_);
pass.push_constant("viewportMetrics", metrics);
pass.draw_procedural(GPU_PRIM_TRIS, 1, 3);
}
void draw(Manager &manager, GPUTexture *dst_color_tx)
{
int2 render_size = {GPU_texture_width(dst_color_tx), GPU_texture_height(dst_color_tx)};
DRW_stats_group_start("Anti-Aliasing");
if (anti_aliasing_enabled_) {
edge_detect_tx_.acquire(render_size, GPU_RG8);
edge_detect_fb_.ensure(GPU_ATTACHMENT_NONE, GPU_ATTACHMENT_TEXTURE(edge_detect_tx_));
manager.submit(edge_detect_ps_);
blend_weight_tx_.acquire(render_size, GPU_RGBA8);
blend_weight_fb_.ensure(GPU_ATTACHMENT_NONE, GPU_ATTACHMENT_TEXTURE(blend_weight_tx_));
manager.submit(blend_weight_ps_);
edge_detect_tx_.release();
}
output_fb_.ensure(GPU_ATTACHMENT_NONE, GPU_ATTACHMENT_TEXTURE(dst_color_tx));
manager.submit(resolve_ps_);
blend_weight_tx_.release();
DRW_stats_group_end();
}
private:
void anti_aliasing_pass(TextureFromPool &color_tx,
TextureFromPool &reveal_tx,
const float4 metrics)
{
if (!anti_aliasing_enabled_) {
return;
}
/* Stage 1: Edge detection. */
edge_detect_ps_.init();
edge_detect_ps_.framebuffer_set(&edge_detect_fb_);
edge_detect_ps_.state_set(DRW_STATE_WRITE_COLOR);
edge_detect_ps_.shader_set(shaders_.static_shader_get(ANTIALIASING_EDGE_DETECT));
edge_detect_ps_.bind_texture("colorTex", &color_tx);
edge_detect_ps_.bind_texture("revealTex", &reveal_tx);
edge_detect_ps_.push_constant("viewportMetrics", metrics);
edge_detect_ps_.push_constant("lumaWeight", luma_weight_);
edge_detect_ps_.clear_color(float4(0.0f));
edge_detect_ps_.draw_procedural(GPU_PRIM_TRIS, 1, 3);
/* Stage 2: Blend Weight/Coord. */
blend_weight_ps_.init();
blend_weight_ps_.framebuffer_set(&blend_weight_fb_);
blend_weight_ps_.state_set(DRW_STATE_WRITE_COLOR);
blend_weight_ps_.shader_set(shaders_.static_shader_get(ANTIALIASING_BLEND_WEIGHT));
blend_weight_ps_.bind_texture("edgesTex", &edge_detect_tx_);
blend_weight_ps_.bind_texture("areaTex", smaa_area_tx_);
blend_weight_ps_.bind_texture("searchTex", smaa_search_tx_);
blend_weight_ps_.push_constant("viewportMetrics", metrics);
blend_weight_ps_.clear_color(float4(0.0f));
blend_weight_ps_.draw_procedural(GPU_PRIM_TRIS, 1, 3);
}
};
} // namespace blender::draw::greasepencil
@@ -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
@@ -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. */
@@ -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) {
@@ -0,0 +1,324 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2023 Blender Foundation. */
/** \file
* \ingroup draw
*/
#include "BKE_gpencil_modifier_legacy.h"
#include "BLI_listbase_wrapper.hh"
#include "DEG_depsgraph_query.h"
#include "DNA_shader_fx_types.h"
#include "DRW_engine.h"
#include "DRW_render.h"
#include "ED_screen.h"
#include "ED_view3d.h"
#include "GPU_capabilities.h"
#include "IMB_imbuf_types.h"
#include "draw_manager.hh"
#include "draw_pass.hh"
#define GP_LIGHT
#include "gpencil_antialiasing.hh"
#include "gpencil_defines.h"
#include "gpencil_engine.h"
#include "gpencil_layer.hh"
#include "gpencil_light.hh"
#include "gpencil_material.hh"
#include "gpencil_object.hh"
#include "gpencil_shader.hh"
#include "gpencil_shader_shared.h"
#include "gpencil_vfx.hh"
namespace blender::draw::greasepencil {
using namespace draw;
class Instance {
private:
ShaderModule &shaders;
LayerModule layers;
MaterialModule materials;
ObjectModule objects;
LightModule lights;
VfxModule vfx;
AntiAliasing anti_aliasing;
/** Contains all gpencil objects in the scene as well as their effect sub-passes. */
PassSortable main_ps_ = {"gp_main_ps"};
/** Contains all composited GPencil object. */
TextureFromPool depth_tx_ = {"gp_depth_tx"};
TextureFromPool color_tx_ = {"gp_color_tx"};
TextureFromPool reveal_tx_ = {"gp_reveal_tx"};
Framebuffer main_fb_ = {"gp_main_fb"};
/** Texture format for all intermediate buffers. */
eGPUTextureFormat texture_format_ = GPU_RGBA16F;
UniformBuffer<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 {
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*/)
{
}
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,
};
}
/** \} */
@@ -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;
@@ -0,0 +1,65 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2023 Blender Foundation. */
/** \file
* \ingroup draw
*/
#pragma once
#include "BKE_grease_pencil.hh"
#include "DRW_gpu_wrapper.hh"
#include "DRW_render.h"
#include "draw_manager.hh"
#include "draw_pass.hh"
namespace blender::draw::greasepencil {
using namespace draw;
class LayerModule {
private:
/** Contains all Objects in the scene. Indexed by gpObject.layer_offset + layer_id. */
StorageVectorBuffer<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)
{
/* 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
@@ -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
@@ -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
@@ -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);
// if (has_blending(layer)) {
// object_subpass.framebuffer_set(*vfx_fb.current());
// }
/* TODO(fclem): Only draw subrange of geometry for this layer. */
object_subpass.draw(geom, handle);
/* TODO: Do layer blending. */
// if (has_blending(layer)) {
// layer_blend_sync(object_ref, object_subpass);
// }
// }
}
else {
/* Fast path. */
object_subpass.bind_texture("gp_pos_tx", position_tx);
object_subpass.bind_texture("gp_col_tx", color_tx);
object_subpass.draw(geom, handle);
}
/* TODO: Do object VFX. */
#if 0
if (object_has_vfx) {
VfxContext vfx_ctx(object_subpass,
layer_fb_,
object_fb_,
object_color_tx_,
layer_color_tx_,
object_reveal_tx_,
layer_reveal_tx_,
is_render_);
/* \note Update this boolean as the actual number of vfx drawn might differ. */
object_has_vfx = vfx.object_sync(main_fb_, object_ref, vfx_ctx, do_material_holdout);
if (object_has_vfx || do_layer_blending) {
is_layer_fb_needed_ = true;
}
}
#endif
}
void end_sync()
{
objects_buf_.push_update();
}
void bind_resources(PassMain::Sub &sub)
{
sub.bind_ssbo(GPENCIL_OBJECT_SLOT, &objects_buf_);
}
void acquire_temporary_buffers(int2 render_size, eGPUTextureFormat format)
{
object_color_tx_.acquire(render_size, format);
object_reveal_tx_.acquire(render_size, format);
object_fb_.ensure(GPU_ATTACHMENT_NONE,
GPU_ATTACHMENT_TEXTURE(object_color_tx_),
GPU_ATTACHMENT_TEXTURE(object_reveal_tx_));
if (is_layer_fb_needed_) {
layer_color_tx_.acquire(render_size, format);
layer_reveal_tx_.acquire(render_size, format);
layer_fb_.ensure(GPU_ATTACHMENT_NONE,
GPU_ATTACHMENT_TEXTURE(layer_color_tx_),
GPU_ATTACHMENT_TEXTURE(layer_reveal_tx_));
}
}
void release_temporary_buffers()
{
object_color_tx_.release();
object_reveal_tx_.release();
layer_color_tx_.release();
layer_reveal_tx_.release();
}
bool scene_has_visible_gpencil_object() const
{
return objects_buf_.size() > 0;
}
};
} // namespace blender::draw::greasepencil
@@ -0,0 +1,116 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2023 Blender Foundation. */
/** \file
* \ingroup draw
*/
#include "gpencil_shader.hh"
namespace blender::draw::greasepencil {
ShaderModule *ShaderModule::g_shader_module = nullptr;
ShaderModule *ShaderModule::module_get()
{
if (g_shader_module == nullptr) {
/* TODO(@fclem) thread-safety. */
g_shader_module = new ShaderModule();
}
return g_shader_module;
}
void ShaderModule::module_free()
{
if (g_shader_module != nullptr) {
/* TODO(@fclem) thread-safety. */
delete g_shader_module;
g_shader_module = nullptr;
}
}
ShaderModule::ShaderModule()
{
for (GPUShader *&shader : shaders_) {
shader = nullptr;
}
#ifdef DEBUG
/* Ensure all shader are described. */
for (auto i : IndexRange(MAX_SHADER_TYPE)) {
const char *name = static_shader_create_info_name_get(eShaderType(i));
if (name == nullptr) {
std::cerr << "GPencil: Missing case for eShaderType(" << i
<< ") in static_shader_create_info_name_get()." << std::endl;
BLI_assert(0);
}
const GPUShaderCreateInfo *create_info = GPU_shader_create_info_get(name);
BLI_assert_msg(create_info != nullptr, "GPencil: Missing create info for static shader.");
}
#endif
}
ShaderModule::~ShaderModule()
{
for (GPUShader *&shader : shaders_) {
DRW_SHADER_FREE_SAFE(shader);
}
}
const char *ShaderModule::static_shader_create_info_name_get(eShaderType shader_type)
{
switch (shader_type) {
case ANTIALIASING_EDGE_DETECT:
return "gpencil_antialiasing_stage_0";
case ANTIALIASING_BLEND_WEIGHT:
return "gpencil_antialiasing_stage_1";
case ANTIALIASING_RESOLVE:
return "gpencil_antialiasing_stage_2";
case GREASE_PENCIL:
return "gpencil_geometry_next";
case LAYER_BLEND:
return "gpencil_layer_blend";
case DEPTH_MERGE:
return "gpencil_depth_merge";
case MASK_INVERT:
return "gpencil_mask_invert";
case FX_COMPOSITE:
return "gpencil_fx_composite";
case FX_COLORIZE:
return "gpencil_fx_colorize";
case FX_BLUR:
return "gpencil_fx_blur";
case FX_GLOW:
return "gpencil_fx_glow";
case FX_PIXEL:
return "gpencil_fx_pixelize";
case FX_RIM:
return "gpencil_fx_rim";
case FX_SHADOW:
return "gpencil_fx_shadow";
case FX_TRANSFORM:
return "gpencil_fx_transform";
/* To avoid compiler warning about missing case. */
case MAX_SHADER_TYPE:
return "";
}
return "";
}
GPUShader *ShaderModule::static_shader_get(eShaderType shader_type)
{
if (shaders_[shader_type] == nullptr) {
const char *shader_name = static_shader_create_info_name_get(shader_type);
shaders_[shader_type] = GPU_shader_create_from_info_name(shader_name);
if (shaders_[shader_type] == nullptr) {
std::cerr << "GPencil: error: Could not compile static shader \"" << shader_name << "\""
<< std::endl;
}
BLI_assert(shaders_[shader_type] != nullptr);
}
return shaders_[shader_type];
}
} // namespace blender::draw::greasepencil
@@ -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
@@ -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
@@ -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
@@ -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);
@@ -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),
@@ -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);
/** \} */
/* -------------------------------------------------------------------- */
@@ -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;
+3
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;
}
+7 -1
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);
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
@@ -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);
/** \} */
/* -------------------------------------------------------------------- */
@@ -0,0 +1,523 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2023 Blender Foundation. */
/** \file
* \ingroup draw
*
* \brief Grease Pencil API for render engines
*/
#include "BKE_curves.hh"
#include "BKE_grease_pencil.h"
#include "BKE_grease_pencil.hh"
#include "BLI_task.hh"
#include "DNA_grease_pencil_types.h"
#include "DRW_engine.h"
#include "DRW_render.h"
#include "GPU_batch.h"
#include "draw_cache_impl.h"
#include "../engines/gpencil/gpencil_defines.h"
#include "../engines/gpencil/gpencil_shader_shared.h"
namespace blender::draw {
struct GreasePencilBatchCache {
/** Instancing Data */
GPUVertBuf *vbo;
GPUVertBuf *vbo_col;
/** Indices in material order, then stroke order with fill first. */
GPUIndexBuf *ibo;
/** Batches */
GPUBatch *geom_batch;
/** Cache is dirty. */
bool is_dirty;
/** Last cached frame. */
int cache_frame;
};
/* -------------------------------------------------------------------- */
/** \name Vertex Formats
* \{ */
/* MUST match the format below. */
struct GreasePencilStrokeVert {
/** Position and radius packed in the same attribute. */
float pos[3], radius;
/** Material Index, Stroke Index, Point Index, Packed aspect + hardness + rotation. */
int32_t mat, stroke_id, point_id, packed_asp_hard_rot;
/** UV and opacity packed in the same attribute. */
float uv_fill[2], u_stroke, opacity;
};
static GPUVertFormat *grease_pencil_stroke_format()
{
static GPUVertFormat format = {0};
if (format.attr_len == 0) {
GPU_vertformat_attr_add(&format, "pos", GPU_COMP_F32, 4, GPU_FETCH_FLOAT);
GPU_vertformat_attr_add(&format, "ma", GPU_COMP_I32, 4, GPU_FETCH_INT);
GPU_vertformat_attr_add(&format, "uv", GPU_COMP_F32, 4, GPU_FETCH_FLOAT);
}
return &format;
}
/* MUST match the format below. */
struct GreasePencilColorVert {
float vcol[4]; /* Vertex color */
float fcol[4]; /* Fill color */
};
static GPUVertFormat *grease_pencil_color_format()
{
static GPUVertFormat format = {0};
if (format.attr_len == 0) {
GPU_vertformat_attr_add(&format, "col", GPU_COMP_F32, 4, GPU_FETCH_FLOAT);
GPU_vertformat_attr_add(&format, "fcol", GPU_COMP_F32, 4, GPU_FETCH_FLOAT);
}
return &format;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Internal Utilities
* \{ */
static bool grease_pencil_batch_cache_valid(const GreasePencil &grease_pencil, int cfra)
{
BLI_assert(grease_pencil.runtime != nullptr);
const GreasePencilBatchCache *cache = static_cast<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
* offsets into the curves for the verticies and triangles. */
int total_points_num = 0;
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();
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()) {
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 = {
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();
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,
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;
}
+29 -8
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;
}
}
@@ -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;
@@ -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");
/** \} */
/* -------------------------------------------------------------------- */
@@ -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;
}
@@ -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);
}
@@ -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
@@ -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:
@@ -1108,7 +1108,7 @@ static const char *template_id_browse_tip(const StructRNA *type)
case ID_PA:
return N_("Browse Particle Settings to be linked");
case ID_GD_LEGACY:
return N_("Browse Grease Pencil Data to be linked");
return N_("Browse Grease Pencil (legacy) Data to be linked");
case ID_MC:
return N_("Browse Movie Clip to be linked");
case ID_MSK:
@@ -1131,6 +1131,8 @@ static const char *template_id_browse_tip(const StructRNA *type)
return N_("Browse Volume Data to be linked");
case ID_SIM:
return N_("Browse Simulation to be linked");
case ID_GP:
return N_("Browse Grease Pencil Data to be linked");
/* Use generic text. */
case ID_LI:
@@ -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}")
@@ -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;
+6 -2
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. */
}
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);
@@ -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;
@@ -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.");
@@ -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);
@@ -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
)
@@ -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;
}
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,
stroke_test_start,
stroke_update_step,
stroke_redraw,
stroke_done,
event->type);
const int return_value = op->type->modal(C, op, event);
if (return_value == OPERATOR_FINISHED) {
return OPERATOR_FINISHED;
}
WM_event_add_modal_handler(C, op);
return OPERATOR_RUNNING_MODAL;
}
static int grease_pencil_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event)
{
return paint_stroke_modal(C, op, event, reinterpret_cast<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(...) */
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);
}
/** \} */
@@ -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
@@ -0,0 +1,176 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2023 Blender Foundation. */
#include "BKE_context.h"
#include "BKE_curves.hh"
#include "BKE_grease_pencil.h"
#include "BKE_grease_pencil.hh"
#include "BKE_scene.h"
#include "DEG_depsgraph_query.h"
#include "ED_view3d.h"
#include "WM_api.h"
#include "WM_types.h"
#include "grease_pencil_intern.hh"
namespace blender::ed::sculpt_paint::greasepencil {
class PaintOperation : public GreasePencilStrokeOperation {
public:
~PaintOperation() override {}
void on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension) override;
void on_stroke_done(const bContext &C) override;
};
/**
* Utility class that actually executes the update when the stroke is updated. That's useful
* because it avoids passing a very large number of parameters between functions.
*/
struct PaintOperationExecutor {
PaintOperationExecutor(const bContext & /*C*/) {}
void execute(PaintOperation & /*self*/,
const bContext &C,
const StrokeExtension &stroke_extension)
{
using namespace blender::bke;
Depsgraph *depsgraph = CTX_data_depsgraph_pointer(&C);
Scene *scene = CTX_data_scene(&C);
ARegion *region = CTX_wm_region(&C);
Object *obact = CTX_data_active_object(&C);
Object *ob_eval = DEG_get_evaluated_object(depsgraph, obact);
/**
* Note: We write to the evaluated object here, so that the additional copy from orig -> eval
* is not needed for every update. After the stroke is done, the result is written to the
* original object.
*/
GreasePencil &grease_pencil = *static_cast<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();
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
@@ -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;
@@ -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();
@@ -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;
}
}
@@ -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;
@@ -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, \
@@ -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:
@@ -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();
@@ -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
+1
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
@@ -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:
+3 -1
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,
+1
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. */
@@ -0,0 +1,466 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2023 Blender Foundation. */
/** \file
* \ingroup DNA
*/
#pragma once
#include "DNA_ID.h"
#include "DNA_curves_types.h"
#include "DNA_listBase.h"
#ifdef __cplusplus
# include "BLI_function_ref.hh"
# include "BLI_map.hh"
# include "BLI_math_vector_types.hh"
# include "BLI_span.hh"
namespace blender::bke {
class GreasePencilRuntime;
class GreasePencilDrawingRuntime;
namespace greasepencil {
class DrawingRuntime;
class TreeNode;
class Layer;
class LayerRuntime;
class LayerGroup;
class LayerGroupRuntime;
struct StrokePoint;
} // namespace greasepencil
} // namespace blender::bke
using GreasePencilRuntimeHandle = blender::bke::GreasePencilRuntime;
using GreasePencilDrawingRuntimeHandle = blender::bke::greasepencil::DrawingRuntime;
using GreasePencilLayerRuntimeHandle = blender::bke::greasepencil::LayerRuntime;
using GreasePencilLayerGroupRuntimeHandle = blender::bke::greasepencil::LayerGroupRuntime;
#else
typedef struct GreasePencilRuntimeHandle GreasePencilRuntimeHandle;
typedef struct GreasePencilDrawingRuntimeHandle GreasePencilDrawingRuntimeHandle;
typedef struct GreasePencilLayerRuntimeHandle GreasePencilLayerRuntimeHandle;
typedef struct GreasePencilLayerGroupRuntimeHandle GreasePencilLayerGroupRuntimeHandle;
#endif
#ifdef __cplusplus
extern "C" {
#endif
struct GreasePencil;
struct BlendDataReader;
struct BlendWriter;
struct Object;
typedef enum GreasePencilStrokeCapType {
GP_STROKE_CAP_TYPE_ROUND = 0,
GP_STROKE_CAP_TYPE_FLAT = 1,
/* Keep last. */
GP_STROKE_CAP_TYPE_MAX,
} GreasePencilStrokeCapType;
/**
* Type of drawing data.
* If `GP_DRAWING` the node is a `GreasePencilDrawing`,
* if `GP_DRAWING_REFERENCE` the node is a `GreasePencilDrawingReference`.
*/
typedef enum GreasePencilDrawingType {
GP_DRAWING = 0,
GP_DRAWING_REFERENCE = 1,
} GreasePencilDrawingType;
/**
* Flag for drawings and drawing references. #GreasePencilDrawingBase.flag
*/
typedef enum GreasePencilDrawingBaseFlag {
/* TODO */
GreasePencilDrawingBaseFlag_TODO
} GreasePencilDrawingBaseFlag;
/**
* Base class for drawings and drawing references (drawings from other objects).
*/
typedef struct GreasePencilDrawingBase {
/**
* One of `GreasePencilDrawingType`.
* Indicates if this is an actual drawing or a drawing referenced from another object.
*/
int8_t type;
char _pad[3];
/**
* Flag. Used to set e.g. the selection status. See `GreasePencilDrawingBaseFlag`.
*/
uint32_t flag;
} GreasePencilDrawingBase;
/**
* A grease pencil drawing is a set of strokes. The data is stored using the `CurvesGeometry` data
* structure and the custom attributes within it.
*/
typedef struct GreasePencilDrawing {
GreasePencilDrawingBase base;
/**
* The stroke data for this drawing.
*/
CurvesGeometry geometry;
/**
* Runtime data on the drawing.
*/
GreasePencilDrawingRuntimeHandle *runtime;
#ifdef __cplusplus
/**
* The triangles for all the fills in the geometry.
*/
blender::Span<blender::uint3> triangles() const;
void tag_positions_changed();
/**
* 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;
/**
* A grease pencil layer mask stores the name of a layer that is the mask.
*/
typedef struct GreasePencilLayerMask {
struct GreasePencilLayerMask *next, *prev;
/**
* The name of the layer that is the mask.
*/
char *layer_name;
/**
* Layer mask flag. See `GreasePencilLayerMaskFlag`.
*/
uint16_t flag;
char _pad[6];
} GreasePencilLayerMask;
/**
* Layer blending modes. #GreasePencilLayer.blend_mode
*/
typedef enum GreasePencilLayerBlendMode {
GP_LAYER_BLEND_NONE = 0,
GP_LAYER_BLEND_HARDLIGHT = 1,
GP_LAYER_BLEND_ADD = 2,
GP_LAYER_BLEND_SUBTRACT = 3,
GP_LAYER_BLEND_MULTIPLY = 4,
GP_LAYER_BLEND_DIVIDE = 5,
} GreasePencilLayerBlendMode;
/**
* Type of layer node.
* If `GP_LAYER_TREE_LEAF` the node is a `GreasePencilLayerTreeLeaf`,
* if `GP_LAYER_TREE_GROUP` the node is a `GreasePencilLayerTreeGroup`.
*/
typedef enum GreasePencilLayerTreeNodeType {
GP_LAYER_TREE_LEAF = 0,
GP_LAYER_TREE_GROUP = 1,
} GreasePencilLayerTreeNodeType;
/**
* Flags for layer tree nodes. #GreasePencilLayerTreeNode.flag
*/
typedef enum GreasePencilLayerTreeNodeFlag {
GP_LAYER_TREE_NODE_HIDE = (1 << 0),
GP_LAYER_TREE_NODE_LOCKED = (1 << 1),
GP_LAYER_TREE_NODE_SELECT = (1 << 2),
GP_LAYER_TREE_NODE_MUTE = (1 << 3),
GP_LAYER_TREE_NODE_USE_LIGHTS = (1 << 4),
GP_LAYER_TREE_NODE_USE_ONION_SKINNING = (1 << 5),
} GreasePencilLayerTreeNodeFlag;
struct GreasePencilLayerTreeGroup;
typedef struct GreasePencilLayerTreeNode {
/* ListBase pointers. */
struct GreasePencilLayerTreeNode *next, *prev;
/* Parent pointer. Can be null. */
struct GreasePencilLayerTreeGroup *parent;
/**
* Name of the layer/group. Dynamic length.
*/
char *name;
/**
* One of `GreasePencilLayerTreeNodeType`.
* Indicates the type of struct this element is.
*/
int8_t type;
/**
* Color tag.
*/
uint8_t color[3];
/**
* Flag. Used to set e.g. the selection, visibility, ... status.
* See `GreasePencilLayerTreeNodeFlag`.
*/
uint32_t flag;
#ifdef __cplusplus
blender::bke::greasepencil::TreeNode &wrap();
const blender::bke::greasepencil::TreeNode &wrap() const;
#endif
} GreasePencilLayerTreeNode;
/**
* A grease pencil layer is a collection of drawings mapped to a specific time on the timeline.
*/
typedef struct GreasePencilLayer {
GreasePencilLayerTreeNode base;
/* Only used for storage in the .blend file. */
GreasePencilLayerFramesMapStorage frames_storage;
/**
* Layer blend mode. See `GreasePencilLayerBlendMode`.
*/
int8_t blend_mode;
char _pad[3];
/**
* Opacity of the layer.
*/
float opacity;
/**
* List of `GreasePencilLayerMask`.
*/
ListBase masks;
/**
* Runtime struct pointer.
*/
GreasePencilLayerRuntimeHandle *runtime;
#ifdef __cplusplus
blender::bke::greasepencil::Layer &wrap();
const blender::bke::greasepencil::Layer &wrap() const;
#endif
} GreasePencilLayer;
typedef struct GreasePencilLayerTreeGroup {
GreasePencilLayerTreeNode base;
/**
* List of `GreasePencilLayerTreeNode`.
*/
ListBase children;
/**
* Runtime struct pointer.
*/
GreasePencilLayerGroupRuntimeHandle *runtime;
#ifdef __cplusplus
blender::bke::greasepencil::LayerGroup &wrap();
const blender::bke::greasepencil::LayerGroup &wrap() const;
#endif
} GreasePencilLayerTreeGroup;
/**
* Flag for the grease pencil data-block. #GreasePencil.flag
*/
typedef enum GreasePencilFlag {
/* TODO */
GreasePencilFlag_TODO
} GreasePencilFlag;
/**
* Onion skinning mode. #GreasePencilOnionSkinningSettings.mode
*/
typedef enum GreasePencilOnionSkinningMode {
GP_ONION_SKINNING_MODE_ABSOLUTE = 0,
GP_ONION_SKINNING_MODE_RELATIVE = 1,
GP_ONION_SKINNING_MODE_SELECTED = 2,
} GreasePencilOnionSkinningMode;
/**
* Flag for filtering the onion skinning per keyframe type.
* #GreasePencilOnionSkinningSettings.filter
* \note needs to match order of `eBezTriple_KeyframeType`.
*/
typedef enum GreasePencilOnionSkinningFilter {
GP_ONION_SKINNING_FILTER_KEYTYPE_KEYFRAME = (1 << 0),
GP_ONION_SKINNING_FILTER_KEYTYPE_EXTREME = (1 << 1),
GP_ONION_SKINNING_FILTER_KEYTYPE_BREAKDOWN = (1 << 2),
GP_ONION_SKINNING_FILTER_KEYTYPE_JITTER = (1 << 3),
GP_ONION_SKINNING_FILTER_KEYTYPE_MOVEHOLD = (1 << 4),
} GreasePencilOnionSkinningFilter;
#define GREASE_PENCIL_ONION_SKINNING_FILTER_ALL \
(GP_ONION_SKINNING_FILTER_KEYTYPE_KEYFRAME | GP_ONION_SKINNING_FILTER_KEYTYPE_EXTREME | \
GP_ONION_SKINNING_FILTER_KEYTYPE_BREAKDOWN | GP_ONION_SKINNING_FILTER_KEYTYPE_JITTER | \
GP_ONION_SKINNING_FILTER_KEYTYPE_MOVEHOLD)
/**
* Per data-block Grease Pencil onion skinning settings.
*/
typedef struct GreasePencilOnionSkinningSettings {
/**
* Opacity for the ghost frames.
*/
float opacity;
/**
* Onion skinning mode. See `GreasePencilOnionSkinningMode`.
*/
int8_t mode;
/**
* Onion skinning filtering flag. See `GreasePencilOnionSkinningFilter`.
*/
uint8_t filter;
char _pad[2];
/**
* Number of ghost frames shown before.
*/
int16_t num_frames_before;
/**
* Number of ghost frames shown after.
*/
int16_t num_frames_after;
/**
* Color of the ghost frames before.
*/
float color_before[3];
/**
* Color of the ghost frames after.
*/
float color_after[3];
char _pad2[4];
} GreasePencilOnionSkinningSettings;
/**
* The grease pencil data-block.
*/
typedef struct GreasePencil {
ID id;
/** Animation data. */
struct AnimData *adt;
/**
* An array of pointers to drawings. The drawing can own its data or reference it from another
* data-block. Note that the order of this array is arbitrary. The mapping of drawings to frames
* is done by the layers. See the `Layer` class in `BKE_grease_pencil.hh`.
*/
GreasePencilDrawingBase **drawing_array;
int drawing_array_size;
char _pad[4];
/* Root group of the layer tree. */
GreasePencilLayerTreeGroup root_group;
/**
* Pointer to the active layer. Can be NULL.
* This pointer does not own the data.
*/
GreasePencilLayer *active_layer;
/**
* An array of materials.
*/
struct Material **material_array;
short material_array_size;
char _pad2[2];
/**
* Global flag on the data-block.
*/
uint32_t flag;
/**
* Onion skinning settings.
*/
GreasePencilOnionSkinningSettings onion_skinning_settings;
/**
* Runtime struct pointer.
*/
GreasePencilRuntimeHandle *runtime;
#ifdef __cplusplus
/* GreasePencilDrawingBase array functions. */
void read_drawing_array(BlendDataReader *reader);
void write_drawing_array(BlendWriter *writer);
void free_drawing_array();
/* Layer tree read/write functions. */
void read_layer_tree(BlendDataReader *reader);
void write_layer_tree(BlendWriter *writer);
/* Drawings read/write access. */
blender::Span<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);
void add_empty_drawings(int add_size);
void remove_drawing(int index);
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
+12 -7
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 {
+2 -1
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;
@@ -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()
@@ -4523,6 +4523,7 @@ static RNAProcessItem PROCESS_ITEMS[] = {
{"rna_dynamicpaint.c", NULL, RNA_def_dynamic_paint},
{"rna_fcurve.c", "rna_fcurve_api.c", RNA_def_fcurve},
{"rna_gpencil_legacy.c", NULL, RNA_def_gpencil},
{"rna_grease_pencil.c", NULL, RNA_def_grease_pencil},
{"rna_curves.c", NULL, RNA_def_curves},
{"rna_image.c", "rna_image_api.c", RNA_def_image},
{"rna_key.c", NULL, RNA_def_key},
+5 -1
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)", ""},
{ID_GP, "GREASEPENCIL_V3", ICON_GREASEPENCIL, "Grease Pencil", ""},
{ID_IM, "IMAGE", ICON_IMAGE_DATA, "Image", ""},
{ID_KE, "KEY", ICON_SHAPEKEY_DATA, "Key", ""},
{ID_LT, "LATTICE", ICON_LATTICE_DATA, "Lattice", ""},
@@ -484,6 +485,9 @@ StructRNA *ID_code_to_RNA_type(short idcode)
return &RNA_Curve;
case ID_GD_LEGACY:
return &RNA_GreasePencil;
case ID_GP:
return &RNA_GreasePencilv3;
break;
case ID_GR:
return &RNA_Collection;
case ID_CV:
@@ -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
@@ -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);
+12
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",
@@ -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;
+4 -1
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)", ""},
{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:
@@ -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,
+1
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)