Skip to content

Commit

Permalink
Canvas item hierarchical culling
Browse files Browse the repository at this point in the history
Adds optional hierarchical culling to the 2D rendering (within VisualServer).

Each canvas item maintains a bound in local space of the item itself and all child / grandchild items. This allows branches to be culled at once when they don't intersect a viewport.
  • Loading branch information
lawnjelly committed Apr 25, 2023
1 parent 15c729e commit b777a9e
Show file tree
Hide file tree
Showing 19 changed files with 857 additions and 19 deletions.
4 changes: 4 additions & 0 deletions doc/classes/ProjectSettings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1437,6 +1437,10 @@
[b]Experimental.[/b] If set to on, uses the [code]GL_STREAM_DRAW[/code] flag for legacy buffer uploads. If off, uses the [code]GL_DYNAMIC_DRAW[/code] flag.
[b]Note:[/b] Use with care. You are advised to leave this as default for exports. A non-default setting that works better on your machine may adversely affect performance for end users.
</member>
<member name="rendering/2d/options/culling_mode" type="int" setter="" getter="" default="1">
The culling mode determines the method used for rejecting canvas items that are outside a viewport. The visual result should be identical, but some modes may be faster for a particular project.
You can either cull items individually ([code]Item mode[/code]), or use hierarchical culling ([code]Node mode[/code]) which has a little more housekeeping but can increase performance by culling large numbers of items at once.
</member>
<member name="rendering/2d/options/ninepatch_mode" type="int" setter="" getter="" default="1">
Choose between fixed mode where corner scalings are preserved matching the artwork, and scaling mode.
Not available in GLES3 when [member rendering/batching/options/use_batching] is off.
Expand Down
1 change: 1 addition & 0 deletions drivers/dummy/rasterizer_dummy.h
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,7 @@ class RasterizerStorageDummy : public RasterizerStorage {
void skeleton_bone_set_transform_2d(RID p_skeleton, int p_bone, const Transform2D &p_transform) {}
Transform2D skeleton_bone_get_transform_2d(RID p_skeleton, int p_bone) const { return Transform2D(); }
uint32_t skeleton_get_revision(RID p_skeleton) const { return 0; }
void skeleton_attach_canvas_item(RID p_skeleton, RID p_canvas_item, bool p_attach) {}

/* Light API */

Expand Down
33 changes: 33 additions & 0 deletions drivers/gles2/rasterizer_storage_gles2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
#include "rasterizer_canvas_gles2.h"
#include "rasterizer_scene_gles2.h"
#include "servers/visual/shader_language.h"
#include "servers/visual/visual_server_canvas.h"
#include "servers/visual/visual_server_globals.h"

GLuint RasterizerStorageGLES2::system_fbo = 0;

Expand Down Expand Up @@ -3772,6 +3774,20 @@ void RasterizerStorageGLES2::skeleton_set_base_transform_2d(RID p_skeleton, cons
skeleton->base_transform_2d = p_base_transform;
}

void RasterizerStorageGLES2::skeleton_attach_canvas_item(RID p_skeleton, RID p_canvas_item, bool p_attach) {
Skeleton *skeleton = skeleton_owner.getornull(p_skeleton);
ERR_FAIL_NULL(skeleton);
ERR_FAIL_COND(!p_canvas_item.is_valid());

if (p_attach) {
skeleton->linked_canvas_items.push_back(p_canvas_item);
} else {
int64_t found = skeleton->linked_canvas_items.find(p_canvas_item);
ERR_FAIL_COND(found == -1);
skeleton->linked_canvas_items.remove_unordered(found);
}
}

uint32_t RasterizerStorageGLES2::skeleton_get_revision(RID p_skeleton) const {
const Skeleton *skeleton = skeleton_owner.getornull(p_skeleton);
ERR_FAIL_COND_V(!skeleton, 0);
Expand Down Expand Up @@ -4101,6 +4117,23 @@ void RasterizerStorageGLES2::_update_skeleton_transform_buffer(const PoolVector<
}

void RasterizerStorageGLES2::update_dirty_skeletons() {
// 2D Skeletons always need to update the polygons so they
// know the bounds have changed.
// TODO : Could we have a separate list for 2D only?
SelfList<Skeleton> *ele = skeleton_update_list.first();

while (ele) {
Skeleton *skeleton = ele->self();

int num_linked = skeleton->linked_canvas_items.size();
for (int n = 0; n < num_linked; n++) {
const RID &rid = skeleton->linked_canvas_items[n];
VSG::canvas->_canvas_item_skeleton_moved(rid);
}

ele = ele->next();
}

if (config.use_skeleton_software) {
return;
}
Expand Down
2 changes: 2 additions & 0 deletions drivers/gles2/rasterizer_storage_gles2.h
Original file line number Diff line number Diff line change
Expand Up @@ -903,6 +903,7 @@ class RasterizerStorageGLES2 : public RasterizerStorage {
Set<RasterizerScene::InstanceBase *> instances;

Transform2D base_transform_2d;
LocalVector<RID> linked_canvas_items;

Skeleton() :
use_2d(false),
Expand All @@ -928,6 +929,7 @@ class RasterizerStorageGLES2 : public RasterizerStorage {
virtual Transform2D skeleton_bone_get_transform_2d(RID p_skeleton, int p_bone) const;
virtual void skeleton_set_base_transform_2d(RID p_skeleton, const Transform2D &p_base_transform);
virtual uint32_t skeleton_get_revision(RID p_skeleton) const;
virtual void skeleton_attach_canvas_item(RID p_skeleton, RID p_canvas_item, bool p_attach);

void _update_skeleton_transform_buffer(const PoolVector<float> &p_data, size_t p_size);

Expand Down
34 changes: 34 additions & 0 deletions drivers/gles3/rasterizer_storage_gles3.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
#include "main/main.h"
#include "rasterizer_canvas_gles3.h"
#include "rasterizer_scene_gles3.h"
#include "servers/visual/visual_server_canvas.h"
#include "servers/visual/visual_server_globals.h"
#include "servers/visual_server.h"

#if defined(IPHONE_ENABLED) || defined(ANDROID_ENABLED)
Expand Down Expand Up @@ -5314,13 +5316,45 @@ void RasterizerStorageGLES3::skeleton_set_base_transform_2d(RID p_skeleton, cons
skeleton->base_transform_2d = p_base_transform;
}

void RasterizerStorageGLES3::skeleton_attach_canvas_item(RID p_skeleton, RID p_canvas_item, bool p_attach) {
Skeleton *skeleton = skeleton_owner.getornull(p_skeleton);
ERR_FAIL_NULL(skeleton);
ERR_FAIL_COND(!p_canvas_item.is_valid());

if (p_attach) {
skeleton->linked_canvas_items.push_back(p_canvas_item);
} else {
int64_t found = skeleton->linked_canvas_items.find(p_canvas_item);
ERR_FAIL_COND(found == -1);
skeleton->linked_canvas_items.remove_unordered(found);
}
}

uint32_t RasterizerStorageGLES3::skeleton_get_revision(RID p_skeleton) const {
const Skeleton *skeleton = skeleton_owner.getornull(p_skeleton);
ERR_FAIL_COND_V(!skeleton, 0);
return skeleton->revision;
}

void RasterizerStorageGLES3::update_dirty_skeletons() {
// 2D Skeletons always need to update the polygons so they
// know the bounds have changed.
// TODO : Could we have a separate list for 2D only?
SelfList<Skeleton> *ele = skeleton_update_list.first();

while (ele) {
Skeleton *skeleton = ele->self();

int num_linked = skeleton->linked_canvas_items.size();
for (int n = 0; n < num_linked; n++) {
const RID &rid = skeleton->linked_canvas_items[n];
VSG::canvas->_canvas_item_skeleton_moved(rid);
}

ele = ele->next();
}

// TODO : Is this update necessary for 2D software skinning?
glActiveTexture(GL_TEXTURE0);

while (skeleton_update_list.first()) {
Expand Down
3 changes: 3 additions & 0 deletions drivers/gles3/rasterizer_storage_gles3.h
Original file line number Diff line number Diff line change
Expand Up @@ -926,7 +926,9 @@ class RasterizerStorageGLES3 : public RasterizerStorage {
GLuint texture;
SelfList<Skeleton> update_list;
Set<RasterizerScene::InstanceBase *> instances; //instances using skeleton

Transform2D base_transform_2d;
LocalVector<RID> linked_canvas_items;

Skeleton() :
use_2d(false),
Expand All @@ -952,6 +954,7 @@ class RasterizerStorageGLES3 : public RasterizerStorage {
virtual Transform2D skeleton_bone_get_transform_2d(RID p_skeleton, int p_bone) const;
virtual void skeleton_set_base_transform_2d(RID p_skeleton, const Transform2D &p_base_transform);
virtual uint32_t skeleton_get_revision(RID p_skeleton) const;
virtual void skeleton_attach_canvas_item(RID p_skeleton, RID p_canvas_item, bool p_attach);

/* Light API */

Expand Down
12 changes: 12 additions & 0 deletions scene/2d/canvas_item.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
#include "scene/resources/style_box.h"
#include "scene/resources/texture.h"
#include "scene/scene_string_names.h"
#include "servers/visual/visual_server_constants.h"
#include "servers/visual/visual_server_raster.h"
#include "servers/visual_server.h"

Expand Down Expand Up @@ -613,6 +614,17 @@ void CanvasItem::_notification(int p_what) {
}
}

#ifdef DEV_ENABLED
void CanvasItem::_name_changed_notify() {
// Even in DEV builds, there is no point in calling this unless we are debugging
// canvas item names. Even calling the stub function will be expensive, as there
// are a lot of canvas items.
#ifdef VISUAL_SERVER_CANVAS_DEBUG_ITEM_NAMES
VisualServer::get_singleton()->canvas_item_set_name(canvas_item, get_name());
#endif
}
#endif

void CanvasItem::update() {
if (!is_inside_tree()) {
return;
Expand Down
4 changes: 4 additions & 0 deletions scene/2d/canvas_item.h
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,10 @@ class CanvasItem : public Node {
void _notification(int p_what);
static void _bind_methods();

#ifdef DEV_ENABLED
virtual void _name_changed_notify();
#endif

public:
enum {
NOTIFICATION_TRANSFORM_CHANGED = SceneTree::NOTIFICATION_TRANSFORM_CHANGED, //unique
Expand Down
17 changes: 17 additions & 0 deletions scene/2d/polygon_2d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,15 @@ void Polygon2D::_skeleton_bone_setup_changed() {

void Polygon2D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
// Must re-establish any existing links with skeletons on re-entering the tree.
update();
} break;
case NOTIFICATION_EXIT_TREE: {
// Always detach skeleton when exiting the tree, so skeletons don't inform
// Polygon2Ds outside the tree that they have moved (this would be useless work).
VS::get_singleton()->canvas_item_attach_skeleton(get_canvas_item(), RID());
} break;
case NOTIFICATION_DRAW: {
if (polygon.size() < 3) {
return;
Expand Down Expand Up @@ -665,3 +674,11 @@ Polygon2D::Polygon2D() {
internal_vertices = 0;
current_skeleton_id = 0;
}

Polygon2D::~Polygon2D() {
// Most definitely don't want to leave references to this deleted canvas item
// in the skeleton.
if (get_canvas_item().is_valid()) {
VS::get_singleton()->canvas_item_attach_skeleton(get_canvas_item(), RID());
}
}
1 change: 1 addition & 0 deletions scene/2d/polygon_2d.h
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ class Polygon2D : public Node2D {
NodePath get_skeleton() const;

Polygon2D();
virtual ~Polygon2D();
};

#endif // POLYGON_2D_H
14 changes: 13 additions & 1 deletion scene/main/node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1051,7 +1051,15 @@ StringName Node::get_name() const {

void Node::_set_name_nocheck(const StringName &p_name) {
data.name = p_name;
#ifdef DEV_ENABLED
_name_changed_notify();
#endif
}

#ifdef DEV_ENABLED
void Node::_name_changed_notify() {
}
#endif

void Node::set_name(const String &p_name) {
String name = p_name.validate_node_name();
Expand All @@ -1078,6 +1086,10 @@ void Node::set_name(const String &p_name) {
get_tree()->node_renamed(this);
get_tree()->tree_changed();
}

#ifdef DEV_ENABLED
_name_changed_notify();
#endif
}

static bool node_hrcr = false;
Expand Down Expand Up @@ -1262,7 +1274,7 @@ void Node::_generate_serial_child_name(const Node *p_child, StringName &name) co
void Node::_add_child_nocheck(Node *p_child, const StringName &p_name) {
//add a child node quickly, without name validation

p_child->data.name = p_name;
p_child->_set_name_nocheck(p_name);
p_child->data.pos = data.children.size();
data.children.push_back(p_child);
p_child->data.parent = this;
Expand Down
3 changes: 3 additions & 0 deletions scene/main/node.h
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,9 @@ class Node : public Object {
virtual void remove_child_notify(Node *p_child);
virtual void move_child_notify(Node *p_child);
virtual void owner_changed_notify();
#ifdef DEV_ENABLED
virtual void _name_changed_notify();
#endif

virtual void _physics_interpolated_changed();

Expand Down
7 changes: 7 additions & 0 deletions servers/visual/rasterizer.h
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,7 @@ class RasterizerStorage {
virtual Transform2D skeleton_bone_get_transform_2d(RID p_skeleton, int p_bone) const = 0;
virtual void skeleton_set_base_transform_2d(RID p_skeleton, const Transform2D &p_base_transform) = 0;
virtual uint32_t skeleton_get_revision(RID p_skeleton) const = 0;
virtual void skeleton_attach_canvas_item(RID p_skeleton, RID p_canvas_item, bool p_attach) = 0;

/* Light API */

Expand Down Expand Up @@ -985,6 +986,7 @@ class RasterizerCanvas {
bool light_masked : 1;
mutable bool custom_rect : 1;
mutable bool rect_dirty : 1;
mutable bool bound_dirty : 1;

Vector<Command *> commands;
mutable Rect2 rect;
Expand Down Expand Up @@ -1024,6 +1026,10 @@ class RasterizerCanvas {
void precalculate_polygon_bone_bounds(const Item::CommandPolygon &p_polygon) const;

public:
// the rect containing this item and all children,
// in local space.
Rect2 local_bound;

const Rect2 &get_rect() const {
if (custom_rect) {
return rect;
Expand Down Expand Up @@ -1201,6 +1207,7 @@ class RasterizerCanvas {
final_modulate = Color(1, 1, 1, 1);
visible = true;
rect_dirty = true;
bound_dirty = true;
custom_rect = false;
behind = false;
material_owner = nullptr;
Expand Down
Loading

0 comments on commit b777a9e

Please sign in to comment.