GridBackground, Content Zoomer and GraphView internals

Hi,

I found myself using the GraphView experimental package for building some custom 2d map editor. It works as expected, but needs some workarounds to get GraphView and GridBackground to do what I need, so I am pretty much bending GraphView’s functionality in order to take a minimum of its features for my needs.

Therefore, I was wondering how to best grab the relevant parts and just build my own classes, so I can later also build a standalone without UnityEditor dependencies.
However I can’t seem to make out the relevant parts or understand the core elements I need for porting to my own clases. I tried some dirty/paste work or removing some parts for testing, but every approach just broke the rendering.
I am using the zoom function and added a “reflection jailbreak” for the GridBackground members so I can set the grid resolution dynamically. The editor view is attached to a custom class derived from GraphView drawn via ImmediateModeElement and GL instructions. Anything I don’t need (like the context menu) I “killed” in my class derived from GraphView. I haven’t even figured out, where the map is rendered on. Removing GridBackground removes the drawn map.

Any help and hint is highly appreciated.

For reference what I’m talking about:

Some time ago i wanted my own GridBackground and i re-created it using the “generateVisualContent” callback, maybe this is usefull for you
(CustomStylePropertyDefault<> is a custom struct, you can replace it with CustomStyleProperty<>)

Im driving the style properties through a Uss attached to the GraphView, but you could replace them with csharp properties

using System.Collections.Generic;

using UnityEditor.Experimental.GraphView;

using UnityEngine;
using UnityEngine.UIElements;

using System;

#nullable enable
namespace GrapherTool.Editors.Views
{
    class GridBackground : VisualElement
    {
        static readonly CustomStylePropertyDefault<float> s_SpacingProperty = new("--spacing", 50);
        static readonly CustomStylePropertyDefault<int> s_ThickLineEvery = new("--thick-line-every", 5);
        static readonly CustomStylePropertyDefault<int> s_LinesWidthProperty = new("--lines-width", 1);
        static readonly CustomStylePropertyDefault<int> s_ThickLinesWidthProperty = new("--thick-lines-width", 10);
        static readonly CustomStylePropertyDefault<Color> s_LineColorProperty = new("--line-color", Color.white);
        static readonly CustomStylePropertyDefault<Color> s_ThickLineColorProperty = new("--thick-line-color", Color.white);

        private float _spacing;
        private int _lineWidth;
        private int _thickLineWidth;
        private int _thickLineEvery;
        private Color _lineColor;
        private Color _thickLineColor;
        private readonly GraphView _graphView;

        public GridBackground(GraphView graphView)
        {
            _graphView = graphView;
            generateVisualContent += GenerateVisualContent;
            schedule.Execute(() =>
            {
                if (HasViewportChanged())
                    MarkDirtyRepaint();

            }).Every(1);
        }
        Vector3 _viewPos;
        Quaternion _viewRot;
        Vector3 _viewScale;
        private bool HasViewportChanged()
        {
            Vector3 pos = _viewPos;
            Quaternion rot = _viewRot;
            Vector3 scale = _viewScale;
            var transform = _graphView.viewTransform;
            _viewPos = transform.position;
            _viewRot = transform.rotation;
            _viewScale = transform.scale;
            return _viewPos != pos || _viewRot != rot || _viewScale != scale;
        }

