using UnityEngine; using UnityEngine.UIElements; namespace Uigenx.Effects { /// /// Fills a from a camera or a blit and shows it as the background /// of a named element. The texture is created on enable and released on disable, so a disabled /// component costs nothing. /// [AddComponentMenu("UI Creator/Render Texture Source")] public sealed class UgxRenderTextureSource : MonoBehaviour { [SerializeField] UIDocument document; [Tooltip("Name of the element that shows the texture.")] [SerializeField] string elementName; [Header("Source")] [Tooltip("Rendered every update. Leave empty to blit `sourceTexture` through `blitMaterial` instead.")] [SerializeField] Camera sourceCamera; [SerializeField] Texture sourceTexture; [SerializeField] Material blitMaterial; [Header("Target")] [SerializeField] int width = 512; [SerializeField] int height = 512; [SerializeField] int depthBits = 16; [SerializeField] RenderTextureFormat format = RenderTextureFormat.ARGB32; [Tooltip("Updates per second. Zero renders once on enable.")] [Range(0f, 120f)] [SerializeField] float hz = 30f; /// Frames to keep looking for the target element before giving up. public const int ResolveRetryFrames = 8; RenderTexture _texture; VisualElement _element; double _nextRenderTime; RenderTexture _previousCameraTarget; int _resolveFramesLeft; public RenderTexture Texture => _texture; public VisualElement Element => _element; void OnEnable() { _resolveFramesLeft = ResolveRetryFrames; if (!TryStart()) return; StartTexture(); } void StartTexture() { if (_element == null || _texture != null) return; _texture = new RenderTexture(Mathf.Max(1, width), Mathf.Max(1, height), depthBits, format) { name = "ugx-" + (string.IsNullOrEmpty(elementName) ? name : elementName), hideFlags = HideFlags.HideAndDontSave, }; _texture.Create(); if (sourceCamera != null) { _previousCameraTarget = sourceCamera.targetTexture; sourceCamera.targetTexture = _texture; } _element.style.backgroundImage = Background.FromRenderTexture(_texture); _nextRenderTime = 0d; RenderOnce(); } void OnDisable() { // Clearing the inline value hands the element back to its stylesheet. Writing the // previously resolved Background instead would pin it forever. if (_element != null) _element.style.backgroundImage = StyleKeyword.Null; _element = null; if (sourceCamera != null) { sourceCamera.targetTexture = _previousCameraTarget; _previousCameraTarget = null; } if (_texture == null) return; _texture.Release(); if (Application.isPlaying) Destroy(_texture); else DestroyImmediate(_texture); _texture = null; } void LateUpdate() { // UIDocument may not have built its root yet when this component is enabled, and // component order is not something a user should have to get right. Retry for a few // frames, then give up quietly; a wrong element name already reported itself. if (_texture == null && _resolveFramesLeft > 0) { _resolveFramesLeft--; if (TryStart()) StartTexture(); } if (_texture == null || hz <= 0f) return; double now = Time.unscaledTimeAsDouble; if (now < _nextRenderTime) return; _nextRenderTime = now + 1d / hz; RenderOnce(); } public void RenderOnce() { if (_texture == null) return; if (sourceCamera != null) { sourceCamera.Render(); return; } if (sourceTexture == null) return; if (blitMaterial != null) Graphics.Blit(sourceTexture, _texture, blitMaterial); else Graphics.Blit(sourceTexture, _texture); } /// Resolves the target element. Reports a wrong name once, a missing root silently. bool TryStart() { if (_element != null) return true; if (document == null) document = GetComponent(); if (document == null) { Debug.LogError("[ugx] UgxRenderTextureSource needs a UIDocument", this); _resolveFramesLeft = 0; return false; } var root = document.rootVisualElement; if (root == null) return false; _element = string.IsNullOrEmpty(elementName) ? root : root.Q(elementName); if (_element != null) return true; Debug.LogError("[ugx] UgxRenderTextureSource cannot find an element named '" + elementName + "'", this); _resolveFramesLeft = 0; return false; } } }