/// <summary> /// Perform effect permutations, before <see cref="Prepare"/>. /// </summary> /// <param name="context"></param> public virtual void PrepareEffectPermutations(RenderDrawContext context) { }
protected abstract void DrawCore(RenderDrawContext context, RenderFrame output);
/// <summary> /// Performs most of the work (computation and resource preparation). Later game simulation might be running during that step. /// </summary> /// <param name="context"></param> public virtual void Prepare(RenderDrawContext context) { }
/// <summary> /// Performs GPU updates and/or draw. /// </summary> /// <param name="context"></param> /// <param name="renderView"></param> /// <param name="renderViewStage"></param> /// <param name="startIndex"></param> /// <param name="endIndex"></param> public virtual void Draw(RenderDrawContext context, RenderView renderView, RenderViewStage renderViewStage, int startIndex, int endIndex) { }
/// <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.MultiSampleLevel = renderNode.RenderStage.Output.MultiSampleLevel; // Bind VAO ProcessPipelineState(Context, renderNodeReference, ref renderNode, renderObject, pipelineState); PostProcessPipelineState?.Invoke(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(); }
protected override void ActivateOutputCore(RenderDrawContext context, RenderFrame output, bool disableDepth) { base.ActivateOutputCore(context, output, disableDepth); context.CommandList.SetViewport(ComputedViewport); }
public PrepareThreadContext(RenderContext renderContext) { MutablePipelineState = new MutablePipelineState(renderContext.GraphicsDevice); Context = renderContext.GetThreadContext(); }
/// <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); // 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)) { 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) { 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); } var asyncEffect = RenderSystem.EffectSystem.LoadEffect(renderEffect.EffectSelector.EffectName, staticCompilerParameters); staticCompilerParameters.Clear(); renderEffect.Effect = asyncEffect.Result; if (renderEffect.Effect == null) { // Effect still compiling, let's find if there is a fallback renderEffect.ClearFallbackParameters(); renderEffect.Effect = ComputeFallbackEffect?.Invoke(renderObject, renderEffect, RenderEffectState.Compiling); renderEffect.PendingEffect = asyncEffect.Task; renderEffect.State = 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 renderEffect.Effect = effect = renderEffect.PendingEffect.Result; renderEffect.State = RenderEffectState.Normal; } 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; } } } }
public void Draw(RenderDrawContext renderDrawContext, RenderView renderView, RenderStage renderStage) { // Sync point: draw (from now, we should execute with a graphics device context to perform rendering) // Look for the RenderViewStage corresponding to this RenderView | RenderStage combination var renderViewStage = RenderViewStage.Invalid; foreach (var currentRenderViewStage in renderView.RenderStages) { if (currentRenderViewStage.Index == renderStage.Index) { renderViewStage = currentRenderViewStage; break; } } if (renderViewStage.Index == -1) { throw new InvalidOperationException("Requested RenderView|RenderStage combination doesn't exist. Please add it to RenderView.RenderStages."); } // Perform updates once per change of RenderView foreach (var renderFeature in RenderFeatures) { renderFeature.Draw(renderDrawContext, renderView, renderViewStage); } // Generate and execute draw jobs var renderNodes = renderViewStage.SortedRenderNodes; var renderNodeCount = renderViewStage.RenderNodes.Count; if (renderNodeCount == 0) { return; } if (!GraphicsDevice.IsDeferred) { int currentStart, currentEnd; for (currentStart = 0; currentStart < renderNodeCount; currentStart = currentEnd) { var currentRenderFeature = renderNodes[currentStart].RootRenderFeature; currentEnd = currentStart + 1; while (currentEnd < renderNodeCount && renderNodes[currentEnd].RootRenderFeature == currentRenderFeature) { currentEnd++; } // Divide into task chunks for parallelism currentRenderFeature.Draw(renderDrawContext, renderView, renderViewStage, currentStart, currentEnd); } } else { // Create at most one batch per processor int batchCount = Math.Min(Environment.ProcessorCount, renderNodeCount); int batchSize = (renderNodeCount + (batchCount - 1)) / batchCount; batchCount = (renderNodeCount + (batchSize - 1)) / batchSize; // Remember state var depthStencilBuffer = renderDrawContext.CommandList.DepthStencilBuffer; int renderTargetCount = renderDrawContext.CommandList.RenderTargetCount; if (renderTargets == null) { renderTargets = new Texture[renderDrawContext.CommandList.RenderTargets.Length]; } for (int i = 0; i < renderTargetCount; ++i) { renderTargets[i] = renderDrawContext.CommandList.RenderTargets[i]; } var viewport = renderDrawContext.CommandList.Viewport; var scissor = renderDrawContext.CommandList.Scissor; // Collect one command list per batch and the main one up to this point if (commandLists == null || commandLists.Length < batchCount + 1) { Array.Resize(ref commandLists, batchCount + 1); } commandLists[0] = renderDrawContext.CommandList.Close(); Dispatcher.For(0, batchCount, () => renderDrawContext.RenderContext.GetThreadContext(), (batchIndex, threadContext) => { threadContext.CommandList.Reset(); threadContext.CommandList.ClearState(); // Transfer state to all command lists threadContext.CommandList.SetRenderTargets(depthStencilBuffer, renderTargetCount, renderTargets); threadContext.CommandList.SetViewport(viewport); threadContext.CommandList.SetScissorRectangle(scissor); var currentStart = batchSize * batchIndex; int currentEnd; var endExclusive = Math.Min(renderNodeCount, currentStart + batchSize); if (endExclusive <= currentStart) { return; } for (; currentStart < endExclusive; currentStart = currentEnd) { var currentRenderFeature = renderNodes[currentStart].RootRenderFeature; currentEnd = currentStart + 1; while (currentEnd < endExclusive && renderNodes[currentEnd].RootRenderFeature == currentRenderFeature) { currentEnd++; } // Divide into task chunks for parallelism currentRenderFeature.Draw(threadContext, renderView, renderViewStage, currentStart, currentEnd); } commandLists[batchIndex + 1] = threadContext.CommandList.Close(); }); GraphicsDevice.ExecuteCommandLists(batchCount + 1, commandLists); renderDrawContext.CommandList.Reset(); renderDrawContext.CommandList.ClearState(); // Reapply previous state renderDrawContext.CommandList.SetRenderTargets(depthStencilBuffer, renderTargetCount, renderTargets); renderDrawContext.CommandList.SetViewport(viewport); renderDrawContext.CommandList.SetScissorRectangle(scissor); } }
/// <summary> /// Performs most of the work (computation and resource preparation). Later game simulation might be running during that step. /// </summary> /// <param name="context"></param> public unsafe void Prepare(RenderDrawContext context) { // Sync point: after extract, before prepare (game simulation could resume now) // Generate and execute prepare effect jobs foreach (var renderFeature in RenderFeatures) // We might be able to parallelize too as long as we resepect render feature dependency graph (probably very few dependencies in practice) { // Divide into task chunks for parallelism renderFeature.PrepareEffectPermutations(context); } // Generate and execute prepare jobs foreach (var renderFeature in RenderFeatures) // We might be able to parallelize too as long as we resepect render feature dependency graph (probably very few dependencies in practice) { // Divide into task chunks for parallelism renderFeature.Prepare(context); } // Sort Dispatcher.ForEach(Views, view => { Dispatcher.For(0, view.RenderStages.Count, () => prepareThreadLocals.Acquire(), (index, local) => { var renderViewStage = view.RenderStages[index]; var renderNodes = renderViewStage.RenderNodes; if (renderNodes.Count == 0) { return; } var renderStage = RenderStages[renderViewStage.Index]; var sortedRenderNodes = renderViewStage.SortedRenderNodes; // Fast clear, since it's cleared properly in Reset() sortedRenderNodes.Resize(renderViewStage.RenderNodes.Count, true); if (renderStage.SortMode != null) { // Make sure sortKeys is big enough if (local.SortKeys == null || local.SortKeys.Length < renderNodes.Count) { Array.Resize(ref local.SortKeys, renderNodes.Count); } // renderNodes[start..end] belongs to the same render feature fixed(SortKey * sortKeysPtr = local.SortKeys) renderStage.SortMode.GenerateSortKey(view, renderViewStage, sortKeysPtr); Dispatcher.Sort(local.SortKeys, 0, renderNodes.Count, Comparer <SortKey> .Default); // Reorder list for (int i = 0; i < renderNodes.Count; ++i) { sortedRenderNodes[i] = renderNodes[local.SortKeys[i].Index]; } } else { // No sorting, copy as is for (int i = 0; i < renderNodes.Count; ++i) { sortedRenderNodes[i] = renderNodes[i]; } } }, state => prepareThreadLocals.Release(state)); }); // Flush the resources uploaded during Prepare context.ResourceGroupAllocator.Flush(); context.RenderContext.Flush(); }
protected virtual void PostDrawCore(RenderDrawContext context) { }
/// <summary> /// Draws a full screen quad using iterating on each pass of this effect. /// </summary> public void Draw(RenderDrawContext context, string nameFormat, params object[] args) { // TODO: this is alocating a string, we should try to not allocate here. Draw(context, name: string.Format(nameFormat, args)); }
/// <summary> /// Activates the output to the current <see cref="GraphicsDevice" />. /// </summary> /// <param name="context">The context.</param> /// <param name="output">The output.</param> /// <param name="disableDepth">if set to <c>true</c> [disable depth].</param> protected virtual void ActivateOutputCore(RenderDrawContext context, RenderFrame output, bool disableDepth) { // Setup the render target context.CommandList.SetRenderTargetsAndViewport(disableDepth ? null : output.DepthStencil, output.RenderTargets); }