Render text to node faces client-side.#17052
Render text to node faces client-side.#17052sofar wants to merge 6 commits intoluanti-org:masterfrom
Conversation
aa5a969 to
fa7b3e0
Compare
I get the impression that LLMs enabling us to do more in less time is not necessarily better. Not talking about
IMO we should decide to have either this or the other feature, but not both. Anyway I'll leave some design- and implementation-related review comments. |
I second this. I use LLMs at work, but any non-test, production code I consider the generated code a PoC at best and always implement it myself (I still get a great speedup from the prototyping and the test code.) |
|
I'll definitely do another revision of this PR in a bit to address the feedback, but I've been focusing my time on some other PRs in the mean time. So, this will come back maybe next week. |
The FrameSpec vectors for animated tiles were allocated with new in fillTileAttribs and owned by NodeVisuals via raw pointers in TileLayer. Multiple copies of TileLayer (in AnimationInfo, MapBlockMesh, etc.) shared the same raw pointer, relying on NodeVisuals to outlive all consumers. Replace the raw pointer with std::shared_ptr so ownership is shared safely. This removes manual delete calls from ~NodeVisuals and makes the lifetime management automatic. This is preparatory work for texture reference counting, where FrameSpec will hold RAII texture handles with non-trivial destructors. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add grabTexture/putTexture methods to ITextureSource for reference counting texture usage. TextureSource now tracks a per-texture refcount and prunes textures with zero references each frame in processQueue(). This is infrastructure for garbage collecting generated textures (composites with modifiers like ^[combine, ^[text, etc.) that currently accumulate without bound. No callers use grab/put yet, so this is a no-op change — all textures retain refcount 0 and pruning has no effect until consumers are wired up. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
MapBlockMesh now grabs texture references for dynamically generated textures (e.g. [text: composites) and puts them when the mesh is destroyed. ItemVisuals grabs/puts animation frame textures similarly. Actual pruning is now enabled with a 10-frame grace period to ensure Irrlicht has finished drawing before the GPU resource is freed. Destruction order in ~Game is adjusted so that the scene manager, ItemVisualsManager, and NodeDefManager are cleaned up before TextureSource is deleted. Only consumers that generate dynamic/composite textures need grab/put wiring. The remaining consumers (Sky, Minimap, HUD, Particles, GenericCAO) only reference base media textures by name. These are never grabbed, so their refcounted flag stays false and they are never pruned. If a future feature adds dynamic texture generation to any of these systems, it will need grab/put wiring at that point. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds native text rendering on node tile surfaces, driven by node metadata. The full pipeline: Node definition: new text_face field in ContentFeatures with tile index, UV rect, and resolution. Serialized over the network. Read/written from Lua node definitions. Metadata: text content stored under the "text" metadata key, with escape sequences for font size, face, weight, color, background, and alignment. Texture modifier: [text: in ImageSource renders styled text onto a transparent overlay via a render target. Base64-encoded text content avoids parser conflicts. Uses FontEngine for proper TrueType rendering with the game's configured font. Text overlay: text is rendered as a separate layer 1 overlay with alpha blending (TILE_MATERIAL_ALPHA), keeping the base tile texture on layer 0 at its original resolution. The text texture is cached independently of the base tile. Texture pre-generation: text metadata is collected on the main thread in collectNodeText() and texture IDs are pre-generated there to avoid blocking roundtrips from the meshgen thread. Mesh rebuild: handleCommand_NodemetaChanged() triggers mesh updates for nodes with text_face when metadata changes. Supported drawtypes: normal, nodebox, signlike, mesh. The modifier also works on entity textures and anywhere else textures are used. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add documentation for: - [text: texture modifier: syntax, parameters, escape sequences for font size/face/weight/color/bgcolor/alignment, and usage examples - text_face node definition field: tile, rect (vector2 pair), resolution_x/y, supported drawtypes, metadata key - Escape sequence helper functions: get_font_size_escape_sequence, get_font_escape_sequence, get_font_weight_escape_sequence, get_alignment_escape_sequence Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Test nodes covering supported configurations: - Normal node (top face, side face) - Nodebox slab - Signlike (wall-mounted, full-face) - Mesh cube, mesh slanted display - Wide sign (3 nodes wide, 1024x341 resolution) - Mono font, bold font - Color test (rainbow text) - Entity with text (cube entity, spawner item) All nodes have right-click text editing with formspec controls for font size, face, weight, alignment, text color, and background color. Includes testtextface.make_text_texture() Lua helper for constructing [text: modifier strings for use on entities. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Summary of changes (not exhaustive):
The rest is detailed in each feedback comment per item. |
|
I don't think the test failure is related to this change. Someone might want to re-run them and see. |
|
It is pretty simple to render the text as an entity on top of the node. That is how all current sign mods work. Adding engine support for drawing text as part of the node mesh seems pretty redundant. The node with entity pattern is also very common and used for other stuff like decorated pot faces, banners, item frames etc. All such use cases are very similar to sign text and should be addressed together, not as separate engine features. So I think it is better to close this and add #15182. I do not think having a dedicated API for drawing text on nodes is warranted when just having a texture modifier will do. |
This argument is moot because the core issue (#1367) already is concept approved, and everyone agrees that this should be added. Can we not rehash the entire discussion in this PR thread? |
It does not feel fair that I cannot voice opinions because I did not participate in a discussion that took place years ago. I do not think the fact that an issue has concept approval means that it cannot be questioned years later. I also think it is worth bringing up when there is a competing PR #15182 that is also concept approved. For all use cases I see, that PR covers it. If support for rendering text on top of node tiles is to be added, then I think it is better to do so in a way that can be used for dynamic decals useful for other mods as well (paintings, decorated pots, etc). Otherwise the API risks becoming bloated in the future with many features that could have been supported in a unified way. Maybe such related use cases were not fully considered when the issue was initially discussed? |
That PR misses a key part. Without modifications, each text variation would require a node registration. You wouldn't be able to to place a sign and then modify the text on it. FWIW I certainly don't think of this PR as "the holy grail" either. I'm just putting my time into driving the feature from an experimental perspective because it's been over 10 years since this feature was wanted, and it's about time some of these long-wanted features get addressed, instead of forgotten. The other PR has been stale for more than a year now, as well. The GC work itself might warrant reviewing, because surely #15182 also would need it. The nodemeta approach can also be applied to that PR. All something that can be done with time and effort. |
Replied in #1367 (comment) as it seems reasonable to continue the discussion there. I posted my initial comment here and not in the 12 year old issue because I doubt it would have gotten attention over there. I just want the point I am making to be understood before the core devs decide how to proceed. |
This is one of the ways that these features fail - the scope gets too big, and people burn out. |
I am not arguing for increasing the scope of the PR. This is what I propose:
Just introducing the texture modifier gets you 95% there. If you check the code for The If this would not be implemented by a texture modifier then I could see the argument for a dedicated text API, but that is not its current design. |
|
I'm wondering now if #14263 shouldn't be the precursor to all this and be the generic layout engine for all things... UI dialogs, entities, nodes. It would allow for a consistent layout for all the things, and possibly expose functionality like having forms on nodes? In any case it would make layout consistent across all uses, and long-term obsolete both formspec and hypertext. Maybe that's just nonsense, too, correct me if it is. |


Adds native text rendering on node tile surfaces via a new text_face node definition field and [text| texture modifier.
How it works: Nodes with text_face define which tile gets text (index, UV rect, resolution). Text content is stored in node metadata under the "text" key. During mesh generation, the engine constructs a
[text|texture modifier string, which renders styled text onto the base tile texture using a render target and the engine's TrueType font system. The composited texture uses a non-array-texture shader fallback since array textures can't be dynamically modified.Text styling via
\x1bescape sequences in the text content: font size (s@), face (f@mono), weight (w@bold), color(c@), background (b@), alignment (a@center). Word wrapping, newlines, and text rotation (0/90/180/270) are supported. Textures are cached by TextureSource — identical text produces identical cache keys. Mesh rebuilds are triggered automatically when text metadata changes on the client.
The [text| modifier is not specific to nodes — it works on any texture string (entities, HUD, formspec), making it a general-purpose text-to-texture primitive. For entities, mods construct the modifier string directly using core.encode_base64().
Supported drawtypes: normal, nodebox (including connected), signlike, mesh. Excluded: glasslike, glasslike_framed, allfaces (incompatible tile mapping).
Closes #1367