text: #include <vsg/all.h>
  
  #ifdef vsgXchange_FOUND
  #    include <vsgXchange/all.h>
  #endif
  
  #include <algorithm>
  #include <chrono>
  #include <iostream>
  #include <thread>
  
  #include "../../shared/AnimationPath.h"
  
  vsg::ref_ptr<vsg::Node> createTextureQuad(vsg::ref_ptr<vsg::Data> sourceData, uint32_t mipmapLevelsHints)
  {
      struct ConvertToRGBA : public vsg::Visitor
      {
          vsg::ref_ptr<vsg::Data> textureData;
  
          void apply(vsg::Data& data) override
          {
              textureData = &data;
          }
  
          void apply(vsg::uintArray2D& fa) override
          {
              // treat as a 24bit depth buffer
              float div = 1.0f / static_cast<float>(1 << 24);
  
              auto rgba = vsg::vec4Array2D::create(fa.width(), fa.height(), vsg::Data::Layout{VK_FORMAT_R32G32B32A32_SFLOAT});
              auto dest_itr = rgba->begin();
              for (auto& v : fa)
              {
                  float m = static_cast<float>(v) * div;
                  (*dest_itr++).set(m, m, m, 1.0);
              }
              textureData = rgba;
          }
  
          void apply(vsg::floatArray2D& fa) override
          {
              auto rgba = vsg::vec4Array2D::create(fa.width(), fa.height(), vsg::Data::Layout{VK_FORMAT_R32G32B32A32_SFLOAT});
              auto dest_itr = rgba->begin();
              for (auto& v : fa)
              {
                  (*dest_itr++).set(v, v, v, 1.0);
              }
              textureData = rgba;
          }
  
          vsg::ref_ptr<vsg::Data> convert(vsg::ref_ptr<vsg::Data> data)
          {
              data->accept(*this);
              return textureData;
          }
  
      } convertToRGBA;
  
      auto textureData = convertToRGBA.convert(sourceData);
      if (!textureData) return {};
  
      // set up search paths to SPIRV shaders and textures
      vsg::Paths searchPaths = vsg::getEnvPaths("VSG_FILE_PATH");
  
      // load shaders
      vsg::ref_ptr<vsg::ShaderStage> vertexShader = vsg::ShaderStage::read(VK_SHADER_STAGE_VERTEX_BIT, "main", vsg::findFile("shaders/vert_PushConstants.spv", searchPaths));
      vsg::ref_ptr<vsg::ShaderStage> fragmentShader = vsg::ShaderStage::read(VK_SHADER_STAGE_FRAGMENT_BIT, "main", vsg::findFile("shaders/frag_PushConstants.spv", searchPaths));
      if (!vertexShader || !fragmentShader)
      {
          std::cout << "Could not create shaders." << std::endl;
          return {};
      }
  
      // set up graphics pipeline
      vsg::DescriptorSetLayoutBindings descriptorBindings{
          {0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT, nullptr} // { binding, descriptorTpe, descriptorCount, stageFlags, pImmutableSamplers}
      };
  
      auto descriptorSetLayout = vsg::DescriptorSetLayout::create(descriptorBindings);
  
      vsg::PushConstantRanges pushConstantRanges{
          {VK_SHADER_STAGE_VERTEX_BIT, 0, 128} // projection view, and model matrices, actual push constant calls autoaatically provided by the VSG's DispatchTraversal
      };
  
      vsg::VertexInputState::Bindings vertexBindingsDescriptions{
          VkVertexInputBindingDescription{0, sizeof(vsg::vec3), VK_VERTEX_INPUT_RATE_VERTEX}, // vertex data
          VkVertexInputBindingDescription{1, sizeof(vsg::vec3), VK_VERTEX_INPUT_RATE_VERTEX}, // colour data
          VkVertexInputBindingDescription{2, sizeof(vsg::vec2), VK_VERTEX_INPUT_RATE_VERTEX}  // tex coord data
      };
  
      vsg::VertexInputState::Attributes vertexAttributeDescriptions{
          VkVertexInputAttributeDescription{0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0}, // vertex data
          VkVertexInputAttributeDescription{1, 1, VK_FORMAT_R32G32B32_SFLOAT, 0}, // colour data
          VkVertexInputAttributeDescription{2, 2, VK_FORMAT_R32G32_SFLOAT, 0},    // tex coord data
      };
  
      vsg::GraphicsPipelineStates pipelineStates{
          vsg::VertexInputState::create(vertexBindingsDescriptions, vertexAttributeDescriptions),
          vsg::InputAssemblyState::create(),
          vsg::RasterizationState::create(),
          vsg::MultisampleState::create(),
          vsg::ColorBlendState::create(),
          vsg::DepthStencilState::create()};
  
      auto pipelineLayout = vsg::PipelineLayout::create(vsg::DescriptorSetLayouts{descriptorSetLayout}, pushConstantRanges);
      auto graphicsPipeline = vsg::GraphicsPipeline::create(pipelineLayout, vsg::ShaderStages{vertexShader, fragmentShader}, pipelineStates);
      auto bindGraphicsPipeline = vsg::BindGraphicsPipeline::create(graphicsPipeline);
  
      // create texture image and associated DescriptorSets and binding
      auto sampler = vsg::Sampler::create();
      sampler->maxLod = mipmapLevelsHints;
      auto texture = vsg::DescriptorImage::create(sampler, textureData, 0, 0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
  
      auto descriptorSet = vsg::DescriptorSet::create(descriptorSetLayout, vsg::Descriptors{texture});
      auto bindDescriptorSets = vsg::BindDescriptorSets::create(VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, vsg::DescriptorSets{descriptorSet});
  
      // create StateGroup as the root of the scene/command graph to hold the GraphicsProgram, and binding of Descriptors to decorate the whole graph
      auto scenegraph = vsg::StateGroup::create();
      scenegraph->add(bindGraphicsPipeline);
      scenegraph->add(bindDescriptorSets);
  
      // set up model transformation node
      auto transform = vsg::MatrixTransform::create(); // VK_SHADER_STAGE_VERTEX_BIT
  
      // add transform to root of the scene graph
      scenegraph->addChild(transform);
  
      // set up vertex and index arrays
      auto vertices = vsg::vec3Array::create(
          {{0.0f, 0.0f, 0.0f},
           {static_cast<float>(textureData->width()), 0.0f, 0.0f},
           {static_cast<float>(textureData->width()), 0.0f, static_cast<float>(textureData->height())},
           {0.0f, 0.0f, static_cast<float>(textureData->height())}}); // VK_FORMAT_R32G32B32_SFLOAT, VK_VERTEX_INPUT_RATE_INSTANCE, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_SHARING_MODE_EXCLUSIVE
  
      auto colors = vsg::vec3Array::create(
          {{1.0f, 1.0f, 1.0f},
           {1.0f, 1.0f, 1.0f},
           {1.0f, 1.0f, 1.0f},
           {1.0f, 1.0f, 1.0f}}); // VK_FORMAT_R32G32B32_SFLOAT, VK_VERTEX_INPUT_RATE_VERTEX, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_SHARING_MODE_EXCLUSIVE
  
      uint8_t origin = textureData->getLayout().origin; // in Vulkan the origin is by default top left.
      float left = 0.0f;
      float right = 1.0f;
      float top = (origin == vsg::TOP_LEFT) ? 0.0f : 1.0f;
      float bottom = (origin == vsg::TOP_LEFT) ? 1.0f : 0.0f;
      auto texcoords = vsg::vec2Array::create(
          {{left, bottom},
           {right, bottom},
           {right, top},
           {left, top}}); // VK_FORMAT_R32G32_SFLOAT, VK_VERTEX_INPUT_RATE_VERTEX, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_SHARING_MODE_EXCLUSIVE
  
      auto indices = vsg::ushortArray::create(
          {0, 1, 2,
           2, 3, 0}); // VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_SHARING_MODE_EXCLUSIVE
  
      // setup geometry
      auto drawCommands = vsg::Commands::create();
      drawCommands->addChild(vsg::BindVertexBuffers::create(0, vsg::DataList{vertices, colors, texcoords}));
      drawCommands->addChild(vsg::BindIndexBuffer::create(indices));
      drawCommands->addChild(vsg::DrawIndexed::create(6, 1, 0, 0, 0));
  
      // add drawCommands to transform
      transform->addChild(drawCommands);
  
      return scenegraph;
  }
  
  int main(int argc, char** argv)
  {
      try
      {
          // set up defaults and read command line arguments to override them
          vsg::CommandLine arguments(&argc, argv);
  
          // set up vsg::Options to pass in filepaths and ReaderWriter's and other IO realted options to use when reading and writing files.
          auto options = vsg::Options::create();
          options->fileCache = vsg::getEnv("VSG_FILE_CACHE");
          options->paths = vsg::getEnvPaths("VSG_FILE_PATH");
  
  #ifdef vsgXchange_all
          // add vsgXchange's support for reading and writing 3rd party file formats
          options->add(vsgXchange::all::create());
  #endif
  
          arguments.read(options);
  
          auto windowTraits = vsg::WindowTraits::create();
          windowTraits->windowTitle = "vsgviewer";
          windowTraits->debugLayer = arguments.read({"--debug", "-d"});
          windowTraits->apiDumpLayer = arguments.read({"--api", "-a"});
          if (arguments.read("--double-buffer")) windowTraits->swapchainPreferences.imageCount = 2;
          if (arguments.read("--triple-buffer")) windowTraits->swapchainPreferences.imageCount = 3; // default
          if (arguments.read("--IMMEDIATE")) windowTraits->swapchainPreferences.presentMode = VK_PRESENT_MODE_IMMEDIATE_KHR;
          if (arguments.read("--FIFO")) windowTraits->swapchainPreferences.presentMode = VK_PRESENT_MODE_FIFO_KHR;
          if (arguments.read("--FIFO_RELAXED")) windowTraits->swapchainPreferences.presentMode = VK_PRESENT_MODE_FIFO_RELAXED_KHR;
          if (arguments.read("--MAILBOX")) windowTraits->swapchainPreferences.presentMode = VK_PRESENT_MODE_MAILBOX_KHR;
          if (arguments.read({"-t", "--test"}))
          {
              windowTraits->swapchainPreferences.presentMode = VK_PRESENT_MODE_IMMEDIATE_KHR;
              windowTraits->fullscreen = true;
          }
          if (arguments.read({"--st", "--small-test"}))
          {
              windowTraits->swapchainPreferences.presentMode = VK_PRESENT_MODE_IMMEDIATE_KHR;
              windowTraits->width = 192, windowTraits->height = 108;
              windowTraits->decoration = false;
          }
          if (arguments.read({"--fullscreen", "--fs"})) windowTraits->fullscreen = true;
          if (arguments.read({"--window", "-w"}, windowTraits->width, windowTraits->height)) { windowTraits->fullscreen = false; }
          if (arguments.read({"--no-frame", "--nf"})) windowTraits->decoration = false;
          if (arguments.read("--or")) windowTraits->overrideRedirect = true;
          if (arguments.read("--d32")) windowTraits->depthFormat = VK_FORMAT_D32_SFLOAT;
          arguments.read("--screen", windowTraits->screenNum);
          arguments.read("--display", windowTraits->display);
          arguments.read("--samples", windowTraits->samples);
          auto numFrames = arguments.value(-1, "-f");
          auto pathFilename = arguments.value(std::string(), "-p");
          auto loadLevels = arguments.value(0, "--load-levels");
          auto horizonMountainHeight = arguments.value(0.0, "--hmh");
          auto mipmapLevelsHint = arguments.value<uint32_t>(0, {"--mipmapLevels", "--mml"});
          if (arguments.read("--rgb")) options->mapRGBtoRGBAHint = false;
  
          if (arguments.errors()) return arguments.writeErrorMessages(std::cerr);
  
          if (argc <= 1)
          {
              std::cout << "Please specify a 3d model or image file on the command line." << std::endl;
              return 1;
          }
  
          auto group = vsg::Group::create();
  
          vsg::Path path;
  
          // read any vsg files
          for (int i = 1; i < argc; ++i)
          {
              vsg::Path filename = arguments[i];
              path = vsg::filePath(filename);
  
              auto object = vsg::read(filename, options);
              if (auto node = object.cast<vsg::Node>(); node)
              {
                  group->addChild(node);
              }
              else if (auto data = object.cast<vsg::Data>(); data)
              {
                  if (auto textureGeometry = createTextureQuad(data, mipmapLevelsHint); textureGeometry)
                  {
                      group->addChild(textureGeometry);
                  }
              }
              else if (object)
              {
                  std::cout << "Unable to view object of type " << object->className() << std::endl;
              }
              else
              {
                  std::cout << "Unable to load file " << filename << std::endl;
              }
          }
  
          if (group->children.empty())
          {
              return 1;
          }
  
          vsg::ref_ptr<vsg::Node> vsg_scene;
          if (group->children.size() == 1)
              vsg_scene = group->children[0];
          else
              vsg_scene = group;
  
          // create the viewer and assign window(s) to it
          auto viewer = vsg::Viewer::create();
          auto window = vsg::Window::create(windowTraits);
          if (!window)
          {
              std::cout << "Could not create windows." << std::endl;
              return 1;
          }
  
          viewer->addWindow(window);
  
          // compute the bounds of the scene graph to help position camera
          vsg::ComputeBounds computeBounds;
          vsg_scene->accept(computeBounds);
          vsg::dvec3 centre = (computeBounds.bounds.min + computeBounds.bounds.max) * 0.5;
          double radius = vsg::length(computeBounds.bounds.max - computeBounds.bounds.min) * 0.6;
          double nearFarRatio = 0.001;
  
          // set up the camera
          auto lookAt = vsg::LookAt::create(centre + vsg::dvec3(0.0, -radius * 3.5, 0.0), centre, vsg::dvec3(0.0, 0.0, 1.0));
  
          vsg::ref_ptr<vsg::ProjectionMatrix> perspective;
          vsg::ref_ptr<vsg::EllipsoidModel> ellipsoidModel(vsg_scene->getObject<vsg::EllipsoidModel>("EllipsoidModel"));
          if (ellipsoidModel)
          {
              perspective = vsg::EllipsoidPerspective::create(lookAt, ellipsoidModel, 30.0, static_cast<double>(window->extent2D().width) / static_cast<double>(window->extent2D().height), nearFarRatio, horizonMountainHeight);
          }
          else
          {
              perspective = vsg::Perspective::create(30.0, static_cast<double>(window->extent2D().width) / static_cast<double>(window->extent2D().height), nearFarRatio * radius, radius * 4.5);
          }
  
          auto camera = vsg::Camera::create(perspective, lookAt, vsg::ViewportState::create(window->extent2D()));
  
          // add close handler to respond the close window button and pressing escape
          viewer->addEventHandler(vsg::CloseHandler::create(viewer));
  
          if (pathFilename.empty())
          {
              viewer->addEventHandler(vsg::Trackball::create(camera, ellipsoidModel));
          }
          else
          {
              std::ifstream in(pathFilename);
              if (!in)
              {
                  std::cout << "AnimationPat: Could not open animation path file \"" << pathFilename << "\".\n";
                  return 1;
              }
  
              vsg::ref_ptr<vsg::AnimationPath> animationPath(new vsg::AnimationPath);
              animationPath->read(in);
  
              viewer->addEventHandler(vsg::AnimationPathHandler::create(camera, animationPath, viewer->start_point()));
          }
  
          // if required pre load specific number of PagedLOD levels.
          if (loadLevels > 0)
          {
              vsg::LoadPagedLOD loadPagedLOD(camera, loadLevels);
  
              if (!path.empty())
              {
                  options->paths.insert(options->paths.begin(), path);
              }
  
              loadPagedLOD.options = options;
  
              auto startTime = std::chrono::steady_clock::now();
  
              vsg_scene->accept(loadPagedLOD);
  
              auto time = std::chrono::duration<float, std::chrono::milliseconds::period>(std::chrono::steady_clock::now() - startTime).count();
              std::cout << "No. of tiles loaed " << loadPagedLOD.numTiles << " in " << time << "ms." << std::endl;
          }
  
          auto commandGraph = vsg::createCommandGraphForView(window, camera, vsg_scene);
          viewer->assignRecordAndSubmitTaskAndPresentation({commandGraph});
  
          viewer->compile();
  
          // rendering main loop
          while (viewer->advanceToNextFrame() && (numFrames < 0 || (numFrames--) > 0))
          {
              // pass any events into EventHandlers assigned to the Viewer
              viewer->handleEvents();
  
              viewer->update();
  
              viewer->recordAndSubmit();
  
              viewer->present();
          }
      }
      catch (const vsg::Exception& ve)
      {
          for (int i = 0; i < argc; ++i) std::cerr << argv[i] << " ";
          std::cerr << "\n[Exception] - " << ve.message << " result = " << ve.result << std::endl;
          return 1;
      }
  
      // clean up done automatically thanks to ref_ptr<>
      return 0;
  }
  