Initial commit: OpenXR simulator runtime, compositor, configurator, docs
This commit is contained in:
@@ -0,0 +1,254 @@
|
||||
// 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;
|
||||
}
|
||||
Reference in New Issue
Block a user