using UnityEngine; using UnityEngine.Experimental.Rendering; using UnityEngine.Rendering; using UnityEngine.Rendering.RenderGraphModule; #if ENABLE_UPSCALER_FRAMEWORK && ENABLE_NVIDIA && ENABLE_NVIDIA_MODULE using UnityEngine.NVIDIA; #endif using System; #if UNITY_EDITOR using UnityEditor; #endif #if ENABLE_UPSCALER_FRAMEWORK && ENABLE_NVIDIA && ENABLE_NVIDIA_MODULE #if UNITY_EDITOR [InitializeOnLoad] #endif static class RegisterDLSS { static RegisterDLSS() => UpscalerRegistry.Register(DLSSIUpscaler.upscalerName); [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] static void InitRuntime() => UpscalerRegistry.Register(DLSSIUpscaler.upscalerName); } /// /// Per-camera context for DLSS upscaling. Wraps the native NVIDIA DLSSContext /// and tracks the settings it was created with for validation. /// Native context creation is deferred until first use (requires CommandBuffer). /// public class DLSSUpscalerContext : PluginUpscalerContext { public DLSSQuality createdWithQuality { get; } public DLSSPreset createdWithPresetQuality { get; } public DLSSPreset createdWithPresetBalanced { get; } public DLSSPreset createdWithPresetPerformance { get; } public DLSSPreset createdWithPresetUltraPerformance { get; } public DLSSPreset createdWithPresetDLAA { get; } /// /// Creates a new DLSS context wrapper. Native context creation is deferred. /// Stores context-creation settings (quality, presets) for init and validation. /// Per-frame settings should be read from io.options in RecordRenderGraph. /// public DLSSUpscalerContext(DLSSOptions options, Vector2Int displayResolution) : base(displayResolution) { createdWithQuality = options.dlssQualityMode; createdWithPresetQuality = options.dlssRenderPresetQuality; createdWithPresetBalanced = options.dlssRenderPresetBalanced; createdWithPresetPerformance = options.dlssRenderPresetPerformance; createdWithPresetUltraPerformance = options.dlssRenderPresetUltraPerformance; createdWithPresetDLAA = options.dlssRenderPresetDLAA; } /// /// Gets the native DLSS context, creating it if necessary. /// /// Command buffer to record creation commands. /// Initialization settings for the native context. /// The native DLSS context. public DLSSContext GetOrCreateNativeContext(CommandBuffer cmd, DLSSCommandInitializationData settings) { m_NativeContext ??= GraphicsDevice.device.CreateFeature(cmd, settings); return m_NativeContext; } /// protected override void DestroyNativeContext(CommandBuffer cmd, DLSSContext context) => GraphicsDevice.device.DestroyFeature(cmd, context); /// protected override bool ValidateOptions(DLSSOptions options) { // Quality mode and preset changes require context recreation return options.dlssQualityMode == createdWithQuality && options.dlssRenderPresetQuality == createdWithPresetQuality && options.dlssRenderPresetBalanced == createdWithPresetBalanced && options.dlssRenderPresetPerformance == createdWithPresetPerformance && options.dlssRenderPresetUltraPerformance == createdWithPresetUltraPerformance && options.dlssRenderPresetDLAA == createdWithPresetDLAA; } } public class DLSSIUpscaler : AbstractUpscaler { public static readonly string upscalerName = "Deep Learning Super Sampling 4"; #region DLSS_UTILITIES static bool CheckDLSSFeatureAvailable() { // check plugin availability if (!UnityEngine.NVIDIA.NVUnityPlugin.IsLoaded()) { Debug.LogWarning("NVUnityPlugin not loaded."); return false; } // check GPU vendor if (!SystemInfo.graphicsDeviceVendor.ToLowerInvariant().Contains("nvidia")) { Debug.LogWarning("DLSS not available on non-NVIDIA graphics cards."); return false; } // check device UnityEngine.NVIDIA.GraphicsDevice device = UnityEngine.NVIDIA.GraphicsDevice.CreateGraphicsDevice(); if (device == null) { Debug.LogWarning("NVUnityPlugin failed to create device."); return false; } // check DLSS feature if(!device.IsFeatureAvailable(UnityEngine.NVIDIA.GraphicsDeviceFeature.DLSS)) { Debug.LogWarning("DLSS not available on the current NVIDIA graphics card."); return false; } return true; } #endregion // DLSS_UTILITIES #region RENDERGRAPH_INTERFACE_DATA class DLSSGraphData { public DLSSUpscalerContext upscalerContext; public bool needsInitSettings; // True when native context doesn't exist yet public DLSSCommandInitializationData initSettings; public DLSSCommandExecutionData execData; public TextureHandle colorInput; public TextureHandle depth; public TextureHandle motionVectors; public TextureHandle colorOutput; }; #endregion #region IUPSCALER_INTERFACE public DLSSIUpscaler() { m_DLSSReady = CheckDLSSFeatureAvailable(); } public override string name => upscalerName; public override bool isTemporal => true; public override bool supportsSharpening => false; public override bool hasQualityMode => true; public override IUpscalerContext CreateContext(UpscalerOptions options, Vector2Int displayResolution) { if (!m_DLSSReady || options is not DLSSOptions dlssOptions) return null; return new DLSSUpscalerContext(dlssOptions, displayResolution); } public override void CalculateJitter(int frameIndex, float upscaleRatio, out Vector2 jitter, out bool allowScaling) { int numPhases = CalculateJitterPhaseCount(upscaleRatio); int haltonIndex = (frameIndex % numPhases) + 1; float x = HaltonSequence.Get(haltonIndex, 2) - 0.5f; float y = HaltonSequence.Get(haltonIndex, 3) - 0.5f; jitter = new Vector2(x, y); allowScaling = false; } public override UpscalerResolutionInfo GetResolutionInfo(Vector2Int displayResolution, UpscalerOptions options) { var dlssOptions = options as DLSSOptions; if (!m_DLSSReady || dlssOptions == null) return UpscalerResolutionInfo.Fixed(displayResolution); // Query the DLSS SDK for the optimal render resolution and the supported [min,max] range for this quality mode. if (GraphicsDevice.device.GetOptimalSettings( (uint)displayResolution.x, (uint)displayResolution.y, dlssOptions.dlssQualityMode, out OptimalDLSSSettingsData optimalSettings)) { var optimal = new Vector2Int((int)optimalSettings.outRenderWidth, (int)optimalSettings.outRenderHeight); var min = new Vector2Int((int)optimalSettings.minWidth, (int)optimalSettings.minHeight); var max = new Vector2Int((int)optimalSettings.maxWidth, (int)optimalSettings.maxHeight); // The render resolution must always stay within [min,max] or DLSS Evaluate fails, so even CustomScaling // reports the range as a clamp (the pipeline drives within it). When the quality mode has no range // (min == max — e.g. UltraPerformance, or an older DLSS library) only the optimal is valid, so pin to it. switch (dlssOptions.resolutionMode) { case UpscalerResolutionMode.CustomScaling: return (min != max) ? UpscalerResolutionInfo.Range(min, max) : UpscalerResolutionInfo.Fixed(optimal); default: // QualityMode return UpscalerResolutionInfo.Fixed(optimal); } } // Fallback if GetOptimalSettings fails return UpscalerResolutionInfo.Fixed(displayResolution); } public override float CalculateMipBias(Vector2Int preUpscaleResolution, Vector2Int postUpscaleResolution) { // NVUnityPlugin should provide this value. float xBias = Mathf.Log((float)preUpscaleResolution.x / postUpscaleResolution.x, 2f); float yBias = Mathf.Log((float)preUpscaleResolution.y / postUpscaleResolution.y, 2f); return Mathf.Min(xBias, yBias) - 1.0f; } static int CalculateJitterPhaseCount(float upscaleRatio) { const float k_BasePhaseCount = 8.0f; // Round half up, don't truncate: upscaleRatio is reconstructed as display/round(renderSize), so a clean preset // (e.g. 1.5x) arrives slightly low (~1.497) and 8*1.497^2 = 17.93 would truncate to 17 instead of 18. return Mathf.FloorToInt(k_BasePhaseCount * upscaleRatio * upscaleRatio + 0.5f); } public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData) { if(!m_DLSSReady) return; Debug.Assert(GraphicsDevice.device != null); UpscalingIO io = frameData.Get(); var upscalerContext = io.context as DLSSUpscalerContext; if (upscalerContext == null) { Debug.LogWarning("DLSSIUpscaler: No valid context provided via io.context. Skipping upscaling."); return; } // describe output texture TextureHandle outputColor; { TextureDesc inputDesc = io.cameraColor.GetDescriptor(renderGraph); TextureDesc outputDesc = inputDesc; outputDesc.width = io.postUpscaleResolution.x; outputDesc.height = io.postUpscaleResolution.y; outputDesc.format = inputDesc.format; outputDesc.msaaSamples = MSAASamples.None; outputDesc.useMipMap = false; outputDesc.autoGenerateMips = false; outputDesc.useDynamicScale = false; outputDesc.anisoLevel = 0; outputDesc.discardBuffer = false; outputDesc.enableRandomWrite = true; // compute shader resource outputDesc.name = "_DLSSOutputTarget"; outputDesc.clearBuffer = false; outputDesc.filterMode = FilterMode.Bilinear; outputColor = renderGraph.CreateTexture(outputDesc); } using (var builder = renderGraph.AddUnsafePass("Deep Learning Super Sampling", out DLSSGraphData passData, new ProfilingSampler("DLSS"))) { float motionVectorSign = io.motionVectorDirection == UpscalingIO.MotionVectorDirection.PreviousFrameToCurrentFrame ? -1.0f : 1.0f; float motionVectorScaleX = io.motionVectorDomain == UpscalingIO.MotionVectorDomain.NDC ? io.motionVectorTextureSize.x : 1.0f; float motionVectorScaleY = io.motionVectorDomain == UpscalingIO.MotionVectorDomain.NDC ? io.motionVectorTextureSize.y : 1.0f; // Setup pass data passData.upscalerContext = upscalerContext; passData.needsInitSettings = !upscalerContext.hasNativeContext; // Only build initialization settings when native context needs to be created if (passData.needsInitSettings) { bool mvLowResolution = io.motionVectorTextureSize.x <= io.preUpscaleResolution.x || io.motionVectorTextureSize.y <= io.preUpscaleResolution.y; passData.initSettings = new DLSSCommandInitializationData(); passData.initSettings.SetFlag(DLSSFeatureFlags.IsHDR, io.hdrInput); passData.initSettings.SetFlag(DLSSFeatureFlags.MVLowRes, mvLowResolution); passData.initSettings.SetFlag(DLSSFeatureFlags.DepthInverted, io.invertedDepth); passData.initSettings.SetFlag(DLSSFeatureFlags.MVJittered, io.jitteredMotionVectors); // The DLSS SDK requires the feature to be created at the quality mode's optimal render size (not the max, // display, or current size) regardless of resolution mode. The per-frame subrect (execData below) is the // actual render size; it may vary but must stay within the quality mode's [min,max] or Evaluate fails. // The pipeline allocates the input color buffer at the max so the subrect always fits. Vector2Int optimalRenderSize = io.preUpscaleResolution; // fallback if the SDK query fails if (GraphicsDevice.device.GetOptimalSettings( (uint)io.postUpscaleResolution.x, (uint)io.postUpscaleResolution.y, upscalerContext.createdWithQuality, out OptimalDLSSSettingsData optimalSettings)) { optimalRenderSize = new Vector2Int((int)optimalSettings.outRenderWidth, (int)optimalSettings.outRenderHeight); } passData.initSettings.inputRTWidth = (uint)optimalRenderSize.x; passData.initSettings.inputRTHeight = (uint)optimalRenderSize.y; passData.initSettings.outputRTWidth = (uint)io.postUpscaleResolution.x; passData.initSettings.outputRTHeight = (uint)io.postUpscaleResolution.y; passData.initSettings.quality = upscalerContext.createdWithQuality; passData.initSettings.presetQualityMode = upscalerContext.createdWithPresetQuality; passData.initSettings.presetBalancedMode = upscalerContext.createdWithPresetBalanced; passData.initSettings.presetPerformanceMode = upscalerContext.createdWithPresetPerformance; passData.initSettings.presetUltraPerformanceMode = upscalerContext.createdWithPresetUltraPerformance; passData.initSettings.presetDlaaMode = upscalerContext.createdWithPresetDLAA; } // Per-frame execution data passData.execData.mvScaleX = motionVectorSign * motionVectorScaleX; passData.execData.mvScaleY = motionVectorSign * motionVectorScaleY; passData.execData.subrectOffsetX = 0; passData.execData.subrectOffsetY = 0; passData.execData.subrectWidth = (uint)io.preUpscaleResolution.x; passData.execData.subrectHeight = (uint)io.preUpscaleResolution.y; passData.execData.jitterOffsetX = io.subpixelJitter.x; passData.execData.jitterOffsetY = io.subpixelJitter.y; passData.execData.preExposure = Mathf.Clamp(io.preExposureValue, 0.20f, 2.0f); // clamp to a reasonable value to prevent ghosting passData.execData.invertYAxis = io.flippedY ? 1u : 0u; passData.execData.invertXAxis = io.flippedX ? 1u : 0u; passData.execData.reset = io.resetHistory ? 1 : 0; // Texture handles builder.UseTexture(io.cameraColor); builder.UseTexture(io.cameraDepth); builder.UseTexture(io.motionVectorColor); builder.UseTexture(outputColor, AccessFlags.Write); passData.colorInput = io.cameraColor; passData.depth = io.cameraDepth; passData.motionVectors = io.motionVectorColor; passData.colorOutput = outputColor; // set render function builder.SetRenderFunc((DLSSGraphData data, UnsafeGraphContext ctx) => { CommandBuffer cmd = CommandBufferHelpers.GetNativeCommandBuffer(ctx.cmd); // Verify init settings were populated if native context needs to be created Debug.Assert(!data.needsInitSettings || data.initSettings.outputRTWidth > 0, "DLSS init settings must be populated when native context doesn't exist"); // Get the native context, creating it if necessary (uses pre-built init settings) DLSSContext nativeContext = data.upscalerContext.GetOrCreateNativeContext(cmd, data.initSettings); Debug.Assert(nativeContext != null); nativeContext.executeData = data.execData; DLSSTextureTable textureTable = new() { colorInput = data.colorInput, depth = data.depth, motionVectors = data.motionVectors, colorOutput = data.colorOutput, }; GraphicsDevice.device.ExecuteDLSS(cmd, nativeContext, textureTable); }); } io.cameraColor = outputColor; } #endregion #region DATA private bool m_DLSSReady = false; #endregion } #endif // ENABLE_UPSCALER_FRAMEWORK && ENABLE_NVIDIA && ENABLE_NVIDIA_MODULE