text: 
  #include <gloperate/pipeline/Stage.h>
  
  #include <algorithm>
  
  #include <cppassist/string/conversion.h>
  #include <cppassist/logging/logging.h>
  
  #include <cppexpose/variant/Variant.h>
  
  #include <glbinding/gl/gl.h>
  
  #include <globjects/Texture.h>
  #include <globjects/Framebuffer.h>
  
  #include <gloperate/base/ExtendedProperties.h>
  #include <gloperate/pipeline/Pipeline.h>
  #include <gloperate/pipeline/AbstractSlot.h>
  
  
  namespace
  {
      enum Query
      {
          PairOneStart = 0,
          PairOneEnd = 1,
          PairTwoStart = 2,
          PairTwoEnd = 3
      };
  }
  
  
  namespace gloperate
  {
  
  
  Stage::CreateConnectedInputProxy::CreateConnectedInputProxy(const std::string & name, Stage * stage)
  : m_name(name)
  , m_stage(stage)
  , m_createdCount(0)
  {
  }
  
  Stage::CreateConnectedInputProxy::~CreateConnectedInputProxy()
  {
      assert(m_createdCount == 1);
  }
  
  Stage::Stage(Environment * environment, const std::string & className, const std::string & name)
  : cppexpose::Object((name.empty()) ? className : name)
  , m_environment(environment)
  , m_alwaysProcess(false)
  , m_timeMeasurement(false)
  , m_useQueryPairOne(true)
  , m_resultAvailable(false)
  , m_lastCPUDuration(0)
  , m_currentCPUDuration(0)
  , m_lastGPUDuration(0)
  {
      // Set object class name
      setClassName(className);
  }
  
  Stage::~Stage()
  {
      if (Pipeline * parent = parentPipeline())
      {
          parent->removeStage(this);
      }
  }
  
  bool Stage::isPipeline() const
  {
      return false;
  }
  
  Environment * Stage::environment() const
  {
      return m_environment;
  }
  
  Pipeline * Stage::parentPipeline() const
  {
      return dynamic_cast<Pipeline *>(m_parent);
  }
  
  bool Stage::requires(const Stage * stage, bool recursive) const
  {
      if (!stage)
      {
          return false;
      }
  
      for (auto slot : m_inputs)
      {
          if (slot->isFeedback() || !slot->isConnected())
              continue;
  
          if (slot->source()->parentStage() == stage || (recursive && slot->source()->parentStage()->requires(stage)))
              return true;
      }
  
      return false;
  }
  
  void Stage::initContext(AbstractGLContext * context)
  {
      cppassist::debug(2, "gloperate") << this->qualifiedName() << ": initContext";
  
      // Create time queries
      gl::glGenQueries(4, m_queries.data());
  
      // Dummy querys for first frame
      if (m_useQueryPairOne)
      {
          gl::glQueryCounter(m_queries[Query::PairOneStart], gl::GL_TIMESTAMP);
          gl::glQueryCounter(m_queries[Query::PairOneEnd], gl::GL_TIMESTAMP);
      }
      else
      {
          gl::glQueryCounter(m_queries[Query::PairTwoStart], gl::GL_TIMESTAMP);
          gl::glQueryCounter(m_queries[Query::PairTwoEnd], gl::GL_TIMESTAMP);
      }
  
      onContextInit(context);
  }
  
  void Stage::deinitContext(AbstractGLContext * context)
  {
      cppassist::debug(2, "gloperate") << this->qualifiedName() << ": deinitContex";
      onContextDeinit(context);
  }
  
  void Stage::process()
  {
      cppassist::debug(1, "gloperate") << this->qualifiedName() << ": processing";
  
      if (m_timeMeasurement)
      {
          // Get currently used queries
          auto usedStartQuery   = m_useQueryPairOne ? Query::PairOneStart : Query::PairTwoStart;
          auto usedEndQuery     = m_useQueryPairOne ? Query::PairOneEnd   : Query::PairTwoEnd;
          auto unusedStartQuery = m_useQueryPairOne ? Query::PairTwoStart : Query::PairOneStart;
          auto unusedEndQuery   = m_useQueryPairOne ? Query::PairTwoEnd   : Query::PairOneEnd;
  
          // Start CPU time measurement
          auto cpu_start = std::chrono::high_resolution_clock::now();
  
          // Start GPU time measurement
          gl::glQueryCounter(m_queries[unusedStartQuery], gl::GL_TIMESTAMP);
  
          // Execute stage
          onProcess();
  
          // Stop CPU time measurement
          auto cpu_end = std::chrono::high_resolution_clock::now();
          m_lastCPUDuration = m_currentCPUDuration;
          m_currentCPUDuration = std::chrono::duration_cast<std::chrono::nanoseconds>(cpu_end - cpu_start).count();
  
          // Stop GPU time measurement
          gl::glQueryCounter(m_queries[unusedEndQuery], gl::GL_TIMESTAMP);
  
          // Get GPU values from last frame
          gl::GLuint64 gpu_start, gpu_end;
          gl::glGetQueryObjectui64v(m_queries[usedStartQuery], gl::GL_QUERY_RESULT, &gpu_start);
          gl::glGetQueryObjectui64v(m_queries[usedEndQuery], gl::GL_QUERY_RESULT, &gpu_end);
          m_lastGPUDuration = gpu_end - gpu_start;
  
          // Switch queries for next frame
          m_useQueryPairOne = !m_useQueryPairOne;
  
          // Emit measured times
          if (m_resultAvailable) {
              timeMeasured(m_lastCPUDuration, m_lastGPUDuration);
          } else m_resultAvailable = true;
      }
      else
      {
          onProcess();
      }
  
      for (auto input : m_inputs)
      {
          input->setChanged(false);
      }
  }
  
  bool Stage::needsProcessing() const
  {
      if (m_alwaysProcess) {
          cppassist::debug(4, "gloperate") << this->qualifiedName() << ": needs processing because it is always processed";
          return true;
      }
  
      for (auto output : m_outputs)
      {
          if (output->isRequired() && !output->isValid()) {
              cppassist::debug(4, "gloperate") << this->qualifiedName() << ": needs processing because output is invalid and required (" << output->qualifiedName()<< ")";
              return true;
          }
      }
  
      cppassist::debug(4, "gloperate") << this->qualifiedName() << ": needs no processing";
      return false;
  }
  
  bool Stage::alwaysProcessed() const
  {
      return m_alwaysProcess;
  }
  
  void Stage::setAlwaysProcessed(bool alwaysProcess)
  {
      cppassist::debug(2, "gloperate") << this->qualifiedName() << ": set always processed to " << alwaysProcess;
      m_alwaysProcess = alwaysProcess;
  }
  
  void Stage::invalidateOutputs()
  {
      cppassist::debug(3, "gloperate") << this->qualifiedName() << ": invalidateOutputs";
  
      for (auto output : m_outputs)
      {
          output->invalidate();
      }
  }
  
  AbstractSlot * Stage::getSlot(const std::string & path)
  {
      const auto names = cppassist::string::split(path, '.', true);
  
      Stage * stage = this;
  
      for (size_t i=0; i<names.size(); i++)
      {
          const std::string & name = names[i];
  
          // Ignore own stage name at the beginning
          if (name == stage->name() && i == 0)
          {
              continue;
          }
  
          // Check if stage is a pipeline and has a substage with the given name
          if (stage->isPipeline())
          {
              Pipeline * pipeline = static_cast<Pipeline *>(stage);
              Stage * sub = pipeline->stage(name);
  
              if (sub)
              {
                  stage = sub;
                  continue;
              }
          }
  
          // If there is no more substage but more names to fetch, return error
          if (i != names.size() - 1)
          {
              return nullptr;
          }
  
          // Check if stage has a slot with that name
          auto inputSlot = stage->input(name);
          if (inputSlot) {
              return inputSlot;
          }
  
          auto outputSlot = stage->output(name);
          if (outputSlot) {
              return outputSlot;
          }
      }
  
      return nullptr;
  }
  
  const std::vector<AbstractSlot *> & Stage::inputs() const
  {
      return m_inputs;
  }
  
  const AbstractSlot * Stage::input(const std::string & name) const
  {
      if (m_inputsMap.find(name) == m_inputsMap.end())
      {
          return nullptr;
      }
  
      return m_inputsMap.at(name);
  }
  
  AbstractSlot * Stage::input(const std::string & name)
  {
      if (m_inputsMap.find(name) == m_inputsMap.end())
      {
          return nullptr;
      }
  
      return m_inputsMap.at(name);
  }
  
  
  Stage::CreateConnectedInputProxy Stage::createInput(const std::string & name)
  {
      return { name, this };
  }
  
  
  void Stage::addInput(AbstractSlot * input)
  {
      assert(input);
  
      // Find free name
      std::string name = getFreeName(input->name());
      input->setName(name);
  
      // Add input as property
      addProperty(input);
  
      // Add input
      registerInput(input);
  }
  
  void Stage::addInput(std::unique_ptr<AbstractSlot> && input)
  {
      assert(input);
  
      // Find free name
      std::string name = getFreeName(input->name());
      input->setName(name);
  
      // Add input as property
      const auto inputPtr = input.get();
      addProperty(std::move(input));
  
      // Add input
      registerInput(inputPtr);
  }
  
  void Stage::registerInput(AbstractSlot * input)
  {
      // Add input
      m_inputs.push_back(input);
  
      if (input->name() != "") {
          m_inputsMap.insert(std::make_pair(input->name(), input));
      }
  
      cppassist::debug(2, "gloperate") << input->qualifiedName() << ": add input to stage";
  
      // Emit signal
      inputAdded(input);
  }
  
  void Stage::removeInput(AbstractSlot * input)
  {
      // Find input
      auto it = std::find(m_inputs.begin(), m_inputs.end(), input);
      if (it != m_inputs.end())
      {
          cppassist::debug(2, "gloperate") << input->qualifiedName() << ": remove input from stage";
  
          // Remove input
          m_inputs.erase(it);
          m_inputsMap.erase(input->name());
  
          // Emit signal
          inputRemoved(input);
      }
  
      // Remove property
      removeProperty(input);
  }
  
  const std::vector<AbstractSlot *> & Stage::outputs() const
  {
      return m_outputs;
  }
  
  const AbstractSlot * Stage::output(const std::string & name) const
  {
      if (m_outputsMap.find(name) == m_outputsMap.end())
      {
          return nullptr;
      }
  
      return m_outputsMap.at(name);
  }
  
  AbstractSlot * Stage::output(const std::string & name)
  {
      if (m_outputsMap.find(name) == m_outputsMap.end())
      {
          return nullptr;
      }
  
      return m_outputsMap.at(name);
  }
  
  void Stage::addOutput(AbstractSlot * output)
  {
      assert(output);
  
      // Find free name
      std::string name = getFreeName(output->name());
      output->setName(name);
  
      // Add output as property
      addProperty(output);
  
      // Add output
      registerOutput(output);
  }
  
  void Stage::addOutput(std::unique_ptr<AbstractSlot> && output)
  {
      assert(output);
  
      // Find free name
      std::string name = getFreeName(output->name());
      output->setName(name);
  
      // Add output as property
      auto outputPtr = output.get();
      addProperty(std::move(output));
  
      // Add output
      registerOutput(outputPtr);
  }
  
  void Stage::registerOutput(AbstractSlot * output)
  {
      // Add output
      m_outputs.push_back(output);
      if (output->name() != "") {
          m_outputsMap.insert(std::make_pair(output->name(), output));
      }
  
      cppassist::debug(2, "gloperate") << output->qualifiedName() << ": add output to stage";
  
      // Emit signal
      outputAdded(output);
  }
  
  void Stage::removeOutput(AbstractSlot * output)
  {
      // Find output
      auto it = std::find(m_outputs.begin(), m_outputs.end(), output);
      if (it != m_outputs.end())
      {
          cppassist::debug(2, "gloperate") << output->qualifiedName() << ": remove output from stage";
  
          // Remove output
          m_outputs.erase(it);
          m_outputsMap.erase(output->name());
  
          // Emit signal
          outputRemoved(output);
      }
  
      // Remove property
      removeProperty(output);
  }
  
  void Stage::outputRequiredChanged(AbstractSlot * slot)
  {
      cppassist::debug(2, "gloperate") << this->qualifiedName() << ": output required changed for " << slot->qualifiedName();
      onOutputRequiredChanged(slot);
  }
  
  void Stage::inputValueChanged(AbstractSlot * slot)
  {
      inputChanged(slot);
  
      onInputValueChanged(slot);
  }
  
  void Stage::inputValueInvalidated(AbstractSlot * slot)
  {
      onInputValueInvalidated(slot);
  }
  
  void Stage::inputOptionsChanged(AbstractSlot * slot)
  {
      inputChanged(slot);
  }
  
  std::string Stage::getFreeName(const std::string & name) const
  {
      std::string nameOut = name;
      int i = 2;
  
      while (propertyExists(nameOut))
      {
          nameOut = name + cppassist::string::toString<int>(i);
          i++;
      }
  
      return nameOut;
  }
  
  AbstractSlot * Stage::createSlot(const std::string & slotType, const std::string & type, const std::string & name)
  {
      if (type == "bool")    return createSlot<bool>                    (slotType, name);
      if (type == "int")     return createSlot<int>                     (slotType, name);
      if (type == "float")   return createSlot<float>                   (slotType, name);
      if (type == "vec2")    return createSlot<glm::vec2>               (slotType, name);
      if (type == "vec3")    return createSlot<glm::vec3>               (slotType, name);
      if (type == "vec4")    return createSlot<glm::vec4>               (slotType, name);
      if (type == "ivec2")   return createSlot<glm::ivec2>              (slotType, name);
      if (type == "ivec3")   return createSlot<glm::ivec3>              (slotType, name);
      if (type == "ivec4")   return createSlot<glm::ivec4>              (slotType, name);
      if (type == "string")  return createSlot<std::string>             (slotType, name);
      if (type == "file")    return createSlot<cppfs::FilePath>         (slotType, name);
      if (type == "color")   return createSlot<gloperate::Color>        (slotType, name);
      if (type == "texture") return createSlot<globjects::Texture *>    (slotType, name);
      if (type == "fbo")     return createSlot<globjects::Framebuffer *>(slotType, name);
  
      return nullptr;
  }
  
  std::string Stage::qualifiedName() const
  {
      std::string path = name();
  
      Pipeline * pipeline = this->parentPipeline();
      while (pipeline)
      {
          path = pipeline->name() + "." + path;
  
          pipeline = pipeline->parentPipeline();
      }
  
      return path;
  }
  
  void Stage::onContextInit(AbstractGLContext *)
  {
  }
  
  void Stage::onContextDeinit(AbstractGLContext *)
  {
  }
  
  void Stage::onProcess()
  {
  }
  
  void Stage::onInputValueChanged(AbstractSlot *)
  {
      // Invalidate all outputs
      invalidateOutputs();
  }
  
  void Stage::onInputValueInvalidated(AbstractSlot *)
  {
      // Invalidate all outputs
      invalidateOutputs();
  }
  
  void Stage::onOutputRequiredChanged(AbstractSlot *)
  {
      // By default, assume nothing is required
      bool required = false;
  
      // Check if any output is required
      for (auto output : m_outputs)
      {
          if (output->isRequired()) {
              required = true;
              break;
          }
      }
  
      // Update all inputs
      for (auto input : m_inputs)
      {
          input->setRequired(required);
      }
  }
  
  void Stage::invalidateInputConnections()
  {
      if (parentPipeline())
      {
          parentPipeline()->invalidateStageOrder();
      }
  }
  
  void Stage::forAllInputs(std::function<void(gloperate::AbstractSlot *)> callback)
  {
      for (const auto input : m_inputs)
      {
          callback(input);
      }
  }
  
  void Stage::forAllOutputs(std::function<void(gloperate::AbstractSlot *)> callback)
  {
      for (const auto output : m_outputs)
      {
          callback(output);
      }
  }
  
  std::uint64_t Stage::lastCPUTime() const
  {
      return m_lastCPUDuration;
  }
  
  std::uint64_t Stage::lastGPUTime() const
  {
      return m_lastGPUDuration;
  }
  
  bool Stage::timeMeasurement() const
  {
      return m_timeMeasurement;
  }
  
  void Stage::setTimeMeasurement(bool enabled, bool)
  {
      m_timeMeasurement    = enabled;
      m_resultAvailable    = false;
      m_currentCPUDuration = 0;
      m_lastCPUDuration    = 0;
      m_lastGPUDuration    = 0;
  }
  
  
  } // namespace gloperate
  