// Copyright (C) 2026 SharpEmu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later using SharpEmu.Libs.Gpu.Images; using SharpEmu.ShaderCompiler; using SharpEmu.ShaderCompiler.Resources; using SharpEmu.Libs.Gpu.Scheduling; using SharpEmu.Libs.Tests.Gpu.Scheduling; using SharpEmu.Libs.Tests.Gpu.Vulkan; using Silk.NET.Vulkan; using Xunit; namespace SharpEmu.Libs.Tests.Gpu.Images; // The image requests the presenter builds from raw target registers, descriptor words and display attributes. [Collection(SchedulingStateCollection.Name)] public sealed class ImageRequestBuildersTests : IClassFixture { private const ulong Base = 0x1_0000_0000; [Fact] public void EightBitUnsignedScaledTextureUsesUnormBackingWithShaderConversion() { Assert.Equal(ImageNumericClass.Float, GuestImageFormat.SampledNumericClass(GuestImageFormat.Format8Uscaled)); Assert.Equal(1u, GuestImageFormat.Remap(GuestImageFormat.Format8Uscaled)); var request = ImageRequestBuilders.Texture( RegisterWords.Texture(Base, GuestPixelFormat.Bits8UScaled, 32, 32), new ShaderImageShape(false, false, false, false, TextureNumericClass.Float)); Assert.Equal(Format.R8Unorm, request.Request.Description.PixelFormat); Assert.True(request.ExactFormat); var twoChannel = ImageRequestBuilders.Texture( RegisterWords.Texture(Base, GuestPixelFormat.Bits8_8UScaled, 32, 32), new ShaderImageShape(false, false, false, false, TextureNumericClass.Float)); Assert.Equal(Format.R8G8Unorm, twoChannel.Request.Description.PixelFormat); Assert.True(twoChannel.ExactFormat); } [Fact] public void StorageWithoutMipOperandUsesTheAvailableMipRange() { var words = RegisterWords.Texture(0x22AC00000, GuestPixelFormat.Bits8_8_8_8UNorm, 512, 512, baseLevel: 0, lastLevel: 9, maxMip: 8); var shape = new ShaderImageShape(false, false, true, false, TextureNumericClass.Float); var request = ImageRequestBuilders.Texture(words, shape).Request; Assert.Equal(9u, request.Description.Resources.Levels); Assert.Equal(0u, request.View.BaseLevel); using var fatal = new FatalScope(); Assert.Throws(() => ImageRequestBuilders.Texture(words, shape with { DynamicMip = true })); } private static readonly ShaderImageShape Sampled2D = new(false, false, false, false, TextureNumericClass.Float); [Theory] [InlineData(GuestPixelFormat.Bits16UNorm, Format.D16Unorm, Format.R16Unorm)] [InlineData(GuestPixelFormat.Bits32Float, Format.D32Sfloat, Format.R32Sfloat)] public void ComparisonTexture_SelectsDepthWithoutChangingTheGuestLayout( GuestPixelFormat guestFormat, Format depthFormat, Format colorFormat) { var words = RegisterWords.Texture(Base, guestFormat, 48, 24); var ordinary = ImageRequestBuilders.Texture(words, Sampled2D); var comparison = ImageRequestBuilders.Texture(words, Sampled2D with { DepthCompare = true }); Assert.Equal(colorFormat, ordinary.Request.Description.PixelFormat); Assert.False(ordinary.Request.Description.IsDepth); Assert.Equal(depthFormat, comparison.Request.Description.PixelFormat); Assert.Equal(depthFormat, comparison.Request.View.Format); Assert.True(comparison.Request.Description.IsDepth); Assert.Equal(ordinary.Request.Description.Data, comparison.Request.Description.Data); Assert.Equal(ordinary.Request.Description.Pitch, comparison.Request.Description.Pitch); Assert.Equal(ordinary.Request.Description.MipLayout[0], comparison.Request.Description.MipLayout[0]); Assert.Equal(ImageRole.Texture, comparison.Request.Role); } [Theory] [InlineData(GuestImageType.Color2D, 1u, false)] [InlineData(GuestImageType.Color2D, 1u, true)] [InlineData(GuestImageType.Color3D, 32u, false)] [InlineData(GuestImageType.Color3D, 32u, true)] [InlineData(GuestImageType.Color2DArray, 4u, false)] [InlineData(GuestImageType.Color2DArray, 4u, true)] public void CompressedTexture_CarriesDccMetadataForEverySlice(GuestImageType type, uint layers, bool storage) { const ulong metadata = 0x1_2000_0000; var words = RegisterWords.Texture(Base, GuestPixelFormat.Bits8_8_8_8UNorm, 32, 32, type, GuestTileMode.RenderTarget, layers: layers); words[6] |= (1u << 21) | (uint)(((metadata >> 8) & 0xFF) << 24); words[7] = (uint)(metadata >> 16); var shape = Sampled2D with { Volume = type == GuestImageType.Color3D, Arrayed = type == GuestImageType.Color2DArray, Storage = storage }; var description = ImageRequestBuilders.Texture(words, shape).Request.Description; Assert.Equal(MetadataKind.Dcc, description.Metadata.Kind); Assert.Equal(metadata, description.Metadata.Range.Address); Assert.Equal((ulong)layers * 0x1000, description.Metadata.Range.Size); } [Fact] public void DemonSlayerColorLut_CarriesItsVolumeDccFootprintAndAlphaPosition() { uint[] words = [0x606D4E00, 0xC3200000, 0x0007C007, 0xA1B00FAC, 0x0000001F, 0x00700000, 0x006B0000, 0x00606D6E]; var description = ImageRequestBuilders.Texture(words, Sampled2D with { Volume = true }).Request.Description; Assert.Equal(GuestImageType.Color3D, description.Type); Assert.Equal(new Extent3D(32, 32, 32), description.Extent); Assert.Equal(Format.A2B10G10R10UnormPack32, description.PixelFormat); Assert.Equal(MetadataKind.Dcc, description.Metadata.Kind); Assert.Equal(0x606D6E0000UL, description.Metadata.Range.Address); Assert.Equal(0x20000UL, description.Metadata.Range.Size); Assert.True(description.Metadata.DccAlphaMsb); } private readonly HeadlessVulkan? _vulkan; public ImageRequestBuildersTests(HeadlessVulkanFixture fixture) => _vulkan = fixture.Vulkan; [Fact] public void ColorTarget_LinearTargetFromRegisters() { var resolution = ImageRequestBuilders.ColorTarget(RegisterWords.Color(Base, 64, 64), 0xF, 0, false); Assert.NotNull(resolution); var request = resolution.Value.Request; Assert.Equal(ImageRole.ColorTarget, request.Role); Assert.Equal(Base, request.Description.Data.Address); Assert.Equal(16384UL, request.Description.Data.Size); Assert.Equal(Format.R8G8B8A8Unorm, request.Description.PixelFormat); Assert.Equal(GuestImageType.Color2D, request.Description.Type); Assert.Equal(new Extent3D(64, 64, 1), request.Description.Extent); Assert.Equal(new SubresourceCount(1, 1), request.Description.Resources); Assert.Equal(64u, request.Description.Pitch); Assert.Equal(4u, request.Description.BytesPerBlock); Assert.Equal(1u, request.Description.Samples); Assert.Equal(GuestTileMode.Linear, request.Description.TileMode); Assert.Equal(16384UL, request.Description.MipLayout[0].Size); Assert.Equal(ImageViewType.Type2D, request.View.Type); Assert.Equal(ImageUsageFlags.ColorAttachmentBit, request.View.Usage); Assert.Equal(new Extent2D(64, 64), resolution.Value.Extent); Assert.Equal(16384UL, resolution.Value.BackingSize); Assert.Equal(1u, resolution.Value.Samples); Assert.Equal(0u, resolution.Value.BaseMipLevel); Assert.False(resolution.Value.MetadataClearSupported); } [Fact] public void ColorTarget_NoTargetWithoutAnAddressOrAWriteMask() { Assert.Null(ImageRequestBuilders.ColorTarget(RegisterWords.Color(0, 64, 64), 0xF, 0, false)); Assert.Null(ImageRequestBuilders.ColorTarget(RegisterWords.Color(Base, 64, 64), 0, 0, false)); Assert.NotNull(ImageRequestBuilders.ColorTarget(RegisterWords.Color(Base, 64, 64), 0, 0, ignoreTargetMask: true)); } [Fact] public void ColorTarget_MipViewSelectsTheLevelExtent() { var words = RegisterWords.Color(Base, 64, 64, GuestTileMode.RenderTarget, maxMip: 1, mipLevel: 1); var resolution = ImageRequestBuilders.ColorTarget(words, 0xF, 0, false); Assert.NotNull(resolution); var request = resolution.Value.Request; Assert.Equal(2u, request.Description.Resources.Levels); Assert.Equal(GuestTileMode.RenderTarget, request.Description.TileMode); Assert.True(request.Description.MipLayout[1].Size > 0); Assert.Equal(1u, request.View.BaseLevel); Assert.Equal(1u, request.View.LevelCount); Assert.Equal(new Extent2D(32, 32), resolution.Value.Extent); Assert.Equal(1u, resolution.Value.BaseMipLevel); Assert.Equal(request.Description.Data.Size, resolution.Value.BackingSize); } [Fact] public void ColorTarget_LayeredViewCoversEveryLayer() { var resolution = ImageRequestBuilders.ColorTarget(RegisterWords.Color(Base, 64, 64, sliceMax: 3), 0xF, 0, false); Assert.NotNull(resolution); var request = resolution.Value.Request; Assert.Equal(new SubresourceCount(1, 4), request.Description.Resources); Assert.Equal(4 * 16384UL, request.Description.Data.Size); Assert.Equal(ImageViewType.Type2DArray, request.View.Type); Assert.Equal(0u, request.View.BaseLayer); Assert.Equal(4u, request.View.LayerCount); Assert.Equal(4 * 16384UL, resolution.Value.BackingSize); } [Fact] public void ColorTarget_VolumeAcceptsExclusiveSliceMax() { var words = RegisterWords.Color( Base, 64, 64, GuestTileMode.Standard4KB, sliceMax: 64, dimension: 2, depth: 63); var resolution = ImageRequestBuilders.ColorTarget(words, 0xF, 0, false); Assert.NotNull(resolution); Assert.Equal(64u, resolution.Value.Request.View.LayerCount); Assert.Equal(64u, resolution.Value.Request.Description.Extent.Depth); } [Fact] public void ColorTarget_VolumeViewEndingOnePastTheLastSliceIsClamped() { var resolution = ImageRequestBuilders.ColorTarget(RegisterWords.Color(Base, 32, 32, GuestTileMode.RenderTarget, sliceMax: 32, dimension: 2, depth: 31), 0xF, 0, false); Assert.NotNull(resolution); var request = resolution.Value.Request; Assert.Equal(32u, request.Description.Extent.Depth); Assert.Equal(0u, request.View.BaseLayer); } [Fact] public void ColorTarget_VolumeViewStartingPastTheLastSliceIsFatal() { using var fatal = new FatalScope(); Assert.Throws(() => ImageRequestBuilders.ColorTarget( RegisterWords.Color(Base, 32, 32, GuestTileMode.RenderTarget, sliceStart: 32, sliceMax: 32, dimension: 2, depth: 31), 0xF, 0, false)); } [Fact] public void ColorTarget_RejectsUnsupportedRegisterStates() { using var fatal = new FatalScope(); Assert.Throws(() => ImageRequestBuilders.ColorTarget(RegisterWords.Color(Base, 64, 64, samplesLog2: 1), 0xF, 0, false)); Assert.Throws(() => ImageRequestBuilders.ColorTarget(RegisterWords.Color(Base, 64, 64, maxMip: 1), 0xF, 0, false)); Assert.Throws(() => ImageRequestBuilders.ColorTarget(RegisterWords.Color(Base, 64, 64, dimension: 3), 0xF, 0, false)); Assert.Equal(3, fatal.Messages.Count); } [Fact] public void SampleCount_DecodesTheLog2Encoding() { Assert.Equal(1u, ImageRequestBuilders.SampleCount(0)); Assert.Equal(2u, ImageRequestBuilders.SampleCount(1)); Assert.Equal(8u, ImageRequestBuilders.SampleCount(3)); Assert.Equal(0u, ImageRequestBuilders.SampleCount(4)); } [Fact] public void ColorTarget_LinearTargetPitchMatchesTheLinearTexturePitch() { var target = ImageRequestBuilders.ColorTarget(RegisterWords.Color(Base, 816, 604), 0xF, 0, false); var texture = ImageRequestBuilders.Texture(RegisterWords.Texture(Base, GuestPixelFormat.Bits8_8_8_8UNorm, 816, 604), Sampled2D); Assert.NotNull(target); var description = target.Value.Request.Description; Assert.Equal(new Extent3D(816, 604, 1), description.Extent); Assert.Equal(832u, description.Pitch); Assert.Equal(832UL * 604 * 4, description.Data.Size); Assert.Equal(texture.Request.Description.Pitch, description.Pitch); Assert.Equal(texture.Request.Description.Data.Size, description.Data.Size); } [Fact] public void Texture_LinearTwoDimensionalFromDescriptor() { var words = RegisterWords.Texture(Base, GuestPixelFormat.Bits8_8_8_8UNorm, 64, 64); var resolution = ImageRequestBuilders.Texture(words, Sampled2D); var request = resolution.Request; Assert.Equal(ImageRole.Texture, request.Role); Assert.Equal(Base, request.Description.Data.Address); Assert.True(request.Description.Data.Size >= 16384UL); Assert.Equal(Format.R8G8B8A8Unorm, request.Description.PixelFormat); Assert.Equal(GuestPixelFormat.Bits8_8_8_8UNorm, request.Description.GuestFormat); Assert.Equal(GuestImageType.Color2D, request.Description.Type); Assert.Equal(new Extent3D(64, 64, 1), request.Description.Extent); Assert.Equal(new SubresourceCount(1, 1), request.Description.Resources); Assert.Equal(4u, request.Description.BytesPerBlock); Assert.Equal(1u, request.Description.Samples); Assert.Equal(request.Description.Data.Size, request.Description.MipLayout[0].Size); Assert.Equal(ImageViewType.Type2D, request.View.Type); Assert.Equal(ImageUsageFlags.SampledBit, request.View.Usage); Assert.Equal(Format.R8G8B8A8Unorm, request.View.Format); Assert.False(resolution.ExactFormat); Assert.Equal(Format.R8G8B8A8Unorm, resolution.ViewFormat); Assert.Equal(ImageRequestBuilders.DestinationSwizzle(new TextureDescriptorWords(words)), resolution.Swizzle); } [Fact] public void Texture_MipWindowFollowsTheDescriptor() { var words = RegisterWords.Texture(Base, GuestPixelFormat.Bits8_8_8_8UNorm, 64, 64, baseLevel: 1, lastLevel: 2, maxMip: 2); var request = ImageRequestBuilders.Texture(words, Sampled2D).Request; Assert.Equal(3u, request.Description.Resources.Levels); Assert.True(request.Description.MipLayout[2].Size > 0); Assert.NotEqual(request.Description.MipLayout[1].Offset, request.Description.MipLayout[2].Offset); Assert.Equal(1u, request.View.BaseLevel); Assert.Equal(2u, request.View.LevelCount); } [Fact] public void Texture_CubeBecomesALayeredTwoDimensionalImage() { var words = RegisterWords.Texture(Base, GuestPixelFormat.Bits8_8_8_8UNorm, 32, 32, GuestImageType.Cube, layers: 6, baseArray: 2); var arrayed = ImageRequestBuilders.Texture(words, Sampled2D with { Arrayed = true }).Request; var single = ImageRequestBuilders.Texture(words, Sampled2D).Request; Assert.Equal(GuestImageType.Color2D, arrayed.Description.Type); Assert.Equal(new SubresourceCount(1, 6), arrayed.Description.Resources); Assert.Equal(ImageViewType.Type2DArray, arrayed.View.Type); Assert.Equal(2u, arrayed.View.BaseLayer); Assert.Equal(4u, arrayed.View.LayerCount); Assert.Equal(ImageViewType.Type2D, single.View.Type); Assert.Equal(2u, single.View.BaseLayer); Assert.Equal(1u, single.View.LayerCount); } [Fact] public void Texture_NullDescriptorBindsAOneTexelImage() { var words = new uint[8]; var sampled = ImageRequestBuilders.Texture(words, Sampled2D); var storage = ImageRequestBuilders.Texture(words, Sampled2D with { Storage = true, NumericClass = TextureNumericClass.Uint }); Assert.Equal(ImageRole.Texture, sampled.Request.Role); Assert.Equal(new Extent3D(1, 1, 1), sampled.Request.Description.Extent); Assert.Equal(Format.R32Sfloat, sampled.Request.Description.PixelFormat); Assert.Equal(ImageUsageFlags.SampledBit, sampled.Request.View.Usage); Assert.Equal(ImageRole.StorageImage, storage.Request.Role); Assert.Equal(Format.R32Uint, storage.Request.Description.PixelFormat); Assert.Equal(ImageUsageFlags.StorageBit, storage.Request.View.Usage); } [Fact] public void Texture_StorageViewOfAnSrgbImageUsesTheLinearFormat() { var words = RegisterWords.Texture(Base, GuestPixelFormat.Bits8_8_8_8Srgb, 64, 64); var resolution = ImageRequestBuilders.Texture(words, Sampled2D with { Storage = true }); Assert.Equal(ImageRole.StorageImage, resolution.Request.Role); Assert.Equal(Format.R8G8B8A8Srgb, resolution.Request.Description.PixelFormat); Assert.Equal(Format.R8G8B8A8Unorm, resolution.Request.View.Format); Assert.Equal(ImageUsageFlags.StorageBit, resolution.Request.View.Usage); Assert.Equal(Format.R8G8B8A8Srgb, resolution.ViewFormat); } [Fact] public void Texture_RejectsAnInvertedMipWindow() { using var fatal = new FatalScope(); var words = RegisterWords.Texture(Base, GuestPixelFormat.Bits8_8_8_8UNorm, 64, 64, baseLevel: 2, lastLevel: 1, maxMip: 2); Assert.Throws(() => ImageRequestBuilders.Texture(words, Sampled2D)); Assert.Contains("baseLevel=2", Assert.Single(fatal.Messages)); } [Fact] public void DepthTarget_ResolvesTheDepthOnlyAttachment() { if (!GatePrerequisites.Ready(_vulkan)) return; var resolution = ImageRequestBuilders.DepthTarget(RegisterWords.Depth(Base, 64, 64, depthClear: true), _vulkan.DeviceInfo); Assert.NotNull(resolution); var value = resolution.Value; Assert.Equal(Format.D32Sfloat, value.Format); Assert.Equal(64u, value.Width); Assert.Equal(64u, value.Height); Assert.Equal(1u, value.Samples); Assert.False(value.HasStencil); Assert.False(value.HasHtile); Assert.Equal(Base, value.DepthAddress); Assert.True(value.DepthSize > 0); Assert.Equal(0UL, value.StencilSize); Assert.True(value.DepthClearEnabled); Assert.False(value.StencilClearEnabled); var request = value.Request; Assert.Equal(ImageRole.DepthTarget, request.Role); Assert.Equal(new GuestSpanCheck(Base, value.DepthSize), new GuestSpanCheck(request.Description.Data.Address, request.Description.Data.Size)); Assert.Equal(GuestTileMode.Depth, request.Description.TileMode); Assert.Equal(value.DepthSize, request.Description.MipLayout[0].Size); Assert.Equal(ImageAspectFlags.DepthBit, request.View.Aspect); Assert.Equal(ImageUsageFlags.DepthStencilAttachmentBit, request.View.Usage); } [Fact] public void DepthTarget_WithStencilSelectsACombinedFormat() { if (!GatePrerequisites.Ready(_vulkan)) return; var words = RegisterWords.Depth(Base, 64, 64, stencilBase: Base + 0x80000); var resolution = ImageRequestBuilders.DepthTarget(words, _vulkan.DeviceInfo); Assert.NotNull(resolution); var value = resolution.Value; Assert.Equal(Format.D32SfloatS8Uint, value.Format); Assert.True(value.HasStencil); Assert.Equal(Base + 0x80000, value.StencilAddress); Assert.True(value.StencilSize > 0); Assert.Equal(value.StencilSize, value.Request.Description.Stencil.Size); Assert.Equal(ImageAspectFlags.DepthBit | ImageAspectFlags.StencilBit, value.Request.View.Aspect); Assert.False(value.StencilClearEnabled); } [Fact] public void DepthTarget_StencilIsCompressedOnlyWithHtileBacking() { if (!GatePrerequisites.Ready(_vulkan)) return; const ulong stencilBase = Base + 0x80000; const ulong htileBase = Base + 0x100000; var words = RegisterWords.Depth(Base, 64, 64, stencilBase: stencilBase) with { ZInfo = (uint)GuestDepthFormat.Z32Float | (1u << 29), StencilInfo = 0x00100981, HtileBase = htileBase, }; var resolution = ImageRequestBuilders.DepthTarget(words, _vulkan.DeviceInfo); Assert.NotNull(resolution); var value = resolution.Value; Assert.True(value.HasStencil); Assert.True(value.HasHtile); Assert.Equal(MetadataKind.Htile, value.Request.Description.Metadata.Kind); Assert.Equal(htileBase, value.Request.Description.Metadata.Range.Address); Assert.True(value.Request.Description.Metadata.StencilCompressed); var missingHtile = words with { ZInfo = (uint)GuestDepthFormat.Z32Float, HtileBase = 0 }; var uncompressed = ImageRequestBuilders.DepthTarget(missingHtile, _vulkan.DeviceInfo); Assert.NotNull(uncompressed); Assert.True(uncompressed.Value.HasStencil); Assert.False(uncompressed.Value.HasHtile); Assert.False(uncompressed.Value.Request.Description.Metadata.StencilCompressed); } [Fact] public void DepthTarget_NoAttachmentWhenNothingIsActive() { if (!GatePrerequisites.Ready(_vulkan)) return; Assert.Null(ImageRequestBuilders.DepthTarget(RegisterWords.Depth(Base, 64, 64, depthTest: false, depthWrite: false), _vulkan.DeviceInfo)); var unbound = new DepthTargetWords(0, 0, 0, 0, false, 0, 0, 2, 0, 0, 0, 0, 0); Assert.Null(ImageRequestBuilders.DepthTarget(unbound, _vulkan.DeviceInfo)); } [Fact] public void DepthTarget_RejectsAMissingDepthBase() { if (!GatePrerequisites.Ready(_vulkan)) return; using var fatal = new FatalScope(); Assert.Throws(() => ImageRequestBuilders.DepthTarget(RegisterWords.Depth(0, 64, 64), _vulkan.DeviceInfo)); Assert.Contains("read=0x0000000000000000", Assert.Single(fatal.Messages)); } [Fact] public void DisplaySurface_FromRegisteredAttributes() { var surface = new DisplaySurfaceWords(Base, 0, 0x8000000022000000, 1920, 1080, 0, 0, 0, 0, false); var request = ImageRequestBuilders.DisplaySurface(surface); Assert.Equal(ImageRole.DisplaySurface, request.Role); Assert.Equal(Base, request.Description.Data.Address); Assert.Equal(request.Description.MipLayout[0].Size, request.Description.Data.Size); Assert.Equal(Format.R8G8B8A8Srgb, request.Description.PixelFormat); Assert.Equal(GuestPixelFormat.Bits8_8_8_8Srgb, request.Description.GuestFormat); Assert.Equal(new Extent3D(1920, 1080, 1), request.Description.Extent); Assert.Equal(GuestTileMode.RenderTarget, request.Description.TileMode); Assert.Equal(4u, request.Description.BytesPerBlock); Assert.Equal(MetadataKind.None, request.Description.Metadata.Kind); Assert.Equal(ImageViewType.Type2D, request.View.Type); Assert.Equal(ImageUsageFlags.TransferSrcBit, request.View.Usage); } [Theory] [InlineData(0x8100070422000000UL, Format.A2B10G10R10UnormPack32, 8UL)] [InlineData(0x8100070400000000UL, Format.A2R10G10B10UnormPack32, 40UL)] [InlineData(0x8100000622000000UL, Format.A2B10G10R10UnormPack32, 32UL)] [InlineData(0x8100000600000000UL, Format.A2R10G10B10UnormPack32, 0UL)] public void DisplaySurface_Packed10BitFormatsPreserveEncodedValues(ulong pixelFormat, Format expectedFormat, ulong option) { var surface = new DisplaySurfaceWords(Base, 0, pixelFormat, 3840, 2160, 0, option, 0, 0, false); var request = ImageRequestBuilders.DisplaySurface(surface); Assert.Equal(expectedFormat, request.Description.PixelFormat); Assert.Equal(GuestPixelFormat.Bits10_10_10_2UNorm, request.Description.GuestFormat); Assert.Equal(4u, request.Description.BytesPerBlock); Assert.Equal(expectedFormat, request.View.Format); Assert.True(DisplayFormatRule.Supports(request.Description)); } [Fact] public void DisplaySurface_RejectsUnsupportedAttributes() { using var fatal = new FatalScope(); Assert.Throws(() => ImageRequestBuilders.DisplaySurface(new DisplaySurfaceWords(Base, 0, 0x8000000022000000, 1920, 1080, 1, 0, 0, 0, false))); Assert.Throws(() => ImageRequestBuilders.DisplaySurface(new DisplaySurfaceWords(Base, 0, 0x1234, 1920, 1080, 0, 0, 0, 0, false))); Assert.Throws(() => ImageRequestBuilders.DisplaySurface(new DisplaySurfaceWords(Base, 0, 0x8000000022000000, 0, 1080, 0, 0, 0, 0, false))); Assert.Throws(() => ImageRequestBuilders.DisplaySurface(new DisplaySurfaceWords(Base, 0, 0x8100070422000000, 1920, 1080, 0, 1, 0, 0, false))); Assert.Equal(4, fatal.Messages.Count); } private readonly record struct GuestSpanCheck(ulong Address, ulong Size); }