using System.Collections; using NUnit.Framework; using Unity.Collections.LowLevel.Unsafe; using UnityEngine; using UnityEngine.TestTools; using UnityEngine.UI; class RawImageTests { const int k_Width = 32; const int k_Height = 32; const double k_FloatCompareDelta = 0.001f; static Texture2D CreateTexture(Color? color = null) { color ??= new Color(0.25f, 0.5f, 0.75f); bool isHDR = color.Value.maxColorComponent > 1.0f; TextureFormat format = isHDR ? TextureFormat.RGBAHalf : TextureFormat.RGBA32; var tex = new Texture2D(k_Width, k_Height, format, false); var colors = new Color[k_Width * k_Height]; for (var i = 0; i < k_Width * k_Height; i++) { colors[i] = color.Value; } tex.SetPixels(colors); tex.Apply(); return tex; } static RawImage CreateRawImage(out GameObject canvasGO) { canvasGO = new GameObject("Canvas", typeof(Canvas)); var rawImageObject = new GameObject("RawImage", typeof(RawImage)); rawImageObject.transform.SetParent(canvasGO.transform); return rawImageObject.GetComponent(); } [UnityTest] public IEnumerator SpriteAssignmentToTexture_DoesNotCrashWhenBuildingMesh() { RawImage rawImage = CreateRawImage(out GameObject canvasGO); Texture2D defaultTexture = CreateTexture(); var sprite = Sprite.Create(defaultTexture, new Rect(0, 0, k_Width, k_Height), Vector2.zero); // Simulates when AnimationClip assigns a UnityEngine.Object value from native side of the wrong type during animation sampling. Texture texture = UnsafeUtility.As(ref sprite); rawImage.texture = texture; // Wait a frame to allow mesh generation. yield return null; // Clean up resources Object.DestroyImmediate(canvasGO); Object.DestroyImmediate(sprite); Object.DestroyImmediate(defaultTexture); } [UnityTest] public IEnumerator SpriteAssignmentToTexture_MainTextureIsNull() { RawImage rawImage = CreateRawImage(out GameObject canvasGO); Texture2D defaultTexture = CreateTexture(); var sprite = Sprite.Create(defaultTexture, new Rect(0, 0, k_Width, k_Height), Vector2.zero); // Simulates when AnimationClip assigns a UnityEngine.Object value from native side of the wrong type during animation sampling. Texture texture = UnsafeUtility.As(ref sprite); rawImage.texture = texture; // Wait a frame to allow mesh generation. yield return null; Assert.IsNull(rawImage.mainTexture, "rawImage.mainTexture == null"); // Clean up resources Object.DestroyImmediate(canvasGO); Object.DestroyImmediate(sprite); Object.DestroyImmediate(defaultTexture); } [UnityTest] public IEnumerator SettingTexture_UpdatesRawImageMainTexture() { RawImage rawImage = CreateRawImage(out GameObject canvasGO); Texture2D tex = CreateTexture(); rawImage.texture = tex; // Wait a frame to allow the RawImage to update. yield return null; Assert.AreEqual(tex, rawImage.mainTexture); // Clean up resources Object.DestroyImmediate(canvasGO); Object.DestroyImmediate(tex); } [UnityTest] public IEnumerator SettingNullTexture_RawImageMainTextureIsDefaultWhiteTexture() { RawImage rawImage = CreateRawImage(out GameObject canvasGO); rawImage.texture = null; // Wait a frame to allow the RawImage to update. yield return null; Assert.AreEqual(rawImage.mainTexture, TestRawImage.DefaultWhiteTexture); // Clean up resources Object.DestroyImmediate(canvasGO); } [UnityTest] public IEnumerator ChangingUVRect_UpdatesUVRectProperty() { RawImage rawImage = CreateRawImage(out GameObject canvasGO); var newUVRect = new Rect(0.1f, 0.2f, 0.5f, 0.5f); rawImage.uvRect = newUVRect; // Wait a frame to allow the RawImage to update. yield return null; Assert.AreEqual(newUVRect, rawImage.uvRect); // Clean up resources Object.DestroyImmediate(canvasGO); } [UnityTest] public IEnumerator SettingColor_UpdatesRawImageColor() { RawImage rawImage = CreateRawImage(out GameObject canvasGO); var newColor = new Color(0.2f, 0.4f, 0.6f, 0.8f); rawImage.color = newColor; // Wait a frame to allow the RawImage to update. yield return null; Assert.AreEqual(newColor, rawImage.color); // Clean up resources Object.DestroyImmediate(canvasGO); } [UnityTest] public IEnumerator RawImage_DefaultTextureIsNull() { RawImage rawImage = CreateRawImage(out GameObject canvasGO); // Wait a frame to allow the RawImage to initialize. yield return null; Assert.IsNull(rawImage.texture); Object.DestroyImmediate(canvasGO); } [UnityTest] public IEnumerator UVRect_GeneratesCorrectVertexUVs() { RawImage rawImage = CreateRawImage(out GameObject canvasGO); Texture2D texture = CreateTexture(); rawImage.texture = texture; // Set a custom UV rect (offset and scaled) var customUVRect = new Rect(0.25f, 0.25f, 0.5f, 0.5f); rawImage.uvRect = customUVRect; // Wait a frame to allow mesh generation yield return null; // Get the generated mesh from the CanvasRenderer var canvasRenderer = rawImage.GetComponent(); Assert.IsNotNull(canvasRenderer, "CanvasRenderer should be present"); Mesh mesh = canvasRenderer.GetMesh(); // Verify we have the expected quad (4 vertices) Assert.AreEqual(4, mesh.vertexCount, "RawImage should generate a quad with 4 vertices"); // Get UV coordinates Vector2[] uvs = mesh.uv; Assert.IsNotNull(uvs, "Mesh should have UV coordinates"); Assert.AreEqual(4, uvs.Length, "Should have 4 UV coordinates"); // Expected UVs based on the custom UV rect // uGUI (Unity UI) typically generates quads with UV order: bottom-left, top-left, top-right, bottom-right var expectedUVs = new[] { new Vector2(customUVRect.xMin, customUVRect.yMin), // Bottom-left new Vector2(customUVRect.xMin, customUVRect.yMax), // Top-left new Vector2(customUVRect.xMax, customUVRect.yMax), // Top-right new Vector2(customUVRect.xMax, customUVRect.yMin), // Bottom-right }; // Verify each UV coordinate matches expected values for (var i = 0; i < expectedUVs.Length; i++) { Assert.AreEqual( expectedUVs[i].x, uvs[i].x, k_FloatCompareDelta, $"UV coordinate {i} X component should match expected value"); Assert.AreEqual( expectedUVs[i].y, uvs[i].y, k_FloatCompareDelta, $"UV coordinate {i} Y component should match expected value"); } // Clean up resources Object.DestroyImmediate(canvasGO); Object.DestroyImmediate(texture); } [UnityTest] public IEnumerator ColorProperty_GeneratesCorrectVertexColors() { RawImage rawImage = CreateRawImage(out GameObject canvasGO); Texture2D texture = CreateTexture(); rawImage.texture = texture; // Set a custom color with transparency var customColor = new Color(0.2f, 0.4f, 0.8f, 0.6f); rawImage.color = customColor; // Wait a frame to allow mesh generation yield return null; // Get the generated mesh from the CanvasRenderer var canvasRenderer = rawImage.GetComponent(); Assert.IsNotNull(canvasRenderer, "CanvasRenderer should be present"); Mesh mesh = canvasRenderer.GetMesh(); // Verify we have the expected quad (4 vertices) Assert.AreEqual(4, mesh.vertexCount, "RawImage should generate a quad with 4 vertices"); // Get vertex colors Color[] colors = mesh.colors; Assert.IsNotNull(colors, "Mesh should have vertex colors"); Assert.AreEqual(4, colors.Length, "Should have 4 vertex colors"); // Verify each vertex color matches the set color for (var i = 0; i < colors.Length; i++) { Assert.AreEqual( customColor.r, colors[i].r, k_FloatCompareDelta, $"Vertex {i} {nameof(Color.r)} component should match expected value"); Assert.AreEqual( customColor.g, colors[i].g, k_FloatCompareDelta, $"Vertex {i} {nameof(Color.g)} component should match expected value"); Assert.AreEqual( customColor.b, colors[i].b, k_FloatCompareDelta, $"Vertex {i} {nameof(Color.b)} component should match expected value"); Assert.AreEqual( customColor.a, colors[i].a, k_FloatCompareDelta, $"Vertex {i} {nameof(Color.a)} component should match expected value"); } // Clean up resources Object.DestroyImmediate(canvasGO); Object.DestroyImmediate(texture); } [UnityTest] public IEnumerator HDRTexture_HandlesHighDynamicRange() { RawImage rawImage = CreateRawImage(out GameObject canvasGO); // Create an HDR texture with color values greater than 1.0 (HDR range) var hdrColor = new Color(2.5f, 1.8f, 3.2f, 1.0f); Texture2D hdrTexture = CreateTexture(hdrColor); // Verify HDR texture was created successfully Assert.AreEqual(TextureFormat.RGBAHalf, hdrTexture.format, "HDR texture should use RGBAHalf format"); rawImage.texture = hdrTexture; // Wait a frame to allow the RawImage to update yield return null; // Verify the HDR texture is properly assigned and handled Assert.AreEqual(hdrTexture, rawImage.mainTexture, "RawImage mainTexture should be set to HDR texture"); // Verify mesh generation doesn't crash with HDR texture var canvasRenderer = rawImage.GetComponent(); Assert.IsNotNull(canvasRenderer, "CanvasRenderer should be present"); Mesh mesh = canvasRenderer.GetMesh(); Assert.IsNotNull(mesh, "Mesh should be generated successfully with HDR texture"); Assert.AreEqual(4, mesh.vertexCount, "RawImage should generate a quad with HDR texture"); // Verify UV coordinates are still properly generated Vector2[] uvs = mesh.uv; Assert.IsNotNull(uvs, "Mesh should have UV coordinates with HDR texture"); Assert.AreEqual(4, uvs.Length, "Should have 4 UV coordinates with HDR texture"); // Test that changing UV rect still works with HDR texture var customUVRect = new Rect(0.1f, 0.1f, 0.8f, 0.8f); rawImage.uvRect = customUVRect; yield return null; mesh = canvasRenderer.GetMesh(); uvs = mesh.uv; // Verify UV rect changes are applied correctly with HDR texture var expectedUVs = new[] { new Vector2(customUVRect.xMin, customUVRect.yMin), // Bottom-left new Vector2(customUVRect.xMin, customUVRect.yMax), // Top-left new Vector2(customUVRect.xMax, customUVRect.yMax), // Top-right new Vector2(customUVRect.xMax, customUVRect.yMin), // Bottom-right }; for (var i = 0; i < expectedUVs.Length; i++) { Assert.AreEqual( expectedUVs[i].x, uvs[i].x, k_FloatCompareDelta, $"HDR texture UV coordinate {i} X component should match expected value"); Assert.AreEqual( expectedUVs[i].y, uvs[i].y, k_FloatCompareDelta, $"HDR texture UV coordinate {i} Y component should match expected value"); } // Clean up resources Object.DestroyImmediate(canvasGO); Object.DestroyImmediate(hdrTexture); } [UnityTest] public IEnumerator DestroyedTexture_HandledGracefully() { RawImage rawImage = CreateRawImage(out GameObject canvasGO); Texture2D texture = CreateTexture(); // Assign texture to RawImage rawImage.texture = texture; // Wait a frame to allow initial setup yield return null; // Verify texture is initially assigned Assert.AreEqual(texture, rawImage.texture, "Texture should be initially assigned"); // Destroy the texture while it's still assigned to RawImage Object.DestroyImmediate(texture); // Wait a frame to allow RawImage to handle the destroyed texture yield return null; // Verify RawImage handles destroyed texture gracefully (should not crash) Assert.DoesNotThrow( () => { var canvasRenderer = rawImage.GetComponent(); Assert.IsNotNull(canvasRenderer, "CanvasRenderer should still be present"); // Attempt to get mesh - should not crash even with destroyed texture Mesh _ = canvasRenderer.GetMesh(); // Mesh might be null or empty, but getting it shouldn't crash }, "RawImage should handle destroyed texture without crashing"); // Verify accessing texture property doesn't crash Assert.DoesNotThrow( () => { Texture _ = rawImage.texture; Texture __ = rawImage.mainTexture; }, "Accessing texture properties should not throw with destroyed texture"); // Clean up resources Object.DestroyImmediate(canvasGO); } [UnityTest] public IEnumerator NegativeUVRect_ClampsCorrectly() { RawImage rawImage = CreateRawImage(out GameObject canvasGO); Texture2D texture = CreateTexture(); rawImage.texture = texture; // Set UV rect with negative values var negativeUVRect = new Rect(-0.5f, -0.3f, 0.8f, 0.6f); rawImage.uvRect = negativeUVRect; // Wait a frame to allow mesh generation yield return null; // Verify the UV rect property reflects what was set (RawImage might store the raw values) Assert.AreEqual(negativeUVRect, rawImage.uvRect, "UV rect property should store the set values"); // Get the generated mesh and verify UV coordinates are handled appropriately var canvasRenderer = rawImage.GetComponent(); Assert.IsNotNull(canvasRenderer, "CanvasRenderer should be present"); Mesh mesh = canvasRenderer.GetMesh(); Assert.IsNotNull(mesh, "Mesh should be generated with negative UV rect"); Assert.AreEqual(4, mesh.vertexCount, "Should generate quad with negative UV rect"); Vector2[] uvs = mesh.uv; Assert.IsNotNull(uvs, "Mesh should have UV coordinates"); Assert.AreEqual(4, uvs.Length, "Should have 4 UV coordinates"); // Verify UV coordinates are within reasonable bounds or handled appropriately // The exact behavior may vary, but it should not crash and should generate valid UVs for (var i = 0; i < uvs.Length; i++) { Assert.IsFalse(float.IsNaN(uvs[i].x), $"UV {i} X coordinate should not be NaN"); Assert.IsFalse(float.IsNaN(uvs[i].y), $"UV {i} Y coordinate should not be NaN"); Assert.IsFalse(float.IsInfinity(uvs[i].x), $"UV {i} X coordinate should not be infinity"); Assert.IsFalse(float.IsInfinity(uvs[i].y), $"UV {i} Y coordinate should not be infinity"); } // Clean up resources Object.DestroyImmediate(canvasGO); Object.DestroyImmediate(texture); } [UnityTest] public IEnumerator PopulateMesh_GeneratesQuadWithCorrectVertexCount() { RawImage rawImage = CreateRawImage(out GameObject canvasGO); Texture2D texture = CreateTexture(); rawImage.texture = texture; // Set up RectTransform with specific size var rectTransform = rawImage.GetComponent(); rectTransform.sizeDelta = new Vector2(100f, 50f); // Wait a frame to allow mesh population yield return null; // Get the populated mesh var canvasRenderer = rawImage.GetComponent(); Assert.IsNotNull(canvasRenderer, "CanvasRenderer should be present"); Mesh mesh = canvasRenderer.GetMesh(); Assert.IsNotNull(mesh, "Mesh should be populated"); // Verify correct vertex count for a quad Assert.AreEqual(4, mesh.vertexCount, "RawImage should generate exactly 4 vertices for a quad"); // Verify we have the expected mesh data Vector3[] vertices = mesh.vertices; Vector2[] uvs = mesh.uv; Color[] colors = mesh.colors; int[] triangles = mesh.triangles; Assert.IsNotNull(vertices, "Mesh should have vertices"); Assert.AreEqual(4, vertices.Length, "Should have 4 vertices"); Assert.IsNotNull(uvs, "Mesh should have UV coordinates"); Assert.AreEqual(4, uvs.Length, "Should have 4 UV coordinates"); Assert.IsNotNull(colors, "Mesh should have vertex colors"); Assert.AreEqual(4, colors.Length, "Should have 4 vertex colors"); Assert.IsNotNull(triangles, "Mesh should have triangle indices"); Assert.AreEqual(6, triangles.Length, "Quad should have 6 triangle indices (2 triangles * 3 indices)"); // Verify triangles form valid indices (0-3 for a quad) for (var i = 0; i < triangles.Length; i++) { Assert.GreaterOrEqual(triangles[i], 0, $"Triangle index {i} should be >= 0"); Assert.Less(triangles[i], 4, $"Triangle index {i} should be < 4 for a quad"); } // Test with different UV rect to ensure mesh updates correctly rawImage.uvRect = new Rect(0.2f, 0.2f, 0.6f, 0.6f); yield return null; mesh = canvasRenderer.GetMesh(); Assert.AreEqual(4, mesh.vertexCount, "Vertex count should remain 4 after UV rect change"); // Clean up resources Object.DestroyImmediate(canvasGO); Object.DestroyImmediate(texture); } }