You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
309 lines
16 KiB
309 lines
16 KiB
using UnityEditor;
|
|
using UnityEngine.Experimental.Rendering;
|
|
using static Unity.Mathematics.math;
|
|
|
|
namespace UnityEngine.Rendering.HighDefinition
|
|
{
|
|
class MipGenerator
|
|
{
|
|
RTHandle[] m_TempColorTargets;
|
|
RTHandle[] m_TempDownsamplePyramid;
|
|
|
|
ComputeShader m_DepthPyramidCS;
|
|
ComputeShader m_ColorPyramidCS;
|
|
Shader m_ColorPyramidPS;
|
|
Material m_ColorPyramidPSMat;
|
|
MaterialPropertyBlock m_PropertyBlock;
|
|
|
|
int m_DepthDownsampleKernel;
|
|
int m_ColorDownsampleKernel;
|
|
int m_ColorGaussianKernel;
|
|
|
|
public MipGenerator(HDRenderPipeline renderPipeline)
|
|
{
|
|
m_TempColorTargets = new RTHandle[xrMaxSliceCount];
|
|
m_TempDownsamplePyramid = new RTHandle[xrMaxSliceCount];
|
|
m_DepthPyramidCS = renderPipeline.runtimeShaders.depthPyramidCS;
|
|
m_ColorPyramidCS = renderPipeline.runtimeShaders.colorPyramidCS;
|
|
|
|
m_DepthDownsampleKernel = m_DepthPyramidCS.FindKernel("KDepthDownsample8DualUav");
|
|
m_ColorDownsampleKernel = m_ColorPyramidCS.FindKernel("KColorDownsample");
|
|
m_ColorGaussianKernel = m_ColorPyramidCS.FindKernel("KColorGaussian");
|
|
|
|
m_ColorPyramidPS = renderPipeline.runtimeShaders.colorPyramidPS;
|
|
m_ColorPyramidPSMat = CoreUtils.CreateEngineMaterial(m_ColorPyramidPS);
|
|
m_PropertyBlock = new MaterialPropertyBlock();
|
|
}
|
|
|
|
public void Release()
|
|
{
|
|
for (int i = 0; i < xrMaxSliceCount; ++i)
|
|
{
|
|
RTHandles.Release(m_TempColorTargets[i]);
|
|
m_TempColorTargets[i] = null;
|
|
RTHandles.Release(m_TempDownsamplePyramid[i]);
|
|
m_TempDownsamplePyramid[i] = null;
|
|
}
|
|
|
|
CoreUtils.Destroy(m_ColorPyramidPSMat);
|
|
}
|
|
|
|
int xrMaxSliceCount
|
|
{
|
|
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)
|
|
{
|
|
HDUtils.CheckRTCreated(texture);
|
|
|
|
var cs = m_DepthPyramidCS;
|
|
int kernel = m_DepthDownsampleKernel;
|
|
|
|
cmd.SetComputeTextureParam(cs, kernel, HDShaderIDs._DepthMipChain, texture);
|
|
|
|
// 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 dstIndex0 = 1; dstIndex0 < info.mipLevelCount;)
|
|
{
|
|
int minCount = Mathf.Min(info.mipLevelCount - dstIndex0, 4);
|
|
int cbCount = 0;
|
|
if (dstIndex0 < info.mipLevelCountCheckerboard)
|
|
{
|
|
cbCount = info.mipLevelCountCheckerboard - dstIndex0;
|
|
Debug.Assert(dstIndex0 == 1, "expected to make checkerboard mips on the first pass");
|
|
Debug.Assert(cbCount <= minCount, "expected fewer checkerboard mips than min mips");
|
|
Debug.Assert(cbCount <= 2, "expected 2 or fewer checkerboard mips for now");
|
|
}
|
|
|
|
Vector2Int srcOffset = info.mipLevelOffsets[dstIndex0 - 1];
|
|
Vector2Int srcSize = info.mipLevelSizes[dstIndex0 - 1];
|
|
int dstIndex1 = Mathf.Min(dstIndex0 + 1, info.mipLevelCount - 1);
|
|
int dstIndex2 = Mathf.Min(dstIndex0 + 2, info.mipLevelCount - 1);
|
|
int dstIndex3 = Mathf.Min(dstIndex0 + 3, info.mipLevelCount - 1);
|
|
|
|
DepthPyramidConstants cb = new DepthPyramidConstants
|
|
{
|
|
_MinDstCount = (uint)minCount,
|
|
_CbDstCount = (uint)cbCount,
|
|
_SrcOffset = srcOffset,
|
|
_SrcLimit = srcSize - Vector2Int.one,
|
|
_DstSize0 = info.mipLevelSizes[dstIndex0],
|
|
_DstSize1 = info.mipLevelSizes[dstIndex1],
|
|
_DstSize2 = info.mipLevelSizes[dstIndex2],
|
|
_DstSize3 = info.mipLevelSizes[dstIndex3],
|
|
_MinDstOffset0 = info.mipLevelOffsets[dstIndex0],
|
|
_MinDstOffset1 = info.mipLevelOffsets[dstIndex1],
|
|
_MinDstOffset2 = info.mipLevelOffsets[dstIndex2],
|
|
_MinDstOffset3 = info.mipLevelOffsets[dstIndex3],
|
|
_CbDstOffset0 = info.mipLevelOffsetsCheckerboard[dstIndex0],
|
|
_CbDstOffset1 = info.mipLevelOffsetsCheckerboard[dstIndex1],
|
|
};
|
|
ConstantBuffer.Push(cmd, cb, cs, HDShaderIDs._DepthPyramidConstants);
|
|
|
|
CoreUtils.SetKeyword(cmd, cs, "ENABLE_CHECKERBOARD", cbCount != 0);
|
|
|
|
Vector2Int dstSize = info.mipLevelSizes[dstIndex0];
|
|
cmd.DispatchCompute(cs, kernel, HDUtils.DivRoundUp(dstSize.x, 8), HDUtils.DivRoundUp(dstSize.y, 8), texture.volumeDepth);
|
|
|
|
dstIndex0 += minCount;
|
|
}
|
|
}
|
|
|
|
// 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!");
|
|
}
|
|
|
|
int srcMipLevel = 0;
|
|
int srcMipWidth = size.x;
|
|
int srcMipHeight = size.y;
|
|
int slices = destination.volumeDepth;
|
|
|
|
// For the fragment version, we need another buffer for the 2 pass blur.
|
|
if (HDRenderPipeline.k_PreferFragment)
|
|
{
|
|
// 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,
|
|
useDynamicScale: true,
|
|
name: "Temp Gaussian Pyramid Target"
|
|
);
|
|
}
|
|
}
|
|
|
|
// 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: true,
|
|
useMipMap: 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
|
|
// Note that we still use a fragment shader to do the first copy because fragment are faster at copying
|
|
// data types like R11G11B10 (default) and pretty similar in term of speed with R16G16B16A16.
|
|
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);
|
|
|
|
var finalTargetSize = new Vector2Int(destination.width, destination.height);
|
|
if (destination.useDynamicScale && isHardwareDrsOn)
|
|
finalTargetSize = DynamicResolutionHandler.instance.ApplyScalesOnSize(finalTargetSize);
|
|
|
|
// 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 / finalTargetSize.x);
|
|
float scaleY = ((float)srcMipHeight / finalTargetSize.y);
|
|
|
|
cmd.SetComputeVectorParam(m_ColorPyramidCS, HDShaderIDs._Size,
|
|
new Vector4(srcMipWidth, srcMipHeight, 0f, 0f));
|
|
|
|
if (HDRenderPipeline.k_PreferFragment)
|
|
{
|
|
// 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
|
|
// Same size as m_TempColorTargets which is the source for vertical blur
|
|
var hardwareBlurSourceTextureSize = new Vector2Int(m_TempDownsamplePyramid[rtIndex].rt.width, m_TempDownsamplePyramid[rtIndex].rt.height);
|
|
if (isHardwareDrsOn)
|
|
hardwareBlurSourceTextureSize = DynamicResolutionHandler.instance.ApplyScalesOnSize(hardwareBlurSourceTextureSize);
|
|
|
|
float blurSourceTextureWidth = (float)hardwareBlurSourceTextureSize.x;
|
|
float blurSourceTextureHeight = (float)hardwareBlurSourceTextureSize.y;
|
|
|
|
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);
|
|
}
|
|
else
|
|
{
|
|
// Downsample.
|
|
cmd.SetComputeTextureParam(m_ColorPyramidCS, m_ColorDownsampleKernel, HDShaderIDs._Source,
|
|
destination, srcMipLevel);
|
|
cmd.SetComputeTextureParam(m_ColorPyramidCS, m_ColorDownsampleKernel, HDShaderIDs._Destination,
|
|
m_TempDownsamplePyramid[rtIndex]);
|
|
cmd.DispatchCompute(m_ColorPyramidCS, m_ColorDownsampleKernel, (dstMipWidth + 7) / 8,
|
|
(dstMipHeight + 7) / 8, TextureXR.slices);
|
|
|
|
// Single pass blur
|
|
cmd.SetComputeVectorParam(m_ColorPyramidCS, HDShaderIDs._Size,
|
|
new Vector4(dstMipWidth, dstMipHeight, 0f, 0f));
|
|
cmd.SetComputeTextureParam(m_ColorPyramidCS, m_ColorGaussianKernel, HDShaderIDs._Source,
|
|
m_TempDownsamplePyramid[rtIndex]);
|
|
cmd.SetComputeTextureParam(m_ColorPyramidCS, m_ColorGaussianKernel, HDShaderIDs._Destination,
|
|
destination, srcMipLevel + 1);
|
|
cmd.DispatchCompute(m_ColorPyramidCS, m_ColorGaussianKernel, (dstMipWidth + 7) / 8,
|
|
(dstMipHeight + 7) / 8, TextureXR.slices);
|
|
}
|
|
|
|
srcMipLevel++;
|
|
srcMipWidth >>= 1;
|
|
srcMipHeight >>= 1;
|
|
|
|
finalTargetSize.x >>= 1;
|
|
finalTargetSize.y >>= 1;
|
|
}
|
|
|
|
return srcMipLevel + 1;
|
|
}
|
|
}
|
|
}
|