Files
2026-11-01 23:39:55 +02:00

500 lines
18 KiB
C#

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<RawImage>();
}
[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<Sprite, Texture>(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<Sprite, Texture>(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<CanvasRenderer>();
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<CanvasRenderer>();
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<CanvasRenderer>();
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<CanvasRenderer>();
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<CanvasRenderer>();
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>();
rectTransform.sizeDelta = new Vector2(100f, 50f);
// Wait a frame to allow mesh population
yield return null;
// Get the populated mesh
var canvasRenderer = rawImage.GetComponent<CanvasRenderer>();
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);
}
}