How to prevent PrefabUtility.InstantiatePrefab from creating material instances?

I have complex class which I want to edit using UI. I made custom editor which generates UI editor by using reflection. UI has hierarchy of foldouts and some types of input fields (uint, float, vector3 …). Well, it works fine, but the profiler shows no batching at all. Every UI element renders individually due to different material instances.


But then I’m trying to do the same UI editor manually by placing my prefabs on scene batching works fine. Can I instantiate prefab in the editor script without creating a material instance? I’m not messing with materials in my scripts at all and not touching renderer.


We would have to see the code that builds the UI.

If any of that code is accessing the .material property rather than .sharedMaterial this will create a new instance of the shared material every time.

Sure, some prefabs aren’t used, I’m still in progress

using System;
using System.Linq;
using System.Reflection;
using Game.Scripts.Models.Scenario.BaseScenarioSettings.SettingTypes;
using Game.Scripts.Models.Scenario.BaseScenarioSettings.SettingTypes.Enum;
using Game.Scripts.Models.Scenario.BaseScenarioSettings.SettingTypes.Float;
using Game.Scripts.Models.Scenario.BaseScenarioSettings.SettingTypes.Uint;
using Game.Scripts.Views.UI;
using Game.Scripts.Views.UI.InputFields;
using Game.Scripts.Views.UI.InputFields.Validators;
using Unity.VisualScripting;
using UnityEditor;
using UnityEditor.Events;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;

namespace Game.Scripts.Controllers.Scenario.Editor
{
    [CustomEditor(typeof(Scenario))]
    public class ScenarioEditor : UnityEditor.Editor
    {
        private SerializedProperty _viewportProp;
        
        private SerializedProperty _modelUpdatedProp;
        
        private SerializedProperty _sectionPrefabProp;
        private SerializedProperty _inputFieldFloatPrefabProp;
        private SerializedProperty _inputFieldUintPrefabProp;
        private SerializedProperty _inputVector3PrefabProp;
        private SerializedProperty _inputVector4PrefabProp;
        private SerializedProperty _inputDropdownPrefabProp;
        
        private SerializedProperty _variableFieldFloatPrefabProp;
        private SerializedProperty _variableFieldUintPrefabProp;
        private SerializedProperty _variableVector3PrefabProp;
        private SerializedProperty _variableVector4PrefabProp;
        private SerializedProperty _variableDropdownPrefabProp;
        
        private SerializedProperty _scenarioRootProp;
        private SerializedProperty _variableParamsRootProp;

        private bool _showSubscribers = false; 
        private Scenario _targetScenario;
        
        void OnEnable()
		{
			_viewportProp = serializedObject.FindProperty("viewport");
			_modelUpdatedProp = serializedObject.FindProperty("modelUpdated");
			
			_sectionPrefabProp = serializedObject.FindProperty("sectionPrefab");
			_inputFieldFloatPrefabProp = serializedObject.FindProperty("inputFieldFloatPrefab");
			_inputFieldUintPrefabProp = serializedObject.FindProperty("inputFieldUintPrefab");
			_inputVector3PrefabProp = serializedObject.FindProperty("inputVector3Prefab");
			_inputVector4PrefabProp = serializedObject.FindProperty("inputVector4Prefab");
			_inputDropdownPrefabProp = serializedObject.FindProperty("inputDropdownPrefab");

			_variableFieldFloatPrefabProp = serializedObject.FindProperty("variableFieldFloatPrefab");
			_variableFieldUintPrefabProp = serializedObject.FindProperty("variableFieldUintPrefab");
			_variableVector3PrefabProp = serializedObject.FindProperty("variableVector3Prefab");
			_variableVector4PrefabProp = serializedObject.FindProperty("variableVector4Prefab");
			_variableDropdownPrefabProp = serializedObject.FindProperty("variableDropdownPrefab");
	        
			_scenarioRootProp = serializedObject.FindProperty("scenarioRoot");
			_variableParamsRootProp = serializedObject.FindProperty("variableParamsRoot");
			
			_targetScenario = target as Scenario;
		}

