Skip to content

Add no-copy versions of TextServer::shaped_text_{get_glyphs,sort_logical,get_ellipsis_glyphs} for gdextensions to use. - #124561

Draft
BenLubar wants to merge 1 commit into
godotengine:masterfrom
BenLubar:text-server-gdextension-nocopy-get-glyphs
Draft

BenLubar wants to merge 1 commit into
godotengine:masterfrom
BenLubar:text-server-gdextension-nocopy-get-glyphs

Conversation

@BenLubar

Copy link
Copy Markdown
Contributor

What problem(s) does this PR solve?

Additional Information

As mentioned in the proposal, this feels like a level-of-abstraction mismatch because nothing else outside of core is exposed in this way. That (and the fact that Godot is currently in feature freeze anyway) is why I'm submitting this as a draft for now.

@BenLubar
BenLubar force-pushed the text-server-gdextension-nocopy-get-glyphs branch 3 times, most recently from eb39c53 to 7d43edc Compare October 16, 2026 21:33
@Nintorch Nintorch added this to the 4.x milestone Oct 17, 2026

@bruvzg bruvzg left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not think it should be exposed in the main API, but it's probably OK to do it in the text server code (I only did one method as example):

diff --git a/servers/text/text_server.h b/servers/text/text_server.h
index 311b2d2243..d4e963cbcb 100644
--- a/servers/text/text_server.h
+++ b/servers/text/text_server.h
@@ -537,8 +537,9 @@ public:
 	bool shaped_text_has_visible_chars(const RID &p_shaped) const;
 
 	virtual const Glyph *shaped_text_get_glyphs(const RID &p_shaped) const = 0;
 	TypedArray<Dictionary> _shaped_text_get_glyphs_wrapper(const RID &p_shaped) const;
+	GDExtensionPtr<const Glyph> _shaped_text_get_glyphs_gde_wrapper(const RID &p_shaped) const;
 	virtual const Glyph *shaped_text_sort_logical(const RID &p_shaped) = 0;
 	TypedArray<Dictionary> _shaped_text_sort_logical_wrapper(const RID &p_shaped);
 	virtual int64_t shaped_text_get_glyph_count(const RID &p_shaped) const = 0;
 
diff --git a/servers/text/text_server.cpp b/servers/text/text_server.cpp
index c58f46ee06..5341e49ad4 100644
--- a/servers/text/text_server.cpp
+++ b/servers/text/text_server.cpp
@@ -479,8 +479,9 @@ void TextServer::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("shaped_text_is_ready", "shaped"), &TextServer::shaped_text_is_ready);
 	ClassDB::bind_method(D_METHOD("shaped_text_has_visible_chars", "shaped"), &TextServer::shaped_text_has_visible_chars);
 
 	ClassDB::bind_method(D_METHOD("shaped_text_get_glyphs", "shaped"), &TextServer::_shaped_text_get_glyphs_wrapper);
+	ClassDB::bind_method(D_METHOD("shaped_text_get_glyphs_ptr", "shaped"), &TextServer::_shaped_text_get_glyphs_gde_wrapper);
 	ClassDB::bind_method(D_METHOD("shaped_text_sort_logical", "shaped"), &TextServer::_shaped_text_sort_logical_wrapper);
 	ClassDB::bind_method(D_METHOD("shaped_text_get_glyph_count", "shaped"), &TextServer::shaped_text_get_glyph_count);
 
 	ClassDB::bind_method(D_METHOD("shaped_text_get_range", "shaped"), &TextServer::shaped_text_get_range);
@@ -2328,8 +2329,12 @@ TypedArray<Dictionary> TextServer::_shaped_text_get_glyphs_wrapper(const RID &p_
 
 	return ret;
 }
 
