geom

Using geom from C# and other languages

What works from GDScript, C#, and other GDExtensions, and what each one costs.

geom is a GDExtension, so its classes are registered in Godot’s ClassDB and every language the engine supports can reach them. What differs is how much typing help you get.

The nodes work everywhere, with no code

GeomDisc2D, GeomRect2D, GeomSphere and the rest are ordinary scene nodes. Add one in the editor, set its properties in the Inspector, and it draws. That path is identical in a GDScript project and a C# one, and needs nothing from this page.

GDScript

First-class. The singleton and the enums are globals, and the enum name works as a type:

extends Node2D
## The GDScript form of the drawing API, and the enum types that go with it.
##
## Nothing here is language-specific -- it exists so the guide's GDScript is
## code CI actually runs, next to C# that says the same thing.


func _process(_delta: float) -> void:
	GeomDraw.reset_all_draw_states()

	# Sizes and modes are named constants on Geom, not bare integers.
	GeomDraw.thickness_space = Geom.SPACE_PIXELS
	GeomDraw.thickness = 6.0
	GeomDraw.blend_mode = Geom.BLEND_ADDITIVE
	GeomDraw.disc_2d_colored(Vector2(100, 100), 40.0, Color(0.42, 0.85, 1.0))

	# The enum name is a type, so a variable can say what it is allowed to hold.
	var mode: Geom.BlendMode = Geom.BlendMode.BLEND_TRANSPARENT
	GeomDraw.blend_mode = mode
	GeomDraw.ring_2d_colored(Vector2(220, 100), 40.0, Color(1.0, 0.78, 0.36))

	# Any language can read the constants by name through ClassDB, which is how
	# a C++ or Rust extension gets at them.
	var additive: int = ClassDB.class_get_integer_constant("Geom", "BLEND_ADDITIVE")
	GeomDraw.blend_mode = additive
	GeomDraw.disc_2d_colored(Vector2(340, 100), 40.0, Color(0.55, 0.98, 0.78))

languages

C#

Godot does not generate C# bindings for a third-party GDExtension. Without help the whole API is Call("disc_2d", ...) with a string for every name and a bare integer for every enum.

The addon ships the missing piece: addons/geom/cs/Geom.cs, a generated file that wraps the singleton and declares the enums as real C# enums. Drop the addon into a C# project and it compiles with the rest of your code — Godot.NET.Sdk picks up every .cs under the project, including under addons/.

using Godot;

Geom.Draw.ResetAllDrawStates();
Geom.Draw.ThicknessSpace = Geom.ThicknessSpace.Pixels;
Geom.Draw.Thickness = 6f;
Geom.Draw.BlendMode = Geom.BlendMode.Additive;
Geom.Draw.Disc2DColored(new Vector2(100, 100), 40f, Colors.Cyan);

The enum names lose their prefixes here, because a C# enum is a real scope where GDScript’s are not: Geom.BlendMode.Additive, not BLEND_ADDITIVE.

To create a node from code, use the factories — an extension class cannot be newed from C#:

var disc = Geom.Nodes.Disc2D();
disc.Set("radius", 40f);
AddChild(disc);

Node properties go through Set and Get. They are not wrapped, because a node is normally configured in the Inspector rather than from code.

One call per shape is the slow way

Every call from C# crosses the Variant boundary, and that crossing costs far more than the shape does. Drawing a thousand discs one call at a time:

one call per shapeone bulk call
GDScript114µs71µs
C#936µs90µs

Per shape, C# is eight times slower than GDScript — GDScript has a typed fast path into the extension and C# has none, so every argument is boxed into a Variant and the method is looked up by name.

The fix is not to call less often but to call with more: the plural calls take the whole batch in one crossing, which is 10× faster from C# and puts it level with idiomatic GDScript.

var centers = new Vector2[4000];
// ... fill centers ...
Geom.Draw.Discs2D(centers, new float[] { 2f }, new Color[] { Colors.Cyan });

There is Discs2D, Rings2D, Lines2D, Rects2D, Pies2D, Arcs2D, Polygons2D and Polylines2D, and the same eight without the suffix for 3D. Sizes and colors broadcast: one entry applies to every shape, n entries apply one each, and an empty color array means the current Color. A length that cannot line up draws nothing and reports why, rather than half a batch.

Polylines2D takes the points of every path in one array and a counts array saying how many belong to each, so twelve points with counts [4, 4, 4] are three disconnected paths rather than one of twelve. Drawing 400 short paths that way measured 3.1× faster than one call each.

Nodes are unaffected either way — they are configured once, not per frame.

Other GDExtensions

A Rust or C++ extension reaches geom the same way C# does, through ClassDB:

let mut draw = Engine::singleton().get_singleton("GeomDraw").unwrap();
draw.call("disc_2d", &[Vector2::new(100.0, 100.0).to_variant(), 40.0.to_variant()]);

There is no generated binding for this case. The constants are readable by name through ClassDB.class_get_integer_constant("Geom", "BLEND_ADDITIVE") rather than hard-coded, which the GDScript example above also shows.

Summary

Nodes in the editorTyped drawing APITyped enumsFast bulk drawing
GDScriptyesyesyesyes
C#yesyes, via the shipped shimyes, as C# enumsyes
Other GDExtensionsyesno, dynamic callsby name lookupyes

The bulk calls matter most for the languages without a typed fast path, which is why they exist — but they are the quickest option from GDScript too.