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;
}
}
}