using System; using UnityEngine.Rendering.RenderGraphModule; namespace UnityEngine.Rendering { /// /// Encapsulates variable shading rate support (VRS) and texture conversion to shading rate image /// public static class Vrs { class ConversionPassData { public TextureHandle sriTextureHandle; public TextureHandle mainTexHandle; public TextureDimension mainTexDimension; public BufferHandle mainTexLutHandle; public BufferHandle validatedShadingRateFragmentSizeHandle; public ComputeShader computeShader; public int kernelIndex; public Vector4 scaleBias; public Vector2Int dispatchSize; public bool yFlip; } class VisualizationPassData { public Material material; public TextureHandle source; public BufferHandle lut; public TextureHandle dummy; public Vector4 visualizationParams; } internal static readonly int shadingRateFragmentSizeCount = Enum.GetNames(typeof(ShadingRateFragmentSize)).Length; static VrsResources s_VrsResources; /// /// Check if conversion of color texture to shading rate image is supported. /// Convenience to abstract all capabilities checks. /// /// Returns true if conversion of color texture to shading rate image is supported, false otherwise. public static bool IsColorMaskTextureConversionSupported() { return SystemInfo.supportsComputeShaders && ShadingRateInfo.supportsPerImageTile && IsInitialized(); } /// /// Checks if VRS resources are initialized. /// Initialization may fail due to platform restrictions. /// /// Returns true if the Vrs resources are initialized. public static bool IsInitialized() { return s_VrsResources != null && s_VrsResources.textureComputeShader != null && s_VrsResources.textureReduceKernel != -1 && s_VrsResources.textureCopyKernel != -1; } /// /// Preprocess resources found in VrsRenderPipelineRuntimeResources for use at runtime. /// public static void InitializeResources() { // GLES3.0/WebGL2 and older GL platforms do not support compute shaders (for conversion). // Unity does not implement VRS for OpenGL. bool isOpenGL = SystemInfo.graphicsDeviceType == GraphicsDeviceType.OpenGLCore || SystemInfo.graphicsDeviceType == GraphicsDeviceType.OpenGLES3; // NOTE: should match "#pragma exclude_renderers" in shaders. bool isPlatformSupported = !isOpenGL; // VRS resources are initialized even on platforms that do not support VRS to allow debugging Color<->VRS conversion. // For example when you are building on a non-VRS platform to a VRS platform. // VRS conversion requires compute shader support. // NOTE: Init might fail. if (SystemInfo.supportsComputeShaders && isPlatformSupported) { var pipelineRuntimeResources = GraphicsSettings.GetRenderPipelineSettings(); s_VrsResources = new VrsResources(pipelineRuntimeResources); } } /// /// Cleanup resources. /// public static void DisposeResources() { s_VrsResources?.Dispose(); s_VrsResources = null; } /// /// Converts a color mask texture to a shading rate image. /// /// Render graph to record conversion commands /// Shading rate images to convert to. /// Texture to convert from. /// True if shading rate image should be generated flipped. /// Shading rate image texture handle created. /// /// sriRtHandle and colorMaskRtHandle are imported with renderGraph before doing the conversion. /// public static TextureHandle ColorMaskTextureToShadingRateImage(RenderGraph renderGraph, RTHandle sriRtHandle, RTHandle colorMaskRtHandle, bool yFlip) { if (renderGraph == null || sriRtHandle == null || colorMaskRtHandle == null) { Debug.LogError($"TextureToShadingRateImage: invalid argument."); return TextureHandle.nullHandle; } var sriTextureHandle = renderGraph.ImportShadingRateImageTexture(sriRtHandle); var colorMaskHandle = renderGraph.ImportTexture(colorMaskRtHandle); return ColorMaskTextureToShadingRateImage(renderGraph, sriTextureHandle, colorMaskHandle, ((Texture)colorMaskRtHandle).dimension, yFlip); } /// /// Converts a color mask texture to a shading rate image. /// /// Render graph to record conversion commands /// Shading rate images to convert to. /// Texture to convert from. /// Texture's dimension. /// True if shading rate image should be generated flipped. /// Shading rate image texture handle created. /// /// sriRtHandle and colorMaskHandle are expected to be imported by renderGraph prior to this call. /// public static TextureHandle ColorMaskTextureToShadingRateImage(RenderGraph renderGraph, TextureHandle sriTextureHandle, TextureHandle colorMaskHandle, TextureDimension colorMaskDimension, bool yFlip) { if (!IsColorMaskTextureConversionSupported()) { Debug.LogError($"ColorMaskTextureToShadingRateImage: conversion not supported."); return TextureHandle.nullHandle; } var sriDesc = sriTextureHandle.GetDescriptor(renderGraph); if (sriDesc.dimension != TextureDimension.Tex2D) { Debug.LogError($"ColorMaskTextureToShadingRateImage: Vrs image not a texture 2D."); return TextureHandle.nullHandle; } if (colorMaskDimension != TextureDimension.Tex2D && colorMaskDimension != TextureDimension.Tex2DArray) { Debug.LogError($"ColorMaskTextureToShadingRateImage: Input texture dimension not supported."); return TextureHandle.nullHandle; } using (var builder = renderGraph.AddComputePass("TextureToShadingRateImage", out var outerPassData, s_VrsResources.conversionProfilingSampler)) { outerPassData.sriTextureHandle = sriTextureHandle; outerPassData.mainTexHandle = colorMaskHandle; outerPassData.mainTexDimension = colorMaskDimension; outerPassData.mainTexLutHandle = renderGraph.ImportBuffer(s_VrsResources.conversionLutBuffer); outerPassData.validatedShadingRateFragmentSizeHandle = renderGraph.ImportBuffer(s_VrsResources.validatedShadingRateFragmentSizeBuffer); outerPassData.computeShader = s_VrsResources.textureComputeShader; outerPassData.kernelIndex = s_VrsResources.textureReduceKernel; outerPassData.scaleBias = new Vector4() { x = 1.0f / (sriDesc.width * s_VrsResources.tileSize.x), y = 1.0f / (sriDesc.height * s_VrsResources.tileSize.y), z = sriDesc.width, w = sriDesc.height, }; outerPassData.dispatchSize = new Vector2Int(sriDesc.width, sriDesc.height); outerPassData.yFlip = yFlip; builder.UseTexture(outerPassData.sriTextureHandle, AccessFlags.Write); builder.UseTexture(outerPassData.mainTexHandle); builder.UseBuffer(outerPassData.mainTexLutHandle); builder.AllowGlobalStateModification(true); builder.SetRenderFunc((ConversionPassData innerPassData, ComputeGraphContext context) => { ConversionDispatch(context.cmd, innerPassData); }); return outerPassData.sriTextureHandle; } } /// /// Converts a shading rate image to a color texture for visualization. /// /// Render graph to record conversion commands /// Texture to convert from. /// Output of conversion. public static void ShadingRateImageToColorMaskTexture(RenderGraph renderGraph, in TextureHandle sriTextureHandle, in TextureHandle colorMaskHandle) { if (s_VrsResources == null) { Debug.LogError($"ShadingRateImageToColorMaskTexture: VRS not initialized."); return; } if (!colorMaskHandle.IsValid()) { Debug.LogError($"ShadingRateImageToColorMaskTexture: Output target handle is not valid."); return; } using (var builder = renderGraph.AddRasterRenderPass("ShadingRateImageToTexture", out var outerPassData, s_VrsResources.visualizationProfilingSampler)) { outerPassData.material = s_VrsResources.visualizationMaterial; if (sriTextureHandle.IsValid()) outerPassData.source = sriTextureHandle; else outerPassData.source = renderGraph.defaultResources.blackTexture; outerPassData.lut = renderGraph.ImportBuffer(s_VrsResources.visualizationLutBuffer); outerPassData.dummy = renderGraph.defaultResources.blackTexture; outerPassData.visualizationParams = new Vector4( 1.0f / s_VrsResources.tileSize.x, 1.0f / s_VrsResources.tileSize.y, 0, 0);; builder.UseTexture(outerPassData.source); builder.UseBuffer(outerPassData.lut); builder.UseTexture(outerPassData.dummy); builder.SetRenderAttachment(colorMaskHandle, 0); builder.AllowPassCulling(false); builder.SetRenderFunc((VisualizationPassData innerPassData, RasterGraphContext context) => { // must setup blitter source via the material: it's a typed texture (uint) innerPassData.material.SetTexture(VrsShaders.s_ShadingRateImage, innerPassData.source); innerPassData.material.SetBuffer(VrsShaders.s_VisualizationLut, innerPassData.lut); innerPassData.material.SetVector(VrsShaders.s_VisualizationParams, innerPassData.visualizationParams); Blitter.BlitTexture(context.cmd, innerPassData.dummy, new Vector4(1, 1, 0, 0), innerPassData.material, 0); }); } } static void ConversionDispatch(ComputeCommandBuffer cmd, ConversionPassData conversionPassData) { var disableTexture2dXArray = new LocalKeyword(conversionPassData.computeShader, VrsShaders.k_DisableTexture2dXArray); if (conversionPassData.mainTexDimension == TextureDimension.Tex2DArray) cmd.DisableKeyword(conversionPassData.computeShader, disableTexture2dXArray); else cmd.EnableKeyword(conversionPassData.computeShader, disableTexture2dXArray); var yFlip = new LocalKeyword(conversionPassData.computeShader, VrsShaders.k_YFlip); if (conversionPassData.yFlip) cmd.EnableKeyword(conversionPassData.computeShader, yFlip); else cmd.DisableKeyword(conversionPassData.computeShader, yFlip); cmd.SetComputeTextureParam(conversionPassData.computeShader, conversionPassData.kernelIndex, VrsShaders.s_MainTex, conversionPassData.mainTexHandle); cmd.SetComputeBufferParam(conversionPassData.computeShader, conversionPassData.kernelIndex, VrsShaders.s_MainTexLut, conversionPassData.mainTexLutHandle); cmd.SetComputeBufferParam(conversionPassData.computeShader, conversionPassData.kernelIndex, VrsShaders.s_ShadingRateNativeValues, conversionPassData.validatedShadingRateFragmentSizeHandle); cmd.SetComputeTextureParam(conversionPassData.computeShader, conversionPassData.kernelIndex, VrsShaders.s_ShadingRateImage, conversionPassData.sriTextureHandle); cmd.SetComputeVectorParam(conversionPassData.computeShader, VrsShaders.s_ScaleBias, conversionPassData.scaleBias); cmd.DispatchCompute(conversionPassData.computeShader, conversionPassData.kernelIndex, conversionPassData.dispatchSize.x, conversionPassData.dispatchSize.y, 1); } /// /// Converts a color mask texture to a shading rate image. /// Use this function to perform the conversion without the RenderGraph. /// /// CommandBuffer used for the compute dispatch. /// Shading rate images to convert to. /// Texture to convert from. /// True if shading rate image should be generated flipped. public static void ColorMaskTextureToShadingRateImageDispatch(CommandBuffer cmd, RTHandle sriDestination, Texture colorMaskSource, bool yFlip = true) { if (sriDestination == null) { Debug.LogError("ColorMaskTextureToShadingRateImageDispatch: VRS destination shading rate texture is null."); return; } if (colorMaskSource == null) { Debug.LogError("ColorMaskTextureToShadingRateImageDispatch: VRS source color texture is null."); return; } if (!IsInitialized()) { Debug.LogError("ColorMaskTextureToShadingRateImageDispatch: VRS is not initialized."); return; } var computeShader = s_VrsResources.textureComputeShader; var kernelIndex = s_VrsResources.textureReduceKernel; var colorLutBuffer = s_VrsResources.conversionLutBuffer; var validatedShadingRateFragmentSizeBuffer = s_VrsResources.validatedShadingRateFragmentSizeBuffer; int sriDestWidth = sriDestination.rt.width; int sriDestHeight = sriDestination.rt.height; var scaleBias = new Vector4() { x = 1.0f / (sriDestWidth * s_VrsResources.tileSize.x), y = 1.0f / (sriDestHeight * s_VrsResources.tileSize.y), z = sriDestWidth, w = sriDestHeight, }; var dispatchSize = new Vector2Int(sriDestWidth, sriDestHeight); var disableTexture2dXArray = new LocalKeyword(computeShader, VrsShaders.k_DisableTexture2dXArray); if (colorMaskSource?.dimension == TextureDimension.Tex2DArray) cmd.DisableKeyword(computeShader, disableTexture2dXArray); else cmd.EnableKeyword(computeShader, disableTexture2dXArray); var yFlipKeyword = new LocalKeyword(computeShader, VrsShaders.k_YFlip); if (yFlip) cmd.EnableKeyword(computeShader, yFlipKeyword); else cmd.DisableKeyword(computeShader, yFlipKeyword); cmd.SetComputeTextureParam(computeShader, kernelIndex, VrsShaders.s_MainTex, colorMaskSource); cmd.SetComputeBufferParam(computeShader, kernelIndex, VrsShaders.s_MainTexLut, colorLutBuffer); cmd.SetComputeBufferParam(computeShader, kernelIndex, VrsShaders.s_ShadingRateNativeValues, validatedShadingRateFragmentSizeBuffer); cmd.SetComputeTextureParam(computeShader, kernelIndex, VrsShaders.s_ShadingRateImage, sriDestination); cmd.SetComputeVectorParam(computeShader, VrsShaders.s_ScaleBias, scaleBias); cmd.DispatchCompute(computeShader, kernelIndex, dispatchSize.x, dispatchSize.y, 1); } /// /// Converts a shading rate image to a color mask texture. /// Use this function to perform the conversion without the RenderGraph. /// /// CommandBuffer used for the compute dispatch. /// Shading rate images to convert from. /// Texture to convert to. public static void ShadingRateImageToColorMaskTextureBlit(CommandBuffer cmd, RTHandle sriSource, RTHandle colorMaskDestination) { if (sriSource == null) { Debug.LogError("ShadingRateImageToColorMaskTextureBlit: VRS source shading rate texture is null."); return; } if (colorMaskDestination == null) { Debug.LogError("ShadingRateImageToColorMaskTextureBlit: VRS destination color texture is null."); return; } if(!IsInitialized()) { Debug.LogError("ShadingRateImageToColorMaskTextureBlit: VRS is not initialized."); return; } RTHandle source = sriSource; RTHandle destination = colorMaskDestination; var material = s_VrsResources.visualizationMaterial; var lut = s_VrsResources.visualizationLutBuffer; Vector4 visualizationParams = new Vector4( 1.0f / s_VrsResources.tileSize.x, 1.0f / s_VrsResources.tileSize.y, 0, 0); material.SetTexture(VrsShaders.s_ShadingRateImage, source); material.SetBuffer(VrsShaders.s_VisualizationLut, lut); material.SetVector(VrsShaders.s_VisualizationParams, visualizationParams); CoreUtils.SetRenderTarget(cmd, destination); Blitter.BlitTexture(cmd, new Vector4(1, 1, 0, 0), material, 0); } } }