        public override void OnInspectorGUI()
        {
            serializedObject.Update();
            
            EditorGUILayout.PropertyField(_viewportProp, new GUIContent ("Viewport")); 

            EditorGUILayout.PropertyField(_sectionPrefabProp, new GUIContent ("Section Prefab"));
            EditorGUILayout.PropertyField(_inputFieldFloatPrefabProp, new GUIContent ("Input Field Float Prefab"));
            EditorGUILayout.PropertyField(_inputFieldUintPrefabProp, new GUIContent ("Input Field Uint Prefab"));
            EditorGUILayout.PropertyField(_inputVector3PrefabProp, new GUIContent ("Input Field Vector3 Prefab"));
            EditorGUILayout.PropertyField(_inputVector4PrefabProp, new GUIContent ("Input Field Vector4 Prefab"));
            EditorGUILayout.PropertyField(_inputDropdownPrefabProp, new GUIContent ("Input Field Dropdown Prefab"));

            EditorGUILayout.PropertyField(_variableFieldFloatPrefabProp, new GUIContent ("Variable Field Float Prefab"));
            EditorGUILayout.PropertyField(_variableFieldUintPrefabProp, new GUIContent ("Variable Field Uint Prefab"));
            EditorGUILayout.PropertyField(_variableVector3PrefabProp, new GUIContent ("Variable Vector3 Prefab"));
            EditorGUILayout.PropertyField(_variableVector4PrefabProp, new GUIContent ("Variable Vector4 Prefab"));
            EditorGUILayout.PropertyField(_variableDropdownPrefabProp, new GUIContent ("Variable Dropdown Prefab"));
	        
            EditorGUILayout.PropertyField(_scenarioRootProp, new GUIContent ("Scenario Root"));
            EditorGUILayout.PropertyField(_variableParamsRootProp, new GUIContent ("Variable Params Root"));
            
            _showSubscribers = EditorGUILayout.Foldout(_showSubscribers, "Model update subscribers");
            if (_showSubscribers)
				EditorGUILayout.PropertyField(_modelUpdatedProp, new GUIContent ("Model update")); 
            
            if (GUILayout.Button("generate"))
            {
                Transform viewport = ((RectTransform)_viewportProp.objectReferenceValue).transform;
				ScrollRect scrollView = viewport.parent.GetComponent<ScrollRect>();
				
                DestroyChildren(viewport);
                ClearPersistentListeners(_targetScenario.modelUpdated);
                scrollView.content = Generate(typeof(Models.Scenario.Scenario), viewport); 
            }
            
            serializedObject.ApplyModifiedProperties();
        }

        private void DestroyChildren(Transform parent)
        {
	        foreach (Transform child in parent)
		        DestroyImmediate(child.gameObject);
        }

        private RectTransform Generate(Type type, Transform parentTransform, string path = null)
        {
	        Section section = PrefabUtility.InstantiatePrefab(_sectionPrefabProp.objectReferenceValue as GameObject, parentTransform).GetComponent<Section>();
	        section.title.text = type.Name;
	        
	        foreach (FieldInfo fieldInfo in type.GetFields())
	        {
		        Type currentType = fieldInfo.FieldType;

		        string currentPath;
		        if (string.IsNullOrEmpty(path))
			        currentPath = fieldInfo.Name;
		        else
					currentPath = path + "." + fieldInfo.Name;
		        
		        if (typeof(ISettingType).IsAssignableFrom(currentType))
			        HandleSettingField(fieldInfo, section.content, currentPath);
		        
		        else if (!currentType.IsPrimitive && currentType != typeof(string))
			        Generate(currentType, section.content, currentPath);
	        }
	        
	        return section.GetComponent<RectTransform>();
        }

