SubViewport & TileMap doesn't respect Visibility Layer / Canvas Cull Mask when Y Sort is enabled.
Enabling Y-Sort for a TileMap node causes it to show up in a SubViewport even when the visibility_layer and canvas_cull_mask don't match.
void TileMap::_update_visibility_layer_for_canvas_items() {
for (unsigned int layer = 0; layer < layers.size(); layer++) {
for (KeyValue<Vector2i, TileMapQuadrant> &E : layers[layer].quadrant_map) {
TileMapQuadrant &q = E.value;
for (const RID &ci : q.canvas_items) {
RS::get_singleton()->canvas_item_set_visibility_layer(ci, get_visibility_layer());
_rendering_update_layer(layer);
void TileMap::set_visibility_layer(uint32_t p_visibility_layer) {
// Set material for the whole tilemap.
CanvasItem::set_visibility_layer(p_visibility_layer);
TileMap::_update_visibility_layer_for_canvas_items();
void TileMap::set_visibility_layer_bit(uint32_t p_visibility_layer, bool p_enable) {
CanvasItem::set_visibility_layer_bit(p_visibility_layer, p_enable);
TileMap::_update_visibility_layer_for_canvas_items();
Not really confident with engine or c++ dev and contributing but happy to make a PR.
Steps to reproduce
Given this scene tree:
and these settings:
Viewport (SubViewportCullMaskSetTo2
):
TileMap (TileMapSetToVisibleOnLayer1
):
Parent Nodes (Node2D_VisibleSetToLayer1And2
and VisibleSetToLayer1And2
):
The tilemap will still be present in the viewport texture of ViewportTextureShowsTileMapAndSprite
Minimal reproduction project
Tile Map Visibility Layer For Canvas Items.zip
I've had a look at tile_map.cpp
and it doesn't look like the visibility_layer
is passed to the canvas_items it creates. tile_map overrides set_light_mask and passes this along, seems it would be appropriate to pass along the visibility_layer as well?
I've patched this locally and seems to work but it involved making set_visibility_layer and set_visibility_layer_bit methods overridable in canvas_item.h.
The tile_map.cpp changes:
void TileMap::_update_visibility_layer_for_canvas_items() {
for (unsigned int layer = 0; layer < layers.size(); layer++) {
for (KeyValue<Vector2i, TileMapQuadrant> &E : layers[layer].quadrant_map) {
TileMapQuadrant &q = E.value;
for (const RID &ci : q.canvas_items) {
RS::get_singleton()->canvas_item_set_visibility_layer(ci, get_visibility_layer());
_rendering_update_layer(layer);
void TileMap::set_visibility_layer(uint32_t p_visibility_layer) {
// Set material for the whole tilemap.
CanvasItem::set_visibility_layer(p_visibility_layer);
TileMap::_update_visibility_layer_for_canvas_items();
void TileMap::set_visibility_layer_bit(uint32_t p_visibility_layer, bool p_enable) {
CanvasItem::set_visibility_layer_bit(p_visibility_layer, p_enable);
TileMap::_update_visibility_layer_for_canvas_items();
The thing is the TileMap's canvas item is parent to the layers' canvas items which are parent to quadrants' canvas items (within the rendering server) so the TileMap's visibility layer should automatically be taken into account, there should be no need to manually propagate the visibility layers to each quadrant. So the issue seems to be not related to the TileMap specifically, I'd guess it's rather something about how y-sorted canvas items are handled within the rendering server.
Meaning the suggested code changes would maybe fix the symptom, but likely not the actual issue.
For confirmation: tile_map_is_not_the_problem.zip
Disabling Y-sorting for either Node2D
or PseudoTileMap
makes the PseudoQuadrant
be properly not rendered.
Thank you for the explanation and example, think I understand how that's working a little better now. I looked into the rendering server and how canvas items are culled when y sorted.
There's _cull_canvas_item and it checks if the item is visible and then if the visibility_layer / canvas_cull_mask match. Y sorted nodes are dealt with in _collect_ysort_children which makes a visible check before adding them to the flattened array, I think this should also check for visibility_layer / canvas_cull_mask?
I tried adding that check (and calling _mark_ysort_dirty() when calling canvas_item_set_visibility_layer ) and it works for the example scene in tile_map_is_not_the_problem.zip but not for the one I originally posted. Think this is because the ysort_children_count that's reset to -1 by _mark_ysort_dirty() is reused for each viewport even though they'd be different lengths for different viewports if visibility_layer is taken into account when culling Y sorted items.