        private void GenerateVisualContent(MeshGenerationContext context)
        {
            var viewContainer = _graphView.contentViewContainer;
          
            float spacing = Mathf.Max(1, _spacing);
            float thickSpacing = spacing * _thickLineEvery;
            Vector2 corner = this.ChangeCoordinatesTo(viewContainer, -Vector2.one);
            Vector2 topLeft = new Vector2(corner.x - (corner.x % spacing), corner.y - (corner.y % spacing)) - new Vector2(spacing, spacing);
            Vector2 topLeftThick = new Vector2(corner.x - (corner.x % thickSpacing), corner.y - (corner.y % thickSpacing)) - new Vector2(thickSpacing, thickSpacing);
            topLeft = viewContainer.ChangeCoordinatesTo(this, topLeft);
            topLeftThick = viewContainer.ChangeCoordinatesTo(this, topLeftThick);

            Vector2 currentPos = topLeft;
            float spacingX = spacing * viewContainer.transform.scale.x;
            float spacingY = spacing * viewContainer.transform.scale.y;
            float maxWidth = resolvedStyle.width;
            float maxHeight = resolvedStyle.height;
            // thin
            var painter = context.painter2D;
            painter.BeginPath();
            while (currentPos.x < maxWidth)
            {
                painter.MoveTo(currentPos);
                painter.LineTo(new(currentPos.x, maxHeight));
                currentPos.x += spacingX;
            }
            currentPos = topLeft;
            while (currentPos.y < maxHeight)
            {
                painter.MoveTo(currentPos);
                painter.LineTo(new(maxWidth, currentPos.y));
                currentPos.y += spacingY;
            }
            painter.strokeColor = _lineColor;
            painter.lineWidth = _lineWidth * viewContainer.transform.scale.x;
            painter.Stroke();
            //thick

            spacingX *= _thickLineEvery;
            spacingY *= _thickLineEvery;
            currentPos = topLeftThick;
            painter.BeginPath();
            while (currentPos.x < maxWidth)
            {
                painter.MoveTo(currentPos);
                painter.LineTo(new(currentPos.x, maxHeight));
                currentPos.x += spacingX;
            }
            currentPos = topLeftThick;
            while (currentPos.y < maxHeight)
            {
                painter.MoveTo(currentPos);
                painter.LineTo(new(maxWidth, currentPos.y));
                currentPos.y += spacingY;
            }
            painter.strokeColor = _thickLineColor;
            painter.lineWidth = _thickLineWidth * viewContainer.transform.scale.x; ;
            painter.Stroke();
        }
        [EventInterest(EventInterestOptions.Inherit)]
        [EventInterest(typeof(CustomStyleResolvedEvent))]
        protected override void HandleEventTrickleDown(EventBase evt)
        {
            base.HandleEventTrickleDown(evt);
            if (evt.eventTypeId.Equals(CustomStyleResolvedEvent.TypeId()))
            {
                var styleEvt = (CustomStyleResolvedEvent)evt;
                _spacing = s_SpacingProperty.GetValueOrDefault(styleEvt.customStyle);
                _lineWidth = s_LinesWidthProperty.GetValueOrDefault(styleEvt.customStyle);
                _thickLineWidth = s_ThickLinesWidthProperty.GetValueOrDefault(styleEvt.customStyle);
                _lineColor = s_LineColorProperty.GetValueOrDefault(styleEvt.customStyle);
                _thickLineColor = s_ThickLineColorProperty.GetValueOrDefault(styleEvt.customStyle);
                _thickLineEvery = s_ThickLineEvery.GetValueOrDefault(styleEvt.customStyle);
            }
        }

    }
}

Thanks for your insights.

So you switch from GL to painter2D. Did you perform any profiling on performance?

From what I’ve understood, my GL routines only draw because there’s a valid target already provided by GridBackground. I dug into it and it appears the internal call HandleUtility.ApplyWireMaterial() is where the juice is. I continued stepping into the code and found that it does apply some texture and some sampler of the scene editor:

            s_HandleWireTextureIndex2D = ShaderUtil.GetTextureBindingIndex(s_HandleWireMaterial2D.shader, Shader.PropertyToID("_MainTex"));
            s_HandleWireTextureSamplerIndex2D = ShaderUtil.GetTextureSamplerBindingIndex(s_HandleWireMaterial2D.shader, Shader.PropertyToID("_MainTex"));

I guess I need to replicate these then for getting an identical background I can paint on.
Unfortunately, further down the road the gate’s closed.

    [MethodImpl(MethodImplOptions.InternalCall)]
    private static extern void Internal_SetHandleWireTextureIndex(int textureIndex, int samplerIndex);

Yes i traveled that very same path, but i didnt arrive at a solution that i liked. Had to use too much hacked reflection methods so i just decided to try Painter2D. I have not profiled it, but it should be much lighter than the default GridBackground, though it does not handle rotations

L0bekWCtVcLVvn603w

Just for completeness:

I used reflection to work around the above issue.

MethodInfo handleUtility = typeof(HandleUtility).GetMethod("ApplyWireMaterial", BindingFlags.NonPublic | BindingFlags.Static, Type.DefaultBinder, Type.EmptyTypes, null);
handleUtility?.Invoke(null, null);
//HandleUtility.ApplyWireMaterial();

However I don’t think this is even necessary. I have yet to test, but I guess simply applying a material should work as well.

        Shader shader = Shader.Find("Hidden/Internal-Colored");
        Material material = new Material(shader);
        material.SetPass(0);
        DrawMesh(); //do the GL work