// UnityEngine.UI was moved to a package in 2019.2.0 // Unfortunately no way to test for this across all Unity versions yet // You can set up the asmdef to reference the new package, but the package doesn't // existing in Unity 2017 etc, and it throws an error due to missing reference #define AVPRO_PACKAGE_UNITYUI #if (UNITY_2019_2_OR_NEWER && AVPRO_PACKAGE_UNITYUI) || (!UNITY_2019_2_OR_NEWER) #if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IOS || UNITY_TVOS #define UNITY_PLATFORM_SUPPORTS_YPCBCR #endif #if UNITY_EDITOR || UNITY_STANDALONE_OSX || UNITY_STANDALONE_WIN || UNITY_WSA_10_0 || UNITY_IOS || UNITY_TVOS || UNITY_ANDROID || (UNITY_WEBGL && UNITY_2017_2_OR_NEWER) #define UNITY_PLATFORM_SUPPORTS_LINEAR #endif #if (!UNITY_STANDALONE_WIN && !UNITY_EDITOR_WIN) && (UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_IOS || UNITY_TVOS || UNITY_VISIONOS || UNITY_ANDROID) #define UNITY_PLATFORM_SUPPORTS_VIDEOTRANSFORM #endif #if (UNITY_EDITOR_WIN || (!UNITY_EDITOR && UNITY_STANDALONE_WIN)) #define UNITY_PLATFORM_SUPPORTS_VIDEOASPECTRATIO #endif using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using UnityEngine.Serialization; //----------------------------------------------------------------------------- // Copyright 2015-2021 RenderHeads Ltd. All rights reserved. //----------------------------------------------------------------------------- namespace RenderHeads.Media.AVProVideo { /// /// Displays the video from MediaPlayer component using uGUI /// [HelpURL("http://renderheads.com/products/avpro-video/")] [AddComponentMenu("AVPro Video/Display uGUI", 200)] //[ExecuteInEditMode] public class DisplayUGUI : MaskableGraphic { [SerializeField] MediaPlayer _mediaPlayer; public MediaPlayer Player { get { return _mediaPlayer; } set { ChangeMediaPlayer(value); } } [Tooltip("Default texture to display when the video texture is preparing")] [SerializeField] Texture _defaultTexture; public Texture DefaultTexture { get { return _defaultTexture; } set { if (_defaultTexture != value) { _defaultTexture = value; } } } [FormerlySerializedAs("m_UVRect")] [SerializeField] Rect _uvRect = new Rect(0f, 0f, 1f, 1f); public Rect UVRect { get { return _uvRect; } set { _uvRect = value; } } [SerializeField] bool _setNativeSize = false; public bool ApplyNativeSize { get { return _setNativeSize; } set { _setNativeSize = value; } } [SerializeField] ScaleMode _scaleMode = ScaleMode.ScaleToFit; public ScaleMode ScaleMode { get { return _scaleMode; } set { _scaleMode = value; } } [SerializeField] bool _noDefaultDisplay = true; public bool NoDefaultDisplay { get { return _noDefaultDisplay; } set { _noDefaultDisplay = value; } } [SerializeField] bool _displayInEditor = true; public bool DisplayInEditor { get { return _displayInEditor; } set { _displayInEditor = value; } } private int _lastWidth; private int _lastHeight; private Orientation _lastOrientation; private bool _flipY; private Texture _lastTexture; private static Shader _shaderStereoPacking; private static Shader _shaderAlphaPacking; private static Shader _shaderAndroidOES; private static Shader _shaderAndroidOESAlphaPacking; private bool _isUserMaterial = true; private Material _material; private List _vertices = new List(4); private static List QuadIndices = new List(new int[] { 0, 1, 2, 2, 3, 0 }); protected override void Awake() { if (_mediaPlayer != null) { _mediaPlayer.Events.AddListener(OnMediaPlayerEvent); } base.Awake(); } // Callback function to handle events private void OnMediaPlayerEvent(MediaPlayer mp, MediaPlayerEvent.EventType et, ErrorCode errorCode) { switch (et) { case MediaPlayerEvent.EventType.FirstFrameReady: if (_isUserMaterial && null != GetRequiredShader()) { Debug.LogWarning("[AVProVideo] Custom material is being used but the video requires our internal shader for correct rendering. Consider removing custom shader or modifying it for AVPro Video support.", this); } LateUpdate(); break; case MediaPlayerEvent.EventType.PropertiesChanged: case MediaPlayerEvent.EventType.ResolutionChanged: case MediaPlayerEvent.EventType.Closing: LateUpdate(); break; } // TODO: remove this, we're just doing this so we can make sure texture is correct when running in EDIT mode LateUpdate(); } private void ChangeMediaPlayer(MediaPlayer player) { if (_mediaPlayer != player) { if (_mediaPlayer != null) { _mediaPlayer.Events.RemoveListener(OnMediaPlayerEvent); } _mediaPlayer = player; if (_mediaPlayer != null) { _mediaPlayer.Events.AddListener(OnMediaPlayerEvent); } LateUpdate(); } } private static Shader EnsureShader(Shader shader, string name) { if (shader == null) { shader = Shader.Find(name); if (shader == null) { Debug.LogWarning("[AVProVideo] Missing shader " + name); } } return shader; } private static Shader EnsureAlphaPackingShader() { _shaderAlphaPacking = EnsureShader(_shaderAlphaPacking, "AVProVideo/Internal/UI/Transparent Packed (stereo)"); return _shaderAlphaPacking; } private static Shader EnsureStereoPackingShader() { _shaderStereoPacking = EnsureShader(_shaderStereoPacking, "AVProVideo/Internal/UI/Stereo"); return _shaderStereoPacking; } private Shader EnsureAndroidOESShader() { _shaderAndroidOES = EnsureShader(_shaderAndroidOES, "AVProVideo/Internal/UI/Stereo - AndroidOES"); return _shaderAndroidOES; } private static Shader EnsureAndroidOESAlphaPackingShader() { _shaderAndroidOESAlphaPacking = EnsureShader(_shaderAndroidOESAlphaPacking, "AVProVideo/Internal/UI/Transparent Packed (stereo) - AndroidOES"); return _shaderAndroidOESAlphaPacking; } protected override void Start() { _isUserMaterial = (this.m_Material != null); if (_isUserMaterial) { _material = new Material(this.material); this.material = _material; } base.Start(); } protected override void OnDestroy() { // Destroy existing material if (_material != null) { this.material = null; #if UNITY_EDITOR Material.DestroyImmediate(_material); #else Material.Destroy(_material); #endif _material = null; } ChangeMediaPlayer(null); base.OnDestroy(); } private Shader GetRequiredShader() { Shader result = null; if (result == null && _mediaPlayer.TextureProducer != null) { switch (_mediaPlayer.TextureProducer.GetTextureStereoPacking()) { case StereoPacking.None: break; case StereoPacking.LeftRight: case StereoPacking.TopBottom: case StereoPacking.TwoTextures: result = EnsureStereoPackingShader(); break; } if (_mediaPlayer.TextureProducer.GetTextureTransparency() == TransparencyMode.Transparent) { result = EnsureAlphaPackingShader(); } switch (_mediaPlayer.TextureProducer.GetTextureAlphaPacking()) { case AlphaPacking.None: break; case AlphaPacking.LeftRight: case AlphaPacking.TopBottom: result = EnsureAlphaPackingShader(); break; } } #if UNITY_PLATFORM_SUPPORTS_LINEAR if (result == null && _mediaPlayer.Info != null) { if (QualitySettings.activeColorSpace == ColorSpace.Linear && !_mediaPlayer.Info.PlayerSupportsLinearColorSpace()) { result = EnsureAlphaPackingShader(); } } #endif if (result == null && _mediaPlayer.TextureProducer != null && _mediaPlayer.TextureProducer.GetTextureCount() == 2) { result = EnsureAlphaPackingShader(); } // if (_mediaPlayer.TextureProducer != null && _mediaPlayer.IsUsingAndroidOESPath() && (_defaultTexture == null || _lastTexture != _defaultTexture )) if (_mediaPlayer.TextureProducer != null && _mediaPlayer.IsUsingAndroidOESPath()) { // This shader handles stereo too result = EnsureAndroidOESShader(); if (_mediaPlayer.TextureProducer.GetTextureTransparency() == TransparencyMode.Transparent) { result = EnsureAndroidOESAlphaPackingShader(); } switch (_mediaPlayer.TextureProducer.GetTextureAlphaPacking()) { case AlphaPacking.None: break; case AlphaPacking.LeftRight: case AlphaPacking.TopBottom: result = EnsureAndroidOESAlphaPackingShader(); break; } } return result; } /// /// Returns the texture used to draw this Graphic. /// public override Texture mainTexture { get { Texture result = Texture2D.whiteTexture; if (HasValidTexture()) { Texture resamplerTex = _mediaPlayer.FrameResampler == null || _mediaPlayer.FrameResampler.OutputTexture == null ? null : _mediaPlayer.FrameResampler.OutputTexture[0]; result = _mediaPlayer.UseResampler ? resamplerTex : _mediaPlayer.TextureProducer.GetTexture(); } else { if (_noDefaultDisplay) { result = null; } else if (_defaultTexture != null) { result = _defaultTexture; } #if UNITY_EDITOR if (result == null && _displayInEditor) { result = Resources.Load("AVProVideoIcon"); } #endif } return result; } } public bool HasValidTexture() { return (Application.isPlaying && _mediaPlayer != null && _mediaPlayer.TextureProducer != null && _mediaPlayer.TextureProducer.GetTexture() != null); } private void UpdateInternalMaterial() { if (_mediaPlayer != null) { // Get required shader Shader currentShader = null; if (_material != null) { currentShader = _material.shader; } Shader nextShader = GetRequiredShader(); // If the shader requirement has changed if (currentShader != nextShader) { // Destroy existing material if (_material != null) { this.material = null; #if UNITY_EDITOR Material.DestroyImmediate(_material); #else Material.Destroy(_material); #endif _material = null; } // Create new material if (nextShader != null) { _material = new Material(nextShader); } } this.material = _material; } } // We do a LateUpdate() to allow for any changes in the texture that may have happened in Update() void LateUpdate() { if (_setNativeSize) { SetNativeSize(); } if (_lastTexture != mainTexture) { _lastTexture = mainTexture; SetVerticesDirty(); SetMaterialDirty(); } if (HasValidTexture()) { if (mainTexture != null) { Orientation orientation = Helper.GetOrientation(_mediaPlayer.Info.GetTextureTransform()); if (mainTexture.width != _lastWidth || mainTexture.height != _lastHeight || orientation != _lastOrientation) { _lastWidth = mainTexture.width; _lastHeight = mainTexture.height; _lastOrientation = orientation; SetVerticesDirty(); SetMaterialDirty(); } } } if (Application.isPlaying) { if (!_isUserMaterial) { UpdateInternalMaterial(); } } if (material != null && _mediaPlayer != null) { // TODO: only run when dirty VideoRender.SetupMaterialForMedia(materialForRendering, _mediaPlayer); } } /// /// Texture to be used. /// public MediaPlayer CurrentMediaPlayer { get { return _mediaPlayer; } set { if (_mediaPlayer != value) { _mediaPlayer = value; //SetVerticesDirty(); SetMaterialDirty(); } } } /// /// UV rectangle used by the texture. /// public Rect uvRect { get { return _uvRect; } set { if (_uvRect == value) { return; } _uvRect = value; SetVerticesDirty(); } } /// /// Adjust the scale of the Graphic to make it pixel-perfect. /// [ContextMenu("Set Native Size")] public override void SetNativeSize() { Texture tex = mainTexture; if (tex != null) { int w = Mathf.RoundToInt(tex.width * uvRect.width); int h = Mathf.RoundToInt(tex.height * uvRect.height); if (_mediaPlayer != null) { #if UNITY_PLATFORM_SUPPORTS_VIDEOTRANSFORM && !(!UNITY_EDITOR && UNITY_ANDROID) if (_mediaPlayer.Info != null) { Orientation ori = Helper.GetOrientation(_mediaPlayer.Info.GetTextureTransform()); if (ori == Orientation.Portrait || ori == Orientation.PortraitFlipped) { w = Mathf.RoundToInt(tex.height * uvRect.width); h = Mathf.RoundToInt(tex.width * uvRect.height); } } #endif if (_mediaPlayer.TextureProducer != null) { if (_mediaPlayer.TextureProducer.GetTextureAlphaPacking() == AlphaPacking.LeftRight || _mediaPlayer.TextureProducer.GetTextureStereoPacking() == StereoPacking.LeftRight) { w /= 2; } else if (_mediaPlayer.TextureProducer.GetTextureAlphaPacking() == AlphaPacking.TopBottom || _mediaPlayer.TextureProducer.GetTextureStereoPacking() == StereoPacking.TopBottom) { h /= 2; } } } rectTransform.anchorMax = rectTransform.anchorMin; rectTransform.sizeDelta = new Vector2(w, h); } } protected override void OnPopulateMesh(VertexHelper vh) { vh.Clear(); _OnFillVBO(_vertices); vh.AddUIVertexStream(_vertices, QuadIndices ); } private void _OnFillVBO(List vbo) { _flipY = false; if (HasValidTexture()) { _flipY = _mediaPlayer.TextureProducer.RequiresVerticalFlip(); } Rect uvRect = _uvRect; Vector4 v = GetDrawingDimensions(_scaleMode, ref uvRect); #if UNITY_PLATFORM_SUPPORTS_VIDEOTRANSFORM Matrix4x4 m = Matrix4x4.identity; if (HasValidTexture()) { m = Helper.GetMatrixForOrientation(Helper.GetOrientation(_mediaPlayer.Info.GetTextureTransform())); } #endif vbo.Clear(); var vert = UIVertex.simpleVert; vert.color = color; vert.position = new Vector2(v.x, v.y); vert.uv0 = new Vector2(uvRect.xMin, uvRect.yMin); if (_flipY) { vert.uv0 = new Vector2(uvRect.xMin, 1.0f - uvRect.yMin); } #if UNITY_PLATFORM_SUPPORTS_VIDEOTRANSFORM vert.uv0 = m.MultiplyPoint3x4(vert.uv0); #endif vbo.Add(vert); vert.position = new Vector2(v.x, v.w); vert.uv0 = new Vector2(uvRect.xMin, uvRect.yMax); if (_flipY) { vert.uv0 = new Vector2(uvRect.xMin, 1.0f - uvRect.yMax); } #if UNITY_PLATFORM_SUPPORTS_VIDEOTRANSFORM vert.uv0 = m.MultiplyPoint3x4(vert.uv0); #endif vbo.Add(vert); vert.position = new Vector2(v.z, v.w); vert.uv0 = new Vector2(uvRect.xMax, uvRect.yMax); if (_flipY) { vert.uv0 = new Vector2(uvRect.xMax, 1.0f - uvRect.yMax); } #if UNITY_PLATFORM_SUPPORTS_VIDEOTRANSFORM vert.uv0 = m.MultiplyPoint3x4(vert.uv0); #endif vbo.Add(vert); vert.position = new Vector2(v.z, v.y); vert.uv0 = new Vector2(uvRect.xMax, uvRect.yMin); if (_flipY) { vert.uv0 = new Vector2(uvRect.xMax, 1.0f - uvRect.yMin); } #if UNITY_PLATFORM_SUPPORTS_VIDEOTRANSFORM vert.uv0 = m.MultiplyPoint3x4(vert.uv0); #endif vbo.Add(vert); } private Vector4 GetDrawingDimensions(ScaleMode scaleMode, ref Rect uvRect) { Vector4 returnSize = Vector4.zero; if (mainTexture != null) { var padding = Vector4.zero; var textureSize = new Vector2(mainTexture.width, mainTexture.height); { // Adjust textureSize based on orientation #if UNITY_PLATFORM_SUPPORTS_VIDEOTRANSFORM && !(!UNITY_EDITOR && UNITY_ANDROID) if (HasValidTexture()) { Matrix4x4 m = Helper.GetMatrixForOrientation(Helper.GetOrientation(_mediaPlayer.Info.GetTextureTransform())); textureSize = m.MultiplyVector(textureSize); textureSize.x = Mathf.Abs(textureSize.x); textureSize.y = Mathf.Abs(textureSize.y); } #endif #if UNITY_PLATFORM_SUPPORTS_VIDEOASPECTRATIO if (HasValidTexture()) { float par = _mediaPlayer.TextureProducer.GetTexturePixelAspectRatio(); if (par > 0f) { if (par > 1f) { textureSize.x *= par; } else { textureSize.y /= par; } } } #endif // Adjust textureSize based on alpha/stereo packing if (_mediaPlayer != null && _mediaPlayer.TextureProducer != null) { if (_mediaPlayer.TextureProducer.GetTextureAlphaPacking() == AlphaPacking.LeftRight || _mediaPlayer.TextureProducer.GetTextureStereoPacking() == StereoPacking.LeftRight) { textureSize.x /= 2; } else if (_mediaPlayer.TextureProducer.GetTextureAlphaPacking() == AlphaPacking.TopBottom || _mediaPlayer.TextureProducer.GetTextureStereoPacking() == StereoPacking.TopBottom) { textureSize.y /= 2; } } } Rect r = GetPixelAdjustedRect(); // Fit the above textureSize into rectangle r int spriteW = Mathf.RoundToInt( textureSize.x ); int spriteH = Mathf.RoundToInt( textureSize.y ); var size = new Vector4( padding.x / spriteW, padding.y / spriteH, (spriteW - padding.z) / spriteW, (spriteH - padding.w) / spriteH ); { if (textureSize.sqrMagnitude > 0.0f) { if (scaleMode == ScaleMode.ScaleToFit) { float spriteRatio = textureSize.x / textureSize.y; float rectRatio = r.width / r.height; if (spriteRatio > rectRatio) { float oldHeight = r.height; r.height = r.width * (1.0f / spriteRatio); r.y += (oldHeight - r.height) * rectTransform.pivot.y; } else { float oldWidth = r.width; r.width = r.height * spriteRatio; r.x += (oldWidth - r.width) * rectTransform.pivot.x; } } else if (scaleMode == ScaleMode.ScaleAndCrop) { float aspectRatio = textureSize.x / textureSize.y; float screenRatio = r.width / r.height; if (screenRatio > aspectRatio) { float adjust = aspectRatio / screenRatio; uvRect = new Rect(uvRect.xMin, (uvRect.yMin * adjust) + (1f - adjust) * 0.5f, uvRect.width, adjust * uvRect.height); } else { float adjust = screenRatio / aspectRatio; uvRect = new Rect(uvRect.xMin * adjust + (0.5f - adjust * 0.5f), uvRect.yMin, adjust * uvRect.width, uvRect.height); } } } } returnSize = new Vector4( r.x + r.width * size.x, r.y + r.height * size.y, r.x + r.width * size.z, r.y + r.height * size.w ); } return returnSize; } } } #endif