acdream/tools/RenderPackValidator/RenderPackSdkValidator.cs

1500 lines
70 KiB
C#

using System.Buffers.Binary;
using AcDream.Plugin.Abstractions.Rendering;
namespace AcDream.Tools.RenderPackValidator;
internal readonly record struct RenderPackSdkValidationResult(
bool Success,
string? Reason)
{
internal static RenderPackSdkValidationResult Valid() => new(true, null);
internal static RenderPackSdkValidationResult Invalid(string reason) =>
new(false, reason);
}
/// <summary>
/// Hardware-independent v1 schema validation. This deliberately does not
/// compile pipelines or promise device compatibility; the graphical client
/// performs those checks before atomic activation.
/// </summary>
internal static class RenderPackSdkValidator
{
private const long MaximumPackBytes = 256L * 1024 * 1024;
private const int MaximumImageDimension = 16_384;
private const int MaximumImageLayers = 256;
private const uint SpirvMagic = 0x0723_0203u;
internal static RenderPackSdkValidationResult ValidateDescriptor(
RenderPackDescriptor? descriptor)
{
if (descriptor is null)
return Invalid("The pack descriptor is missing.");
if (!StableId.IsValid(descriptor.Id))
return Invalid("The pack id must be a stable lowercase logical id.");
if (string.IsNullOrWhiteSpace(descriptor.DisplayName))
return Invalid($"Pack '{descriptor.Id}' has no display name.");
if (string.IsNullOrWhiteSpace(descriptor.FeatureSummary))
return Invalid($"Pack '{descriptor.Id}' has no feature summary.");
if (descriptor.PackVersion is null)
return Invalid($"Pack '{descriptor.Id}' has no version.");
if (!RenderPackApi.IsSupported(descriptor.PackApiVersion))
{
return Invalid(
$"Pack '{descriptor.Id}' requires render-pack API "
+ $"{descriptor.PackApiVersion}; this SDK supports "
+ $"{RenderPackApi.MinimumSupported}..{RenderPackApi.Current}.");
}
if (!Enum.IsDefined(descriptor.HighestTier))
return Invalid($"Pack '{descriptor.Id}' declares an unknown tier.");
string? nullList = FirstNullList(descriptor);
if (nullList is not null)
return Invalid($"Pack '{descriptor.Id}' has a null {nullList} declaration list.");
if (descriptor.RequiredCapabilities.Any(static value => !Enum.IsDefined(value))
|| descriptor.OptionalCapabilities.Any(static value => !Enum.IsDefined(value)))
{
return Invalid($"Pack '{descriptor.Id}' declares an unknown capability.");
}
RenderPackSdkValidationResult semanticCapabilities =
ValidateSemanticCapabilities(descriptor);
if (!semanticCapabilities.Success) return semanticCapabilities;
RenderPackSdkValidationResult uniqueIds = ValidateUniqueIds(descriptor);
if (!uniqueIds.Success) return uniqueIds;
RenderPackSdkValidationResult resources = ValidateResources(descriptor);
if (!resources.Success) return resources;
RenderPackSdkValidationResult passes = ValidatePasses(descriptor);
if (!passes.Success) return passes;
RenderPackSdkValidationResult replays = ValidateReplays(descriptor);
if (!replays.Success) return replays;
RenderPackSdkValidationResult variants = ValidateVariants(descriptor);
if (!variants.Success) return variants;
RenderPackSdkValidationResult settings = ValidateSettings(descriptor);
if (!settings.Success) return settings;
RenderPackSdkValidationResult presets = ValidatePresets(descriptor);
if (!presets.Success) return presets;
RenderPackSdkValidationResult semantics = ValidateSemanticRoles(descriptor);
if (!semantics.Success) return semantics;
return ValidateAtmosphere(descriptor);
}
internal static RenderPackSdkValidationResult ValidateAssets(
RenderPackDescriptor descriptor,
IRenderPackAssets assets)
{
ArgumentNullException.ThrowIfNull(descriptor);
ArgumentNullException.ThrowIfNull(assets);
ShaderValidationRequest[] requests = descriptor.Passes
.SelectMany(static pass => new[]
{
new ShaderValidationRequest(
pass.VertexShaderAsset, RenderPackShaderStage.Vertex, pass, null),
new ShaderValidationRequest(
pass.FragmentShaderAsset, RenderPackShaderStage.Fragment, pass, null),
})
.Concat(descriptor.PipelineVariants.SelectMany(static variant => new[]
{
new ShaderValidationRequest(
variant.VertexShaderAsset, RenderPackShaderStage.Vertex, null, variant),
new ShaderValidationRequest(
variant.FragmentShaderAsset, RenderPackShaderStage.Fragment, null, variant),
}))
.ToArray();
byte[] readBuffer = new byte[4096];
foreach (IGrouping<string, ShaderValidationRequest> group in
requests.GroupBy(static request => request.Key, StringComparer.Ordinal))
{
string key = group.Key;
if (!SafeRelativePath.IsValid(key))
return Invalid($"Pack '{descriptor.Id}' declares unsafe asset key '{key}'.");
try
{
using Stream stream = assets.OpenRead(key);
if (stream is null || !stream.CanRead)
return Invalid($"Pack '{descriptor.Id}' asset '{key}' is not readable.");
using var destination = new MemoryStream();
while (true)
{
int count = stream.Read(readBuffer);
if (count == 0)
break;
if (destination.Length + count > RenderPackShaderAbi.MaximumShaderAssetBytes)
{
return Invalid(
$"Pack '{descriptor.Id}' asset '{key}' exceeds "
+ $"the {RenderPackShaderAbi.MaximumShaderAssetBytes}-byte "
+ "shader ceiling.");
}
destination.Write(readBuffer, 0, count);
}
byte[] spirv = destination.ToArray();
if (spirv.Length < 4
|| (spirv.Length & 3) != 0
|| BinaryPrimitives.ReadUInt32LittleEndian(spirv) != SpirvMagic)
{
return Invalid(
$"Pack '{descriptor.Id}' asset '{key}' is not valid SPIR-V.");
}
foreach (ShaderValidationRequest request in group)
{
RenderPackSpirvValidationResult validation = request.Pass is not null
? RenderPackSpirvValidator.ValidatePassShader(
spirv, request.Stage, request.Pass)
: RenderPackSpirvValidator.ValidatePipelineVariantShader(
spirv, request.Stage, request.Variant!);
if (!validation.Success)
{
return Invalid(
$"Pack '{descriptor.Id}' asset '{key}' fails render-pack shader ABI v1: "
+ validation.Reason + ".");
}
}
}
catch (Exception exception) when (exception is IOException
or UnauthorizedAccessException
or ArgumentException
or NotSupportedException)
{
return Invalid(
$"Pack '{descriptor.Id}' asset '{key}' could not be opened: "
+ exception.GetBaseException().Message);
}
}
return RenderPackSdkValidationResult.Valid();
}
private sealed record ShaderValidationRequest(
string Key,
RenderPackShaderStage Stage,
RenderPassDeclaration? Pass,
PipelineVariantDeclaration? Variant);
private static RenderPackSdkValidationResult ValidateSemanticRoles(
RenderPackDescriptor descriptor)
{
RenderPackSdkValidationResult unique = UniqueNonCustomSemantics(
descriptor, descriptor.Resources, static value => value.Semantic,
RenderResourceSemantic.Custom, "resource");
if (!unique.Success) return unique;
unique = UniqueNonCustomSemantics(
descriptor, descriptor.Passes, static value => value.Semantic,
RenderPassSemantic.CustomFullscreen, "pass");
if (!unique.Success) return unique;
unique = UniqueNonCustomSemantics(
descriptor, descriptor.PipelineVariants, static value => value.Semantic,
RenderPipelineVariantSemantic.Custom, "pipeline variant");
if (!unique.Success) return unique;
unique = UniqueNonCustomSemantics(
descriptor, descriptor.QualityPresets, static value => value.Semantic,
RenderQualitySemantic.Custom, "quality preset");
if (!unique.Success) return unique;
unique = UniqueNonCustomSemantics(
descriptor, descriptor.Settings, static value => value.Semantic,
RenderSettingSemantic.Custom, "setting");
if (!unique.Success) return unique;
RenderSettingDeclaration? automaticSetting = descriptor.Settings.FirstOrDefault(
static value => value.Semantic == RenderSettingSemantic.AutomaticQuality);
if (automaticSetting is not null && automaticSetting.Kind != RenderSettingKind.Boolean)
{
return Invalid(
$"Pack '{descriptor.Id}' AutomaticQuality setting must be Boolean.");
}
if (automaticSetting is not null
|| descriptor.QualityPresets.Any(static value =>
value.Semantic == RenderQualitySemantic.Automatic))
{
foreach (RenderQualitySemantic semantic in new[]
{
RenderQualitySemantic.Low,
RenderQualitySemantic.Medium,
RenderQualitySemantic.High,
})
{
if (!descriptor.QualityPresets.Any(value =>
value.Semantic == semantic && value.AutoEligible))
{
return Invalid(
$"Pack '{descriptor.Id}' declares Automatic quality but has no "
+ $"AutoEligible '{semantic}' semantic preset.");
}
}
}
bool atmosphericExecutor = descriptor.Passes.Any(static value =>
value.Semantic != RenderPassSemantic.CustomFullscreen);
if (!atmosphericExecutor)
return RenderPackSdkValidationResult.Valid();
bool directionalShadowOnly = descriptor.Passes.Any(static value =>
value.Semantic == RenderPassSemantic.DirectionalShadowDepth)
&& descriptor.Passes.All(static value =>
value.Semantic is RenderPassSemantic.CustomFullscreen
or RenderPassSemantic.DirectionalShadowDepth);
if (directionalShadowOnly)
return ValidateDirectionalShadowProfile(descriptor);
RenderPassSemantic[] requiredPasses =
[
RenderPassSemantic.DirectionalShadowDepth,
RenderPassSemantic.SunOcclusion,
RenderPassSemantic.SunRays,
RenderPassSemantic.VolumetricShafts,
RenderPassSemantic.BloomDownsample,
RenderPassSemantic.BloomBlurHorizontal,
RenderPassSemantic.BloomBlurVertical,
RenderPassSemantic.FilmicComposite,
];
foreach (RenderPassSemantic semantic in requiredPasses)
{
if (!descriptor.Passes.Any(value => value.Semantic == semantic))
{
return Invalid(
$"Pack '{descriptor.Id}' requests the atmospheric executor but "
+ $"does not declare required pass semantic '{semantic}'.");
}
}
RenderResourceSemantic[] requiredResources =
[
RenderResourceSemantic.MainWorldHdr,
RenderResourceSemantic.BloomPing,
RenderResourceSemantic.BloomPong,
RenderResourceSemantic.SunOcclusionMask,
RenderResourceSemantic.SunRays,
RenderResourceSemantic.DirectionalShadowDepth,
RenderResourceSemantic.VolumetricShafts,
];
foreach (RenderResourceSemantic semantic in requiredResources)
{
if (!descriptor.Resources.Any(value => value.Semantic == semantic))
{
return Invalid(
$"Pack '{descriptor.Id}' requests the atmospheric executor but "
+ $"does not declare required resource semantic '{semantic}'.");
}
}
if (descriptor.SceneReplays.Count(value =>
value.Semantic == RenderSceneReplaySemantic.OutdoorDirectionalShadowCasters) != 1)
{
return Invalid(
$"Pack '{descriptor.Id}' must declare exactly one outdoor directional-shadow replay.");
}
bool usesMultiview = UsesMultiview(descriptor);
List<RenderPipelineVariantSemantic> requiredVariants =
[
RenderPipelineVariantSemantic.TerrainDirectionalShadowCaster,
RenderPipelineVariantSemantic.WorldOpaqueDirectionalShadowCaster,
RenderPipelineVariantSemantic.WorldAlphaCutoutDirectionalShadowCaster,
RenderPipelineVariantSemantic.TerrainDirectionalShadowReceiver,
RenderPipelineVariantSemantic.WorldDirectionalShadowReceiver,
];
if (usesMultiview)
{
requiredVariants.Add(RenderPipelineVariantSemantic.TerrainMultiviewDirectionalShadowCaster);
requiredVariants.Add(RenderPipelineVariantSemantic.WorldOpaqueMultiviewDirectionalShadowCaster);
requiredVariants.Add(RenderPipelineVariantSemantic.WorldAlphaCutoutMultiviewDirectionalShadowCaster);
}
foreach (RenderPipelineVariantSemantic semantic in requiredVariants)
{
if (!descriptor.PipelineVariants.Any(value => value.Semantic == semantic))
{
return Invalid(
$"Pack '{descriptor.Id}' does not declare required pipeline-variant "
+ $"semantic '{semantic}'.");
}
}
if (descriptor.AtmospherePolicy?.DirectionalShadowLightElevationResponse
is not { Count: >= 2 }
|| descriptor.AtmospherePolicy.VolumetricShaftSunElevationResponse
is not { Count: >= 2 })
{
return Invalid(
$"Pack '{descriptor.Id}' must declare directional-shadow and "
+ "volumetric-shaft elevation response curves.");
}
RenderPackSdkValidationResult shapes =
ValidateAtmosphericSemanticShapes(descriptor);
if (!shapes.Success) return shapes;
return ValidateAtmosphericSemanticEdges(descriptor);
}
private static RenderPackSdkValidationResult ValidateDirectionalShadowProfile(
RenderPackDescriptor descriptor)
{
if (descriptor.HighestTier < RenderPackTier.Tier2)
return Invalid($"Pack '{descriptor.Id}' declares directional shadows below Tier2.");
RenderCapability[] requiredCapabilities =
[
RenderCapability.MainWorldColorIntermediate,
RenderCapability.FullscreenPasses,
RenderCapability.AuthoredCelestialDirectionalLight,
RenderCapability.AuthoredWeather,
RenderCapability.DirectionalShadowMaps,
RenderCapability.OutdoorDirectionalShadowCasterReplay,
RenderCapability.AnimatedCasterTransforms,
RenderCapability.AlphaCutoutShadowCasters,
];
foreach (RenderCapability capability in requiredCapabilities)
{
if (!descriptor.RequiredCapabilities.Contains(capability))
{
return Invalid(
$"Pack '{descriptor.Id}' directional shadows must explicitly require "
+ $"capability '{capability}'.");
}
}
if (descriptor.Passes.Count(static value =>
value.Semantic == RenderPassSemantic.DirectionalShadowDepth) != 1
|| descriptor.Resources.Count(static value =>
value.Semantic == RenderResourceSemantic.DirectionalShadowDepth) != 1)
{
return Invalid(
$"Pack '{descriptor.Id}' must declare exactly one directional-shadow "
+ "pass and resource.");
}
if (descriptor.SceneReplays.Count != 1
|| descriptor.SceneReplays[0].Semantic
!= RenderSceneReplaySemantic.OutdoorDirectionalShadowCasters)
{
return Invalid(
$"Pack '{descriptor.Id}' must declare exactly one outdoor "
+ "directional-shadow replay.");
}
RenderPipelineVariantSemantic[] semantics =
[
RenderPipelineVariantSemantic.TerrainDirectionalShadowCaster,
RenderPipelineVariantSemantic.WorldOpaqueDirectionalShadowCaster,
RenderPipelineVariantSemantic.WorldAlphaCutoutDirectionalShadowCaster,
RenderPipelineVariantSemantic.TerrainDirectionalShadowReceiver,
RenderPipelineVariantSemantic.WorldDirectionalShadowReceiver,
];
RenderPipelineBaseSemantic[] bases =
[
RenderPipelineBaseSemantic.Terrain,
RenderPipelineBaseSemantic.WorldMesh,
RenderPipelineBaseSemantic.WorldMesh,
RenderPipelineBaseSemantic.Terrain,
RenderPipelineBaseSemantic.WorldMesh,
];
RenderMaterialClass[] materials =
[
RenderMaterialClass.Opaque,
RenderMaterialClass.Opaque | RenderMaterialClass.AnimatedOpaque,
RenderMaterialClass.AlphaCutout | RenderMaterialClass.AnimatedAlphaCutout,
RenderMaterialClass.Opaque,
RenderMaterialClass.Opaque | RenderMaterialClass.AlphaCutout
| RenderMaterialClass.AnimatedOpaque
| RenderMaterialClass.AnimatedAlphaCutout,
];
RenderSemanticInput[][] inputs =
[
[RenderSemanticInput.CameraMatrices],
[RenderSemanticInput.CameraMatrices, RenderSemanticInput.ShadowCasterTransforms],
[RenderSemanticInput.CameraMatrices, RenderSemanticInput.ShadowCasterTransforms],
[RenderSemanticInput.DirectionalShadowMaps,
RenderSemanticInput.SelectedCelestialDirectionalLight],
[RenderSemanticInput.DirectionalShadowMaps,
RenderSemanticInput.SelectedCelestialDirectionalLight],
];
int expectedVariantCount = UsesMultiview(descriptor) ? 8 : semantics.Length;
if (descriptor.PipelineVariants.Count != expectedVariantCount)
{
return Invalid(
$"Pack '{descriptor.Id}' directional shadows require exactly {expectedVariantCount} "
+ "semantic pipeline variants.");
}
if (UsesMultiview(descriptor))
{
RenderPipelineVariantSemantic[] multiview =
[
RenderPipelineVariantSemantic.TerrainMultiviewDirectionalShadowCaster,
RenderPipelineVariantSemantic.WorldOpaqueMultiviewDirectionalShadowCaster,
RenderPipelineVariantSemantic.WorldAlphaCutoutMultiviewDirectionalShadowCaster,
];
for (int i = 0; i < multiview.Length; i++)
{
PipelineVariantDeclaration? variant = descriptor.PipelineVariants
.SingleOrDefault(value => value.Semantic == multiview[i]);
if (variant is null
|| variant.BaseSemantic != bases[i]
|| variant.CompatibleMaterials != materials[i]
|| !variant.SemanticInputs.SequenceEqual(inputs[i]))
{
return Invalid(
$"Pipeline variant semantic '{multiview[i]}' does not match the fixed "
+ "multiview directional-shadow executor contract.");
}
}
}
for (int i = 0; i < semantics.Length; i++)
{
PipelineVariantDeclaration? variant = descriptor.PipelineVariants
.SingleOrDefault(value => value.Semantic == semantics[i]);
if (variant is null
|| variant.BaseSemantic != bases[i]
|| variant.CompatibleMaterials != materials[i]
|| !variant.SemanticInputs.SequenceEqual(inputs[i]))
{
return Invalid(
$"Pipeline variant semantic '{semantics[i]}' does not match the fixed "
+ "directional-shadow executor contract.");
}
}
foreach (RenderSettingSemantic semantic in new[]
{
RenderSettingSemantic.DirectionalShadowStrength,
RenderSettingSemantic.DirectionalShadowReachMetres,
RenderSettingSemantic.DirectionalShadowPcfTaps,
})
{
if (!descriptor.Settings.Any(value => value.Semantic == semantic))
{
return Invalid(
$"Pack '{descriptor.Id}' does not declare required directional-shadow "
+ $"setting semantic '{semantic}'.");
}
}
if (descriptor.AtmospherePolicy?.DirectionalShadowLightElevationResponse
is not { Count: >= 2 })
{
return Invalid(
$"Pack '{descriptor.Id}' must declare a directional-shadow "
+ "light-elevation response curve.");
}
RenderPassDeclaration shadow = descriptor.Passes.Single(value =>
value.Semantic == RenderPassSemantic.DirectionalShadowDepth);
RenderResourceDeclaration depth = descriptor.Resources.Single(value =>
value.Semantic == RenderResourceSemantic.DirectionalShadowDepth);
if (shadow.Hook != RenderPassHook.ShadowDepthBeforeWorld
|| shadow.ResourceReads.Count != 0
|| shadow.ResourceWrites.Count != 1
|| !string.Equals(
shadow.ResourceWrites[0], depth.Id, StringComparison.OrdinalIgnoreCase))
{
return Invalid(
$"Pack '{descriptor.Id}' directional-shadow pass must run before the world, "
+ "read no declared resource, and write its directional-depth resource.");
}
if (depth.Kind != RenderResourceKind.Image2DArray
|| depth.Format != RenderFormatClass.DirectionalDepth
|| depth.Extent?.Mode != RenderExtentMode.AbsolutePixels
|| depth.Usage != (RenderResourceUsage.Sampled | RenderResourceUsage.DepthAttachment)
|| depth.Lifetime != RenderResourceLifetime.ActivePack)
{
return Invalid(
$"Pack '{descriptor.Id}' directional-shadow resource does not match the "
+ "host executor's array-depth contract.");
}
SceneReplayDeclaration replay = descriptor.SceneReplays[0];
const RenderCasterClass requiredCasters = RenderCasterClass.Terrain
| RenderCasterClass.OpaqueWorld
| RenderCasterClass.AlphaCutoutWorld
| RenderCasterClass.AnimatedOpaque
| RenderCasterClass.AnimatedAlphaCutout;
if (replay.CasterClasses != requiredCasters || replay.ViewCount != 4)
{
return Invalid(
$"Pack '{descriptor.Id}' outdoor directional-shadow replay must declare "
+ "all five headline caster classes and four maximum cascade views.");
}
RenderPassDeclaration[] outputs = descriptor.Passes
.Where(static value => value.Hook == RenderPassHook.ToneMap
&& value.ResourceWrites.Count == 0)
.ToArray();
if (outputs.Length != 1
|| outputs[0].Semantic != RenderPassSemantic.CustomFullscreen
|| !outputs[0].SemanticInputs.Contains(RenderSemanticInput.WorldColor))
{
return Invalid(
$"Pack '{descriptor.Id}' directional shadows require exactly one custom "
+ "ToneMap output-copy pass sampling WorldColor.");
}
if (descriptor.Passes.Any(value =>
value.Semantic == RenderPassSemantic.CustomFullscreen
&& value.Hook is RenderPassHook.ShadowDepthBeforeWorld
or RenderPassHook.AfterToneMapBeforePrivateViewports))
{
return Invalid(
$"Pack '{descriptor.Id}' uses an unsupported custom pass hook in its "
+ "directional-shadow graph.");
}
return RenderPackSdkValidationResult.Valid();
}
private static RenderPackSdkValidationResult ValidateAtmosphericSemanticShapes(
RenderPackDescriptor descriptor)
{
RenderPackSdkValidationResult result = Resource(
RenderResourceSemantic.MainWorldHdr,
RenderResourceKind.Image2D,
RenderFormatClass.HdrColor,
RenderExtentMode.RelativeToMainWorld,
RenderResourceUsage.Sampled | RenderResourceUsage.ColorAttachment);
if (!result.Success) return result;
foreach (RenderResourceSemantic semantic in new[]
{
RenderResourceSemantic.BloomPing,
RenderResourceSemantic.BloomPong,
RenderResourceSemantic.SunRays,
RenderResourceSemantic.VolumetricShafts,
})
{
result = Resource(
semantic,
RenderResourceKind.Image2D,
RenderFormatClass.HdrColor,
RenderExtentMode.RelativeToMainWorld,
RenderResourceUsage.Sampled | RenderResourceUsage.ColorAttachment);
if (!result.Success) return result;
}
result = Resource(
RenderResourceSemantic.SunOcclusionMask,
RenderResourceKind.Image2D,
RenderFormatClass.SingleChannel,
RenderExtentMode.RelativeToMainWorld,
RenderResourceUsage.Sampled | RenderResourceUsage.ColorAttachment);
if (!result.Success) return result;
result = Resource(
RenderResourceSemantic.DirectionalShadowDepth,
RenderResourceKind.Image2DArray,
RenderFormatClass.DirectionalDepth,
RenderExtentMode.AbsolutePixels,
RenderResourceUsage.Sampled | RenderResourceUsage.DepthAttachment);
if (!result.Success) return result;
result = Variant(
RenderPipelineVariantSemantic.TerrainDirectionalShadowCaster,
RenderPipelineBaseSemantic.Terrain,
RenderMaterialClass.Opaque,
[RenderSemanticInput.CameraMatrices]);
if (!result.Success) return result;
result = Variant(
RenderPipelineVariantSemantic.WorldOpaqueDirectionalShadowCaster,
RenderPipelineBaseSemantic.WorldMesh,
RenderMaterialClass.Opaque | RenderMaterialClass.AnimatedOpaque,
[RenderSemanticInput.CameraMatrices, RenderSemanticInput.ShadowCasterTransforms]);
if (!result.Success) return result;
result = Variant(
RenderPipelineVariantSemantic.WorldAlphaCutoutDirectionalShadowCaster,
RenderPipelineBaseSemantic.WorldMesh,
RenderMaterialClass.AlphaCutout | RenderMaterialClass.AnimatedAlphaCutout,
[RenderSemanticInput.CameraMatrices, RenderSemanticInput.ShadowCasterTransforms]);
if (!result.Success) return result;
if (UsesMultiview(descriptor))
{
result = Variant(
RenderPipelineVariantSemantic.TerrainMultiviewDirectionalShadowCaster,
RenderPipelineBaseSemantic.Terrain,
RenderMaterialClass.Opaque,
[RenderSemanticInput.CameraMatrices]);
if (!result.Success) return result;
result = Variant(
RenderPipelineVariantSemantic.WorldOpaqueMultiviewDirectionalShadowCaster,
RenderPipelineBaseSemantic.WorldMesh,
RenderMaterialClass.Opaque | RenderMaterialClass.AnimatedOpaque,
[RenderSemanticInput.CameraMatrices, RenderSemanticInput.ShadowCasterTransforms]);
if (!result.Success) return result;
result = Variant(
RenderPipelineVariantSemantic.WorldAlphaCutoutMultiviewDirectionalShadowCaster,
RenderPipelineBaseSemantic.WorldMesh,
RenderMaterialClass.AlphaCutout | RenderMaterialClass.AnimatedAlphaCutout,
[RenderSemanticInput.CameraMatrices, RenderSemanticInput.ShadowCasterTransforms]);
if (!result.Success) return result;
}
result = Variant(
RenderPipelineVariantSemantic.TerrainDirectionalShadowReceiver,
RenderPipelineBaseSemantic.Terrain,
RenderMaterialClass.Opaque,
[RenderSemanticInput.DirectionalShadowMaps,
RenderSemanticInput.SelectedCelestialDirectionalLight]);
if (!result.Success) return result;
result = Variant(
RenderPipelineVariantSemantic.WorldDirectionalShadowReceiver,
RenderPipelineBaseSemantic.WorldMesh,
RenderMaterialClass.Opaque | RenderMaterialClass.AlphaCutout
| RenderMaterialClass.AnimatedOpaque
| RenderMaterialClass.AnimatedAlphaCutout,
[RenderSemanticInput.DirectionalShadowMaps,
RenderSemanticInput.SelectedCelestialDirectionalLight]);
if (!result.Success) return result;
SceneReplayDeclaration replay = descriptor.SceneReplays.Single(value =>
value.Semantic == RenderSceneReplaySemantic.OutdoorDirectionalShadowCasters);
const RenderCasterClass requiredCasters = RenderCasterClass.Terrain
| RenderCasterClass.OpaqueWorld
| RenderCasterClass.AlphaCutoutWorld
| RenderCasterClass.AnimatedOpaque
| RenderCasterClass.AnimatedAlphaCutout;
if (replay.CasterClasses != requiredCasters || replay.ViewCount != 4)
{
return Invalid(
$"Pack '{descriptor.Id}' outdoor directional-shadow replay must declare "
+ "all five headline caster classes and four maximum cascade views.");
}
return RenderPackSdkValidationResult.Valid();
RenderPackSdkValidationResult Resource(
RenderResourceSemantic semantic,
RenderResourceKind kind,
RenderFormatClass format,
RenderExtentMode extentMode,
RenderResourceUsage usage)
{
RenderResourceDeclaration resource = descriptor.Resources.Single(value =>
value.Semantic == semantic);
if (resource.Kind != kind
|| resource.Format != format
|| resource.Extent?.Mode != extentMode
|| resource.Usage != usage
|| resource.Lifetime != RenderResourceLifetime.ActivePack)
{
return Invalid(
$"Resource semantic '{semantic}' does not match the fixed atmospheric "
+ "executor's kind, format, extent, usage, and lifetime contract.");
}
return RenderPackSdkValidationResult.Valid();
}
RenderPackSdkValidationResult Variant(
RenderPipelineVariantSemantic semantic,
RenderPipelineBaseSemantic baseSemantic,
RenderMaterialClass materials,
IReadOnlyList<RenderSemanticInput> inputs)
{
PipelineVariantDeclaration variant = descriptor.PipelineVariants.Single(value =>
value.Semantic == semantic);
if (variant.BaseSemantic != baseSemantic
|| variant.CompatibleMaterials != materials
|| !variant.SemanticInputs.SequenceEqual(inputs))
{
return Invalid(
$"Pipeline variant semantic '{semantic}' does not match the fixed "
+ "atmospheric executor's base, material, and input contract.");
}
return RenderPackSdkValidationResult.Valid();
}
}
private static RenderPackSdkValidationResult ValidateAtmosphericSemanticEdges(
RenderPackDescriptor descriptor)
{
if (descriptor.Passes.Count != 8
|| descriptor.Resources.Count != 7
|| descriptor.PipelineVariants.Count != (UsesMultiview(descriptor) ? 8 : 5)
|| descriptor.SceneReplays.Count != 1)
{
return Invalid(
$"Pack '{descriptor.Id}' requests the fixed atmospheric executor; API v1 "
+ "requires exactly 8 semantic passes, 7 semantic resources, the hinted semantic "
+ "pipeline variants, and 1 semantic scene replay.");
}
RenderPackSdkValidationResult Hook(RenderPassSemantic semantic, RenderPassHook hook)
{
RenderPassDeclaration pass = descriptor.Passes.Single(value => value.Semantic == semantic);
return pass.Hook == hook
? RenderPackSdkValidationResult.Valid()
: Invalid(
$"Pass semantic '{semantic}' must run at hook '{hook}', not '{pass.Hook}'.");
}
RenderPackSdkValidationResult result = Hook(
RenderPassSemantic.DirectionalShadowDepth,
RenderPassHook.ShadowDepthBeforeWorld);
if (!result.Success) return result;
foreach (RenderPassSemantic semantic in new[]
{
RenderPassSemantic.SunOcclusion,
RenderPassSemantic.SunRays,
RenderPassSemantic.VolumetricShafts,
RenderPassSemantic.BloomDownsample,
RenderPassSemantic.BloomBlurHorizontal,
RenderPassSemantic.BloomBlurVertical,
})
{
result = Hook(semantic, RenderPassHook.AtmosphereBeforeToneMap);
if (!result.Success) return result;
}
result = Hook(RenderPassSemantic.FilmicComposite, RenderPassHook.ToneMap);
if (!result.Success) return result;
RenderPassSemantic[] declaredOrder = descriptor.Passes
.Where(static pass => pass.Hook is RenderPassHook.AtmosphereBeforeToneMap
or RenderPassHook.ToneMap)
.Select(static pass => pass.Semantic)
.ToArray();
RenderPassSemantic[] requiredOrder =
[
RenderPassSemantic.SunOcclusion,
RenderPassSemantic.SunRays,
RenderPassSemantic.VolumetricShafts,
RenderPassSemantic.BloomDownsample,
RenderPassSemantic.BloomBlurHorizontal,
RenderPassSemantic.BloomBlurVertical,
RenderPassSemantic.FilmicComposite,
];
if (!declaredOrder.SequenceEqual(requiredOrder))
{
return Invalid(
$"Pack '{descriptor.Id}' atmospheric pass order does not match the "
+ "renderer-owned semantic execution order.");
}
result = Edge(RenderPassSemantic.DirectionalShadowDepth, [],
RenderResourceSemantic.DirectionalShadowDepth);
if (!result.Success) return result;
result = Edge(RenderPassSemantic.SunOcclusion, [],
RenderResourceSemantic.SunOcclusionMask);
if (!result.Success) return result;
result = Edge(RenderPassSemantic.SunRays,
[RenderResourceSemantic.SunOcclusionMask], RenderResourceSemantic.SunRays);
if (!result.Success) return result;
result = Edge(RenderPassSemantic.VolumetricShafts,
[RenderResourceSemantic.DirectionalShadowDepth], RenderResourceSemantic.VolumetricShafts);
if (!result.Success) return result;
result = Edge(RenderPassSemantic.BloomDownsample,
[RenderResourceSemantic.SunRays, RenderResourceSemantic.VolumetricShafts],
RenderResourceSemantic.BloomPing);
if (!result.Success) return result;
result = Edge(RenderPassSemantic.BloomBlurHorizontal,
[RenderResourceSemantic.BloomPing], RenderResourceSemantic.BloomPong);
if (!result.Success) return result;
result = Edge(RenderPassSemantic.BloomBlurVertical,
[RenderResourceSemantic.BloomPong], RenderResourceSemantic.BloomPing);
if (!result.Success) return result;
return Edge(RenderPassSemantic.FilmicComposite,
[RenderResourceSemantic.BloomPing, RenderResourceSemantic.SunRays,
RenderResourceSemantic.VolumetricShafts],
output: null);
RenderPackSdkValidationResult Edge(
RenderPassSemantic passSemantic,
IReadOnlyList<RenderResourceSemantic> reads,
RenderResourceSemantic? output)
{
RenderPassDeclaration pass = descriptor.Passes.Single(value =>
value.Semantic == passSemantic);
RenderResourceSemantic[] actualReads = pass.ResourceReads
.Select(id => descriptor.Resources.Single(resource => string.Equals(
resource.Id, id, StringComparison.OrdinalIgnoreCase)).Semantic)
.ToArray();
if (!actualReads.SequenceEqual(reads))
{
return Invalid(
$"Pass semantic '{passSemantic}' declares resource reads that do not "
+ "match its renderer-owned execution edges.");
}
RenderResourceSemantic[] actualWrites = pass.ResourceWrites
.Select(id => descriptor.Resources.Single(resource => string.Equals(
resource.Id, id, StringComparison.OrdinalIgnoreCase)).Semantic)
.ToArray();
RenderResourceSemantic[] expectedWrites = output is { } semantic ? [semantic] : [];
return actualWrites.SequenceEqual(expectedWrites)
? RenderPackSdkValidationResult.Valid()
: Invalid(
$"Pass semantic '{passSemantic}' declares a resource output that does "
+ "not match its renderer-owned execution edge.");
}
}
private static RenderPackSdkValidationResult UniqueNonCustomSemantics<T, TSemantic>(
RenderPackDescriptor descriptor,
IEnumerable<T?> values,
Func<T, TSemantic> select,
TSemantic custom,
string kind)
where T : class
where TSemantic : struct, Enum
{
var seen = new HashSet<TSemantic>();
foreach (T? value in values)
{
if (value is null)
continue;
TSemantic semantic = select(value);
if (!Enum.IsDefined(semantic))
return Invalid($"Pack '{descriptor.Id}' declares an unknown {kind} semantic.");
if (!EqualityComparer<TSemantic>.Default.Equals(semantic, custom)
&& !seen.Add(semantic))
{
return Invalid(
$"Pack '{descriptor.Id}' declares duplicate {kind} semantic '{semantic}'.");
}
}
return RenderPackSdkValidationResult.Valid();
}
private static RenderPackSdkValidationResult ValidateUniqueIds(
RenderPackDescriptor descriptor)
{
var ids = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
IEnumerable<(string Kind, string? Id)> declarations =
descriptor.Resources.Select(static value => ("resource", value?.Id))
.Concat(descriptor.Passes.Select(static value => ("pass", value?.Id)))
.Concat(descriptor.SceneReplays.Select(
static value => ("scene replay", value?.Id)))
.Concat(descriptor.PipelineVariants.Select(
static value => ("pipeline variant", value?.Id)))
.Concat(descriptor.QualityPresets.Select(
static value => ("quality preset", value?.Id)))
.Concat(descriptor.Settings.Select(static value => ("setting", value?.Id)));
foreach ((string kind, string? id) in declarations)
{
if (!StableId.IsValid(id))
return Invalid($"Pack '{descriptor.Id}' has an invalid {kind} id.");
if (!ids.Add($"{kind}:{id}"))
return Invalid($"Pack '{descriptor.Id}' declares duplicate {kind} id '{id}'.");
}
return RenderPackSdkValidationResult.Valid();
}
private static RenderPackSdkValidationResult ValidateResources(
RenderPackDescriptor descriptor)
{
long declaredBytes = 0;
foreach (RenderResourceDeclaration? resource in descriptor.Resources)
{
if (resource is null)
return Invalid($"Pack '{descriptor.Id}' contains a null resource declaration.");
if (!Enum.IsDefined(resource.Kind)
|| !Enum.IsDefined(resource.Format)
|| !Enum.IsDefined(resource.Lifetime)
|| !ValidFlags(resource.Usage, RenderResourceUsage.TransferDestination
| RenderResourceUsage.TransferSource
| RenderResourceUsage.Storage
| RenderResourceUsage.DepthAttachment
| RenderResourceUsage.ColorAttachment
| RenderResourceUsage.Sampled))
{
return Invalid($"Resource '{resource.Id}' declares an unknown enum value.");
}
if (resource.Kind == RenderResourceKind.Buffer
|| resource.Format == RenderFormatClass.StructuredData
|| resource.Usage.HasFlag(RenderResourceUsage.Storage))
{
return Invalid(
$"Resource '{resource.Id}' uses a buffer/structured/storage "
+ "declaration reserved and unbindable in render-pack API v1.");
}
if (resource.Kind == RenderResourceKind.Image2DArray
&& resource.Format != RenderFormatClass.DirectionalDepth)
{
return Invalid(
$"Resource '{resource.Id}' uses a color image array; render-pack API "
+ "v1 reserves image arrays for directional depth maps.");
}
if (resource.EstimatedResidentBytes < 0 || resource.SizeBytes < 0)
return Invalid($"Resource '{resource.Id}' declares negative bytes.");
if (resource.Usage == RenderResourceUsage.None)
return Invalid($"Resource '{resource.Id}' declares no usage.");
if (resource.Kind == RenderResourceKind.Buffer && resource.Extent is not null)
return Invalid($"Buffer resource '{resource.Id}' must not declare an image extent.");
if (resource.Kind == RenderResourceKind.Buffer && resource.SizeBytes <= 0)
return Invalid($"Buffer resource '{resource.Id}' must declare positive SizeBytes.");
if (resource.Kind != RenderResourceKind.Buffer)
{
if (resource.Extent is null)
return Invalid($"Image resource '{resource.Id}' has no extent.");
RenderExtentDeclaration extent = resource.Extent;
if (!Enum.IsDefined(extent.Mode)
|| !FinitePositive(extent.Width)
|| !FinitePositive(extent.Height)
|| extent.Layers <= 0
|| extent.Layers > MaximumImageLayers)
{
return Invalid($"Image resource '{resource.Id}' has an invalid extent.");
}
if (extent.Mode == RenderExtentMode.AbsolutePixels
&& (extent.Width > MaximumImageDimension
|| extent.Height > MaximumImageDimension
|| extent.Width != Math.Truncate(extent.Width)
|| extent.Height != Math.Truncate(extent.Height)))
{
return Invalid(
$"Image resource '{resource.Id}' exceeds or fractionalizes "
+ "the absolute image limit.");
}
if (extent.Mode != RenderExtentMode.AbsolutePixels
&& (extent.Width > 1.0 || extent.Height > 1.0))
{
return Invalid(
$"Relative resource '{resource.Id}' must use a scale in (0, 1].");
}
}
if (!TryAdd(ref declaredBytes, resource.EstimatedResidentBytes))
return Invalid($"Pack '{descriptor.Id}' resource byte total overflows.");
}
if (declaredBytes > MaximumPackBytes)
{
return Invalid(
$"Pack '{descriptor.Id}' declares {declaredBytes} resident bytes; "
+ $"the SDK ceiling is {MaximumPackBytes}.");
}
return RenderPackSdkValidationResult.Valid();
}
private static RenderPackSdkValidationResult ValidatePasses(
RenderPackDescriptor descriptor)
{
HashSet<string> resources = descriptor.Resources
.Select(static value => value.Id)
.ToHashSet(StringComparer.OrdinalIgnoreCase);
var written = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
Dictionary<string, RenderResourceDeclaration> resourceDeclarations =
descriptor.Resources.ToDictionary(
static value => value.Id,
StringComparer.OrdinalIgnoreCase);
int priorHook = int.MinValue;
foreach (RenderPassDeclaration? pass in descriptor.Passes)
{
if (pass is null)
return Invalid($"Pack '{descriptor.Id}' contains a null pass declaration.");
if (!Enum.IsDefined(pass.Hook))
return Invalid($"Pass '{pass.Id}' declares an unknown hook.");
if ((int)pass.Hook < priorHook)
return Invalid($"Pass '{pass.Id}' moves backward in renderer hook order.");
priorHook = (int)pass.Hook;
if (!SafeRelativePath.IsValid(pass.VertexShaderAsset)
|| !SafeRelativePath.IsValid(pass.FragmentShaderAsset))
{
return Invalid($"Pass '{pass.Id}' declares an unsafe shader asset key.");
}
if (pass.SemanticInputs is null
|| pass.ResourceReads is null
|| pass.ResourceWrites is null)
{
return Invalid($"Pass '{pass.Id}' contains a null binding list.");
}
if (pass.SemanticInputs.Any(static value => !Enum.IsDefined(value)))
return Invalid($"Pass '{pass.Id}' declares an unknown semantic input.");
if (pass.SemanticInputs.Count
!= pass.SemanticInputs.Distinct().Count())
{
return Invalid($"Pass '{pass.Id}' declares a duplicate semantic input.");
}
if (pass.ResourceReads.Count
!= pass.ResourceReads.Distinct(StringComparer.OrdinalIgnoreCase).Count()
|| pass.ResourceWrites.Count
!= pass.ResourceWrites.Distinct(StringComparer.OrdinalIgnoreCase).Count())
{
return Invalid($"Pass '{pass.Id}' declares a duplicate resource binding.");
}
if (pass.ResourceWrites.Count > 1)
{
return Invalid(
$"Pass '{pass.Id}' writes {pass.ResourceWrites.Count} resources; "
+ "render-pack API v1 supports one attachment per declared pass.");
}
if (pass.ResourceWrites.Count == 0
&& pass.Hook is not RenderPassHook.ToneMap
and not RenderPassHook.AfterToneMapBeforePrivateViewports)
{
return Invalid(
$"Pass '{pass.Id}' has no declared output at hook '{pass.Hook}'.");
}
foreach (string read in pass.ResourceReads)
{
if (!resources.Contains(read))
return Invalid($"Pass '{pass.Id}' reads unknown resource '{read}'.");
if (!written.Contains(read))
return Invalid($"Pass '{pass.Id}' reads resource '{read}' before it is written.");
if (!resourceDeclarations[read].Usage.HasFlag(RenderResourceUsage.Sampled))
return Invalid($"Pass '{pass.Id}' samples non-sampled resource '{read}'.");
}
foreach (string write in pass.ResourceWrites)
{
if (!resources.Contains(write))
return Invalid($"Pass '{pass.Id}' writes unknown resource '{write}'.");
if (pass.ResourceReads.Contains(write, StringComparer.OrdinalIgnoreCase))
return Invalid($"Pass '{pass.Id}' reads and writes resource '{write}'.");
RenderResourceDeclaration resource = resourceDeclarations[write];
RenderResourceUsage attachment =
resource.Format == RenderFormatClass.DirectionalDepth
? RenderResourceUsage.DepthAttachment
: RenderResourceUsage.ColorAttachment;
if (!resource.Usage.HasFlag(attachment))
return Invalid($"Pass '{pass.Id}' writes non-attachment resource '{write}'.");
written.Add(write);
}
int sampledInputs = pass.SemanticInputs.Count(static value =>
value is RenderSemanticInput.WorldColor
or RenderSemanticInput.SceneDepth
or RenderSemanticInput.SceneNormals);
bool shadowSemantic = pass.SemanticInputs.Contains(
RenderSemanticInput.DirectionalShadowMaps);
sampledInputs += pass.ResourceReads.Count(read =>
{
RenderResourceDeclaration resource = resourceDeclarations[read];
return resource.Usage.HasFlag(RenderResourceUsage.Sampled)
&& !(shadowSemantic
&& resource.Format == RenderFormatClass.DirectionalDepth);
});
if (sampledInputs > RenderPackShaderAbi.SampledPassInputCapacity)
{
return Invalid(
$"Pass '{pass.Id}' requires {sampledInputs} sampled inputs; "
+ "render-pack API v1 exposes at most four (TextureIndexA-D)."
+ " DirectionalShadowMaps uses binding 6 and does not count.");
}
}
return RenderPackSdkValidationResult.Valid();
}
private static RenderPackSdkValidationResult ValidateSemanticCapabilities(
RenderPackDescriptor descriptor)
{
foreach (RenderPassDeclaration shadowPass in descriptor.Passes.Where(static pass =>
pass.Semantic == RenderPassSemantic.DirectionalShadowDepth))
{
if (!shadowPass.SemanticInputs.Contains(
RenderSemanticInput.SelectedCelestialDirectionalLight)
|| shadowPass.SemanticInputs.Contains(RenderSemanticInput.SunDirection))
{
return Invalid(
$"Directional-shadow pass '{shadowPass.Id}' must declare "
+ $"'{RenderSemanticInput.SelectedCelestialDirectionalLight}' and must not "
+ "alias the sun-specific atmospheric direction.");
}
}
IEnumerable<RenderSemanticInput> inputs = descriptor.Passes
.SelectMany(static pass => pass?.SemanticInputs ?? [])
.Concat(descriptor.PipelineVariants.SelectMany(
static variant => variant?.SemanticInputs ?? []));
foreach (RenderSemanticInput input in inputs.Distinct())
{
RenderCapability? required = input switch
{
RenderSemanticInput.WorldColor =>
RenderCapability.MainWorldColorIntermediate,
RenderSemanticInput.SceneDepth =>
RenderCapability.SceneDepthSampling,
RenderSemanticInput.SceneNormals =>
RenderCapability.SceneNormalSampling,
RenderSemanticInput.SunDirection =>
RenderCapability.AuthoredSunDirection,
RenderSemanticInput.SelectedCelestialDirectionalLight =>
RenderCapability.AuthoredCelestialDirectionalLight,
RenderSemanticInput.SunScreenPosition =>
RenderCapability.AuthoredSunScreenPosition,
RenderSemanticInput.ActiveDayGroup or RenderSemanticInput.Weather =>
RenderCapability.AuthoredWeather,
RenderSemanticInput.CameraMatrices or RenderSemanticInput.FrameTime =>
RenderCapability.FullscreenPasses,
RenderSemanticInput.ShadowCasterTransforms =>
RenderCapability.AnimatedCasterTransforms,
RenderSemanticInput.DirectionalShadowMaps =>
RenderCapability.DirectionalShadowMaps,
_ => null,
};
if (required is { } capability
&& !descriptor.RequiredCapabilities.Contains(capability))
{
return Invalid(
$"Pack '{descriptor.Id}' declares semantic '{input}' but does not "
+ $"require capability '{capability}'.");
}
}
return RenderPackSdkValidationResult.Valid();
}
private static RenderPackSdkValidationResult ValidateReplays(
RenderPackDescriptor descriptor)
{
const RenderCasterClass known = RenderCasterClass.Terrain
| RenderCasterClass.OpaqueWorld
| RenderCasterClass.AlphaCutoutWorld
| RenderCasterClass.AnimatedOpaque
| RenderCasterClass.AnimatedAlphaCutout;
foreach (SceneReplayDeclaration? replay in descriptor.SceneReplays)
{
if (replay is null)
return Invalid($"Pack '{descriptor.Id}' contains a null scene replay.");
if (!Enum.IsDefined(replay.Semantic)
|| !ValidFlags(replay.CasterClasses, known))
{
return Invalid($"Scene replay '{replay.Id}' declares an unknown enum value.");
}
if (replay.ViewCount is <= 0 or > 4)
return Invalid($"Scene replay '{replay.Id}' must request 1..4 views.");
if (replay.CasterClasses == RenderCasterClass.None)
return Invalid($"Scene replay '{replay.Id}' declares no caster classes.");
}
return RenderPackSdkValidationResult.Valid();
}
private static RenderPackSdkValidationResult ValidateVariants(
RenderPackDescriptor descriptor)
{
const RenderMaterialClass known = RenderMaterialClass.Opaque
| RenderMaterialClass.AlphaCutout
| RenderMaterialClass.AnimatedOpaque
| RenderMaterialClass.AnimatedAlphaCutout;
foreach (PipelineVariantDeclaration? variant in descriptor.PipelineVariants)
{
if (variant is null)
return Invalid($"Pack '{descriptor.Id}' contains a null pipeline variant.");
if (!Enum.IsDefined(variant.BaseSemantic)
|| !ValidFlags(variant.CompatibleMaterials, known))
{
return Invalid($"Pipeline variant '{variant.Id}' declares an unknown enum value.");
}
if (!SafeRelativePath.IsValid(variant.VertexShaderAsset)
|| !SafeRelativePath.IsValid(variant.FragmentShaderAsset))
{
return Invalid(
$"Pipeline variant '{variant.Id}' declares an unsafe shader asset key.");
}
if (variant.CompatibleMaterials == RenderMaterialClass.None)
{
return Invalid(
$"Pipeline variant '{variant.Id}' declares no compatible materials.");
}
if (variant.SemanticInputs is null)
return Invalid($"Pipeline variant '{variant.Id}' has a null semantic-input list.");
if (variant.SemanticInputs.Any(static value => !Enum.IsDefined(value)))
{
return Invalid(
$"Pipeline variant '{variant.Id}' declares an unknown semantic input.");
}
}
return RenderPackSdkValidationResult.Valid();
}
private static RenderPackSdkValidationResult ValidateSettings(
RenderPackDescriptor descriptor)
{
if (descriptor.Settings.Count > RenderPackShaderAbi.PackSettingScalarCapacity)
{
return Invalid(
$"Pack '{descriptor.Id}' declares {descriptor.Settings.Count} settings; "
+ "render-pack API v1 exposes at most "
+ $"{RenderPackShaderAbi.PackSettingScalarCapacity} shader setting slots.");
}
foreach (RenderSettingDeclaration? setting in descriptor.Settings)
{
if (setting is null)
return Invalid($"Pack '{descriptor.Id}' contains a null setting.");
if (!Enum.IsDefined(setting.Kind))
return Invalid($"Setting '{setting.Id}' declares an unknown kind.");
if (string.IsNullOrWhiteSpace(setting.DisplayName))
return Invalid($"Setting '{setting.Id}' has no display name.");
if (setting.DefaultValue is null || setting.Choices is null)
return Invalid($"Setting '{setting.Id}' contains a null value list.");
if (setting.Minimum is { } min && !double.IsFinite(min)
|| setting.Maximum is { } max && !double.IsFinite(max)
|| setting.Step is { } step && (!double.IsFinite(step) || step <= 0))
{
return Invalid($"Setting '{setting.Id}' has invalid bounds.");
}
if (setting.Minimum is { } minimum
&& setting.Maximum is { } maximum
&& minimum > maximum)
{
return Invalid($"Setting '{setting.Id}' has an inverted range.");
}
if (setting.Kind == RenderSettingKind.Choice
&& (setting.Choices.Count == 0
|| !setting.Choices.Contains(setting.DefaultValue, StringComparer.Ordinal)))
{
return Invalid(
$"Choice setting '{setting.Id}' has no choices or an unknown default.");
}
if (!ValidSettingValue(setting, setting.DefaultValue))
return Invalid($"Setting '{setting.Id}' has an invalid default value.");
}
return RenderPackSdkValidationResult.Valid();
}
private static RenderPackSdkValidationResult ValidatePresets(
RenderPackDescriptor descriptor)
{
if (descriptor.QualityPresets.Count == 0)
return Invalid($"Pack '{descriptor.Id}' declares no quality presets.");
HashSet<string> resources = descriptor.Resources
.Select(static value => value.Id)
.ToHashSet(StringComparer.OrdinalIgnoreCase);
HashSet<string> settings = descriptor.Settings
.Select(static value => value.Id)
.ToHashSet(StringComparer.OrdinalIgnoreCase);
Dictionary<string, RenderSettingDeclaration> settingDeclarations =
descriptor.Settings.ToDictionary(
static value => value.Id,
StringComparer.OrdinalIgnoreCase);
foreach (RenderQualityPreset? preset in descriptor.QualityPresets)
{
if (preset is null)
return Invalid($"Pack '{descriptor.Id}' contains a null quality preset.");
if (string.IsNullOrWhiteSpace(preset.DisplayName))
return Invalid($"Quality preset '{preset.Id}' has no display name.");
if (preset.RequiredCapabilities is null
|| preset.ResourceOverrides is null
|| preset.SettingOverrides is null)
{
return Invalid($"Quality preset '{preset.Id}' contains a null declaration list.");
}
if (preset.RequiredCapabilities.Any(static value => !Enum.IsDefined(value)))
return Invalid($"Quality preset '{preset.Id}' declares an unknown capability.");
const RenderQualityExecutionHints supportedHints =
RenderQualityExecutionHints.FusedAtmosphericPostProcess
| RenderQualityExecutionHints.MultiviewDirectionalShadowCascades;
if ((preset.ExecutionHints & ~supportedHints) != 0)
return Invalid($"Quality preset '{preset.Id}' declares an unknown execution hint.");
if ((preset.ExecutionHints & RenderQualityExecutionHints.MultiviewDirectionalShadowCascades) != 0
&& (preset.Semantic != RenderQualitySemantic.Low
|| !preset.RequiredCapabilities.Contains(
RenderCapability.MultiviewDirectionalShadowCascades)))
{
return Invalid(
$"Quality preset '{preset.Id}' must be Low and require multiview directional-shadow capability.");
}
if (preset.MaxResidentGpuBytes is < 0 or > MaximumPackBytes)
return Invalid($"Quality preset '{preset.Id}' exceeds the pack memory ceiling.");
if (!FiniteNonNegative(preset.MaxIncrementalGpuMillisecondsP50)
|| !FiniteNonNegative(preset.MaxIncrementalGpuMillisecondsP99)
|| !FiniteNonNegative(preset.MaxIncrementalCpuMillisecondsP50)
|| !FiniteNonNegative(preset.MaxIncrementalCpuMillisecondsP99)
|| preset.MaxIncrementalGpuMillisecondsP50
> preset.MaxIncrementalGpuMillisecondsP99
|| preset.MaxIncrementalCpuMillisecondsP50
> preset.MaxIncrementalCpuMillisecondsP99)
{
return Invalid($"Quality preset '{preset.Id}' has invalid performance budgets.");
}
foreach (RenderQualityResourceOverride? value in preset.ResourceOverrides)
{
if (value is null)
return Invalid($"Quality preset '{preset.Id}' contains a null resource override.");
if (!resources.Contains(value.ResourceId))
{
return Invalid(
$"Quality preset '{preset.Id}' overrides unknown resource "
+ $"'{value.ResourceId}'.");
}
if (value.SizeBytes < 0 || value.EstimatedResidentBytes < 0)
return Invalid($"Quality preset '{preset.Id}' declares negative resource bytes.");
if (value.Extent is { } extent && !ValidExtent(extent))
{
return Invalid(
$"Quality preset '{preset.Id}' declares an invalid "
+ $"extent for resource '{value.ResourceId}'.");
}
}
var overriddenSettings = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (RenderQualitySettingOverride? value in preset.SettingOverrides)
{
if (value is null)
return Invalid($"Quality preset '{preset.Id}' contains a null setting override.");
if (!settings.Contains(value.SettingId))
{
return Invalid(
$"Quality preset '{preset.Id}' overrides unknown setting "
+ $"'{value.SettingId}'.");
}
if (!overriddenSettings.Add(value.SettingId))
{
return Invalid(
$"Quality preset '{preset.Id}' overrides setting "
+ $"'{value.SettingId}' more than once.");
}
if (!ValidSettingValue(
settingDeclarations[value.SettingId],
value.Value))
{
return Invalid(
$"Quality preset '{preset.Id}' supplies an invalid value "
+ $"for setting '{value.SettingId}'.");
}
}
}
return RenderPackSdkValidationResult.Valid();
}
private static RenderPackSdkValidationResult ValidateAtmosphere(
RenderPackDescriptor descriptor)
{
AtmospherePolicyDeclaration? policy = descriptor.AtmospherePolicy;
if (policy is null)
return RenderPackSdkValidationResult.Valid();
if (policy.SunElevationResponse is null
|| policy.ActiveDayGroupMultipliers is null
|| policy.DirectionalShadowLightElevationResponse is null
|| policy.VolumetricShaftSunElevationResponse is null)
{
return Invalid($"Pack '{descriptor.Id}' has a null atmosphere-policy list.");
}
RenderPackSdkValidationResult curve = ValidateCurve(
policy.SunElevationResponse,
"sun-elevation");
if (!curve.Success) return curve;
curve = ValidateCurve(
policy.DirectionalShadowLightElevationResponse,
"directional-shadow light-elevation",
unitInterval: true);
if (!curve.Success) return curve;
curve = ValidateDirectionalShadowHorizon(
policy.DirectionalShadowLightElevationResponse);
if (!curve.Success) return curve;
curve = ValidateCurve(
policy.VolumetricShaftSunElevationResponse,
"volumetric-shaft sun-elevation",
unitInterval: true);
if (!curve.Success) return curve;
var groups = new HashSet<int>();
foreach (ActiveDayGroupMultiplier? value in policy.ActiveDayGroupMultipliers)
{
if (value is null
|| !groups.Add(value.ActiveDayGroup)
|| !FiniteNonNegative(value.Multiplier))
{
return Invalid(
$"Pack '{descriptor.Id}' has an invalid active-day-group mapping.");
}
}
return RenderPackSdkValidationResult.Valid();
RenderPackSdkValidationResult ValidateCurve(
IReadOnlyList<SunElevationResponsePoint> points,
string name,
bool unitInterval = false)
{
double priorElevation = double.NegativeInfinity;
foreach (SunElevationResponsePoint? point in points)
{
if (point is null
|| !double.IsFinite(point.ElevationDegrees)
|| point.ElevationDegrees is < -90 or > 90
|| !FiniteNonNegative(point.Multiplier)
|| (unitInterval && point.Multiplier > 1)
|| point.ElevationDegrees <= priorElevation)
{
return Invalid(
$"Pack '{descriptor.Id}' has an invalid {name} response curve.");
}
priorElevation = point.ElevationDegrees;
}
return RenderPackSdkValidationResult.Valid();
}
RenderPackSdkValidationResult ValidateDirectionalShadowHorizon(
IReadOnlyList<SunElevationResponsePoint> points)
{
bool hasExactHorizonPoint = false;
SunElevationResponsePoint? firstAboveHorizon = null;
foreach (SunElevationResponsePoint point in points)
{
if (point.ElevationDegrees <= 0d)
{
if (point.Multiplier != 0d)
return InvalidHorizon();
hasExactHorizonPoint |= point.ElevationDegrees == 0d;
continue;
}
firstAboveHorizon = point;
break;
}
return !hasExactHorizonPoint
&& firstAboveHorizon is { Multiplier: not 0d }
? InvalidHorizon()
: RenderPackSdkValidationResult.Valid();
RenderPackSdkValidationResult InvalidHorizon() => Invalid(
$"Pack '{descriptor.Id}' directional-shadow light-elevation "
+ "curve must resolve to zero at and below the 0-degree "
+ "authored horizon.");
}
}
private static string? FirstNullList(RenderPackDescriptor value)
{
if (value.RequiredCapabilities is null) return "required-capability";
if (value.OptionalCapabilities is null) return "optional-capability";
if (value.Resources is null) return "resource";
if (value.Passes is null) return "pass";
if (value.SceneReplays is null) return "scene-replay";
if (value.PipelineVariants is null) return "pipeline-variant";
if (value.QualityPresets is null) return "quality-preset";
if (value.Settings is null) return "setting";
return null;
}
private static bool FinitePositive(double value) =>
double.IsFinite(value) && value > 0;
private static bool FiniteNonNegative(double value) =>
double.IsFinite(value) && value >= 0;
private static bool UsesMultiview(RenderPackDescriptor descriptor) =>
descriptor.QualityPresets.Any(preset =>
(preset.ExecutionHints
& RenderQualityExecutionHints.MultiviewDirectionalShadowCascades) != 0);
private static bool ValidSettingValue(
RenderSettingDeclaration setting,
string? value) =>
RenderPackSettingValueCodec.TryEncode(setting, value, out _);
private static bool ValidExtent(RenderExtentDeclaration extent)
{
if (!Enum.IsDefined(extent.Mode)
|| !FinitePositive(extent.Width)
|| !FinitePositive(extent.Height)
|| extent.Layers is <= 0 or > MaximumImageLayers)
{
return false;
}
return extent.Mode == RenderExtentMode.AbsolutePixels
? extent.Width <= MaximumImageDimension
&& extent.Height <= MaximumImageDimension
&& extent.Width == Math.Truncate(extent.Width)
&& extent.Height == Math.Truncate(extent.Height)
: extent.Width <= 1.0 && extent.Height <= 1.0;
}
private static bool ValidFlags<T>(T value, T known)
where T : struct, Enum
{
ulong actual = unchecked((ulong)Convert.ToInt64(value));
ulong permitted = unchecked((ulong)Convert.ToInt64(known));
return (actual & ~permitted) == 0;
}
private static bool TryAdd(ref long total, long value)
{
if (value > long.MaxValue - total)
return false;
total += value;
return true;
}
private static RenderPackSdkValidationResult Invalid(string reason) =>
RenderPackSdkValidationResult.Invalid(reason);
}