255 lines
9.1 KiB
C++
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;
|
|
}
|