port from perforce
This commit is contained in:
367
aiwaz/Aiwaz/Commands/CommandBuffer.cpp
Normal file
367
aiwaz/Aiwaz/Commands/CommandBuffer.cpp
Normal file
@@ -0,0 +1,367 @@
|
||||
#include "stdafx.h"
|
||||
#include <algorithm>
|
||||
#include "CommandBuffer.h"
|
||||
|
||||
|
||||
CommandBuffer::CommandBuffer(bool argCommandOwer, bool argChildBufferOwner)
|
||||
: m_CommandOwer(argCommandOwer)
|
||||
, m_ChildBufferOwner(argChildBufferOwner)
|
||||
, m_OptimizedCommandBuffer(NULL)
|
||||
, m_ParentCommandBuffer(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
CommandBuffer::~CommandBuffer()
|
||||
{
|
||||
delete m_OptimizedCommandBuffer;
|
||||
}
|
||||
|
||||
|
||||
void CommandBuffer::Clear()
|
||||
{
|
||||
if (!m_CommandOwer)
|
||||
m_CommandList.clear();
|
||||
else
|
||||
{
|
||||
while (!m_CommandList.empty())
|
||||
{
|
||||
delete m_CommandList[0];
|
||||
m_CommandList.erase(m_CommandList.begin());
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_ChildBufferOwner)
|
||||
{
|
||||
while (!m_ChildCommandBuffers.empty())
|
||||
this->RemoveChildCommandBuffer(*m_ChildCommandBuffers.back());
|
||||
}
|
||||
else
|
||||
{
|
||||
while (!m_ChildCommandBuffers.empty())
|
||||
{
|
||||
delete m_ChildCommandBuffers[0];
|
||||
m_ChildCommandBuffers.erase(m_ChildCommandBuffers.begin());
|
||||
}
|
||||
}
|
||||
|
||||
while (!m_GatheredCommandChains.empty())
|
||||
{
|
||||
delete m_GatheredCommandChains[0];
|
||||
m_GatheredCommandChains.erase(m_GatheredCommandChains.begin());
|
||||
}
|
||||
|
||||
if (m_OptimizedCommandBuffer != NULL)
|
||||
m_OptimizedCommandBuffer->Clear();
|
||||
}
|
||||
|
||||
|
||||
void CommandBuffer::AddCommand( Command& argCommand )
|
||||
{
|
||||
m_CommandList.push_back(&argCommand);
|
||||
}
|
||||
|
||||
|
||||
void CommandBuffer::Perform( bool argUseOptimizedList )
|
||||
{
|
||||
if (argUseOptimizedList)
|
||||
{
|
||||
if (m_OptimizedCommandBuffer != NULL)
|
||||
m_OptimizedCommandBuffer->Perform(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
int subChainStartCommandIndex = -1;
|
||||
for (uint32 i = 0; i < m_CommandList.size();)
|
||||
{
|
||||
if (m_CommandList[i]->Flags & CommandFlags::SubChainStart)
|
||||
subChainStartCommandIndex = i;
|
||||
|
||||
switch (m_CommandList[i]->Owner->ExecuteCommand(m_CommandList[i]->Type, *const_cast<CommandBuffer*>(this), i))
|
||||
{
|
||||
case CommandExecuteResult::None:
|
||||
if (m_CommandList[i]->Flags & CommandFlags::SubChainEnd)
|
||||
subChainStartCommandIndex = -1;
|
||||
i++;
|
||||
break;
|
||||
case CommandExecuteResult::RetrySubChain:
|
||||
if (subChainStartCommandIndex == -1)
|
||||
std::wcout << std::red << "Failed to restart sub command chain." << std::white << std::endl;
|
||||
else
|
||||
i = subChainStartCommandIndex;
|
||||
break;
|
||||
case CommandExecuteResult::RetrySubChainSkipHead:
|
||||
if (subChainStartCommandIndex == -1)
|
||||
std::wcout << std::red << "Failed to restart headless sub command chain." << std::white << std::endl;
|
||||
else
|
||||
i = subChainStartCommandIndex + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void CommandBuffer::Optimize()
|
||||
{
|
||||
m_HashToCommandChain.clear();
|
||||
if (m_OptimizedCommandBuffer == NULL)
|
||||
m_OptimizedCommandBuffer = new CommandBuffer(false, true);
|
||||
else
|
||||
m_OptimizedCommandBuffer->Clear();
|
||||
|
||||
std::vector<Command*> tempChain;
|
||||
this->SearchDrawableCommandChain(this, tempChain);
|
||||
this->FlushGatheredCommandChains();
|
||||
}
|
||||
|
||||
|
||||
void CommandBuffer::AddChildCommandBuffer( ICommandBuffer& argCommandBuffer )
|
||||
{
|
||||
m_ChildCommandBuffers.push_back(&argCommandBuffer);
|
||||
argCommandBuffer.set_ParentBuffer(this);
|
||||
}
|
||||
|
||||
|
||||
void CommandBuffer::RemoveChildCommandBuffer( ICommandBuffer& argCommandBuffer )
|
||||
{
|
||||
std::vector<ICommandBuffer*>::iterator iter = std::find(m_ChildCommandBuffers.begin(), m_ChildCommandBuffers.end(), &argCommandBuffer);
|
||||
if (iter != m_ChildCommandBuffers.end())
|
||||
{
|
||||
argCommandBuffer.set_ParentBuffer(NULL);
|
||||
m_ChildCommandBuffers.erase(iter);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::vector<Command*>& CommandBuffer::get_BufferedCommands()
|
||||
{
|
||||
return m_CommandList;
|
||||
}
|
||||
|
||||
|
||||
const std::vector<ICommandBuffer*>& CommandBuffer::get_ChildCommandBuffers() const
|
||||
{
|
||||
return m_ChildCommandBuffers;
|
||||
}
|
||||
|
||||
|
||||
ICommandBuffer* CommandBuffer::get_ParentBuffer() const
|
||||
{
|
||||
return m_ParentCommandBuffer;
|
||||
}
|
||||
|
||||
|
||||
void CommandBuffer::set_ParentBuffer( ICommandBuffer* ar_CommandBuffer_ )
|
||||
{
|
||||
m_ParentCommandBuffer = ar_CommandBuffer_;
|
||||
}
|
||||
|
||||
|
||||
void CommandBuffer::SearchDrawableCommandChain(ICommandBuffer* ar_Buffer_, std::vector<Command*>& argCurrentChain)
|
||||
{
|
||||
for (uint32 i = 0; i < ar_Buffer_->get_BufferedCommands().size();)
|
||||
{
|
||||
Command& currentCommand = *ar_Buffer_->get_BufferedCommands()[i];
|
||||
UINT32 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();
|
||||
argCurrentChain.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 (argCurrentChain.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 (argCurrentChain.empty()
|
||||
&& (commandFlags & CommandFlags::StartChain) == 0
|
||||
&& (commandFlags & CommandFlags::EndChain) == 0)
|
||||
{
|
||||
bool foundPlace = false;
|
||||
std::vector<Command*>& commands = ar_Buffer_->get_BufferedCommands();
|
||||
for (uint32 k = i + 1; k < commands.size(); ++k)
|
||||
if (commands[k]->Flags & CommandFlags::StartChain)
|
||||
{
|
||||
commands.insert(commands.begin() + k + 1, ¤tCommand);
|
||||
commands.erase(commands.begin() + 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
|
||||
&& !argCurrentChain.empty())
|
||||
{
|
||||
argCurrentChain.clear();
|
||||
argCurrentChain.push_back(¤tCommand);
|
||||
++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 (!argCurrentChain.empty())
|
||||
{
|
||||
CommandChainInternal* chain = new CommandChainInternal();
|
||||
chain->m_Chain = argCurrentChain;
|
||||
|
||||
chain->m_Chain.push_back(¤tCommand);
|
||||
std::sort(chain->m_Chain.begin(), chain->m_Chain.end(), CommandChainInternal::CommandSort());
|
||||
|
||||
//m_OptimizedCommandBuffer->get_BufferedCommands().insert(m_OptimizedCommandBuffer->get_BufferedCommands().end(), chain.begin(), chain.end());
|
||||
m_GatheredCommandChains.push_back(chain);
|
||||
|
||||
// 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 (uint32 k = 0; k < argCurrentChain.size(); ++k)
|
||||
{
|
||||
if (argCurrentChain[k]->Flags & CommandFlags::SubChainEnd)
|
||||
{
|
||||
argCurrentChain.erase(argCurrentChain.begin() + k + 1, argCurrentChain.end());
|
||||
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
|
||||
{
|
||||
m_OptimizedCommandBuffer->get_BufferedCommands().push_back(¤tCommand);
|
||||
}
|
||||
++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
|
||||
{
|
||||
argCurrentChain.push_back(¤tCommand);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
// We may have child buffers, continue our search there.
|
||||
for (uint32 i = 0; i < ar_Buffer_->get_ChildCommandBuffers().size(); ++i)
|
||||
{
|
||||
std::vector<Command*> currentChain = argCurrentChain;
|
||||
this->SearchDrawableCommandChain(ar_Buffer_->get_ChildCommandBuffers()[i], argCurrentChain);
|
||||
argCurrentChain = currentChain;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void CommandBuffer::FlushGatheredCommandChains()
|
||||
{
|
||||
std::vector<Command*> flushedCommands;
|
||||
for (uint32 i = 0; i < m_GatheredCommandChains.size(); ++i)
|
||||
m_GatheredCommandChains[i]->UpdatePriority();
|
||||
|
||||
while (!m_GatheredCommandChains.empty())
|
||||
{
|
||||
std::sort(m_GatheredCommandChains.begin(), m_GatheredCommandChains.end(), CommandChainInternal::CommandChainSort());
|
||||
|
||||
uint32 currentPriority = 0;
|
||||
std::vector<uint32> checkables;
|
||||
for (uint32 i = 0; i < m_GatheredCommandChains.size();)
|
||||
{
|
||||
if (m_GatheredCommandChains[i]->m_Chain.empty())
|
||||
m_GatheredCommandChains.erase(m_GatheredCommandChains.begin() + i);
|
||||
else if (checkables.empty() || currentPriority == m_GatheredCommandChains[i]->m_Priority)
|
||||
{
|
||||
currentPriority = m_GatheredCommandChains[i]->m_Priority;
|
||||
checkables.push_back(i);
|
||||
++i;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
if (checkables.size() == 1)
|
||||
{
|
||||
uint32 thisIndex = checkables.front();
|
||||
flushedCommands.insert( flushedCommands.end(),
|
||||
m_GatheredCommandChains[thisIndex]->m_Chain.begin(),
|
||||
m_GatheredCommandChains[thisIndex]->m_Chain.end());
|
||||
m_GatheredCommandChains.erase(m_GatheredCommandChains.begin() + thisIndex);
|
||||
if (m_GatheredCommandChains.empty())
|
||||
break;
|
||||
}
|
||||
|
||||
std::vector<Command*> optimizedCommandChain;
|
||||
|
||||
Command* lastCommand = NULL;
|
||||
while (checkables.size() > 1)
|
||||
{
|
||||
if (!checkables.empty() && checkables.front() != 0)
|
||||
break;
|
||||
for (uint32 i = 0; i < checkables.size();)
|
||||
{
|
||||
uint32 thisIndex = checkables[i];
|
||||
CommandChainInternal* currentCommandChainInternal = m_GatheredCommandChains[thisIndex];
|
||||
Command* thisCommand = *currentCommandChainInternal->m_Chain.begin();
|
||||
if (i == 0 || (thisCommand->CommandInfo == lastCommand->CommandInfo && 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.push_back(thisCommand);
|
||||
currentCommandChainInternal->m_Chain.erase(currentCommandChainInternal->m_Chain.begin());
|
||||
if (currentCommandChainInternal->m_Chain.empty())
|
||||
{
|
||||
checkables.erase(checkables.begin() + i);
|
||||
break;
|
||||
}
|
||||
else
|
||||
++i;
|
||||
}
|
||||
else
|
||||
{
|
||||
// do not check this list further, a difference was detected
|
||||
checkables.erase(checkables.begin() + i);
|
||||
}
|
||||
}
|
||||
|
||||
// resort our m_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.empty())
|
||||
for (uint32 i = checkables.front() + 1; i < checkables.size(); ++i)
|
||||
if (checkables[i] != i)
|
||||
{
|
||||
std::swap(m_GatheredCommandChains[i], m_GatheredCommandChains[checkables[i]]);
|
||||
checkables[i] = i;
|
||||
}
|
||||
}
|
||||
|
||||
flushedCommands.insert(flushedCommands.end(), optimizedCommandChain.begin(), optimizedCommandChain.end());
|
||||
}
|
||||
|
||||
// remove unnecessary SubChainEnd commands
|
||||
bool openPass = false;
|
||||
for (int i = flushedCommands.size() - 1; i >= 0; --i)
|
||||
{
|
||||
if (flushedCommands[i]->Flags == CommandFlags::SubChainEnd)
|
||||
{
|
||||
if (!openPass)
|
||||
openPass = true;
|
||||
else
|
||||
flushedCommands.erase(flushedCommands.begin() + i);
|
||||
}
|
||||
else if (flushedCommands[i]->Flags == CommandFlags::SubChainStart)
|
||||
openPass = false;
|
||||
|
||||
}
|
||||
m_OptimizedCommandBuffer->get_BufferedCommands().insert(m_OptimizedCommandBuffer->get_BufferedCommands().end(), flushedCommands.begin(), flushedCommands.end());
|
||||
}
|
||||
96
aiwaz/Aiwaz/Commands/CommandBuffer.h
Normal file
96
aiwaz/Aiwaz/Commands/CommandBuffer.h
Normal file
@@ -0,0 +1,96 @@
|
||||
#pragma once
|
||||
|
||||
#include "ICommandUser.h"
|
||||
|
||||
|
||||
class CommandBuffer
|
||||
: public ICommandBuffer
|
||||
{
|
||||
public:
|
||||
CommandBuffer(bool argCommandOwer, bool argChildBufferOwner);
|
||||
virtual ~CommandBuffer();
|
||||
virtual void Clear();
|
||||
virtual void AddCommand(Command& argCommand);
|
||||
virtual void Perform(bool argUseOptimizedList);
|
||||
virtual void Optimize();
|
||||
virtual void AddChildCommandBuffer(ICommandBuffer& argCommandBuffer);
|
||||
virtual void RemoveChildCommandBuffer(ICommandBuffer& argCommandBuffer);
|
||||
virtual std::vector<Command*>& get_BufferedCommands();
|
||||
virtual const std::vector<ICommandBuffer*>& get_ChildCommandBuffers() const;
|
||||
virtual ICommandBuffer* get_ParentBuffer() const;
|
||||
virtual void set_ParentBuffer(ICommandBuffer* ar_CommandBuffer_);
|
||||
|
||||
protected:
|
||||
void SearchDrawableCommandChain(ICommandBuffer* ar_Buffer_, std::vector<Command*>& argCurrentChain);
|
||||
void FlushGatheredCommandChains();
|
||||
|
||||
private:
|
||||
|
||||
struct CommandChainInternal
|
||||
{
|
||||
CommandChainInternal() : m_Priority(0) {}
|
||||
|
||||
void UpdatePriority()
|
||||
{
|
||||
m_Priority = -1;
|
||||
for (uint32 i = 0; i < m_Chain.size(); ++i)
|
||||
{
|
||||
if (m_Chain[i]->Flags & CommandFlags::SubChainStart)
|
||||
{
|
||||
m_Priority = m_Chain[i]->SubPriority;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UINT8 m_Priority;
|
||||
std::vector<Command*> m_Chain;
|
||||
|
||||
struct CommandChainSort
|
||||
{
|
||||
bool operator()(CommandChainInternal* ar_Left_, CommandChainInternal* ar_Right_) const
|
||||
{
|
||||
if (ar_Left_->m_Priority == ar_Right_->m_Priority)
|
||||
return ar_Left_->m_Chain.size() < ar_Right_->m_Chain.size();
|
||||
return ar_Left_->m_Priority < ar_Right_->m_Priority;
|
||||
}
|
||||
};
|
||||
|
||||
struct CommandSort
|
||||
{
|
||||
bool operator()(Command* ar_Left_, Command* ar_Right_) const
|
||||
{
|
||||
return ar_Left_->Priority < ar_Right_->Priority;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
private:
|
||||
ICommandBuffer* m_ParentCommandBuffer;
|
||||
ICommandBuffer* m_OptimizedCommandBuffer;
|
||||
std::vector<Command*> m_CommandList;
|
||||
std::vector<ICommandBuffer*> m_ChildCommandBuffers;
|
||||
|
||||
bool m_CommandOwer;
|
||||
bool m_ChildBufferOwner;
|
||||
|
||||
std::map<uint32, std::vector<Command*> > m_HashToCommandChain;
|
||||
|
||||
intptr_t GenerateHashValue(const std::vector<Command*>& argCommands) const
|
||||
{
|
||||
intptr_t hash = 0;
|
||||
for (uint32 i = 0; i < argCommands.size(); ++i)
|
||||
{
|
||||
hash += (intptr_t)argCommands[i];
|
||||
hash += (hash << 10);
|
||||
hash ^= (hash >> 6);
|
||||
|
||||
hash += (hash << 3);
|
||||
hash ^= (hash >> 11);
|
||||
hash += (hash << 15);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
std::vector<CommandChainInternal*> m_GatheredCommandChains;
|
||||
};
|
||||
57
aiwaz/Aiwaz/Commands/CommandUserBase.cpp
Normal file
57
aiwaz/Aiwaz/Commands/CommandUserBase.cpp
Normal file
@@ -0,0 +1,57 @@
|
||||
#include "stdafx.h"
|
||||
#include <algorithm>
|
||||
#include "CommandUserBase.h"
|
||||
|
||||
|
||||
CommandUserBase::CommandUserBase()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
CommandUserBase::~CommandUserBase()
|
||||
{
|
||||
this->UnassignFromSceneNodes();
|
||||
while (!m_Commands.empty())
|
||||
{
|
||||
delete m_Commands[0];
|
||||
m_Commands.erase(m_Commands.begin());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const std::vector<Command*>& CommandUserBase::GetCommands() const
|
||||
{
|
||||
return m_Commands;
|
||||
}
|
||||
|
||||
|
||||
void CommandUserBase::AssignToRenderCommandNode(IRenderCommandNode& argNode)
|
||||
{
|
||||
m_RenderCommandNodesIamAssignedTo.push_back(&argNode);
|
||||
argNode.MarkDirty();
|
||||
}
|
||||
|
||||
|
||||
void CommandUserBase::UnassignFromRenderCommandNode(IRenderCommandNode& argNode)
|
||||
{
|
||||
std::vector<IRenderCommandNode*>::iterator found = std::find(m_RenderCommandNodesIamAssignedTo.begin(), m_RenderCommandNodesIamAssignedTo.end(), &argNode);
|
||||
if (found != m_RenderCommandNodesIamAssignedTo.end())
|
||||
{
|
||||
m_RenderCommandNodesIamAssignedTo.erase(found);
|
||||
argNode.MarkDirty();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void CommandUserBase::UnassignFromSceneNodes()
|
||||
{
|
||||
while (!m_RenderCommandNodesIamAssignedTo.empty())
|
||||
m_RenderCommandNodesIamAssignedTo.back()->RemoveCommandUser(*const_cast<CommandUserBase*>(this));
|
||||
}
|
||||
|
||||
|
||||
void CommandUserBase::MarkCommandsAsDirty()
|
||||
{
|
||||
for (uint32 i = 0; i < m_RenderCommandNodesIamAssignedTo.size(); ++i)
|
||||
m_RenderCommandNodesIamAssignedTo[i]->MarkDirty();
|
||||
}
|
||||
32
aiwaz/Aiwaz/Commands/CommandUserBase.h
Normal file
32
aiwaz/Aiwaz/Commands/CommandUserBase.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include "ICommandUser.h"
|
||||
#include "IRenderCommandNode.h"
|
||||
|
||||
|
||||
class CommandUserBase
|
||||
: public ICommandUser
|
||||
{
|
||||
public:
|
||||
CommandUserBase();
|
||||
virtual ~CommandUserBase();
|
||||
|
||||
virtual const std::vector<Command*>& GetCommands() const;
|
||||
virtual CommandExecuteResult::Enumeration ExecuteCommand(unsigned char argCommandType, ICommandBuffer& argCurrentBuffer, uint32 argCurrentPositon) = 0;
|
||||
|
||||
virtual void AssignToRenderCommandNode(IRenderCommandNode& argNode);
|
||||
virtual void UnassignFromRenderCommandNode(IRenderCommandNode& argNode);
|
||||
|
||||
virtual bool get_IsPreconditionForNextCommands() const { return false; }
|
||||
|
||||
virtual string8 get_UserName() const = 0;
|
||||
|
||||
protected:
|
||||
void UnassignFromSceneNodes();
|
||||
void MarkCommandsAsDirty();
|
||||
|
||||
std::vector<Command*> m_Commands;
|
||||
|
||||
private:
|
||||
std::vector<IRenderCommandNode*> m_RenderCommandNodesIamAssignedTo;
|
||||
};
|
||||
Reference in New Issue
Block a user