Files
bluflame/aiwaz/Backup/Aiwaz.Resources/Commands/CommandBuffer.cs
2026-04-18 22:31:51 +02:00

343 lines
13 KiB
C#

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<Command> 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<Command>
{
public int Compare(Command x, Command y)
{
return x.Priority.CompareTo(y.Priority);
}
}
public class CommandChainComparsion : IComparer<CommandChainInternal>
{
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<CommandChainInternal> gatheredCommandChains = new List<CommandChainInternal>();
public List<Command> BufferedCommands { get; protected set; }
public List<CommandBuffer> ChildCommandBuffers { get; protected set; }
public CommandBuffer ParentBuffer { get; set; }
#region ICommandBuffer Members
public CommandBuffer()
{
BufferedCommands = new List<Command>();
ChildCommandBuffers = new List<CommandBuffer>();
}
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<Command> tempChain = new List<Command>();
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<Command> 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<Command>(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<Command>(currentChain);
this.SearchDrawableCommandChain(buffer, ref currentChain);
currentChain = newCurrentChain;
}
}
void FlushGatheredCommandChains()
{
var flushedCommands = new List<Command>();
gatheredCommandChains.ForEach(item => item.UpdatePriority());
while (gatheredCommandChains.Count > 0)
{
gatheredCommandChains.Sort(new CommandChainInternal.CommandChainComparsion());
int currentPriority = 0;
List<int> checkables = new List<int>();
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<Command> optimizedCommandChain = new List<Command>();
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
}
}