Files
xr-simulator/tools/configurator/ConfiguratorMain.cpp
T

255 lines
9.1 KiB
C++

// OpenXR Simulator configurator.
//
// A small GLFW + Dear ImGui tool that edits the runtime's JSON config file. The runtime DLL reads
// the same file (resolved via XRSIM_CONFIG, else <dll-dir>/openxr_simulator_config.json).
#define GLFW_INCLUDE_NONE
#ifdef _WIN32
# ifndef WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN
# endif
# include <windows.h>
#endif
#include <GL/gl.h>
#include <GLFW/glfw3.h>
#include "imgui.h"
#include "imgui_impl_glfw.h"
#include "imgui_impl_opengl3.h"
#include "Config.h"
#include "HeadsetPresets.h"
#include <cstring>
#include <string>
namespace
{
int PresentModeIndex( xrsim::PresentMode mode )
{
switch ( mode )
{
case xrsim::PresentMode::Mailbox: return 0;
case xrsim::PresentMode::Immediate: return 1;
case xrsim::PresentMode::Fifo: return 2;
}
return 0;
}
int DisplayModeIndex( xrsim::DisplayMode mode )
{
switch ( mode )
{
case xrsim::DisplayMode::SideBySide: return 0;
case xrsim::DisplayMode::SingleEyeLeft: return 1;
case xrsim::DisplayMode::SingleEyeRight: return 2;
}
return 0;
}
} // namespace
int main( int, char ** )
{
if ( glfwInit() != GLFW_TRUE )
{
return 1;
}
const char * glslVersion = "#version 130";
glfwWindowHint( GLFW_CONTEXT_VERSION_MAJOR, 3 );
glfwWindowHint( GLFW_CONTEXT_VERSION_MINOR, 0 );
GLFWwindow * window =
glfwCreateWindow( 920, 720, "OpenXR Simulator Configurator", nullptr, nullptr );
if ( window == nullptr )
{
glfwTerminate();
return 1;
}
glfwMakeContextCurrent( window );
glfwSwapInterval( 1 );
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGui::StyleColorsDark();
ImGui_ImplGlfw_InitForOpenGL( window, true );
ImGui_ImplOpenGL3_Init( glslVersion );
std::string configPath = xrsim::SimulatorConfig::ResolvePath();
xrsim::SimulatorConfig config = xrsim::SimulatorConfig::LoadFrom( configPath );
std::string status;
while ( glfwWindowShouldClose( window ) == 0 )
{
glfwPollEvents();
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
const ImGuiViewport * viewport = ImGui::GetMainViewport();
ImGui::SetNextWindowPos( viewport->WorkPos );
ImGui::SetNextWindowSize( viewport->WorkSize );
ImGui::Begin(
"OpenXR Simulator Config", nullptr,
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize
);
ImGui::TextWrapped(
"These settings are read by openxr_simulator.dll at runtime startup. Restart the "
"application after saving."
);
ImGui::Separator();
static char pathBuffer[1024] = {};
if ( pathBuffer[0] == '\0' )
{
std::strncpy( pathBuffer, configPath.c_str(), sizeof( pathBuffer ) - 1 );
}
if ( ImGui::InputText( "Config path", pathBuffer, sizeof( pathBuffer ) ) )
{
configPath = pathBuffer;
}
if ( ImGui::Button( "Reload" ) )
{
config = xrsim::SimulatorConfig::LoadFrom( configPath );
status = "Reloaded.";
}
ImGui::SameLine();
if ( ImGui::Button( "Save" ) )
{
status = config.Save( configPath ) ? "Saved." : "Failed to save!";
}
ImGui::SameLine();
if ( ImGui::Button( "Reset to defaults" ) )
{
config = xrsim::SimulatorConfig{};
status = "Reset to defaults (not saved yet).";
}
if ( !status.empty() )
{
ImGui::SameLine();
ImGui::TextColored( ImVec4( 0.5f, 0.9f, 0.5f, 1.0f ), "%s", status.c_str() );
}
ImGui::Separator();
{
constexpr int presetCount = static_cast<int>( xrsim::kHeadsetPresets.size() );
static const char * presetItems[presetCount + 1] = {};
if ( presetItems[0] == nullptr )
{
presetItems[0] = "Custom";
for ( int i = 0; i < presetCount; ++i )
{
presetItems[i + 1] = xrsim::kHeadsetPresets[i].name;
}
}
static int presetIndex = 0;
if ( ImGui::Combo( "Headset preset", &presetIndex, presetItems, presetCount + 1 ) &&
presetIndex > 0 )
{
const xrsim::HeadsetPreset & preset = xrsim::kHeadsetPresets[presetIndex - 1];
config.systemName = preset.name;
config.eyeWidth = preset.eyeWidth;
config.eyeHeight = preset.eyeHeight;
config.refreshRateHz = preset.refreshRateHz;
config.fovLeftDeg = preset.fovLeftDeg;
config.fovRightDeg = preset.fovRightDeg;
config.fovUpDeg = preset.fovUpDeg;
config.fovDownDeg = preset.fovDownDeg;
config.ipdMeters = preset.ipdMeters;
status = std::string( "Applied preset: " ) + preset.name;
}
}
ImGui::Separator();
if ( ImGui::CollapsingHeader( "Headset", ImGuiTreeNodeFlags_DefaultOpen ) )
{
char nameBuffer[256];
std::strncpy( nameBuffer, config.systemName.c_str(), sizeof( nameBuffer ) - 1 );
nameBuffer[sizeof( nameBuffer ) - 1] = '\0';
if ( ImGui::InputText( "System name", nameBuffer, sizeof( nameBuffer ) ) )
{
config.systemName = nameBuffer;
}
ImGui::InputScalar( "Vendor id", ImGuiDataType_U32, &config.vendorId );
}
if ( ImGui::CollapsingHeader( "Display", ImGuiTreeNodeFlags_DefaultOpen ) )
{
ImGui::InputScalar( "Eye width", ImGuiDataType_U32, &config.eyeWidth );
ImGui::InputScalar( "Eye height", ImGuiDataType_U32, &config.eyeHeight );
ImGui::InputScalar( "Refresh rate (Hz)", ImGuiDataType_U32, &config.refreshRateHz );
ImGui::SliderFloat( "IPD (m)", &config.ipdMeters, 0.04f, 0.09f, "%.3f" );
ImGui::InputScalar( "Swapchain images", ImGuiDataType_U32, &config.swapchainImageCount );
}
if ( ImGui::CollapsingHeader( "Field of view (half-angle degrees)", ImGuiTreeNodeFlags_DefaultOpen ) )
{
ImGui::SliderFloat( "Left", &config.fovLeftDeg, 20.0f, 80.0f, "%.1f" );
ImGui::SliderFloat( "Right", &config.fovRightDeg, 20.0f, 80.0f, "%.1f" );
ImGui::SliderFloat( "Up", &config.fovUpDeg, 20.0f, 80.0f, "%.1f" );
ImGui::SliderFloat( "Down", &config.fovDownDeg, 20.0f, 80.0f, "%.1f" );
if ( ImGui::Button( "Symmetric" ) )
{
config.fovRightDeg = config.fovLeftDeg;
config.fovUpDeg = config.fovLeftDeg;
config.fovDownDeg = config.fovLeftDeg;
}
}
if ( ImGui::CollapsingHeader( "Compositor window", ImGuiTreeNodeFlags_DefaultOpen ) )
{
char titleBuffer[256];
std::strncpy( titleBuffer, config.windowTitle.c_str(), sizeof( titleBuffer ) - 1 );
titleBuffer[sizeof( titleBuffer ) - 1] = '\0';
if ( ImGui::InputText( "Window title", titleBuffer, sizeof( titleBuffer ) ) )
{
config.windowTitle = titleBuffer;
}
ImGui::InputInt( "Window width", &config.windowWidth );
ImGui::InputInt( "Window height", &config.windowHeight );
int presentIndex = PresentModeIndex( config.presentMode );
const char * presentItems[] = { "Mailbox (uncapped)", "Immediate (uncapped, tearing)",
"FIFO (vsync)" };
if ( ImGui::Combo( "Present mode", &presentIndex, presentItems, IM_ARRAYSIZE( presentItems ) ) )
{
config.presentMode = static_cast<xrsim::PresentMode>( presentIndex );
}
int displayIndex = DisplayModeIndex( config.displayMode );
const char * displayItems[] = { "Side by side", "Single eye (left)", "Single eye (right)" };
if ( ImGui::Combo( "Display mode", &displayIndex, displayItems, IM_ARRAYSIZE( displayItems ) ) )
{
config.displayMode = static_cast<xrsim::DisplayMode>( displayIndex );
}
}
ImGui::Separator();
ImGui::TextDisabled(
"Note: 'Mailbox' avoids capping the application to the monitor refresh rate."
);
ImGui::End();
ImGui::Render();
int displayWidth = 0, displayHeight = 0;
glfwGetFramebufferSize( window, &displayWidth, &displayHeight );
glViewport( 0, 0, displayWidth, displayHeight );
glClearColor( 0.10f, 0.10f, 0.12f, 1.0f );
glClear( GL_COLOR_BUFFER_BIT );
ImGui_ImplOpenGL3_RenderDrawData( ImGui::GetDrawData() );
glfwSwapBuffers( window );
}
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown();
ImGui::DestroyContext();
glfwDestroyWindow( window );
glfwTerminate();
return 0;
}