using System; using UnityEngine.Experimental.Rendering; #if UNITY_PS5 using WW1.PlayStation; #endif namespace UnityEngine.Rendering.PostProcessing { internal class PSSRUpscaler : Upscaler { #if UNITY_PS5 public static bool IsSupported => UnityEngine.PS5.Utility.isTrinityMode && PluginInitialized; private static readonly bool PluginInitialized; private static uint _currentContext; private readonly PSSRPlugin.NativeData _dispatchParams = new(); private readonly PSSRPlugin.NativeData _destroyParams = new(); private RenderTexture _inputColor; private readonly RenderTexture[] _inputDepth = new RenderTexture[2]; private readonly RenderTexture[] _inputMotionVectors = new RenderTexture[2]; private Texture2D _outputColor; private bool _contextInitialized; private uint _frameCount; static PSSRUpscaler() { try { if (PSSRPlugin.Init() < 0) { Debug.LogError("Failed to initialize PSSR plugin!"); PluginInitialized = false; return; } } catch (DllNotFoundException) { Debug.LogError("PSSR plugin not found!"); PluginInitialized = false; return; } PluginInitialized = true; _currentContext = 0; } public override void CreateContext(PostProcessRenderContext context, Upscaling config) { if (!PluginInitialized) { Debug.LogWarning("PSSR plugin is not initialized!"); return; } PSSRPlugin.InitParams initParams; initParams.contextIndex = _currentContext; initParams.displayWidth = (uint)config.UpscaleSize.x; initParams.displayHeight = (uint)config.UpscaleSize.y; initParams.maxRenderWidth = (uint)config.MaxRenderSize.x; initParams.maxRenderHeight = (uint)config.MaxRenderSize.y; initParams.autoKeepCopies = 0u; // We use double buffered depth and motion vector copies created by Unity CreateRenderTexture(ref _inputColor, "PSSR Input Color", config.MaxRenderSize, context.sourceFormat, true); CreateRenderTextureArray(_inputDepth, "PSSR Input Depth", config.MaxRenderSize, GraphicsFormat.R32_SFloat, true); CreateRenderTextureArray(_inputMotionVectors, "PSSR Input Motion Vectors", config.MaxRenderSize, GraphicsFormat.R16G16_SFloat, true); if (PSSRPlugin.CreateContext(ref initParams, out IntPtr outputColorTexturePtr) >= 0 && outputColorTexturePtr != IntPtr.Zero) { // PSSR requires an output color texture in a very particular format (k11_11_10Float with kStandard256B tile mode and a specific alignment) that Unity cannot create directly. // So instead we let the plugin create that texture and then import it into Unity as a generic 32bpp texture from a native pointer. _outputColor = Texture2D.CreateExternalTexture(config.UpscaleSize.x, config.UpscaleSize.y, TextureFormat.RGBA32, false, true, outputColorTexturePtr); _dispatchParams.Initialize(); _destroyParams.Initialize(); _contextInitialized = true; } } public override void DestroyContext() { base.DestroyContext(); if (_contextInitialized) { // Rotate between contexts to reduce the risk of race conditions between old and new contexts uint previousContext = _currentContext; _currentContext = (_currentContext + 1) % PSSRPlugin.MaxNumContexts; CommandBuffer cmd = new(); PSSRPlugin.IssuePluginEvent(cmd, PSSRPlugin.Event.Destroy, new IntPtr(previousContext)); Graphics.ExecuteCommandBuffer(cmd); cmd.Release(); _contextInitialized = false; } if (_outputColor != null) { Object.Destroy(_outputColor); _outputColor = null; } DestroyRenderTextureArray(_inputMotionVectors); DestroyRenderTextureArray(_inputDepth); DestroyRenderTexture(ref _inputColor); _destroyParams.Destroy(); _dispatchParams.Destroy(); } public override void Render(PostProcessRenderContext context, Upscaling config) { var cmd = context.command; if (!PluginInitialized || !_contextInitialized) { cmd.BlitFullscreenTriangle(context.source, context.destination); return; } cmd.BeginSample("PSSR"); if (config.Reset || _frameCount == 0) { cmd.SetRenderTarget(_inputDepth[0]); cmd.ClearRenderTarget(false, true, Color.clear); cmd.SetRenderTarget(_inputDepth[1]); cmd.ClearRenderTarget(false, true, Color.clear); cmd.SetRenderTarget(_inputMotionVectors[0]); cmd.ClearRenderTarget(false, true, Color.clear); cmd.SetRenderTarget(_inputMotionVectors[1]); cmd.ClearRenderTarget(false, true, Color.clear); } int frameIndex = (int)(_frameCount++ % 2); // We need to provide copies of the previous depth and motion vector buffers anyway, so we can turn this otherwise wasteful copying into a benefit PrepareInputs(cmd, context, config, _inputColor, _inputDepth[frameIndex], _inputMotionVectors[frameIndex]); Texture reactiveMask = config.transparencyAndCompositionMask; if (config.autoGenerateReactiveMask || config.autoGenerateTransparencyAndComposition) { reactiveMask = GenerateReactiveMask(cmd, context, config); } var flags = PSSRPlugin.OptionFlags.None; if (SystemInfo.usesReversedZBuffer) flags |= PSSRPlugin.OptionFlags.ReverseDepth; if (config.exposureSource == Upscaling.ExposureSource.Auto) flags |= PSSRPlugin.OptionFlags.AutoExposure; ref var dispatchParams = ref _dispatchParams.Value; dispatchParams.contextIndex = _currentContext; dispatchParams.color = ToNativePtr(_inputColor); dispatchParams.depth = ToNativePtr(_inputDepth[frameIndex]); dispatchParams.prevDepth = ToNativePtr(_inputDepth[frameIndex ^ 1]); dispatchParams.motionVectors = ToNativePtr(_inputMotionVectors[frameIndex]); dispatchParams.prevMotionVectors = ToNativePtr(_inputMotionVectors[frameIndex ^ 1]); dispatchParams.exposure = ToNativePtr(config.exposureSource switch { Upscaling.ExposureSource.Manual when config.exposure != null => config.exposure, Upscaling.ExposureSource.Unity => context.autoExposureTexture, _ => null, }); dispatchParams.reactiveMask = ToNativePtr(reactiveMask); dispatchParams.outputColor = ToNativePtr(_outputColor); var scaledRenderSize = config.GetScaledRenderSize(context.camera); dispatchParams.renderWidth = (uint)scaledRenderSize.x; dispatchParams.renderHeight = (uint)scaledRenderSize.y; dispatchParams.jitter = config.JitterOffset; dispatchParams.motionVectorScale = new Vector2(-scaledRenderSize.x, -scaledRenderSize.y); dispatchParams.FromCamera(context.camera); dispatchParams.preExposure = config.preExposure; dispatchParams.resetHistory = config.Reset ? 1u : 0u; dispatchParams.flags = flags; PSSRPlugin.IssuePluginEvent(cmd, PSSRPlugin.Event.Dispatch, _dispatchParams); if (config.performSharpenPass) { // PSSR output is already pretty sharp, and we don't want to over-sharpen the image, so cut the sharpness range by half ApplySharpening(cmd, context, config.UpscaleSize, config.sharpness * 0.5f, _outputColor, context.destination); } else { // Convert the PSSR output from R11G11B10 to the expected destination format cmd.BlitFullscreenTriangle(_outputColor, context.destination); } cmd.EndSample("PSSR"); } private static IntPtr ToNativePtr(Texture texture) { return texture != null ? texture.GetNativeTexturePtr() : IntPtr.Zero; } #else public static bool IsSupported => false; public override void CreateContext(PostProcessRenderContext context, Upscaling config) { throw new NotImplementedException(); } public override void DestroyContext() { base.DestroyContext(); } public override void Render(PostProcessRenderContext context, Upscaling config) { throw new NotImplementedException(); } #endif } }