using System; using System.Collections.Generic; using System.Diagnostics; using UnityEngine.Rendering; using UnityEngine.Rendering.PostProcessing; namespace UnityEngine.Experimental.Rendering.LightweightPipeline { public sealed class ScriptableRenderer { // Lights are culled per-object. In platforms that don't use StructuredBuffer // the engine will set 4 light indices in the following constant unity_4LightIndices0 // Additionally the engine set unity_4LightIndices1 but LWRP doesn't use that. const int k_MaxConstantLocalLights = 4; // LWRP uses a fixed constant buffer to hold light data. This must match the value of // MAX_VISIBLE_LIGHTS 16 in Input.hlsl const int k_MaxVisibleLocalLights = 16; const int k_MaxVertexLights = 4; public int maxSupportedLocalLightsPerPass { get { return useComputeBufferForPerObjectLightIndices ? k_MaxVisibleLocalLights : k_MaxConstantLocalLights; } } // TODO: Profile performance of using ComputeBuffer on mobiles that support it public static bool useComputeBufferForPerObjectLightIndices { get { // TODO: Graphics Emulation are breaking StructuredBuffers disabling it for now until // we have a fix for it return false; // return SystemInfo.supportsComputeShaders && // SystemInfo.graphicsDeviceType != GraphicsDeviceType.OpenGLCore && // !Application.isMobilePlatform && // Application.platform != RuntimePlatform.WebGLPlayer; } } public int maxVisibleLocalLights { get { return k_MaxVisibleLocalLights; } } public int maxSupportedVertexLights { get { return k_MaxVertexLights; } } public PostProcessRenderContext postProcessingContext { get; private set; } public ComputeBuffer perObjectLightIndices { get; private set; } static Mesh s_FullscreenMesh = null; static Mesh fullscreenMesh { get { if (s_FullscreenMesh != null) return s_FullscreenMesh; float topV = 1.0f; float bottomV = 0.0f; Mesh mesh = new Mesh { name = "Fullscreen Quad" }; mesh.SetVertices(new List { new Vector3(-1.0f, -1.0f, 0.0f), new Vector3(-1.0f, 1.0f, 0.0f), new Vector3(1.0f, -1.0f, 0.0f), new Vector3(1.0f, 1.0f, 0.0f) }); mesh.SetUVs(0, new List { new Vector2(0.0f, bottomV), new Vector2(0.0f, topV), new Vector2(1.0f, bottomV), new Vector2(1.0f, topV) }); mesh.SetIndices(new[] { 0, 1, 2, 2, 1, 3 }, MeshTopology.Triangles, 0, false); mesh.UploadMeshData(true); return mesh; } } List m_ActiveRenderPassQueue = new List(); List m_LegacyShaderPassNames = new List() { new ShaderPassName("Always"), new ShaderPassName("ForwardBase"), new ShaderPassName("PrepassBase"), new ShaderPassName("Vertex"), new ShaderPassName("VertexLMRGBM"), new ShaderPassName("VertexLM"), }; const string k_ReleaseResourcesTag = "Release Resources"; readonly Material[] m_Materials; public ScriptableRenderer(LightweightPipelineAsset pipelineAsset) { m_Materials = new[] { CoreUtils.CreateEngineMaterial("Hidden/InternalErrorShader"), CoreUtils.CreateEngineMaterial(pipelineAsset.copyDepthShader), CoreUtils.CreateEngineMaterial(pipelineAsset.samplingShader), CoreUtils.CreateEngineMaterial(pipelineAsset.blitShader), CoreUtils.CreateEngineMaterial(pipelineAsset.screenSpaceShadowShader), }; postProcessingContext = new PostProcessRenderContext(); } public void Dispose() { if (perObjectLightIndices != null) { perObjectLightIndices.Release(); perObjectLightIndices = null; } for (int i = 0; i < m_Materials.Length; ++i) CoreUtils.Destroy(m_Materials[i]); } public void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { for (int i = 0; i < m_ActiveRenderPassQueue.Count; ++i) m_ActiveRenderPassQueue[i].Execute(this, context, ref renderingData); DisposePasses(ref context); } public Material GetMaterial(MaterialHandles handle) { int handleID = (int)handle; if (handleID >= m_Materials.Length) { Debug.LogError(string.Format("Material {0} is not registered.", Enum.GetName(typeof(MaterialHandles), handleID))); return null; } return m_Materials[handleID]; } public void Clear() { m_ActiveRenderPassQueue.Clear(); } public void EnqueuePass(ScriptableRenderPass pass) { m_ActiveRenderPassQueue.Add(pass); } public void SetupPerObjectLightIndices(ref CullResults cullResults, ref LightData lightData) { if (lightData.totalAdditionalLightsCount == 0) return; List visibleLights = lightData.visibleLights; int[] perObjectLightIndexMap = cullResults.GetLightIndexMap(); int directionalLightCount = 0; // Disable all directional lights from the perobject light indices // Pipeline handles them globally for (int i = 0; i < visibleLights.Count; ++i) { VisibleLight light = visibleLights[i]; if (light.lightType == LightType.Directional) { perObjectLightIndexMap[i] = -1; ++directionalLightCount; } else perObjectLightIndexMap[i] -= directionalLightCount; } cullResults.SetLightIndexMap(perObjectLightIndexMap); // if not using a compute buffer, engine will set indices in 2 vec4 constants // unity_4LightIndices0 and unity_4LightIndices1 if (useComputeBufferForPerObjectLightIndices) { int lightIndicesCount = cullResults.GetLightIndicesCount(); if (lightIndicesCount > 0) { if (perObjectLightIndices == null) { perObjectLightIndices = new ComputeBuffer(lightIndicesCount, sizeof(int)); } else if (perObjectLightIndices.count < lightIndicesCount) { perObjectLightIndices.Release(); perObjectLightIndices = new ComputeBuffer(lightIndicesCount, sizeof(int)); } cullResults.FillLightIndices(perObjectLightIndices); } } } public void RenderPostProcess(CommandBuffer cmd, ref CameraData cameraData, RenderTextureFormat colorFormat, RenderTargetIdentifier source, RenderTargetIdentifier dest, bool opaqueOnly) { Camera camera = cameraData.camera; postProcessingContext.Reset(); postProcessingContext.camera = camera; postProcessingContext.source = source; postProcessingContext.sourceFormat = colorFormat; postProcessingContext.destination = dest; postProcessingContext.command = cmd; postProcessingContext.flip = !cameraData.isStereoEnabled && camera.targetTexture == null; if (opaqueOnly) cameraData.postProcessLayer.RenderOpaqueOnly(postProcessingContext); else cameraData.postProcessLayer.Render(postProcessingContext); } [Conditional("DEVELOPMENT_BUILD"), Conditional("UNITY_EDITOR")] public void RenderObjectsWithError(ScriptableRenderContext context, ref CullResults cullResults, Camera camera, FilterRenderersSettings filterSettings, SortFlags sortFlags) { Material errorMaterial = GetMaterial(MaterialHandles.Error); if (errorMaterial != null) { DrawRendererSettings errorSettings = new DrawRendererSettings(camera, m_LegacyShaderPassNames[0]); for (int i = 1; i < m_LegacyShaderPassNames.Count; ++i) errorSettings.SetShaderPassName(i, m_LegacyShaderPassNames[i]); errorSettings.sorting.flags = sortFlags; errorSettings.rendererConfiguration = RendererConfiguration.None; errorSettings.SetOverrideMaterial(errorMaterial, 0); context.DrawRenderers(cullResults.visibleRenderers, ref errorSettings, filterSettings); } } public static RenderTextureDescriptor CreateRenderTextureDescriptor(ref CameraData cameraData, float scaler = 1.0f) { Camera camera = cameraData.camera; RenderTextureDescriptor desc; float renderScale = cameraData.renderScale; if (cameraData.isStereoEnabled) { return XRGraphicsConfig.eyeTextureDesc; } else { desc = new RenderTextureDescriptor(camera.pixelWidth, camera.pixelHeight); } desc.colorFormat = cameraData.isHdrEnabled ? RenderTextureFormat.DefaultHDR : RenderTextureFormat.Default; desc.enableRandomWrite = false; desc.sRGB = true; desc.width = (int)((float)desc.width * renderScale * scaler); desc.height = (int)((float)desc.height * renderScale * scaler); return desc; } public static bool RequiresIntermediateColorTexture(ref CameraData cameraData, RenderTextureDescriptor baseDescriptor) { if (cameraData.isOffscreenRender) return false; bool isScaledRender = !Mathf.Approximately(cameraData.renderScale, 1.0f); bool isTargetTexture2DArray = baseDescriptor.dimension == TextureDimension.Tex2DArray; return cameraData.isSceneViewCamera || isScaledRender || cameraData.isHdrEnabled || cameraData.postProcessEnabled || cameraData.requiresOpaqueTexture || isTargetTexture2DArray || !cameraData.isDefaultViewport; } public static ClearFlag GetCameraClearFlag(Camera camera) { ClearFlag clearFlag = ClearFlag.None; CameraClearFlags cameraClearFlags = camera.clearFlags; if (cameraClearFlags != CameraClearFlags.Nothing) { clearFlag |= ClearFlag.Depth; if (cameraClearFlags == CameraClearFlags.Color || cameraClearFlags == CameraClearFlags.Skybox) clearFlag |= ClearFlag.Color; } return clearFlag; } public static RendererConfiguration GetRendererConfiguration(int localLightsCount) { RendererConfiguration configuration = RendererConfiguration.PerObjectReflectionProbes | RendererConfiguration.PerObjectLightmaps | RendererConfiguration.PerObjectLightProbe; if (localLightsCount > 0) { if (useComputeBufferForPerObjectLightIndices) configuration |= RendererConfiguration.ProvideLightIndices; else configuration |= RendererConfiguration.PerObjectLightIndices8; } return configuration; } public static void RenderFullscreenQuad(CommandBuffer cmd, Material material, MaterialPropertyBlock properties = null, int shaderPassId = 0) { cmd.DrawMesh(fullscreenMesh, Matrix4x4.identity, material, 0, shaderPassId, properties); } public static void CopyTexture(CommandBuffer cmd, RenderTargetIdentifier source, RenderTargetIdentifier dest, Material material) { // TODO: In order to issue a copyTexture we need to also check if source and dest have same size //if (SystemInfo.copyTextureSupport != CopyTextureSupport.None) // cmd.CopyTexture(source, dest); //else cmd.Blit(source, dest, material); } void DisposePasses(ref ScriptableRenderContext context) { CommandBuffer cmd = CommandBufferPool.Get(k_ReleaseResourcesTag); for (int i = 0; i < m_ActiveRenderPassQueue.Count; ++i) m_ActiveRenderPassQueue[i].FrameCleanup(cmd); context.ExecuteCommandBuffer(cmd); CommandBufferPool.Release(cmd); } } }