252 lines
12 KiB
C#
252 lines
12 KiB
C#
namespace UnityEngine.Rendering.HighDefinition
|
|
{
|
|
class MipGenerator
|
|
{
|
|
RTHandle[] m_TempColorTargets;
|
|
RTHandle[] m_TempDownsamplePyramid;
|
|
|
|
ComputeShader m_DepthPyramidCS;
|
|
Shader m_ColorPyramidPS;
|
|
Material m_ColorPyramidPSMat;
|
|
MaterialPropertyBlock m_PropertyBlock;
|
|
|
|
int m_DepthDownsampleKernel;
|
|
|
|
int[] m_SrcOffset;
|
|
int[] m_DstOffset;
|
|
|
|
public MipGenerator(RenderPipelineResources defaultResources)
|
|
{
|
|
m_TempColorTargets = new RTHandle[tmpTargetCount];
|
|
m_TempDownsamplePyramid = new RTHandle[tmpTargetCount];
|
|
m_DepthPyramidCS = defaultResources.shaders.depthPyramidCS;
|
|
|
|
m_DepthDownsampleKernel = m_DepthPyramidCS.FindKernel("KDepthDownsample8DualUav");
|
|
|
|
m_SrcOffset = new int[4];
|
|
m_DstOffset = new int[4];
|
|
m_ColorPyramidPS = defaultResources.shaders.colorPyramidPS;
|
|
m_ColorPyramidPSMat = CoreUtils.CreateEngineMaterial(m_ColorPyramidPS);
|
|
m_PropertyBlock = new MaterialPropertyBlock();
|
|
}
|
|
|
|
public void Release()
|
|
{
|
|
for (int i = 0; i < tmpTargetCount; ++i)
|
|
{
|
|
RTHandles.Release(m_TempColorTargets[i]);
|
|
m_TempColorTargets[i] = null;
|
|
RTHandles.Release(m_TempDownsamplePyramid[i]);
|
|
m_TempDownsamplePyramid[i] = null;
|
|
}
|
|
|
|
CoreUtils.Destroy(m_ColorPyramidPSMat);
|
|
}
|
|
|
|
private int tmpTargetCount
|
|
{
|
|
get
|
|
{
|
|
if (TextureXR.useTexArray)
|
|
return 2;
|
|
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
// Generates an in-place depth pyramid
|
|
// TODO: Mip-mapping depth is problematic for precision at lower mips, generate a packed atlas instead
|
|
public void RenderMinDepthPyramid(CommandBuffer cmd, RenderTexture texture, HDUtils.PackedMipChainInfo info, bool mip1AlreadyComputed)
|
|
{
|
|
HDUtils.CheckRTCreated(texture);
|
|
|
|
var cs = m_DepthPyramidCS;
|
|
int kernel = m_DepthDownsampleKernel;
|
|
|
|
// TODO: Do it 1x MIP at a time for now. In the future, do 4x MIPs per pass, or even use a single pass.
|
|
// Note: Gather() doesn't take a LOD parameter and we cannot bind an SRV of a MIP level,
|
|
// and we don't support Min samplers either. So we are forced to perform 4x loads.
|
|
for (int i = 1; i < info.mipLevelCount; i++)
|
|
{
|
|
if (mip1AlreadyComputed && i == 1) continue;
|
|
|
|
Vector2Int dstSize = info.mipLevelSizes[i];
|
|
Vector2Int dstOffset = info.mipLevelOffsets[i];
|
|
Vector2Int srcSize = info.mipLevelSizes[i - 1];
|
|
Vector2Int srcOffset = info.mipLevelOffsets[i - 1];
|
|
Vector2Int srcLimit = srcOffset + srcSize - Vector2Int.one;
|
|
|
|
m_SrcOffset[0] = srcOffset.x;
|
|
m_SrcOffset[1] = srcOffset.y;
|
|
m_SrcOffset[2] = srcLimit.x;
|
|
m_SrcOffset[3] = srcLimit.y;
|
|
|
|
m_DstOffset[0] = dstOffset.x;
|
|
m_DstOffset[1] = dstOffset.y;
|
|
m_DstOffset[2] = 0;
|
|
m_DstOffset[3] = 0;
|
|
|
|
cmd.SetComputeIntParams(cs, HDShaderIDs._SrcOffsetAndLimit, m_SrcOffset);
|
|
cmd.SetComputeIntParams(cs, HDShaderIDs._DstOffset, m_DstOffset);
|
|
cmd.SetComputeTextureParam(cs, kernel, HDShaderIDs._DepthMipChain, texture);
|
|
|
|
cmd.DispatchCompute(cs, kernel, HDUtils.DivRoundUp(dstSize.x, 8), HDUtils.DivRoundUp(dstSize.y, 8), texture.volumeDepth);
|
|
}
|
|
}
|
|
|
|
// Generates the gaussian pyramid of source into destination
|
|
// We can't do it in place as the color pyramid has to be read while writing to the color
|
|
// buffer in some cases (e.g. refraction, distortion)
|
|
// Returns the number of mips
|
|
public int RenderColorGaussianPyramid(CommandBuffer cmd, Vector2Int size, Texture source, RenderTexture destination)
|
|
{
|
|
// Select between Tex2D and Tex2DArray versions of the kernels
|
|
bool sourceIsArray = (source.dimension == TextureDimension.Tex2DArray);
|
|
int rtIndex = sourceIsArray ? 1 : 0;
|
|
// Sanity check
|
|
if (sourceIsArray)
|
|
{
|
|
Debug.Assert(source.dimension == destination.dimension, "MipGenerator source texture does not match dimension of destination!");
|
|
}
|
|
|
|
// Check if format has changed since last time we generated mips
|
|
if (m_TempColorTargets[rtIndex] != null && m_TempColorTargets[rtIndex].rt.graphicsFormat != destination.graphicsFormat)
|
|
{
|
|
RTHandles.Release(m_TempColorTargets[rtIndex]);
|
|
m_TempColorTargets[rtIndex] = null;
|
|
}
|
|
|
|
// Only create the temporary target on-demand in case the game doesn't actually need it
|
|
if (m_TempColorTargets[rtIndex] == null)
|
|
{
|
|
m_TempColorTargets[rtIndex] = RTHandles.Alloc(
|
|
Vector2.one * 0.5f,
|
|
sourceIsArray ? TextureXR.slices : 1,
|
|
dimension: source.dimension,
|
|
filterMode: FilterMode.Bilinear,
|
|
colorFormat: destination.graphicsFormat,
|
|
enableRandomWrite: true,
|
|
useMipMap: false,
|
|
enableMSAA: false,
|
|
useDynamicScale: true,
|
|
name: "Temp Gaussian Pyramid Target"
|
|
);
|
|
}
|
|
|
|
int srcMipLevel = 0;
|
|
int srcMipWidth = size.x;
|
|
int srcMipHeight = size.y;
|
|
int slices = destination.volumeDepth;
|
|
|
|
int tempTargetWidth = srcMipWidth >> 1;
|
|
int tempTargetHeight = srcMipHeight >> 1;
|
|
|
|
// Check if format has changed since last time we generated mips
|
|
if (m_TempDownsamplePyramid[rtIndex] != null && m_TempDownsamplePyramid[rtIndex].rt.graphicsFormat != destination.graphicsFormat)
|
|
{
|
|
RTHandles.Release(m_TempDownsamplePyramid[rtIndex]);
|
|
m_TempDownsamplePyramid[rtIndex] = null;
|
|
}
|
|
|
|
if (m_TempDownsamplePyramid[rtIndex] == null)
|
|
{
|
|
m_TempDownsamplePyramid[rtIndex] = RTHandles.Alloc(
|
|
Vector2.one * 0.5f,
|
|
sourceIsArray ? TextureXR.slices : 1,
|
|
dimension: source.dimension,
|
|
filterMode: FilterMode.Bilinear,
|
|
colorFormat: destination.graphicsFormat,
|
|
enableRandomWrite: false,
|
|
useMipMap: false,
|
|
enableMSAA: false,
|
|
useDynamicScale: true,
|
|
name: "Temporary Downsampled Pyramid"
|
|
);
|
|
|
|
cmd.SetRenderTarget(m_TempDownsamplePyramid[rtIndex]);
|
|
cmd.ClearRenderTarget(false, true, Color.black);
|
|
}
|
|
|
|
bool isHardwareDrsOn = DynamicResolutionHandler.instance.HardwareDynamicResIsEnabled();
|
|
var hardwareTextureSize = new Vector2Int(source.width, source.height);
|
|
if (isHardwareDrsOn)
|
|
hardwareTextureSize = DynamicResolutionHandler.instance.ApplyScalesOnSize(hardwareTextureSize);
|
|
|
|
float sourceScaleX = (float)size.x / (float)hardwareTextureSize.x;
|
|
float sourceScaleY = (float)size.y / (float)hardwareTextureSize.y;
|
|
|
|
// Copies src mip0 to dst mip0
|
|
m_PropertyBlock.SetTexture(HDShaderIDs._BlitTexture, source);
|
|
m_PropertyBlock.SetVector(HDShaderIDs._BlitScaleBias, new Vector4(sourceScaleX, sourceScaleY, 0f, 0f));
|
|
m_PropertyBlock.SetFloat(HDShaderIDs._BlitMipLevel, 0f);
|
|
cmd.SetRenderTarget(destination, 0, CubemapFace.Unknown, -1);
|
|
cmd.SetViewport(new Rect(0, 0, srcMipWidth, srcMipHeight));
|
|
cmd.DrawProcedural(Matrix4x4.identity, HDUtils.GetBlitMaterial(source.dimension), 0, MeshTopology.Triangles, 3, 1, m_PropertyBlock);
|
|
|
|
int finalTargetMipWidth = destination.width;
|
|
int finalTargetMipHeight = destination.height;
|
|
|
|
// Note: smaller mips are excluded as we don't need them and the gaussian compute works
|
|
// on 8x8 blocks
|
|
while (srcMipWidth >= 8 || srcMipHeight >= 8)
|
|
{
|
|
int dstMipWidth = Mathf.Max(1, srcMipWidth >> 1);
|
|
int dstMipHeight = Mathf.Max(1, srcMipHeight >> 1);
|
|
|
|
// Scale for downsample
|
|
float scaleX = ((float)srcMipWidth / finalTargetMipWidth);
|
|
float scaleY = ((float)srcMipHeight / finalTargetMipHeight);
|
|
|
|
// Downsample.
|
|
m_PropertyBlock.SetTexture(HDShaderIDs._BlitTexture, destination);
|
|
m_PropertyBlock.SetVector(HDShaderIDs._BlitScaleBias, new Vector4(scaleX, scaleY, 0f, 0f));
|
|
m_PropertyBlock.SetFloat(HDShaderIDs._BlitMipLevel, srcMipLevel);
|
|
cmd.SetRenderTarget(m_TempDownsamplePyramid[rtIndex], 0, CubemapFace.Unknown, -1);
|
|
cmd.SetViewport(new Rect(0, 0, dstMipWidth, dstMipHeight));
|
|
cmd.DrawProcedural(Matrix4x4.identity, HDUtils.GetBlitMaterial(source.dimension), 1, MeshTopology.Triangles, 3, 1, m_PropertyBlock);
|
|
|
|
// In this mip generation process, source viewport can be smaller than the source render target itself because of the RTHandle system
|
|
// We are not using the scale provided by the RTHandle system for two reasons:
|
|
// - Source might be a planar probe which will not be scaled by the system (since it's actually the final target of probe rendering at the exact size)
|
|
// - When computing mip size, depending on even/odd sizes, the scale computed for mip 0 might miss a texel at the border.
|
|
// This can result in a shift in the mip map downscale that depends on the render target size rather than the actual viewport
|
|
// (Two rendering at the same viewport size but with different RTHandle reference size would yield different results which can break automated testing)
|
|
// So in the end we compute a specific scale for downscale and blur passes at each mip level.
|
|
|
|
// Scales for Blur
|
|
float blurSourceTextureWidth = (float)m_TempDownsamplePyramid[rtIndex].rt.width; // Same size as m_TempColorTargets which is the source for vertical blur
|
|
float blurSourceTextureHeight = (float)m_TempDownsamplePyramid[rtIndex].rt.height;
|
|
scaleX = ((float)dstMipWidth / blurSourceTextureWidth);
|
|
scaleY = ((float)dstMipHeight / blurSourceTextureHeight);
|
|
|
|
// Blur horizontal.
|
|
m_PropertyBlock.SetTexture(HDShaderIDs._Source, m_TempDownsamplePyramid[rtIndex]);
|
|
m_PropertyBlock.SetVector(HDShaderIDs._SrcScaleBias, new Vector4(scaleX, scaleY, 0f, 0f));
|
|
m_PropertyBlock.SetVector(HDShaderIDs._SrcUvLimits, new Vector4((dstMipWidth - 0.5f) / blurSourceTextureWidth, (dstMipHeight - 0.5f) / blurSourceTextureHeight, 1.0f / blurSourceTextureWidth, 0f));
|
|
m_PropertyBlock.SetFloat(HDShaderIDs._SourceMip, 0);
|
|
cmd.SetRenderTarget(m_TempColorTargets[rtIndex], 0, CubemapFace.Unknown, -1);
|
|
cmd.SetViewport(new Rect(0, 0, dstMipWidth, dstMipHeight));
|
|
cmd.DrawProcedural(Matrix4x4.identity, m_ColorPyramidPSMat, rtIndex, MeshTopology.Triangles, 3, 1, m_PropertyBlock);
|
|
|
|
// Blur vertical.
|
|
m_PropertyBlock.SetTexture(HDShaderIDs._Source, m_TempColorTargets[rtIndex]);
|
|
m_PropertyBlock.SetVector(HDShaderIDs._SrcScaleBias, new Vector4(scaleX, scaleY, 0f, 0f));
|
|
m_PropertyBlock.SetVector(HDShaderIDs._SrcUvLimits, new Vector4((dstMipWidth - 0.5f) / blurSourceTextureWidth, (dstMipHeight - 0.5f) / blurSourceTextureHeight, 0f, 1.0f / blurSourceTextureHeight));
|
|
m_PropertyBlock.SetFloat(HDShaderIDs._SourceMip, 0);
|
|
cmd.SetRenderTarget(destination, srcMipLevel + 1, CubemapFace.Unknown, -1);
|
|
cmd.SetViewport(new Rect(0, 0, dstMipWidth, dstMipHeight));
|
|
cmd.DrawProcedural(Matrix4x4.identity, m_ColorPyramidPSMat, rtIndex, MeshTopology.Triangles, 3, 1, m_PropertyBlock);
|
|
|
|
srcMipLevel++;
|
|
srcMipWidth = srcMipWidth >> 1;
|
|
srcMipHeight = srcMipHeight >> 1;
|
|
|
|
finalTargetMipWidth = finalTargetMipWidth >> 1;
|
|
finalTargetMipHeight = finalTargetMipHeight >> 1;
|
|
}
|
|
|
|
return srcMipLevel + 1;
|
|
}
|
|
}
|
|
}
|