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.
 
 
 
 

304 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;
}
}
}