using System; using System.Collections.Generic; using System.Linq; using System.Text; using Aiwaz.Contracts; using Aiwaz.Core; namespace Aiwaz.Resources { public class CommandBuffer { private class CommandChainInternal { public int Priority = 0; public List Chain; public void UpdatePriority() { Priority = -1; foreach (var command in Chain) if ((command.Flags & CommandFlags.SubChainStart) != CommandFlags.None) { Priority = command.SubPriority; break; } } public class CommandComparsion : IComparer { public int Compare(Command x, Command y) { return x.Priority.CompareTo(y.Priority); } } public class CommandChainComparsion : IComparer { public int Compare(CommandChainInternal x, CommandChainInternal y) { if (x.Priority == y.Priority) return x.Chain.Count.CompareTo(y.Chain.Count); return x.Priority.CompareTo(y.Priority); } } } private CommandBuffer optimizedCommandBuffer = null; private List gatheredCommandChains = new List(); public List BufferedCommands { get; protected set; } public List ChildCommandBuffers { get; protected set; } public CommandBuffer ParentBuffer { get; set; } #region ICommandBuffer Members public CommandBuffer() { BufferedCommands = new List(); ChildCommandBuffers = new List(); } public void Clear() { BufferedCommands.Clear(); ChildCommandBuffers.Clear(); gatheredCommandChains.Clear(); if (optimizedCommandBuffer != null) optimizedCommandBuffer.Clear(); } public void Perform(bool useOptimizedList) { if (!useOptimizedList) { int subChainStartCommandIndex = -1; for (int i = 0; i < BufferedCommands.Count;) { var command = BufferedCommands[i]; if ((command.Flags & CommandFlags.SubChainStart) != CommandFlags.None) subChainStartCommandIndex = i; switch (command.Owner.ExecuteCommand(command.Type, this, i)) { case CommandExecuteResult.None: if ((command.Flags & CommandFlags.SubChainEnd) != CommandFlags.None) subChainStartCommandIndex = -1; i++; break; case CommandExecuteResult.RetrySubChain: if (subChainStartCommandIndex == -1) throw new ActionFailedException("Failed to restart sub command chain."); else i = subChainStartCommandIndex; break; case CommandExecuteResult.RetrySubChainSkipHead: if (subChainStartCommandIndex == -1) throw new ActionFailedException("Failed to restart headless sub command chain."); else i = subChainStartCommandIndex + 1; break; } } } else { if (optimizedCommandBuffer != null) optimizedCommandBuffer.Perform(false); } } public void Optimize() { if (optimizedCommandBuffer == null) optimizedCommandBuffer = new CommandBuffer(); optimizedCommandBuffer.Clear(); List tempChain = new List(); this.SearchDrawableCommandChain(this, ref tempChain); this.FlushGatheredCommandChains(); } public void AddChildCommandBuffer(CommandBuffer commandBuffer) { ChildCommandBuffers.Add(commandBuffer); commandBuffer.ParentBuffer = this; } public void RemoveChildCommandBuffer(CommandBuffer commandBuffer) { if (ChildCommandBuffers.Remove(commandBuffer)) commandBuffer.ParentBuffer = null; } private void SearchDrawableCommandChain(CommandBuffer incomingBuffer, ref List currentChain) { for (int i = 0; i < incomingBuffer.BufferedCommands.Count;) { var currentCommand = incomingBuffer.BufferedCommands[i]; var commandFlags = currentCommand.Flags; // A command is start AND end of a command chain, this indicates that a serious state change will come, // so we will need to flush the current command chains. if ((commandFlags & CommandFlags.FlushChain) != 0) { this.FlushGatheredCommandChains(); currentChain.Clear(); } // Old way commented out due problems with transformations before shaders // A command was found which does not belong to a build up current chain (due no chain is building herself up..) // so we drop it, we simply do not care about some run away /*if (currentChain.empty() && (commandFlags & CommandFlags.StartChain) == 0 && (commandFlags & CommandFlags.EndChain) == 0) { ++i; continue; }*/ // New way is to search the list for a start chain command, then we put this command after the start chain command if (currentChain.Count == 0 && (commandFlags & CommandFlags.StartChain) == 0 && (commandFlags & CommandFlags.EndChain) == 0) { var foundPlace = false; var commands = incomingBuffer.BufferedCommands; for (int k = i + 1; k < commands.Count; ++k) if ((commands[k].Flags & CommandFlags.StartChain) != CommandFlags.None) { commands.Insert(k + 1, currentCommand); commands.RemoveAt(i); foundPlace = true; break; } if (!foundPlace) ++i; continue; } // A new command chain should be initialized, clear our current temp chain and add this command into it. // All old commands remaining in the chain are already used or where completely useless (SetShader . SetParameter . NextPass). else if ((commandFlags & CommandFlags.StartChain) != 0 && currentChain.Count > 0) { currentChain.Clear(); currentChain.Add(currentCommand); ++i; } // We should end our chain, now we need to optimize this chain and add this into the gathered chain list, // at the next flush command we will put this list into our optimized list, with some more optimizations. else if ((commandFlags & CommandFlags.EndChain) != 0) { // We have a filled chain (at least 1 operation following by this operation) if (currentChain.Count > 0) { var commandChainInternal = new CommandChainInternal(); commandChainInternal.Chain = new List(currentChain); commandChainInternal.Chain.Add(currentCommand); commandChainInternal.Chain.Sort(new CommandChainInternal.CommandComparsion()); //m_OptimizedCommandBuffer.get_BufferedCommands().insert(m_OptimizedCommandBuffer.get_BufferedCommands().end(), chain.begin(), chain.end()); gatheredCommandChains.Add(commandChainInternal); // Search for the SubChainEnd flagged command , this should be found right after the SubChainStart flagged commands // both commands will mark the beginning of our chain . delete everything behind this command will ensure // that we will use a fresh new command chain, only filled with what we need (StartSubChain . EndSubChain) for (int k = 0; k < currentChain.Count; ++k) if ((currentChain[k].Flags & CommandFlags.SubChainEnd) != CommandFlags.None) { currentChain.RemoveRange(k + 1, currentChain.Count - (k + 1)); break; } } // Our chain was empty, this means a solo command was found, put it into our optimized list and continue, // there is not much to do here. else { optimizedCommandBuffer.BufferedCommands.Add(currentCommand); } ++i; } // No start and no end of the command chain, just put this command into our chain, // commands of this type are for example, SetVertexBuffer, SetIndexBuffer, SetTransformation,... else { currentChain.Add(currentCommand); ++i; } } // We may have child buffers, continue our search there. foreach (var buffer in incomingBuffer.ChildCommandBuffers) { var newCurrentChain = new List(currentChain); this.SearchDrawableCommandChain(buffer, ref currentChain); currentChain = newCurrentChain; } } void FlushGatheredCommandChains() { var flushedCommands = new List(); gatheredCommandChains.ForEach(item => item.UpdatePriority()); while (gatheredCommandChains.Count > 0) { gatheredCommandChains.Sort(new CommandChainInternal.CommandChainComparsion()); int currentPriority = 0; List checkables = new List(); for (int i = 0; i < gatheredCommandChains.Count;) { if (gatheredCommandChains[i].Chain.Count == 0) gatheredCommandChains.RemoveAt(i); else if (checkables.Count == 0 || currentPriority == gatheredCommandChains[i].Priority) { currentPriority = gatheredCommandChains[i].Priority; checkables.Add(i); ++i; } else break; } if (checkables.Count == 1) { int thisIndex = checkables.First(); flushedCommands.AddRange(gatheredCommandChains[thisIndex].Chain); gatheredCommandChains.RemoveAt(thisIndex); if (gatheredCommandChains.Count == 0) break; } List optimizedCommandChain = new List(); Command lastCommand = null; while (checkables.Count > 1) { if (checkables.Count > 0 && checkables.First() != 0) break; for (int i = 0; i < checkables.Count;) { int thisIndex = checkables[i]; var currentCommandChainInternal = gatheredCommandChains[thisIndex]; var thisCommand = currentCommandChainInternal.Chain.First(); if (i == 0 || (thisCommand.CommandInfo.RawValue == lastCommand.CommandInfo.RawValue && thisCommand.Owner == lastCommand.Owner)) { // we found a similarity (or the first element), put this into our optimized temp list lastCommand = thisCommand; if (i == 0 || (thisCommand.Flags & CommandFlags.Unique) != 0) optimizedCommandChain.Add(thisCommand); currentCommandChainInternal.Chain.RemoveAt(0); if (currentCommandChainInternal.Chain.Count == 0) { checkables.RemoveAt(i); break; } else ++i; } else { // do not check this list further, a difference was detected checkables.RemoveAt(i); } } // resort our gatheredCommandChains so that we could use the remaining content of a list properly, only do that if the checkable list has no direct preceeding elements ({0,2,3} instead {0,1,2}) if (checkables.Count > 0) for (int i = checkables.First() + 1; i < checkables.Count; ++i) if (checkables[i] != i) { var tmp = gatheredCommandChains[i]; gatheredCommandChains[i] = gatheredCommandChains[checkables[i]]; gatheredCommandChains[checkables[i]] = tmp; checkables[i] = i; } } flushedCommands.AddRange(optimizedCommandChain); } // remove unnecessary SubChainEnd commands bool openPass = false; for (int i = flushedCommands.Count - 1; i >= 0; --i) { if ((flushedCommands[i].Flags & CommandFlags.SubChainEnd) == CommandFlags.SubChainEnd) { if (!openPass) openPass = true; else flushedCommands.RemoveAt(i); } else if ((flushedCommands[i].Flags & CommandFlags.SubChainStart) == CommandFlags.SubChainStart) openPass = false; } optimizedCommandBuffer.BufferedCommands.AddRange(flushedCommands); } #endregion } }