Files
2026-11-01 23:39:55 +02:00

266 lines
15 KiB
C#

using System;
using UnityEngine.Rendering.RenderGraphModule;
using System.Runtime.CompilerServices; // AggressiveInlining
namespace UnityEngine.Rendering.Universal
{
internal sealed class UpscalerPostProcessPass : PostProcessPass
{
public const string k_UpscaledColorTargetName = "CameraColorUpscaled";
Texture2D[] m_BlueNoise16LTex;
bool m_IsValid;
#if ENABLE_UPSCALER_FRAMEWORK
bool m_WarnedHardwareDrsTemporalUnsupported;
bool m_WarnedMissingMotionData;
#endif
public UpscalerPostProcessPass(Texture2D[] blueNoise16LTex)
{
this.renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing - 1;
this.profilingSampler = null; // Use default name
m_BlueNoise16LTex = blueNoise16LTex;
m_IsValid = m_BlueNoise16LTex != null && m_BlueNoise16LTex.Length > 0;
}
public override void Dispose()
{
m_IsValid = false;
}
public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
{
#if ENABLE_UPSCALER_FRAMEWORK
if (!m_IsValid)
return;
UniversalPostProcessingData postProcessingData = frameData.Get<UniversalPostProcessingData>();
if (postProcessingData.activeUpscaler == null)
return;
UniversalCameraData cameraData = frameData.Get<UniversalCameraData>();
if (cameraData.imageScalingMode != ImageScalingMode.Upscaling)
return;
// Skip temporal upscaling when the camera uses hardware DRS (ScalableBufferManager). A temporal upscaler
// reconstructs to full resolution mid-frame, but ScalableBufferManager is a single global scale with no per-stage
// render->display transition, so the post-upscale chain (UberPost, final blit) keeps writing into the
// ScalableBufferManager-scaled sub-rect and only that sub-rect of the screen updates.
// Gate on camera.allowDynamicResolution (the stable opt-in), not the live ScalableBufferManager factor,
// which would flip per-frame as the app crosses factor 1.0.
if (cameraData.camera.allowDynamicResolution && postProcessingData.activeUpscaler.isTemporal)
{
if (Debug.isDebugBuild && !m_WarnedHardwareDrsTemporalUnsupported)
{
m_WarnedHardwareDrsTemporalUnsupported = true;
Debug.LogWarning(
"Hardware Dynamic Resolution (Allow Dynamic Resolution / ScalableBufferManager) is not supported " +
$"with the temporal upscaler '{postProcessingData.activeUpscaler.name}' in URP yet (in any " +
"Resolution Mode); the upscaler is skipped and the camera falls back to hardware DRS without " +
"temporal upscaling. To use the temporal upscaler, disable Allow Dynamic Resolution on the camera" +
" and control the resolution via Render Scale or the upscaler's quality mode if available.");
}
return;
}
// Left the skip path: reset so re-entering it warns again.
if (Debug.isDebugBuild)
m_WarnedHardwareDrsTemporalUnsupported = false;
UniversalResourceData resourceData = frameData.Get<UniversalResourceData>();
var sourceTexture = resourceData.cameraColor;
var srcDesc = sourceTexture.GetDescriptor(renderGraph);
// Create a context item containing upscaling inputs
UpscalingIO io = frameData.Create<UpscalingIO>();
io.cameraColor = sourceTexture;
io.cameraDepth = resourceData.cameraDepth;
io.motionVectorColor = resourceData.motionVectorColor;
io.motionVectorDomain = UpscalingIO.MotionVectorDomain.NDC;
io.motionVectorDirection = UpscalingIO.MotionVectorDirection.PreviousFrameToCurrentFrame;
io.jitteredMotionVectors = false; // URP has no jittering in MVs
// io.exposureTexture; // TODO: set exposure texture when available
io.preExposureValue = 1.0f; // TODO: set if exposure value is pre-multiplied
io.hdrDisplayInformation = cameraData.isHDROutputActive ? cameraData.hdrDisplayInformation : new HDROutputUtils.HDRDisplayInformation(-1, -1, -1, 160.0f);
io.postUpscaleResolution = new Vector2Int(cameraData.pixelWidth, cameraData.pixelHeight);
// Report DRS as active whenever the camera is aliased by hardware DRS / ScalableBufferManager (SBM)
// (descriptor.useDynamicScale), not only when the
// current factor is < 1.0: the upscaler bakes its dynamic-resolution state at context-creation time (e.g.
// FSR2's EnableDynamicResolution flag) and isn't recreated when the factor changes, so a context created at
// factor 1.0 would otherwise run with DRS off while later receiving a varying sub-rect. Use the SBM scale
// captured on cameraData at setup, not the live global, which another pass could mutate before this point.
Vector2 hwDrsScale = cameraData.hardwareDynamicResolutionScale;
bool cameraUsesHardwareDrs = cameraData.cameraTargetDescriptor.useDynamicScale;
io.dynamicResolution = cameraUsesHardwareDrs ? DynamicResolutionType.Hardware : (DynamicResolutionType?)null;
// preUpscaleResolution is the actually-rendered region (the descriptor scaled by the SBM factor), not the
// full descriptor allocation, so the upscaler reconstructs from what was rendered.
io.preUpscaleResolution = cameraUsesHardwareDrs
? new Vector2Int(
Mathf.CeilToInt(hwDrsScale.x * cameraData.cameraTargetDescriptor.width),
Mathf.CeilToInt(hwDrsScale.y * cameraData.cameraTargetDescriptor.height))
: new Vector2Int(cameraData.cameraTargetDescriptor.width, cameraData.cameraTargetDescriptor.height);
// The max render size = the full camera-target allocation (the descriptor). Under SBM the rendered region
// (preUpscaleResolution) is a sub-rect of it; without DRS they're equal. Upscalers allocate history at this.
io.maxPreUpscaleResolution = new Vector2Int(cameraData.cameraTargetDescriptor.width, cameraData.cameraTargetDescriptor.height);
cameraData.camera.TryGetComponent<UniversalAdditionalCameraData>(out var additionalCameraData);
MotionVectorsPersistentData motionData = additionalCameraData != null ? additionalCameraData.motionVectorsPersistentData : null;
if (motionData == null) // Per-camera motion data is required for upscaling (camera matrices, positions, previous-frame sizes).
{
if (Debug.isDebugBuild && !m_WarnedMissingMotionData)
{
m_WarnedMissingMotionData = true;
Debug.LogWarning("UpscalerPostProcessPass: camera has no UniversalAdditionalCameraData/" +
"MotionVectorsPersistentData, which the temporal upscaler requires; skipping upscaling for this camera.");
}
return;
}
if (Debug.isDebugBuild) // Saw valid motion data: reset so a later missing-data camera warns again.
m_WarnedMissingMotionData = false;
// Track the previous frame's render resolution for temporal upscalers via the per-camera persistent state.
io.previousPreUpscaleResolution = motionData.previousPreUpscaleResolution;
motionData.previousPreUpscaleResolution = io.preUpscaleResolution;
io.motionVectorTextureSize = io.preUpscaleResolution;
io.enableTexArray = cameraData.xr.enabled && cameraData.xr.singlePassEnabled;
io.cameraInstanceID = EntityId.ToULong(cameraData.camera.GetEntityId());
io.nearClipPlane = cameraData.camera.nearClipPlane;
io.farClipPlane = cameraData.camera.farClipPlane;
io.fieldOfViewDegrees = cameraData.camera.fieldOfView;
io.invertedDepth = SystemInfo.usesReversedZBuffer;
io.flippedY = SystemInfo.graphicsUVStartsAtTop;
io.flippedX = false;
io.hdrInput = Experimental.Rendering.GraphicsFormatUtility.IsHDRFormat(srcDesc.format);
io.numActiveViews = cameraData.xr.enabled ? cameraData.xr.viewCount : 1;
io.projectionMatrices = motionData.projectionStereo;
io.previousProjectionMatrices = motionData.previousProjectionStereo;
io.previousPreviousProjectionMatrices = motionData.previousPreviousProjectionStereo;
io.viewMatrices = motionData.viewStereo;
io.previousViewMatrices = motionData.previousViewStereo;
io.previousPreviousViewMatrices = motionData.previousPreviousViewStereo;
// Per-view world-space camera positions into persistent per-camera buffers (resized only on view-count
// change) to avoid a per-frame allocation. Mono reuses the stored camera position; each XR eye is at a
// distinct position, derived from its view matrix via CoreMatrixUtils.GetWorldPositionFromOrthonormalViewMatrix
// instead of a full inverse.
var camPosViews = motionData.GetWorldSpaceCameraPosViews(io.numActiveViews);
var prevCamPosViews = motionData.GetPreviousWorldSpaceCameraPosViews(io.numActiveViews);
var prevPrevCamPosViews = motionData.GetPreviousPreviousWorldSpaceCameraPosViews(io.numActiveViews);
if (io.numActiveViews == 1)
{
camPosViews[0] = motionData.worldSpaceCameraPos;
prevCamPosViews[0] = motionData.previousWorldSpaceCameraPos;
prevPrevCamPosViews[0] = motionData.previousPreviousWorldSpaceCameraPos;
}
else
{
for (int i = 0; i < io.numActiveViews; i++)
{
camPosViews[i] = CoreMatrixUtils.GetWorldPositionFromOrthonormalViewMatrix(motionData.viewStereo[i]);
prevCamPosViews[i] = CoreMatrixUtils.GetWorldPositionFromOrthonormalViewMatrix(motionData.previousViewStereo[i]);
prevPrevCamPosViews[i] = CoreMatrixUtils.GetWorldPositionFromOrthonormalViewMatrix(motionData.previousPreviousViewStereo[i]);
}
}
io.worldSpaceCameraPositions = camPosViews;
io.previousWorldSpaceCameraPositions = prevCamPosViews;
io.previousPreviousWorldSpaceCameraPositions = prevPrevCamPosViews;
io.resetHistory = cameraData.resetHistory;
io.frameIndex = TemporalAA.CalculateTaaFrameIndex(ref cameraData.taaSettings);
io.deltaTime = motionData.deltaTime;
io.previousDeltaTime = motionData.lastDeltaTime;
io.blueNoiseTextureSet = m_BlueNoise16LTex;
// The motion scaling feature is only active outside of test environments. If we allowed it to run
// during automated graphics tests, the results of each test run would be dependent on system
// performance.
#if LWRP_DEBUG_STATIC_POSTFX
io.enableMotionScaling = false;
#else
io.enableMotionScaling = true;
#endif
// Acquire the per-camera context for the active upscaler
// In XR multi-pass rendering, encode eye information into the camera ID to ensure separate contexts per eye
var upscaler = postProcessingData.activeUpscaler;
ulong viewId = io.cameraInstanceID;
if (cameraData.xr.enabled && !cameraData.xr.singlePassEnabled)
viewId = (ulong)HashCode.Combine(io.cameraInstanceID, cameraData.xr.multipassId);
// Fetch the framework-owned options for this upscaler (per-camera overrides are future work).
UpscalerOptions upscalerOptions = UniversalRenderPipeline.upscaling.GetGlobalOptions(upscaler);
io.context = UniversalRenderPipeline.upscaling.AcquireContext(
viewId,
upscaler,
upscalerOptions,
io.postUpscaleResolution
);
// Use jitter already computed during camera setup
io.subpixelJitter = cameraData.subpixelJitter;
// Per-frame settings (sharpness, etc.); upscalers read these from io.options, not from the context.
io.options = upscalerOptions;
// Insert the active upscaler's render graph passes
upscaler.RecordRenderGraph(renderGraph, frameData);
// Update the camera resolution to reflect the upscaled size
var dstDesc = io.cameraColor.GetDescriptor(renderGraph);
UpdateCameraResolution(renderGraph, cameraData, new Vector2Int(dstDesc.width, dstDesc.height));
// Use the output texture of upscaling
resourceData.cameraColor = io.cameraColor;
#endif
}
private class UpdateCameraResolutionPassData
{
internal Vector2Int newCameraTargetSize;
}
// Updates render target descriptors and shader constants to reflect a new render size
// This should be called immediately after the resolution changes mid-frame (typically after an upscaling operation).
static internal void UpdateCameraResolution(RenderGraph renderGraph, UniversalCameraData cameraData, Vector2Int newCameraTargetSize)
{
// Update the camera data descriptor to reflect post-upscaled sizes
cameraData.cameraTargetDescriptor.width = newCameraTargetSize.x;
cameraData.cameraTargetDescriptor.height = newCameraTargetSize.y;
// Update the shader constants to reflect the new camera resolution
using (var builder = renderGraph.AddUnsafePass<UpdateCameraResolutionPassData>("Update Camera Resolution", out var passData))
{
passData.newCameraTargetSize = newCameraTargetSize;
// This pass only modifies shader constants
builder.AllowGlobalStateModification(true);
// Wrap constant modification into a pass to force graph execution timeline.
builder.SetRenderFunc(static (UpdateCameraResolutionPassData data, UnsafeGraphContext ctx) =>
{
ctx.cmd.SetGlobalVector(
ShaderPropertyId.screenSize,
new Vector4(
data.newCameraTargetSize.x,
data.newCameraTargetSize.y,
1.0f / data.newCameraTargetSize.x,
1.0f / data.newCameraTargetSize.y
)
);
});
}
}
// Precomputed shader ids to same some CPU cycles (mostly affects mobile)
public static class ShaderConstants
{
}
}
}