/// <summary> /// This is a subpart of effect permutation preparation: /// set the shader classes that are going to be responsible to compute extended render target colors. /// </summary> /// <param name="context"></param> private void PrepareRenderTargetExtensionsMixins(RenderDrawContext context) { var renderEffectKey = RenderEffectKey; var renderEffects = RenderData.GetData(renderEffectKey); // TODO dispatcher foreach (var node in RenderNodes) { var renderNode = node; var renderObject = renderNode.RenderObject; // Get RenderEffect var staticObjectNode = renderObject.StaticObjectNode; var staticEffectObjectNode = staticObjectNode * EffectPermutationSlotCount + effectSlots[renderNode.RenderStage.Index].Index; var renderEffect = renderEffects[staticEffectObjectNode]; if (renderEffect != null) { var renderStage = renderNode.RenderStage; var renderStageShaderSource = renderStage.OutputValidator.ShaderSource; if (renderStageShaderSource != null) { renderEffect.EffectValidator.ValidateParameter(XenkoEffectBaseKeys.RenderTargetExtensions, renderStageShaderSource); } } } }
/// <param name="context"></param> /// <inheritdoc/> public override void PrepareEffectPermutationsImpl(RenderThreadContext context) { var renderEffects = RenderData.GetData(RenderEffectKey); foreach (var renderObject in RenderObjects) { var staticObjectNode = renderObject.StaticObjectNode; for (int i = 0; i < EffectPermutationSlotCount; ++i) { var staticEffectObjectNode = staticObjectNode * EffectPermutationSlotCount + i; var renderEffect = renderEffects[staticEffectObjectNode]; var renderSkybox = (RenderSkybox)renderObject; // Skip effects not used during this frame if (renderEffect == null || !renderEffect.IsUsedDuringThisFrame(RenderSystem)) { continue; } var parameters = renderSkybox.Background == SkyboxBackground.Irradiance ? renderSkybox.Skybox.DiffuseLightingParameters : renderSkybox.Skybox.Parameters; var shader = parameters.Get(SkyboxKeys.Shader); if (shader == null) { renderEffect.EffectValidator.ShouldSkip = true; } renderEffect.EffectValidator.ValidateParameter(SkyboxKeys.Shader, shader); } } transformRenderFeature.PrepareEffectPermutations(context); }
/// <inheritdoc/> public override void Draw(RenderDrawContext context, RenderView renderView, RenderViewStage renderViewStage, int startIndex, int endIndex) { var commandList = context.CommandList; var renderParticleNodeData = RenderData.GetData(renderParticleNodeKey); // TODO: stackalloc? var descriptorSetsLocal = descriptorSets.Value; if (descriptorSetsLocal == null || descriptorSetsLocal.Length < EffectDescriptorSetSlotCount) { descriptorSetsLocal = descriptorSets.Value = new DescriptorSet[EffectDescriptorSetSlotCount]; } for (var index = startIndex; index < endIndex; index++) { var renderNodeReference = renderViewStage.SortedRenderNodes[index].RenderNode; // Get effect var renderEffect = GetRenderNode(renderNodeReference).RenderEffect; if (renderEffect.Effect == null) { continue; } // Get the extra node data var nodeData = renderParticleNodeData[renderNodeReference]; if (nodeData.IndexCount <= 0) { continue; } var resourceGroupOffset = ComputeResourceGroupOffset(renderNodeReference); // Update cbuffer renderEffect.Reflection.BufferUploader.Apply(commandList, ResourceGroupPool, resourceGroupOffset); // Bind descriptor sets for (int i = 0; i < descriptorSetsLocal.Length; ++i) { var resourceGroup = ResourceGroupPool[resourceGroupOffset++]; if (resourceGroup != null) { descriptorSetsLocal[i] = resourceGroup.DescriptorSet; } } commandList.SetPipelineState(renderEffect.PipelineState); commandList.SetDescriptorSets(0, descriptorSetsLocal); // Bind the buffers and draw commandList.SetVertexBuffer(0, nodeData.VertexBuffer, nodeData.VertexBufferOffset, nodeData.VertexBufferStride); commandList.SetIndexBuffer(nodeData.IndexBuffer, nodeData.IndexBufferOffset, ParticleBufferContext.IndexStride != sizeof(short)); commandList.DrawIndexed(nodeData.IndexCount, 0); } }
/// <inheritdoc/> public override void PrepareEffectPermutationsImpl(RenderDrawContext context) { base.PrepareEffectPermutationsImpl(context); var renderEffects = RenderData.GetData(renderEffectKey); int effectSlotCount = EffectPermutationSlotCount; // Update existing materials foreach (var material in allMaterialInfos) { material.Key.Setup(context.RenderContext); } for (int k = 0; k < RenderObjects.Count; k++) { var renderObject = RenderObjects[k]; var staticObjectNode = renderObject.StaticObjectNode; var renderParticleEmitter = (RenderParticleEmitter)renderObject; var material = renderParticleEmitter.ParticleEmitter.Material; var materialInfo = renderParticleEmitter.ParticleMaterialInfo; for (int i = 0; i < effectSlotCount; ++i) { var staticEffectObjectNode = staticObjectNode * effectSlotCount + i; var renderEffect = renderEffects[staticEffectObjectNode]; // Skip effects not used during this frame if (renderEffect == null || !renderEffect.IsUsedDuringThisFrame(RenderSystem)) { continue; } if (materialInfo == null || materialInfo.Material != material) { // First time this material is initialized, let's create associated info if (!allMaterialInfos.TryGetValue(material, out materialInfo)) { materialInfo = new ParticleMaterialInfo(material); allMaterialInfos.Add(material, materialInfo); } renderParticleEmitter.ParticleMaterialInfo = materialInfo; // Update new materials material.Setup(context.RenderContext); } // TODO: Iterate PermuatationParameters automatically? material.ValidateEffect(context.RenderContext, ref renderEffect.EffectValidator); } } }
/// <summary> /// Builds the shared vertex and index buffers used by the particle systems /// </summary> /// <param name="renderDrawContext"><see cref="RenderDrawContext"/> to access the command list and the graphics context</param> private void BuildParticleBuffers(RenderDrawContext renderDrawContext) { // Build particle buffers var commandList = renderDrawContext.CommandList; // Build the vertex buffer with particles data if (particleBufferContext.VertexBuffer == null || particleBufferContext.VertexBufferSize == 0) { return; } var renderParticleNodeData = RenderData.GetData(renderParticleNodeKey); var mappedVertices = commandList.MapSubresource(particleBufferContext.VertexBuffer, 0, MapMode.WriteNoOverwrite, false, 0, particleBufferContext.VertexBufferSize); var sharedBufferPtr = mappedVertices.DataBox.DataPointer; //for (int renderNodeIndex = 0; renderNodeIndex < RenderNodes.Count; renderNodeIndex++) Dispatcher.For(0, RenderNodes.Count, (renderNodeIndex) => { var renderNode = RenderNodes[renderNodeIndex]; var renderParticleEmitter = (RenderParticleEmitter)renderNode.RenderObject; var renderNodeReference = new RenderNodeReference(renderNodeIndex); var nodeData = renderParticleNodeData[renderNodeReference]; if (nodeData.IndexCount <= 0) { return; // Nothing to draw, nothing to build } nodeData.VertexBuffer = particleBufferContext.VertexBuffer; nodeData.IndexBuffer = particleBufferContext.IndexBuffer; renderParticleNodeData[renderNodeReference] = nodeData; Matrix viewInverse; // TODO Build this per view, not per node!!! Matrix.Invert(ref renderNode.RenderView.View, out viewInverse); renderParticleEmitter.ParticleEmitter.BuildVertexBuffer(sharedBufferPtr + nodeData.VertexBufferOffset, ref viewInverse, ref renderNode.RenderView.ViewProjection); }); commandList.UnmapSubresource(mappedVertices); }
/// <inheritdoc/> public override unsafe void Draw(RenderDrawContext context, RenderView renderView, RenderViewStage renderViewStage, int startIndex, int endIndex) { var commandList = context.CommandList; // register all texture usage foreach (var renderObject in RenderObjects) { var renderParticleEmitter = (RenderParticleEmitter)renderObject; Context.StreamingManager?.StreamResources(renderParticleEmitter.ParticleEmitter.Material.Parameters); } // Per view - this code was moved here from Prepare(...) so that we can apply the correct Viewport { var view = renderView; var viewFeature = view.Features[Index]; Matrix.Multiply(ref view.View, ref view.Projection, out view.ViewProjection); // Copy ViewProjection to PerFrame cbuffer foreach (var viewLayout in viewFeature.Layouts) { var resourceGroup = viewLayout.Entries[view.Index].Resources; var mappedCB = resourceGroup.ConstantBuffer.Data; // PerView constant buffer var perViewOffset = viewLayout.GetConstantBufferOffset(this.perViewCBufferOffset); if (perViewOffset != -1) { var perView = (ParticleUtilitiesPerView *)((byte *)mappedCB + perViewOffset); perView->ViewMatrix = view.View; perView->ProjectionMatrix = view.Projection; perView->ViewProjectionMatrix = view.ViewProjection; perView->ViewFrustum = new Vector4(view.ViewSize.X, view.ViewSize.Y, view.NearClipPlane, view.FarClipPlane); perView->Viewport = new Vector4(0, 0, ((float)context.CommandList.Viewport.Width) / ((float)context.CommandList.RenderTarget.Width), ((float)context.CommandList.Viewport.Height) / ((float)context.CommandList.RenderTarget.Height)); } } } var renderParticleNodeData = RenderData.GetData(renderParticleNodeKey); // TODO: stackalloc? var descriptorSetsLocal = descriptorSets.Value; if (descriptorSetsLocal == null || descriptorSetsLocal.Length < EffectDescriptorSetSlotCount) { descriptorSetsLocal = descriptorSets.Value = new DescriptorSet[EffectDescriptorSetSlotCount]; } for (var index = startIndex; index < endIndex; index++) { var renderNodeReference = renderViewStage.SortedRenderNodes[index].RenderNode; // Get effect var renderEffect = GetRenderNode(renderNodeReference).RenderEffect; if (renderEffect.Effect == null) { continue; } // Get the extra node data var nodeData = renderParticleNodeData[renderNodeReference]; if (nodeData.IndexCount <= 0) { continue; } var resourceGroupOffset = ComputeResourceGroupOffset(renderNodeReference); // Update cbuffer renderEffect.Reflection.BufferUploader.Apply(commandList, ResourceGroupPool, resourceGroupOffset); // Bind descriptor sets for (int i = 0; i < descriptorSetsLocal.Length; ++i) { var resourceGroup = ResourceGroupPool[resourceGroupOffset++]; if (resourceGroup != null) { descriptorSetsLocal[i] = resourceGroup.DescriptorSet; } } commandList.SetPipelineState(renderEffect.PipelineState); commandList.SetDescriptorSets(0, descriptorSetsLocal); // Bind the buffers and draw commandList.SetVertexBuffer(0, nodeData.VertexBuffer, nodeData.VertexBufferOffset, nodeData.VertexBufferStride); commandList.SetIndexBuffer(nodeData.IndexBuffer, nodeData.IndexBufferOffset, ParticleBufferContext.IndexStride != sizeof(short)); commandList.DrawIndexed(nodeData.IndexCount, 0); } }
/// <inheritdoc/> public override unsafe void Prepare(RenderDrawContext context) { // Inspect each RenderObject (= ParticleEmitter) to determine if its required vertex buffer size has changed foreach (var renderObject in RenderObjects) { var renderParticleEmitter = (RenderParticleEmitter)renderObject; renderParticleEmitter.ParticleEmitter.PrepareForDraw(out renderParticleEmitter.HasVertexBufferChanged, out renderParticleEmitter.VertexSize, out renderParticleEmitter.VertexCount); // TODO: ParticleMaterial should set this up var materialInfo = (ParticleMaterialInfo)renderParticleEmitter.ParticleMaterialInfo; var colorShade = renderParticleEmitter.Color.ToColorSpace(context.GraphicsDevice.ColorSpace); materialInfo?.Material.Parameters.Set(ParticleBaseKeys.ColorScale, colorShade); } // Calculate the total vertex buffer size required int totalVertexBufferSize = 0; int highestIndexCount = 0; var renderParticleNodeData = RenderData.GetData(renderParticleNodeKey); // Reset pipeline states if necessary for (int renderNodeIndex = 0; renderNodeIndex < RenderNodes.Count; renderNodeIndex++) { var renderNode = RenderNodes[renderNodeIndex]; var renderParticleEmitter = (RenderParticleEmitter)renderNode.RenderObject; if (renderParticleEmitter.HasVertexBufferChanged) { // Reset pipeline state, so input layout is regenerated if (renderNode.RenderEffect != null) { renderNode.RenderEffect.PipelineState = null; } } // Write some attributes back which we will need for rendering later var vertexBuilder = renderParticleEmitter.ParticleEmitter.VertexBuilder; var newNodeData = new RenderAttributesPerNode { VertexBufferOffset = totalVertexBufferSize, VertexBufferSize = renderParticleEmitter.VertexSize * renderParticleEmitter.VertexCount, VertexBufferStride = renderParticleEmitter.VertexSize, IndexCount = vertexBuilder.LivingQuads * vertexBuilder.IndicesPerQuad, }; renderParticleNodeData[new RenderNodeReference(renderNodeIndex)] = newNodeData; totalVertexBufferSize += newNodeData.VertexBufferSize; if (newNodeData.IndexCount > highestIndexCount) { highestIndexCount = newNodeData.IndexCount; } } base.Prepare(context); // Assign descriptor sets to each render node var resourceGroupPool = ResourceGroupPool; for (int renderNodeIndex = 0; renderNodeIndex < RenderNodes.Count; renderNodeIndex++) { var renderNodeReference = new RenderNodeReference(renderNodeIndex); var renderNode = RenderNodes[renderNodeIndex]; var renderParticleEmitter = (RenderParticleEmitter)renderNode.RenderObject; // Ignore fallback effects if (renderNode.RenderEffect.State != RenderEffectState.Normal) { continue; } // Collect materials and create associated MaterialInfo (includes reflection) first time // TODO: We assume same material will generate same ResourceGroup (i.e. same resources declared in same order) // Need to offer some protection if this invariant is violated (or support it if it can actually happen in real scenario) var material = renderParticleEmitter.ParticleEmitter.Material; var materialInfo = renderParticleEmitter.ParticleMaterialInfo; var materialParameters = material.Parameters; if (!MaterialRenderFeature.UpdateMaterial(RenderSystem, context, materialInfo, perMaterialDescriptorSetSlot.Index, renderNode.RenderEffect, materialParameters)) { continue; } var descriptorSetPoolOffset = ComputeResourceGroupOffset(renderNodeReference); resourceGroupPool[descriptorSetPoolOffset + perMaterialDescriptorSetSlot.Index] = materialInfo.Resources; } particleBufferContext.AllocateBuffers(context, totalVertexBufferSize, highestIndexCount); BuildParticleBuffers(context); }
/// <inheritdoc/> public override void Prepare(RenderDrawContext context) { EffectObjectNodes.Clear(false); // Make sure descriptor set pool is large enough var expectedDescriptorSetPoolSize = RenderNodes.Count * effectDescriptorSetSlots.Count; if (ResourceGroupPool.Length < expectedDescriptorSetPoolSize) { Array.Resize(ref ResourceGroupPool, expectedDescriptorSetPoolSize); } // Allocate PerFrame, PerView and PerDraw resource groups and constant buffers var renderEffects = RenderData.GetData(RenderEffectKey); int effectSlotCount = EffectPermutationSlotCount; foreach (var view in RenderSystem.Views) { var viewFeature = view.Features[Index]; Dispatcher.ForEach(viewFeature.RenderNodes, () => prepareThreadContext.Value, (renderNodeReference, batch) => { var threadContext = batch.Context; var renderNode = this.GetRenderNode(renderNodeReference); var renderObject = renderNode.RenderObject; // Get RenderEffect var staticObjectNode = renderObject.StaticObjectNode; var staticEffectObjectNode = staticObjectNode * effectSlotCount + effectSlots[renderNode.RenderStage.Index].Index; var renderEffect = renderEffects[staticEffectObjectNode]; // Not compiled yet? if (renderEffect.Effect == null) { renderNode.RenderEffect = renderEffect; renderNode.EffectObjectNode = EffectObjectNodeReference.Invalid; renderNode.Resources = null; RenderNodes[renderNodeReference.Index] = renderNode; return; } var renderEffectReflection = renderEffect.Reflection; // PerView resources/cbuffer var viewLayout = renderEffectReflection.PerViewLayout; if (viewLayout != null) { var viewCount = RenderSystem.Views.Count; if (viewLayout.Entries?.Length < viewCount) { // TODO: Should this be a first loop? lock (viewLayout) { if (viewLayout.Entries?.Length < viewCount) { var newEntries = new ResourceGroupEntry[viewCount]; for (int index = 0; index < viewLayout.Entries.Length; index++) { newEntries[index] = viewLayout.Entries[index]; } for (int index = viewLayout.Entries.Length; index < viewCount; index++) { newEntries[index].Resources = new ResourceGroup(); } viewLayout.Entries = newEntries; } } } if (viewLayout.Entries[view.Index].MarkAsUsed(RenderSystem)) { threadContext.ResourceGroupAllocator.PrepareResourceGroup(viewLayout, BufferPoolAllocationType.UsedMultipleTime, viewLayout.Entries[view.Index].Resources); // Register it in list of view layouts to update for this frame viewFeature.Layouts.Add(viewLayout); } } // PerFrame resources/cbuffer var frameLayout = renderEffect.Reflection.PerFrameLayout; if (frameLayout != null && frameLayout.Entry.MarkAsUsed(RenderSystem)) { threadContext.ResourceGroupAllocator.PrepareResourceGroup(frameLayout, BufferPoolAllocationType.UsedMultipleTime, frameLayout.Entry.Resources); // Register it in list of view layouts to update for this frame FrameLayouts.Add(frameLayout); } // PerDraw resources/cbuffer // Get nodes var viewObjectNode = GetViewObjectNode(renderNode.ViewObjectNode); // Allocate descriptor set renderNode.Resources = threadContext.ResourceGroupAllocator.AllocateResourceGroup(); if (renderEffectReflection.PerDrawLayout != null) { threadContext.ResourceGroupAllocator.PrepareResourceGroup(renderEffectReflection.PerDrawLayout, BufferPoolAllocationType.UsedOnce, renderNode.Resources); } // Create EffectObjectNode var effectObjectNodeIndex = EffectObjectNodes.Add(new EffectObjectNode(renderEffect, viewObjectNode.ObjectNode)); // Link to EffectObjectNode (created right after) // TODO: rewrite this renderNode.EffectObjectNode = new EffectObjectNodeReference(effectObjectNodeIndex); renderNode.RenderEffect = renderEffect; // Bind well-known descriptor sets var descriptorSetPoolOffset = ComputeResourceGroupOffset(renderNodeReference); ResourceGroupPool[descriptorSetPoolOffset + perFrameDescriptorSetSlot.Index] = frameLayout?.Entry.Resources; ResourceGroupPool[descriptorSetPoolOffset + perViewDescriptorSetSlot.Index] = renderEffect.Reflection.PerViewLayout?.Entries[view.Index].Resources; ResourceGroupPool[descriptorSetPoolOffset + perDrawDescriptorSetSlot.Index] = renderNode.Resources; // Create resource group for everything else in case of fallback effects if (renderEffect.State != RenderEffectState.Normal && renderEffect.FallbackParameters != null) { if (renderEffect.FallbackParameterUpdater.ResourceGroups == null) { // First time renderEffect.FallbackParameterUpdater = new EffectParameterUpdater(renderEffect.Reflection.FallbackUpdaterLayout, renderEffect.FallbackParameters); } renderEffect.FallbackParameterUpdater.Update(RenderSystem.GraphicsDevice, threadContext.ResourceGroupAllocator, renderEffect.FallbackParameters); var fallbackResourceGroupMapping = renderEffect.Reflection.FallbackResourceGroupMapping; for (int i = 0; i < fallbackResourceGroupMapping.Length; ++i) { ResourceGroupPool[descriptorSetPoolOffset + fallbackResourceGroupMapping[i]] = renderEffect.FallbackParameterUpdater.ResourceGroups[i]; } } // Compile pipeline state object (if first time or need change) // TODO GRAPHICS REFACTOR how to invalidate if we want to change some state? (setting to null should be fine) if (renderEffect.PipelineState == null) { var mutablePipelineState = batch.MutablePipelineState; var pipelineState = mutablePipelineState.State; pipelineState.SetDefaults(); // Effect pipelineState.EffectBytecode = renderEffect.Effect.Bytecode; pipelineState.RootSignature = renderEffect.Reflection.RootSignature; // Extract outputs from render stage pipelineState.Output = renderNode.RenderStage.Output; pipelineState.RasterizerState.MultisampleCount = renderNode.RenderStage.Output.MultisampleCount; // Bind VAO ProcessPipelineState(Context, renderNodeReference, ref renderNode, renderObject, pipelineState); foreach (var pipelineProcessor in PipelineProcessors) { pipelineProcessor.Process(renderNodeReference, ref renderNode, renderObject, pipelineState); } mutablePipelineState.Update(); renderEffect.PipelineState = mutablePipelineState.CurrentState; } RenderNodes[renderNodeReference.Index] = renderNode; }); viewFeature.RenderNodes.Close(); viewFeature.Layouts.Close(); } EffectObjectNodes.Close(); FrameLayouts.Close(); }
/// <param name="context"></param> /// <inheritdoc/> public override void PrepareEffectPermutations(RenderDrawContext context) { base.PrepareEffectPermutations(context); // TODO: Temporary until we have a better system for handling permutations var renderEffects = RenderData.GetData(RenderEffectKey); int effectSlotCount = EffectPermutationSlotCount; Dispatcher.ForEach(RenderSystem.Views, view => { var viewFeature = view.Features[Index]; Dispatcher.ForEach(viewFeature.RenderNodes, renderNodeReference => { var renderNode = this.GetRenderNode(renderNodeReference); var renderObject = renderNode.RenderObject; // Get RenderEffect var staticObjectNode = renderObject.StaticObjectNode; var staticEffectObjectNode = staticObjectNode * effectSlotCount + effectSlots[renderNode.RenderStage.Index].Index; var renderEffect = renderEffects[staticEffectObjectNode]; var effectSelector = renderObject.ActiveRenderStages[renderNode.RenderStage.Index].EffectSelector; // Create it (first time) or regenerate it if effect changed if (renderEffect == null || effectSelector != renderEffect.EffectSelector) { renderEffect = new RenderEffect(renderObject.ActiveRenderStages[renderNode.RenderStage.Index].EffectSelector); renderEffects[staticEffectObjectNode] = renderEffect; } // Is it the first time this frame that we check this RenderEffect? if (renderEffect.MarkAsUsed(RenderSystem)) { renderEffect.EffectValidator.BeginEffectValidation(); } }); }); // Step1: Perform permutations PrepareEffectPermutationsImpl(context); PrepareRenderTargetExtensionsMixins(context); var currentTime = DateTime.UtcNow; // Step2: Compile effects Dispatcher.ForEach(RenderObjects, renderObject => { //var renderObject = RenderObjects[index]; var staticObjectNode = renderObject.StaticObjectNode; for (int i = 0; i < effectSlotCount; ++i) { var staticEffectObjectNode = staticObjectNode * effectSlotCount + i; var renderEffect = renderEffects[staticEffectObjectNode]; // Skip if not used if (renderEffect == null) { continue; } // Skip reflection update unless a state change requires it renderEffect.IsReflectionUpdateRequired = false; if (!renderEffect.IsUsedDuringThisFrame(RenderSystem)) { continue; } // Skip if nothing changed if (renderEffect.EffectValidator.ShouldSkip) { // Reset pending effect, as it is now obsolete anyway renderEffect.Effect = null; renderEffect.State = RenderEffectState.Skip; } else if (renderEffect.EffectValidator.EndEffectValidation() && (renderEffect.Effect == null || !renderEffect.Effect.SourceChanged) && !(renderEffect.State == RenderEffectState.Error && currentTime >= renderEffect.RetryTime)) { InvalidateEffectPermutation(renderObject, renderEffect); // Still, let's check if there is a pending effect compiling var pendingEffect = renderEffect.PendingEffect; if (pendingEffect == null || !pendingEffect.IsCompleted) { continue; } renderEffect.ClearFallbackParameters(); if (pendingEffect.IsFaulted) { // The effect can fail compilation asynchronously renderEffect.State = RenderEffectState.Error; renderEffect.Effect = ComputeFallbackEffect?.Invoke(renderObject, renderEffect, RenderEffectState.Error); } else { renderEffect.State = RenderEffectState.Normal; renderEffect.Effect = pendingEffect.Result; } renderEffect.PendingEffect = null; } else { // Reset pending effect, as it is now obsolete anyway renderEffect.PendingEffect = null; renderEffect.State = RenderEffectState.Normal; // CompilerParameters are ThreadStatic if (staticCompilerParameters == null) { staticCompilerParameters = new CompilerParameters(); } foreach (var effectValue in renderEffect.EffectValidator.EffectValues) { staticCompilerParameters.SetObject(effectValue.Key, effectValue.Value); } TaskOrResult <Effect> asyncEffect; try { // The effect can fail compilation synchronously asyncEffect = RenderSystem.EffectSystem.LoadEffect(renderEffect.EffectSelector.EffectName, staticCompilerParameters); staticCompilerParameters.Clear(); } catch { staticCompilerParameters.Clear(); renderEffect.ClearFallbackParameters(); renderEffect.State = RenderEffectState.Error; renderEffect.Effect = ComputeFallbackEffect?.Invoke(renderObject, renderEffect, RenderEffectState.Error); continue; } renderEffect.Effect = asyncEffect.Result; if (renderEffect.Effect == null) { // Effect still compiling, let's find if there is a fallback renderEffect.ClearFallbackParameters(); renderEffect.PendingEffect = asyncEffect.Task; renderEffect.State = RenderEffectState.Compiling; renderEffect.Effect = ComputeFallbackEffect?.Invoke(renderObject, renderEffect, RenderEffectState.Compiling); } } renderEffect.IsReflectionUpdateRequired = true; } }); // Step3: Uupdate reflection infos (offset, etc...) foreach (var renderObject in RenderObjects) { var staticObjectNode = renderObject.StaticObjectNode; for (int i = 0; i < effectSlotCount; ++i) { var staticEffectObjectNode = staticObjectNode * effectSlotCount + i; var renderEffect = renderEffects[staticEffectObjectNode]; // Skip if not used if (renderEffect == null || !renderEffect.IsReflectionUpdateRequired) { continue; } var effect = renderEffect.Effect; if (effect == null && renderEffect.State == RenderEffectState.Compiling) { // Need to wait for completion because we have nothing else renderEffect.PendingEffect.Wait(); if (!renderEffect.PendingEffect.IsFaulted) { renderEffect.Effect = effect = renderEffect.PendingEffect.Result; renderEffect.State = RenderEffectState.Normal; } else { renderEffect.ClearFallbackParameters(); renderEffect.State = RenderEffectState.Error; renderEffect.Effect = effect = ComputeFallbackEffect?.Invoke(renderObject, renderEffect, RenderEffectState.Error); } } var effectHashCode = effect != null ? (uint)effect.GetHashCode() : 0; // Effect is last 16 bits renderObject.StateSortKey = (renderObject.StateSortKey & 0xFFFF0000) | (effectHashCode & 0x0000FFFF); if (effect != null) { RenderEffectReflection renderEffectReflection; if (!InstantiatedEffects.TryGetValue(effect, out renderEffectReflection)) { renderEffectReflection = new RenderEffectReflection(); // Build root signature automatically from reflection renderEffectReflection.DescriptorReflection = EffectDescriptorSetReflection.New(RenderSystem.GraphicsDevice, effect.Bytecode, effectDescriptorSetSlots, "PerFrame"); renderEffectReflection.ResourceGroupDescriptions = new ResourceGroupDescription[renderEffectReflection.DescriptorReflection.Layouts.Count]; // Compute ResourceGroup hashes for (int index = 0; index < renderEffectReflection.DescriptorReflection.Layouts.Count; index++) { var descriptorSet = renderEffectReflection.DescriptorReflection.Layouts[index]; if (descriptorSet.Layout == null) { continue; } var constantBufferReflection = effect.Bytecode.Reflection.ConstantBuffers.FirstOrDefault(x => x.Name == descriptorSet.Name); renderEffectReflection.ResourceGroupDescriptions[index] = new ResourceGroupDescription(descriptorSet.Layout, constantBufferReflection); } renderEffectReflection.RootSignature = RootSignature.New(RenderSystem.GraphicsDevice, renderEffectReflection.DescriptorReflection); renderEffectReflection.BufferUploader.Compile(RenderSystem.GraphicsDevice, renderEffectReflection.DescriptorReflection, effect.Bytecode); // Prepare well-known descriptor set layouts renderEffectReflection.PerDrawLayout = CreateDrawResourceGroupLayout(renderEffectReflection.ResourceGroupDescriptions[perDrawDescriptorSetSlot.Index], renderEffect.State); renderEffectReflection.PerFrameLayout = CreateFrameResourceGroupLayout(renderEffectReflection.ResourceGroupDescriptions[perFrameDescriptorSetSlot.Index], renderEffect.State); renderEffectReflection.PerViewLayout = CreateViewResourceGroupLayout(renderEffectReflection.ResourceGroupDescriptions[perViewDescriptorSetSlot.Index], renderEffect.State); InstantiatedEffects.Add(effect, renderEffectReflection); // Notify a new effect has been compiled EffectCompiled?.Invoke(RenderSystem, effect, renderEffectReflection); } // Setup fallback parameters if (renderEffect.State != RenderEffectState.Normal && renderEffectReflection.FallbackUpdaterLayout == null) { // Process all "non standard" layouts var layoutMapping = new int[renderEffectReflection.DescriptorReflection.Layouts.Count - 3]; var layouts = new DescriptorSetLayoutBuilder[renderEffectReflection.DescriptorReflection.Layouts.Count - 3]; int layoutMappingIndex = 0; for (int index = 0; index < renderEffectReflection.DescriptorReflection.Layouts.Count; index++) { var layout = renderEffectReflection.DescriptorReflection.Layouts[index]; // Skip well-known layouts (already handled) if (layout.Name == "PerDraw" || layout.Name == "PerFrame" || layout.Name == "PerView") { continue; } layouts[layoutMappingIndex] = layout.Layout; layoutMapping[layoutMappingIndex++] = index; } renderEffectReflection.FallbackUpdaterLayout = new EffectParameterUpdaterLayout(RenderSystem.GraphicsDevice, effect, layouts); renderEffectReflection.FallbackResourceGroupMapping = layoutMapping; } // Update effect renderEffect.Effect = effect; renderEffect.Reflection = renderEffectReflection; // Invalidate pipeline state (new effect) renderEffect.PipelineState = null; renderEffects[staticEffectObjectNode] = renderEffect; } else { renderEffect.Reflection = RenderEffectReflection.Empty; renderEffect.PipelineState = null; } } } }
/// <inheritdoc/> public override unsafe void Prepare(RenderDrawContext context) { // Inspect each RenderObject (= ParticleEmitter) to determine if its required vertex buffer size has changed foreach (var renderObject in RenderObjects) { var renderParticleEmitter = (RenderParticleEmitter)renderObject; renderParticleEmitter.ParticleEmitter.PrepareForDraw(out renderParticleEmitter.HasVertexBufferChanged, out renderParticleEmitter.VertexSize, out renderParticleEmitter.VertexCount); // TODO: ParticleMaterial should set this up var materialInfo = (ParticleMaterialInfo)renderParticleEmitter.ParticleMaterialInfo; materialInfo?.Material.Parameters.Set(ParticleBaseKeys.ColorScale, renderParticleEmitter.RenderParticleSystem.ParticleSystemComponent.Color); } // Calculate the total vertex buffer size required int totalVertexBufferSize = 0; int highestIndexCount = 0; var renderParticleNodeData = RenderData.GetData(renderParticleNodeKey); // Reset pipeline states if necessary for (int renderNodeIndex = 0; renderNodeIndex < RenderNodes.Count; renderNodeIndex++) { var renderNode = RenderNodes[renderNodeIndex]; var renderParticleEmitter = (RenderParticleEmitter)renderNode.RenderObject; if (renderParticleEmitter.HasVertexBufferChanged) { // Reset pipeline state, so input layout is regenerated if (renderNode.RenderEffect != null) { renderNode.RenderEffect.PipelineState = null; } } // Write some attributes back which we will need for rendering later var vertexBuilder = renderParticleEmitter.ParticleEmitter.VertexBuilder; var newNodeData = new RenderAttributesPerNode { VertexBufferOffset = totalVertexBufferSize, VertexBufferSize = renderParticleEmitter.VertexSize * renderParticleEmitter.VertexCount, VertexBufferStride = renderParticleEmitter.VertexSize, IndexCount = vertexBuilder.LivingQuads * vertexBuilder.IndicesPerQuad, }; renderParticleNodeData[new RenderNodeReference(renderNodeIndex)] = newNodeData; totalVertexBufferSize += newNodeData.VertexBufferSize; if (newNodeData.IndexCount > highestIndexCount) { highestIndexCount = newNodeData.IndexCount; } } base.Prepare(context); // Assign descriptor sets to each render node var resourceGroupPool = ResourceGroupPool; for (int renderNodeIndex = 0; renderNodeIndex < RenderNodes.Count; renderNodeIndex++) { var renderNodeReference = new RenderNodeReference(renderNodeIndex); var renderNode = RenderNodes[renderNodeIndex]; var renderParticleEmitter = (RenderParticleEmitter)renderNode.RenderObject; // Ignore fallback effects if (renderNode.RenderEffect.State != RenderEffectState.Normal) { continue; } // Collect materials and create associated MaterialInfo (includes reflection) first time // TODO: We assume same material will generate same ResourceGroup (i.e. same resources declared in same order) // Need to offer some protection if this invariant is violated (or support it if it can actually happen in real scenario) var material = renderParticleEmitter.ParticleEmitter.Material; var materialInfo = renderParticleEmitter.ParticleMaterialInfo; var materialParameters = material.Parameters; if (!MaterialRenderFeature.UpdateMaterial(RenderSystem, context, materialInfo, perMaterialDescriptorSetSlot.Index, renderNode.RenderEffect, materialParameters)) { continue; } var descriptorSetPoolOffset = ComputeResourceGroupOffset(renderNodeReference); resourceGroupPool[descriptorSetPoolOffset + perMaterialDescriptorSetSlot.Index] = materialInfo.Resources; } // Per view // TODO: Transform sub render feature? for (int index = 0; index < RenderSystem.Views.Count; index++) { var view = RenderSystem.Views[index]; var viewFeature = view.Features[Index]; // TODO GRAPHICS REFACTOR: Happens in several places Matrix.Multiply(ref view.View, ref view.Projection, out view.ViewProjection); // Copy ViewProjection to PerFrame cbuffer foreach (var viewLayout in viewFeature.Layouts) { var resourceGroup = viewLayout.Entries[view.Index].Resources; var mappedCB = resourceGroup.ConstantBuffer.Data; // PerView constant buffer var perViewOffset = viewLayout.GetConstantBufferOffset(this.perViewCBufferOffset); if (perViewOffset != -1) { var perView = (ParticleUtilitiesPerView *)((byte *)mappedCB + perViewOffset); perView->ViewMatrix = view.View; perView->ProjectionMatrix = view.Projection; perView->ViewProjectionMatrix = view.ViewProjection; perView->ViewFrustum = new Vector4(view.ViewSize.X, view.ViewSize.Y, view.NearClipPlane, view.FarClipPlane); } } } particleBufferContext.AllocateBuffers(context, totalVertexBufferSize, highestIndexCount); BuildParticleBuffers(context); }