+GDExtensionPtr<const Glyph> TextServer::_shaped_text_get_glyphs_gde_wrapper(const RID &p_shaped) const {
+	return GDExtensionPtr(shaped_text_get_glyphs(p_shaped));
+}
+
 TypedArray<Dictionary> TextServer::_shaped_text_sort_logical_wrapper(const RID &p_shaped) {
 	Array ret;
 
 	const Glyph *glyphs = shaped_text_sort_logical(p_shaped);

This will require a little change on the godot-cpp side as well:

diff --git a/binding_generator.py b/binding_generator.py
index 1629ba1..a8a8640 100644
--- a/binding_generator.py
+++ b/binding_generator.py
@@ -534,8 +534,14 @@ singletons = []
 
 
 def generate_builtin_bindings(api, output_dir, build_config, hooks=None):
     global builtin_classes
+    global native_structures
+
+    for native_struct in api["native_structures"]:
+        if native_struct["name"] == "ObjectID":
+            continue
+        native_structures.append(native_struct["name"])
 
     core_gen_folder = Path(output_dir) / "include" / "godot_cpp" / "core"
     include_gen_folder = Path(output_dir) / "include" / "godot_cpp" / "variant"
     source_gen_folder = Path(output_dir) / "src" / "variant"
@@ -1409,12 +1415,15 @@ def generate_builtin_class_source(builtin_api, size, used_classes, fully_used_cl
             is_ref = False
 
             if "return_type" in method:
                 return_type = method["return_type"]
+                return_type_base = return_type.removeprefix("const ").removesuffix("*")
                 if is_enum(return_type):
                     method_call += f"return ({get_gdextension_type(correct_type(return_type))})::godot::internal::_call_builtin_method_ptr_ret<int64_t>("
                 elif is_pod_type(return_type) or is_variant(return_type):
                     method_call += f"return ::godot::internal::_call_builtin_method_ptr_ret<{get_gdextension_type(correct_type(return_type))}>("
+                elif return_type_base in native_structures:
+                    method_call += f"return ::godot::internal::_call_builtin_method_ptr_ret<{return_type}>("
                 elif is_refcounted(return_type):
                     method_call += f"return Ref<{return_type}>::_gde_internal_constructor(::godot::internal::_call_builtin_method_ptr_ret_obj<{return_type}>("
                     is_ref = True
                 else:
@@ -1546,13 +1555,13 @@ def generate_engine_classes_bindings(api, output_dir, use_template_get_node, hoo
         if class_api["name"] in CLASS_ALIASES:
             class_api["alias_for"] = class_api["name"]
             class_api["name"] = CLASS_ALIASES[class_api["alias_for"]]
         engine_classes[class_api["name"]] = class_api["is_refcounted"]
+
     for native_struct in api["native_structures"]:
         if native_struct["name"] == "ObjectID":
             continue
         engine_classes[native_struct["name"]] = False
-        native_structures.append(native_struct["name"])
 
     for singleton in api["singletons"]:
         # Generate code for the ClassDB singleton under a different name.
         if singleton["name"] in CLASS_ALIASES:
@@ -1760,10 +1769,10 @@ def generate_engine_classes_bindings(api, output_dir, use_template_get_node, hoo
             includes.sort()
 
             for include in includes:
                 result.append(f"#include <{include}>")
-        else:
-            result.append("#include <godot_cpp/core/method_ptrcall.hpp>")
+
+        result.append("#include <godot_cpp/core/method_ptrcall.hpp>")
 
         result.append("")
 
         result.append("namespace godot {")
@@ -2218,8 +2227,9 @@ def generate_engine_class_source(class_api, used_classes, fully_used_classes, us
             is_ref = False
             if not vararg:
                 if has_return:
                     return_type = method["return_value"]["type"]
+                    return_type_base = return_type.removeprefix("const ").removesuffix("*")
                     meta_type = method["return_value"]["meta"] if "meta" in method["return_value"] else None
                     if is_enum(return_type):
                         if method["is_static"]:
                             method_call += f"return ({get_gdextension_type(correct_type(return_type, meta_type))})::godot::internal::_call_native_mb_ret<int64_t>(_gde_method_bind, nullptr"
@@ -2235,8 +2245,13 @@ def generate_engine_class_source(class_api, used_classes, fully_used_classes, us
                             method_call += f"return Ref<{return_type}>::_gde_internal_constructor(::godot::internal::_call_native_mb_ret_obj<{return_type}>(_gde_method_bind, nullptr"
                         else:
                             method_call += f"return Ref<{return_type}>::_gde_internal_constructor(::godot::internal::_call_native_mb_ret_obj<{return_type}>(_gde_method_bind, _owner"
                         is_ref = True
+                    elif return_type_base in native_structures:
+                        if method["is_static"]:
+                            method_call += f"return ::godot::internal::_call_native_mb_ret<{get_gdextension_type(correct_type(return_type, meta_type))}>(_gde_method_bind, nullptr"
+                        else:
+                            method_call += f"return ::godot::internal::_call_native_mb_ret<{get_gdextension_type(correct_type(return_type, meta_type))}>(_gde_method_bind, _owner"
                     else:
                         if method["is_static"]:
                             method_call += f"return ::godot::internal::_call_native_mb_ret_obj<{return_type}>(_gde_method_bind, nullptr"
                         else:

…ical,get_ellipsis_glyphs}` for gdextensions to use.

Co-authored-by: bruvzg <7645683+bruvzg@users.noreply.github.com>
@BenLubar
BenLubar force-pushed the text-server-gdextension-nocopy-get-glyphs branch from 7d43edc to cb5a16c Compare October 25, 2026 22:39
@BenLubar

Copy link
Copy Markdown
Contributor Author

Was not aware that there was already (most of) the infrastructure for returning pointers from ClassDB methods in place. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants