From f4a4956b7aa03449122af1decc9cd17e41d80abc Mon Sep 17 00:00:00 2001 From: Relintai Date: Mon, 1 May 2023 13:34:35 +0200 Subject: [PATCH] Ported: Fix Polygon2D skinned bounds (for culling) The bound Rect2 was previously incorrect because bone transforms need to be applied to verts in bone space, rather than local space. This was previously resulting in skinned Polygon2Ds being incorrectly culled. - lawnjelly https://github.com/godotengine/godot/commit/dd6c213dac4d029fb04e4d92cd025b274a452419 --- doc/classes/RenderingServer.xml | 8 + scene/2d/polygon_2d.cpp | 10 + servers/rendering/rasterizer.cpp | 172 +++++++++++++++++- servers/rendering/rasterizer.h | 93 ++++------ servers/rendering/rendering_server_canvas.cpp | 32 ++++ servers/rendering/rendering_server_canvas.h | 2 + servers/rendering/rendering_server_raster.h | 2 + servers/rendering/rendering_server_wrap_mt.h | 2 + servers/rendering_server.cpp | 1 + servers/rendering_server.h | 10 + 10 files changed, 276 insertions(+), 56 deletions(-) diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml index 61ba72f76..e4f8a6da0 100644 --- a/doc/classes/RenderingServer.xml +++ b/doc/classes/RenderingServer.xml @@ -717,6 +717,14 @@ Modulates all colors in the given canvas. + ] + + + + + Returns the bounding rectangle for a canvas item in local space, as calculated by the renderer. This bound is used internally for culling. + [b]Warning:[/b] This function is intended for debugging in the editor, and will pass through and return a zero [Rect2] in exported projects. + diff --git a/scene/2d/polygon_2d.cpp b/scene/2d/polygon_2d.cpp index 94148e1a6..622e2283a 100644 --- a/scene/2d/polygon_2d.cpp +++ b/scene/2d/polygon_2d.cpp @@ -117,6 +117,16 @@ void Polygon2D::_notification(int p_what) { if (skeleton_node) { RS::get_singleton()->canvas_item_attach_skeleton(get_canvas_item(), skeleton_node->get_skeleton()); new_skeleton_id = skeleton_node->get_instance_id(); + + // Sync the offset transform between the Polygon2D and the skeleton. + // This is needed for accurate culling in VisualServer. + Transform2D global_xform_skel = skeleton_node->get_global_transform(); + Transform2D global_xform_poly = get_global_transform(); + + // find the difference + Transform2D global_xform_offset = global_xform_skel.affine_inverse() * global_xform_poly; + RS::get_singleton()->canvas_item_set_skeleton_relative_xform(get_canvas_item(), global_xform_offset); + } else { RS::get_singleton()->canvas_item_attach_skeleton(get_canvas_item(), RID()); } diff --git a/servers/rendering/rasterizer.cpp b/servers/rendering/rasterizer.cpp index 3dd01afe0..635b81181 100644 --- a/servers/rendering/rasterizer.cpp +++ b/servers/rendering/rasterizer.cpp @@ -456,7 +456,7 @@ void RasterizerStorage::multimesh_set_physics_interpolation_quality(RID p_multim ERR_FAIL_COND((p_quality < 0) || (p_quality > 1)); MMInterpolator *mmi = _multimesh_get_interpolator(p_multimesh); if (mmi) { - mmi->quality = (int) p_quality; + mmi->quality = (int)p_quality; } } @@ -529,3 +529,173 @@ int RasterizerStorage::multimesh_get_visible_instances(RID p_multimesh) const { AABB RasterizerStorage::multimesh_get_aabb(RID p_multimesh) const { return _multimesh_get_aabb(p_multimesh); } + +// The bone bounds are determined by rigging, +// as such they can be calculated as a one off operation, +// rather than each call to get_rect(). +void RasterizerCanvas::Item::precalculate_polygon_bone_bounds(const Item::CommandPolygon &p_polygon) const { + p_polygon.skinning_data->dirty = false; + p_polygon.skinning_data->untransformed_bound = Rect2(Vector2(), Vector2(-1, -1)); // negative means unused. + + int num_points = p_polygon.points.size(); + const Point2 *pp = &p_polygon.points[0]; + + // Calculate bone AABBs. + int bone_count = RasterizerStorage::base_singleton->skeleton_get_bone_count(skeleton); + + // Get some local aliases + LocalVector &active_bounds = p_polygon.skinning_data->active_bounds; + LocalVector &active_bone_ids = p_polygon.skinning_data->active_bone_ids; + active_bounds.clear(); + active_bone_ids.clear(); + + // Uses dynamic allocation, but shouldn't happen very often. + // If happens more often, use alloca. + LocalVector bone_to_active_bone_mapping; + bone_to_active_bone_mapping.resize(bone_count); + + for (int n = 0; n < bone_count; n++) { + bone_to_active_bone_mapping[n] = -1; + } + + const Transform2D &item_transform = skinning_data->skeleton_relative_xform; + + bool some_were_untransformed = false; + + for (int n = 0; n < num_points; n++) { + Point2 p = pp[n]; + bool bone_space = false; + float total_weight = 0; + + for (int k = 0; k < 4; k++) { + int bone_id = p_polygon.bones[n * 4 + k]; + float w = p_polygon.weights[n * 4 + k]; + if (w == 0) { + continue; + } + total_weight += w; + + // Ensure the point is in "bone space" / rigged space. + if (!bone_space) { + bone_space = true; + p = item_transform.xform(p); + } + + // get the active bone, or create a new active bone + DEV_ASSERT(bone_id < bone_count); + int32_t &active_bone = bone_to_active_bone_mapping[bone_id]; + if (active_bone != -1) { + active_bounds[active_bone].expand_to(p); + } else { + // Increment the number of active bones stored. + active_bone = active_bounds.size(); + active_bounds.resize(active_bone + 1); + active_bone_ids.resize(active_bone + 1); + + // First point for the bone + DEV_ASSERT(bone_id <= UINT16_MAX); + active_bone_ids[active_bone] = bone_id; + active_bounds[active_bone] = Rect2(p, Vector2(0.00001, 0.00001)); + } + } + + // If some points were not rigged, + // we want to add them directly to an "untransformed bound", + // and merge this with the skinned bound later. + // Also do this if a point is not FULLY weighted, + // because the untransformed position is still having an influence. + if (!bone_space || (total_weight < 0.99f)) { + if (some_were_untransformed) { + p_polygon.skinning_data->untransformed_bound.expand_to(pp[n]); + } else { + // First point + some_were_untransformed = true; + p_polygon.skinning_data->untransformed_bound = Rect2(pp[n], Vector2()); + } + } + } +} + +Rect2 RasterizerCanvas::Item::calculate_polygon_bounds(const Item::CommandPolygon &p_polygon) const { + int num_points = p_polygon.points.size(); + + // If there is no skeleton, or the bones data is invalid... + // Note : Can we check the second more efficiently? by checking if polygon.skinning_data is set perhaps? + if (skeleton == RID() || !(num_points && p_polygon.bones.size() == num_points * 4 && p_polygon.weights.size() == p_polygon.bones.size())) { + // With no skeleton, all points are untransformed. + Rect2 r; + const Point2 *pp = &p_polygon.points[0]; + r.position = pp[0]; + + for (int n = 1; n < num_points; n++) { + r.expand_to(pp[n]); + } + + return r; + } + + // Skinned skeleton is present. + ERR_FAIL_COND_V_MSG(!skinning_data, Rect2(), "Skinned Polygon2D must have skeleton_relative_xform set for correct culling."); + + // Ensure the polygon skinning data is created... + // (This isn't stored on every polygon to save memory). + if (!p_polygon.skinning_data) { + p_polygon.skinning_data = memnew(Item::CommandPolygon::SkinningData); + } + + Item::CommandPolygon::SkinningData &pdata = *p_polygon.skinning_data; + + // This should only occur when rigging has changed. + // Usually a one off in games. + if (pdata.dirty) { + precalculate_polygon_bone_bounds(p_polygon); + } + + // We only deal with the precalculated ACTIVE bone AABBs using the skeleton. + // (No need to bother with bones that are unused for this poly.) + int num_active_bones = pdata.active_bounds.size(); + if (!num_active_bones) { + return pdata.untransformed_bound; + } + + // No need to make a dynamic allocation here in 99% of cases. + Rect2 *bptr = nullptr; + LocalVector bone_aabbs; + if (num_active_bones <= 1024) { + bptr = (Rect2 *)alloca(sizeof(Rect2) * num_active_bones); + } else { + bone_aabbs.resize(num_active_bones); + bptr = bone_aabbs.ptr(); + } + + // Copy across the precalculated bone bounds. + memcpy(bptr, pdata.active_bounds.ptr(), sizeof(Rect2) * num_active_bones); + + const Transform2D &item_transform_inv = skinning_data->skeleton_relative_xform_inv; + + Rect2 aabb; + bool first_bone = true; + + for (int n = 0; n < num_active_bones; n++) { + int bone_id = pdata.active_bone_ids[n]; + const Transform2D &mtx = RasterizerStorage::base_singleton->skeleton_bone_get_transform_2d(skeleton, bone_id); + Rect2 baabb = mtx.xform(bptr[n]); + + if (first_bone) { + aabb = baabb; + first_bone = false; + } else { + aabb = aabb.merge(baabb); + } + } + + // Transform the polygon AABB back into local space from bone space. + aabb = item_transform_inv.xform(aabb); + + // If some were untransformed... + if (pdata.untransformed_bound.size.x >= 0) { + return pdata.untransformed_bound.merge(aabb); + } + + return aabb; +} diff --git a/servers/rendering/rasterizer.h b/servers/rendering/rasterizer.h index ceebb6c1d..00e638e3c 100644 --- a/servers/rendering/rasterizer.h +++ b/servers/rendering/rasterizer.h @@ -771,9 +771,26 @@ public: bool antialiased; bool antialiasing_use_indices; + struct SkinningData { + bool dirty = true; + LocalVector active_bounds; + LocalVector active_bone_ids; + Rect2 untransformed_bound; + }; + + mutable SkinningData *skinning_data; + CommandPolygon() { type = TYPE_POLYGON; count = 0; + skinning_data = NULL; + } + + virtual ~CommandPolygon() { + if (skinning_data) { + memdelete(skinning_data); + skinning_data = NULL; + } } }; @@ -840,6 +857,12 @@ public: Item *next; + struct SkinningData { + Transform2D skeleton_relative_xform; + Transform2D skeleton_relative_xform_inv; + }; + SkinningData *skinning_data; + struct CopyBackBuffer { Rect2 rect; Rect2 screen_rect; @@ -856,6 +879,11 @@ public: Rect2 global_rect_cache; + private: + Rect2 calculate_polygon_bounds(const Item::CommandPolygon &p_polygon) const; + void precalculate_polygon_bone_bounds(const Item::CommandPolygon &p_polygon) const; + + public: const Rect2 &get_rect() const { if (custom_rect) { return rect; @@ -951,61 +979,8 @@ public: } break; case Item::Command::TYPE_POLYGON: { const Item::CommandPolygon *polygon = static_cast(c); - int l = polygon->points.size(); - const Point2 *pp = &polygon->points[0]; - r.position = pp[0]; - for (int j = 1; j < l; j++) { - r.expand_to(pp[j]); - } - - if (skeleton != RID()) { - // calculate bone AABBs - int bone_count = RasterizerStorage::base_singleton->skeleton_get_bone_count(skeleton); - - Vector bone_aabbs; - bone_aabbs.resize(bone_count); - Rect2 *bptr = bone_aabbs.ptrw(); - - for (int j = 0; j < bone_count; j++) { - bptr[j].size = Vector2(-1, -1); //negative means unused - } - if (l && polygon->bones.size() == l * 4 && polygon->weights.size() == polygon->bones.size()) { - for (int j = 0; j < l; j++) { - Point2 p = pp[j]; - for (int k = 0; k < 4; k++) { - int idx = polygon->bones[j * 4 + k]; - float w = polygon->weights[j * 4 + k]; - if (w == 0) { - continue; - } - - if (bptr[idx].size.x < 0) { - //first - bptr[idx] = Rect2(p, Vector2(0.00001, 0.00001)); - } else { - bptr[idx].expand_to(p); - } - } - } - - Rect2 aabb; - bool first_bone = true; - for (int j = 0; j < bone_count; j++) { - Transform2D mtx = RasterizerStorage::base_singleton->skeleton_bone_get_transform_2d(skeleton, j); - Rect2 baabb = mtx.xform(bone_aabbs[j]); - - if (first_bone) { - aabb = baabb; - first_bone = false; - } else { - aabb = aabb.merge(baabb); - } - } - - r = r.merge(aabb); - } - } - + DEV_ASSERT(polygon); + r = calculate_polygon_bounds(*polygon); } break; case Item::Command::TYPE_MESH: { const Item::CommandMesh *mesh = static_cast(c); @@ -1063,12 +1038,19 @@ public: final_clip_owner = nullptr; material_owner = nullptr; light_masked = false; + + if (skinning_data) { + memdelete(skinning_data); + skinning_data = NULL; + } } + Item() { light_mask = 1; skeleton_revision = 0; vp_render = nullptr; next = nullptr; + skinning_data = NULL; final_clip_owner = nullptr; clip = false; final_modulate = Color(1, 1, 1, 1); @@ -1082,6 +1064,7 @@ public: light_masked = false; update_when_visible = false; } + virtual ~Item() { clear(); if (copy_back_buffer) { diff --git a/servers/rendering/rendering_server_canvas.cpp b/servers/rendering/rendering_server_canvas.cpp index 2d916b75d..0525abad4 100644 --- a/servers/rendering/rendering_server_canvas.cpp +++ b/servers/rendering/rendering_server_canvas.cpp @@ -927,6 +927,38 @@ void RenderingServerCanvas::canvas_item_set_z_as_relative_to_parent(RID p_item, canvas_item->z_relative = p_enable; } +Rect2 RenderingServerCanvas::_debug_canvas_item_get_rect(RID p_item) { + Item *canvas_item = canvas_item_owner.getornull(p_item); + ERR_FAIL_COND_V(!canvas_item, Rect2()); + return canvas_item->get_rect(); +} + +void RenderingServerCanvas::canvas_item_set_skeleton_relative_xform(RID p_item, Transform2D p_relative_xform) { + Item *canvas_item = canvas_item_owner.getornull(p_item); + ERR_FAIL_COND(!canvas_item); + + if (!canvas_item->skinning_data) { + canvas_item->skinning_data = memnew(Item::SkinningData); + } + canvas_item->skinning_data->skeleton_relative_xform = p_relative_xform; + canvas_item->skinning_data->skeleton_relative_xform_inv = p_relative_xform.affine_inverse(); + + // Set any Polygon2Ds pre-calced bone bounds to dirty. + for (int n = 0; n < canvas_item->commands.size(); n++) { + Item::Command *c = canvas_item->commands[n]; + if (c->type == Item::Command::TYPE_POLYGON) { + Item::CommandPolygon *polygon = static_cast(c); + + // Make sure skinning data is present. + if (!polygon->skinning_data) { + polygon->skinning_data = memnew(Item::CommandPolygon::SkinningData); + } + + polygon->skinning_data->dirty = true; + } + } +} + void RenderingServerCanvas::canvas_item_attach_skeleton(RID p_item, RID p_skeleton) { Item *canvas_item = canvas_item_owner.getornull(p_item); ERR_FAIL_COND(!canvas_item); diff --git a/servers/rendering/rendering_server_canvas.h b/servers/rendering/rendering_server_canvas.h index c15bfe380..f23c6af60 100644 --- a/servers/rendering/rendering_server_canvas.h +++ b/servers/rendering/rendering_server_canvas.h @@ -207,6 +207,8 @@ public: void canvas_item_set_z_as_relative_to_parent(RID p_item, bool p_enable); void canvas_item_set_copy_to_backbuffer(RID p_item, bool p_enable, const Rect2 &p_rect); void canvas_item_attach_skeleton(RID p_item, RID p_skeleton); + void canvas_item_set_skeleton_relative_xform(RID p_item, Transform2D p_relative_xform); + Rect2 _debug_canvas_item_get_rect(RID p_item); void canvas_item_clear(RID p_item); void canvas_item_set_draw_index(RID p_item, int p_index); diff --git a/servers/rendering/rendering_server_raster.h b/servers/rendering/rendering_server_raster.h index 7d101d721..6e80a282d 100644 --- a/servers/rendering/rendering_server_raster.h +++ b/servers/rendering/rendering_server_raster.h @@ -608,6 +608,8 @@ public: BIND2(canvas_item_set_z_as_relative_to_parent, RID, bool) BIND3(canvas_item_set_copy_to_backbuffer, RID, bool, const Rect2 &) BIND2(canvas_item_attach_skeleton, RID, RID) + BIND2(canvas_item_set_skeleton_relative_xform, RID, Transform2D) + BIND1R(Rect2, _debug_canvas_item_get_rect, RID) BIND1(canvas_item_clear, RID) BIND2(canvas_item_set_draw_index, RID, int) diff --git a/servers/rendering/rendering_server_wrap_mt.h b/servers/rendering/rendering_server_wrap_mt.h index 046f85637..42245719a 100644 --- a/servers/rendering/rendering_server_wrap_mt.h +++ b/servers/rendering/rendering_server_wrap_mt.h @@ -521,6 +521,8 @@ public: FUNC2(canvas_item_set_z_as_relative_to_parent, RID, bool) FUNC3(canvas_item_set_copy_to_backbuffer, RID, bool, const Rect2 &) FUNC2(canvas_item_attach_skeleton, RID, RID) + FUNC2(canvas_item_set_skeleton_relative_xform, RID, Transform2D) + FUNC1R(Rect2, _debug_canvas_item_get_rect, RID) FUNC1(canvas_item_clear, RID) FUNC2(canvas_item_set_draw_index, RID, int) diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp index 01b3aaeb6..5e6e5d6ee 100644 --- a/servers/rendering_server.cpp +++ b/servers/rendering_server.cpp @@ -2137,6 +2137,7 @@ void RenderingServer::_bind_methods() { ClassDB::bind_method(D_METHOD("canvas_item_set_draw_index", "item", "index"), &RenderingServer::canvas_item_set_draw_index); ClassDB::bind_method(D_METHOD("canvas_item_set_material", "item", "material"), &RenderingServer::canvas_item_set_material); ClassDB::bind_method(D_METHOD("canvas_item_set_use_parent_material", "item", "enabled"), &RenderingServer::canvas_item_set_use_parent_material); + ClassDB::bind_method(D_METHOD("debug_canvas_item_get_rect", "item"), &RenderingServer::debug_canvas_item_get_rect); ClassDB::bind_method(D_METHOD("canvas_light_create"), &RenderingServer::canvas_light_create); ClassDB::bind_method(D_METHOD("canvas_light_attach_to_canvas", "light", "canvas"), &RenderingServer::canvas_light_attach_to_canvas); ClassDB::bind_method(D_METHOD("canvas_light_set_enabled", "light", "enabled"), &RenderingServer::canvas_light_set_enabled); diff --git a/servers/rendering_server.h b/servers/rendering_server.h index cbb878cbc..ed4013442 100644 --- a/servers/rendering_server.h +++ b/servers/rendering_server.h @@ -938,6 +938,16 @@ public: virtual void canvas_item_set_copy_to_backbuffer(RID p_item, bool p_enable, const Rect2 &p_rect) = 0; virtual void canvas_item_attach_skeleton(RID p_item, RID p_skeleton) = 0; + virtual void canvas_item_set_skeleton_relative_xform(RID p_item, Transform2D p_relative_xform) = 0; + + Rect2 debug_canvas_item_get_rect(RID p_item) { +#ifdef TOOLS_ENABLED + return _debug_canvas_item_get_rect(p_item); +#else + return Rect2(); +#endif + } + virtual Rect2 _debug_canvas_item_get_rect(RID p_item) = 0; virtual void canvas_item_clear(RID p_item) = 0; virtual void canvas_item_set_draw_index(RID p_item, int p_index) = 0;