diff options
| author | gdkchan <gab.dark.100@gmail.com> | 2024-04-07 18:25:55 -0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-04-07 18:25:55 -0300 |
| commit | 3e6e0e4afaa3c3ffb118cb17b61feb16966a7eeb (patch) | |
| tree | a4652499c089b0853e39c382cad82a9db4d6ad08 /src/Ryujinx.Graphics.Gpu/Shader | |
| parent | 808803d97a0c06809bf000687c252f960048fcf0 (diff) | |
Add support for large sampler arrays on Vulkan (#6489)
* Add support for large sampler arrays on Vulkan
* Shader cache version bump
* Format whitespace
* Move DescriptorSetManager to PipelineLayoutCacheEntry to allow different pool sizes per layout
* Handle array textures with different types on the same buffer
* Somewhat better caching system
* Avoid useless buffer data modification checks
* Move redundant bindings update checking to the backend
* Fix an issue where texture arrays would get the same bindings across stages on Vulkan
* Backport some fixes from part 2
* Fix typo
* PR feedback
* Format whitespace
* Add some missing XML docs
Diffstat (limited to 'src/Ryujinx.Graphics.Gpu/Shader')
8 files changed, 226 insertions, 44 deletions
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs b/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs index 4e1cb4e1..6e36753e 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs @@ -17,6 +17,8 @@ namespace Ryujinx.Graphics.Gpu.Shader public BufferDescriptor[][] ConstantBufferBindings { get; } public BufferDescriptor[][] StorageBufferBindings { get; } + public int[] TextureCounts { get; } + public int MaxTextureBinding { get; } public int MaxImageBinding { get; } @@ -34,6 +36,8 @@ namespace Ryujinx.Graphics.Gpu.Shader ConstantBufferBindings = new BufferDescriptor[stageCount][]; StorageBufferBindings = new BufferDescriptor[stageCount][]; + TextureCounts = new int[stageCount]; + int maxTextureBinding = -1; int maxImageBinding = -1; int offset = isCompute ? 0 : 1; @@ -59,13 +63,19 @@ namespace Ryujinx.Graphics.Gpu.Shader var result = new TextureBindingInfo( target, descriptor.Binding, + descriptor.ArrayLength, descriptor.CbufSlot, descriptor.HandleIndex, descriptor.Flags); - if (descriptor.Binding > maxTextureBinding) + if (descriptor.ArrayLength <= 1) { - maxTextureBinding = descriptor.Binding; + if (descriptor.Binding > maxTextureBinding) + { + maxTextureBinding = descriptor.Binding; + } + + TextureCounts[i]++; } return result; @@ -80,11 +90,12 @@ namespace Ryujinx.Graphics.Gpu.Shader target, format, descriptor.Binding, + descriptor.ArrayLength, descriptor.CbufSlot, descriptor.HandleIndex, descriptor.Flags); - if (descriptor.Binding > maxImageBinding) + if (descriptor.ArrayLength <= 1 && descriptor.Binding > maxImageBinding) { maxImageBinding = descriptor.Binding; } diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs index de6432bc..681838a9 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs @@ -27,6 +27,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache /// <param name="cb1Data">The constant buffer 1 data of the shader</param> /// <param name="oldSpecState">Shader specialization state of the cached shader</param> /// <param name="newSpecState">Shader specialization state of the recompiled shader</param> + /// <param name="counts">Resource counts shared across all shader stages</param> /// <param name="stageIndex">Shader stage index</param> public DiskCacheGpuAccessor( GpuContext context, @@ -109,18 +110,32 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache } /// <inheritdoc/> - public TextureFormat QueryTextureFormat(int handle, int cbufSlot) + public SamplerType QuerySamplerType(int handle, int cbufSlot) { - _newSpecState.RecordTextureFormat(_stageIndex, handle, cbufSlot); - (uint format, bool formatSrgb) = _oldSpecState.GetFormat(_stageIndex, handle, cbufSlot); - return ConvertToTextureFormat(format, formatSrgb); + _newSpecState.RecordTextureSamplerType(_stageIndex, handle, cbufSlot); + return _oldSpecState.GetTextureTarget(_stageIndex, handle, cbufSlot).ConvertSamplerType(); } /// <inheritdoc/> - public SamplerType QuerySamplerType(int handle, int cbufSlot) + public int QueryTextureArrayLengthFromBuffer(int slot) { - _newSpecState.RecordTextureSamplerType(_stageIndex, handle, cbufSlot); - return _oldSpecState.GetTextureTarget(_stageIndex, handle, cbufSlot).ConvertSamplerType(); + if (!_oldSpecState.TextureArrayFromBufferRegistered(_stageIndex, 0, slot)) + { + throw new DiskCacheLoadException(DiskCacheLoadResult.MissingTextureArrayLength); + } + + int arrayLength = _oldSpecState.GetTextureArrayFromBufferLength(_stageIndex, 0, slot); + _newSpecState.RegisterTextureArrayLengthFromBuffer(_stageIndex, 0, slot, arrayLength); + + return arrayLength; + } + + /// <inheritdoc/> + public TextureFormat QueryTextureFormat(int handle, int cbufSlot) + { + _newSpecState.RecordTextureFormat(_stageIndex, handle, cbufSlot); + (uint format, bool formatSrgb) = _oldSpecState.GetFormat(_stageIndex, handle, cbufSlot); + return ConvertToTextureFormat(format, formatSrgb); } /// <inheritdoc/> diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs index 5036186b..b6a277a2 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs @@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache private const ushort FileFormatVersionMajor = 1; private const ushort FileFormatVersionMinor = 2; private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor; - private const uint CodeGenVersion = 6462; + private const uint CodeGenVersion = 6489; private const string SharedTocFileName = "shared.toc"; private const string SharedDataFileName = "shared.data"; diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheLoadResult.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheLoadResult.cs index ba23f70e..d5abb9e5 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheLoadResult.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheLoadResult.cs @@ -21,6 +21,11 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache InvalidCb1DataLength, /// <summary> + /// The cache is missing the length of a texture array used by the shader. + /// </summary> + MissingTextureArrayLength, + + /// <summary> /// The cache is missing the descriptor of a texture used by the shader. /// </summary> MissingTextureDescriptor, @@ -60,6 +65,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache DiskCacheLoadResult.Success => "No error.", DiskCacheLoadResult.NoAccess => "Could not access the cache file.", DiskCacheLoadResult.InvalidCb1DataLength => "Constant buffer 1 data length is too low.", + DiskCacheLoadResult.MissingTextureArrayLength => "Texture array length missing from the cache file.", DiskCacheLoadResult.MissingTextureDescriptor => "Texture descriptor missing from the cache file.", DiskCacheLoadResult.FileCorruptedGeneric => "The cache file is corrupted.", DiskCacheLoadResult.FileCorruptedInvalidMagic => "Magic check failed, the cache file is corrupted.", diff --git a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs index 95763f31..1d22ab93 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs @@ -72,6 +72,7 @@ namespace Ryujinx.Graphics.Gpu.Shader public ReadOnlySpan<ulong> GetCode(ulong address, int minimumSize) { int size = Math.Max(minimumSize, 0x1000 - (int)(address & 0xfff)); + return MemoryMarshal.Cast<byte, ulong>(_channel.MemoryManager.GetSpan(address, size)); } @@ -119,6 +120,27 @@ namespace Ryujinx.Graphics.Gpu.Shader return _state.GraphicsState.HasUnalignedStorageBuffer || _state.ComputeState.HasUnalignedStorageBuffer; } + /// <inheritdoc/> + public SamplerType QuerySamplerType(int handle, int cbufSlot) + { + _state.SpecializationState?.RecordTextureSamplerType(_stageIndex, handle, cbufSlot); + return GetTextureDescriptor(handle, cbufSlot).UnpackTextureTarget().ConvertSamplerType(); + } + + /// <inheritdoc/> + public int QueryTextureArrayLengthFromBuffer(int slot) + { + int size = _compute + ? _channel.BufferManager.GetComputeUniformBufferSize(slot) + : _channel.BufferManager.GetGraphicsUniformBufferSize(_stageIndex, slot); + + int arrayLength = size / Constants.TextureHandleSizeInBytes; + + _state.SpecializationState?.RegisterTextureArrayLengthFromBuffer(_stageIndex, 0, slot, arrayLength); + + return arrayLength; + } + //// <inheritdoc/> public TextureFormat QueryTextureFormat(int handle, int cbufSlot) { @@ -128,13 +150,6 @@ namespace Ryujinx.Graphics.Gpu.Shader } /// <inheritdoc/> - public SamplerType QuerySamplerType(int handle, int cbufSlot) - { - _state.SpecializationState?.RecordTextureSamplerType(_stageIndex, handle, cbufSlot); - return GetTextureDescriptor(handle, cbufSlot).UnpackTextureTarget().ConvertSamplerType(); - } - - /// <inheritdoc/> public bool QueryTextureCoordNormalized(int handle, int cbufSlot) { _state.SpecializationState?.RecordTextureCoordNormalized(_stageIndex, handle, cbufSlot); diff --git a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs index a5b31363..06e5edf1 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs @@ -20,6 +20,9 @@ namespace Ryujinx.Graphics.Gpu.Shader private int _reservedTextures; private int _reservedImages; + private int _staticTexturesCount; + private int _staticImagesCount; + /// <summary> /// Creates a new GPU accessor. /// </summary> @@ -48,7 +51,7 @@ namespace Ryujinx.Graphics.Gpu.Shader _reservedImages = rrc.ReservedImages; } - public int QueryBindingConstantBuffer(int index) + public int CreateConstantBufferBinding(int index) { int binding; @@ -64,62 +67,84 @@ namespace Ryujinx.Graphics.Gpu.Shader return binding + _reservedConstantBuffers; } - public int QueryBindingStorageBuffer(int index) + public int CreateImageBinding(int count, bool isBuffer) { int binding; if (_context.Capabilities.Api == TargetApi.Vulkan) { - binding = GetBindingFromIndex(index, _context.Capabilities.MaximumStorageBuffersPerStage, "Storage buffer"); + if (count == 1) + { + int index = _staticImagesCount++; + + if (isBuffer) + { + index += (int)_context.Capabilities.MaximumImagesPerStage; + } + + binding = GetBindingFromIndex(index, _context.Capabilities.MaximumImagesPerStage * 2, "Image"); + } + else + { + binding = (int)GetDynamicBaseIndexDual(_context.Capabilities.MaximumImagesPerStage) + _resourceCounts.ImagesCount++; + } } else { - binding = _resourceCounts.StorageBuffersCount++; + binding = _resourceCounts.ImagesCount; + + _resourceCounts.ImagesCount += count; } - return binding + _reservedStorageBuffers; + return binding + _reservedImages; } - public int QueryBindingTexture(int index, bool isBuffer) + public int CreateStorageBufferBinding(int index) { int binding; if (_context.Capabilities.Api == TargetApi.Vulkan) { - if (isBuffer) - { - index += (int)_context.Capabilities.MaximumTexturesPerStage; - } - - binding = GetBindingFromIndex(index, _context.Capabilities.MaximumTexturesPerStage * 2, "Texture"); + binding = GetBindingFromIndex(index, _context.Capabilities.MaximumStorageBuffersPerStage, "Storage buffer"); } else { - binding = _resourceCounts.TexturesCount++; + binding = _resourceCounts.StorageBuffersCount++; } - return binding + _reservedTextures; + return binding + _reservedStorageBuffers; } - public int QueryBindingImage(int index, bool isBuffer) + public int CreateTextureBinding(int count, bool isBuffer) { int binding; if (_context.Capabilities.Api == TargetApi.Vulkan) { - if (isBuffer) + if (count == 1) { - index += (int)_context.Capabilities.MaximumImagesPerStage; - } + int index = _staticTexturesCount++; + + if (isBuffer) + { + index += (int)_context.Capabilities.MaximumTexturesPerStage; + } - binding = GetBindingFromIndex(index, _context.Capabilities.MaximumImagesPerStage * 2, "Image"); + binding = GetBindingFromIndex(index, _context.Capabilities.MaximumTexturesPerStage * 2, "Texture"); + } + else + { + binding = (int)GetDynamicBaseIndexDual(_context.Capabilities.MaximumTexturesPerStage) + _resourceCounts.TexturesCount++; + } } else { - binding = _resourceCounts.ImagesCount++; + binding = _resourceCounts.TexturesCount; + + _resourceCounts.TexturesCount += count; } - return binding + _reservedImages; + return binding + _reservedTextures; } private int GetBindingFromIndex(int index, uint maxPerStage, string resourceName) @@ -148,6 +173,16 @@ namespace Ryujinx.Graphics.Gpu.Shader }; } + private static uint GetDynamicBaseIndexDual(uint maxPerStage) + { + return GetDynamicBaseIndex(maxPerStage) * 2; + } + + private static uint GetDynamicBaseIndex(uint maxPerStage) + { + return maxPerStage * Constants.ShaderStages; + } + public int QueryHostGatherBiasPrecision() => _context.Capabilities.GatherBiasPrecision; public bool QueryHostReducedPrecision() => _context.Capabilities.ReduceShaderPrecision; diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs index c2258026..ea8f164f 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs @@ -132,6 +132,9 @@ namespace Ryujinx.Graphics.Gpu.Shader AddDualDescriptor(stages, ResourceType.TextureAndSampler, ResourceType.BufferTexture, TextureSetIndex, textureBinding, texturesPerStage); AddDualDescriptor(stages, ResourceType.Image, ResourceType.BufferImage, ImageSetIndex, imageBinding, imagesPerStage); + AddArrayDescriptors(info.Textures, stages, TextureSetIndex, isImage: false); + AddArrayDescriptors(info.Images, stages, TextureSetIndex, isImage: true); + AddUsage(info.CBuffers, stages, UniformSetIndex, isStorage: false); AddUsage(info.SBuffers, stages, StorageSetIndex, isStorage: true); AddUsage(info.Textures, stages, TextureSetIndex, isImage: false); @@ -170,6 +173,30 @@ namespace Ryujinx.Graphics.Gpu.Shader } /// <summary> + /// Adds all array descriptors (those with an array length greater than one). + /// </summary> + /// <param name="textures">Textures to be added</param> + /// <param name="stages">Stages where the textures are used</param> + /// <param name="setIndex">Descriptor set index where the textures will be bound</param> + /// <param name="isImage">True for images, false for textures</param> + private void AddArrayDescriptors(IEnumerable<TextureDescriptor> textures, ResourceStages stages, int setIndex, bool isImage) + { + foreach (TextureDescriptor texture in textures) + { + if (texture.ArrayLength > 1) + { + bool isBuffer = (texture.Type & SamplerType.Mask) == SamplerType.TextureBuffer; + + ResourceType type = isBuffer + ? (isImage ? ResourceType.BufferImage : ResourceType.BufferTexture) + : (isImage ? ResourceType.Image : ResourceType.TextureAndSampler); + + _resourceDescriptors[setIndex].Add(new ResourceDescriptor(texture.Binding, texture.ArrayLength, type, stages)); + } + } + } + + /// <summary> /// Adds buffer usage information to the list of usages. /// </summary> /// <param name="stages">Shader stages where the resource is used</param> @@ -181,7 +208,7 @@ namespace Ryujinx.Graphics.Gpu.Shader { for (int index = 0; index < count; index++) { - _resourceUsages[setIndex].Add(new ResourceUsage(binding + index, type, stages)); + _resourceUsages[setIndex].Add(new ResourceUsage(binding + index, 1, type, stages)); } } @@ -198,6 +225,7 @@ namespace Ryujinx.Graphics.Gpu.Shader { _resourceUsages[setIndex].Add(new ResourceUsage( buffer.Binding, + 1, isStorage ? ResourceType.StorageBuffer : ResourceType.UniformBuffer, stages)); } @@ -220,10 +248,7 @@ namespace Ryujinx.Graphics.Gpu.Shader ? (isImage ? ResourceType.BufferImage : ResourceType.BufferTexture) : (isImage ? ResourceType.Image : ResourceType.TextureAndSampler); - _resourceUsages[setIndex].Add(new ResourceUsage( - texture.Binding, - type, - stages)); + _resourceUsages[setIndex].Add(new ResourceUsage(texture.Binding, texture.ArrayLength, type, stages)); } } diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs index 1477b738..c90a0b8f 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs @@ -30,6 +30,7 @@ namespace Ryujinx.Graphics.Gpu.Shader { PrimitiveTopology = 1 << 1, TransformFeedback = 1 << 3, + TextureArrayFromBuffer = 1 << 4, } private QueriedStateFlags _queriedState; @@ -153,6 +154,7 @@ namespace Ryujinx.Graphics.Gpu.Shader } private readonly Dictionary<TextureKey, Box<TextureSpecializationState>> _textureSpecialization; + private readonly Dictionary<TextureKey, int> _textureArraySpecialization; private KeyValuePair<TextureKey, Box<TextureSpecializationState>>[] _allTextures; private Box<TextureSpecializationState>[][] _textureByBinding; private Box<TextureSpecializationState>[][] _imageByBinding; @@ -163,6 +165,7 @@ namespace Ryujinx.Graphics.Gpu.Shader private ShaderSpecializationState() { _textureSpecialization = new Dictionary<TextureKey, Box<TextureSpecializationState>>(); + _textureArraySpecialization = new Dictionary<TextureKey, int>(); } /// <summary> @@ -324,6 +327,19 @@ namespace Ryujinx.Graphics.Gpu.Shader } /// <summary> + /// Indicates that the coordinate normalization state of a given texture was used during the shader translation process. + /// </summary> + /// <param name="stageIndex">Shader stage where the texture is used</param> + /// <param name="handle">Offset in words of the texture handle on the texture buffer</param> + /// <param name="cbufSlot">Slot of the texture buffer constant buffer</param> + /// <param name="length">Number of elements in the texture array</param> + public void RegisterTextureArrayLengthFromBuffer(int stageIndex, int handle, int cbufSlot, int length) + { + _textureArraySpecialization[new TextureKey(stageIndex, handle, cbufSlot)] = length; + _queriedState |= QueriedStateFlags.TextureArrayFromBuffer; + } + + /// <summary> /// Indicates that the format of a given texture was used during the shader translation process. /// </summary> /// <param name="stageIndex">Shader stage where the texture is used</param> @@ -380,6 +396,17 @@ namespace Ryujinx.Graphics.Gpu.Shader } /// <summary> + /// Checks if a given texture array (from constant buffer) was registerd on this specialization state. + /// </summary> + /// <param name="stageIndex">Shader stage where the texture is used</param> + /// <param name="handle">Offset in words of the texture handle on the texture buffer</param> + /// <param name="cbufSlot">Slot of the texture buffer constant buffer</param> + public bool TextureArrayFromBufferRegistered(int stageIndex, int handle, int cbufSlot) + { + return _textureArraySpecialization.ContainsKey(new TextureKey(stageIndex, handle, cbufSlot)); + } + + /// <summary> /// Gets the recorded format of a given texture. /// </summary> /// <param name="stageIndex">Shader stage where the texture is used</param> @@ -414,6 +441,17 @@ namespace Ryujinx.Graphics.Gpu.Shader } /// <summary> + /// Gets the recorded length of a given texture array (from constant buffer). + /// </summary> + /// <param name="stageIndex">Shader stage where the texture is used</param> + /// <param name="handle">Offset in words of the texture handle on the texture buffer</param> + /// <param name="cbufSlot">Slot of the texture buffer constant buffer</param> + public int GetTextureArrayFromBufferLength(int stageIndex, int handle, int cbufSlot) + { + return _textureArraySpecialization[new TextureKey(stageIndex, handle, cbufSlot)]; + } + + /// <summary> /// Gets texture specialization state for a given texture, or create a new one if not present. /// </summary> /// <param name="stageIndex">Shader stage where the texture is used</param> @@ -548,6 +586,12 @@ namespace Ryujinx.Graphics.Gpu.Shader return Matches(channel, ref poolState, checkTextures, isCompute: false); } + /// <summary> + /// Converts special vertex attribute groups to their generic equivalents, for comparison purposes. + /// </summary> + /// <param name="channel">GPU channel</param> + /// <param name="type">Vertex attribute type</param> + /// <returns>Filtered attribute</returns> private static AttributeType FilterAttributeType(GpuChannel channel, AttributeType type) { type &= ~(AttributeType.Packed | AttributeType.PackedRgb10A2Signed); @@ -838,6 +882,22 @@ namespace Ryujinx.Graphics.Gpu.Shader specState._textureSpecialization[textureKey] = textureState; } + if (specState._queriedState.HasFlag(QueriedStateFlags.TextureArrayFromBuffer)) + { + dataReader.Read(ref count); + + for (int index = 0; index < count; index++) + { + TextureKey textureKey = default; + int length = 0; + + dataReader.ReadWithMagicAndSize(ref textureKey, TexkMagic); + dataReader.Read(ref length); + + specState._textureArraySpecialization[textureKey] = length; + } + } + return specState; } @@ -902,6 +962,21 @@ namespace Ryujinx.Graphics.Gpu.Shader dataWriter.WriteWithMagicAndSize(ref textureKey, TexkMagic); dataWriter.WriteWithMagicAndSize(ref textureState.Value, TexsMagic); } + + if (_queriedState.HasFlag(QueriedStateFlags.TextureArrayFromBuffer)) + { + count = (ushort)_textureArraySpecialization.Count; + dataWriter.Write(ref count); + + foreach (var kv in _textureArraySpecialization) + { + var textureKey = kv.Key; + var length = kv.Value; + + dataWriter.WriteWithMagicAndSize(ref textureKey, TexkMagic); + dataWriter.Write(ref length); + } + } } } } |
