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))

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 shape | one bulk call | |
|---|---|---|
| GDScript | 114µs | 71µs |
| C# | 936µs | 90µ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 editor | Typed drawing API | Typed enums | Fast bulk drawing | |
|---|---|---|---|---|
| GDScript | yes | yes | yes | yes |
| C# | yes | yes, via the shipped shim | yes, as C# enums | yes |
| Other GDExtensions | yes | no, dynamic calls | by name lookup | yes |
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.