        private void HandleSettingField(FieldInfo fieldInfo, RectTransform parent, string path)
        {
	        Type type = fieldInfo.FieldType;
	        
	        IInputField inputField = null;
	        
	        if (type == typeof(SettingFloat))
		        inputField = PrefabUtility.InstantiatePrefab(_inputFieldFloatPrefabProp.objectReferenceValue, parent).GetComponent<FloatFiled>();
	        else if (type == typeof(SettingUint))
		        inputField = PrefabUtility.InstantiatePrefab(_inputFieldUintPrefabProp.objectReferenceValue, parent).GetComponent<UintField>();
	        else if (type.BaseType == typeof(ASettingEnum))
	        {
		        inputField = PrefabUtility.InstantiatePrefab(_inputDropdownPrefabProp.objectReferenceValue, parent).GetComponent<DropdownField>();

		        type.TypeInitializer.Invoke(null, null);
		        PropertyInfo property = type.BaseType.GetProperty("Options");
		        string[] options = (string[])property.GetValue(null);

		        DropdownValidator dropdownValidator = (DropdownValidator)inputField.Validator;
		        dropdownValidator.tmpDropdown.AddOptions(options.ToList());
	        }
	        else if (type == typeof(SettingVector3))
	        {
		        inputField = PrefabUtility.InstantiatePrefab(_inputVector3PrefabProp.objectReferenceValue, parent).GetComponent<Vector3Field>();
		        Vector3Field vector3Field = inputField as Vector3Field;
		        vector3Field.x.Validator.Path = path + ".X";
		        vector3Field.y.Validator.Path = path + ".Y";
		        vector3Field.z.Validator.Path = path + ".Z";
	        }
	        else if (type == typeof(SettingVector4))
	        {
		        inputField = PrefabUtility.InstantiatePrefab(_inputVector4PrefabProp.objectReferenceValue, parent).GetComponent<Vector4Field>();
		        Vector4Field vector4Field = inputField as Vector4Field;
		        vector4Field.x.Validator.Path = path + ".X";
		        vector4Field.y.Validator.Path = path + ".Y";
		        vector4Field.z.Validator.Path = path + ".Z";
		        vector4Field.w.Validator.Path = path + ".W";
	        }
	        
	        inputField.Title.text = fieldInfo.Name;
	        inputField.Validator.Path = path;
	        UnityEventTools.AddPersistentListener(inputField.Validator.ValueChangedWithPath, _targetScenario.SetFiledByPath);
	        UnityEventTools.AddPersistentListener(_targetScenario.modelUpdated, inputField.Validator.UpdateValue);
        }

        private void ClearPersistentListeners(UnityEvent<Models.Scenario.Scenario> unityEvent)
        {
	        while (unityEvent.GetPersistentEventCount() != 0)
		        UnityEventTools.RemovePersistentListener(unityEvent, 0);
        }
    }
}

Well, you are using IMGUI. Refactor to UI Toolkit and you’ll even write less code too.
Not sure if that’s the root of the issue but IMGUI is pretty much legacy and not recommended for use anymore.

I changed the tags accordingly because UGUI isn’t being used here.

Yes, it’s better to be made in the ui toolkit, but I just want a single button and prefab fields in the editor. UGUI IS used. The editor script just puts UGUI prefabs according to the class structure.

I also tried a simpler case with a script which spawns three float input fields in Awake. Same problem(.

using UnityEngine;

public class test : MonoBehaviour
{
    [SerializeField] private Canvas canvas;
    [SerializeField] private GameObject prefab;

    private void Awake()
    {
        for (uint i = 0; i < 3; ++i)
        {
            Instantiate(prefab, canvas.transform);
        }
    }
}


It looks like the input field prefab has its own canvas as the root object. That means multiple canvases and I doubt they share resources.

After instantiating, try getting all children of the instance and parent them on the existing canvas, then destroy the instance to get rid of the extra canvas.

Otherwise I wonder if this is editor-specific. Try making a build.

I figured out that TMP Text is breaking batching… Is there a way to fix this?

Ah yeah … I think this comes up every now and then, check for best practices, I’m only aware that it can be an issue.

Well, I found a pretty lame way to fix it. I’ll just add in foldout prefab 20-30 input fields of all types and delete unused during generation. Thanks for help!