ゲームグラフィックス特論
読み取り中…
検索中…
一致する文字列を見つけられません
GgApp.cpp
[詳解]
1/*
2
3ゲームグラフィックス特論用補助プログラム GLFW3 版
4
5Copyright (c) 2011-2025 Kohe Tokoi. All Rights Reserved.
6
7Permission is hereby granted, free of charge, to any person obtaining a copy
8of this software and associated documentation files (the "Software"), to deal
9in the Software without restriction, including without limitation the rights
10to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11copies or substantial portions of the Software.
12
13The above copyright notice and this permission notice shall be included in
14all copies or substantial portions of the Software.
15
16THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19KOHE TOKOI BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
20AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
23*/
24
32#include "GgApp.h"
33using namespace gg;
34
35//
36// GLFW のエラー表示
37//
38static void glfwErrorCallback(int error, const char* description)
39{
40#if defined(__aarch64__)
41 if (error == 65544) return;
42#endif
43 throw std::runtime_error(description);
44}
45
46//
47// GgApp クラスのコンストラクタ
48//
49GgApp::GgApp(int major, int minor)
50{
51 // GLFW のエラー処理関数を登録する
52 glfwSetErrorCallback(glfwErrorCallback);
53
54 // GLFW を初期化する
55 if (glfwInit() == GL_FALSE) throw std::runtime_error("Can't initialize GLFW");
56
57 // OpenGL の major 番号が指定されていれば
58 if (major > 0)
59 {
60 // OpenGL のバージョンを指定する
61 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, major);
62 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, minor);
63
64#if defined(GL_GLES_PROTOTYPES)
65 // OpenGL ES 3 のコンテキストを指定する
66 glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
67 glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_EGL_CONTEXT_API);
68#else
69 // OpenGL Version 3.2 以降なら
70 if (major * 10 + minor >= 32)
71 {
72 // Core Profile を選択する (macOS の都合)
73 glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
74 glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
75 }
76#endif
77 }
78
79#if defined(IMGUI_VERSION)
80 // ImGui のバージョンをチェックする
81 IMGUI_CHECKVERSION();
82
83 // ImGui のコンテキストを作成する
84 ImGui::CreateContext();
85#endif
86}
87
88//
89// デストラクタ
90//
92{
93#if defined(IMGUI_VERSION)
94 // Shutdown Platform/Renderer bindings
95 ImGui_ImplOpenGL3_Shutdown();
96 ImGui_ImplGlfw_Shutdown();
97 ImGui::DestroyContext();
98#endif
99
100 // プログラム終了時に GLFW を終了する
101 glfwTerminate();
102}
103
104//
105// マウスや矢印キーによる平行移動量を初期化する
106//
107void GgApp::Window::HumanInterface::resetTranslation()
108{
109 // 平行移動量を初期化する
110 for (auto& t : translation)
111 {
112 std::fill(t.begin(), t.end(), GgVector{ 0.0f, 0.0f, 0.0f, 1.0f });
113 }
114
115 // 矢印キーの設定値を初期化する
116 std::fill(arrow.begin(), arrow.end(), std::array<int, 2>{ 0, 0 });
117
118 // マウスホイールの回転量を初期化する
119 std::fill(wheel.begin(), wheel.end(), 0.0f);
120}
121
122//
123// 平行移動量と回転量を更新する (X, Y のみ, Z は wheel() で計算する)
124//
125void GgApp::Window::HumanInterface::calcTranslation(int button, const std::array<GLfloat, 3>& velocity)
126{
127 // マウスの相対変位
128 assert(button >= GLFW_MOUSE_BUTTON_1 && button < GLFW_MOUSE_BUTTON_1 + GG_BUTTON_COUNT);
129 const auto dx{ (mouse[0] - rotation[button].getStart(0)) * rotation[button].getScale(0) };
130 const auto dy{ (rotation[button].getStart(1) - mouse[1]) * rotation[button].getScale(1) };
131
132 // 平行移動量
133 auto& t{ translation[button] };
134
135 // 平行移動量の更新
136 t[1][0] = dx * velocity[0] + t[0][0];
137 t[1][1] = dy * velocity[1] + t[0][1];
138
139 // 回転量の更新
140 rotation[button].motion(mouse[0], mouse[1]);
141}
142
143//
144// ウィンドウのサイズ変更時の処理
145//
146void GgApp::Window::resize(GLFWwindow* window, int width, int height)
147{
148 // このインスタンスの this ポインタを得る
149 auto* const instance{ static_cast<Window*>(glfwGetWindowUserPointer(window)) };
150
151 if (instance)
152 {
153 // ウィンドウのサイズを保存する
154 instance->size[0] = width;
155 instance->size[1] = height;
156
157 // トラックボール処理の範囲を設定する
158 for (auto& current_if : instance->interfaceData)
159 {
160 for (auto& t : current_if.rotation)
161 {
162 t.region(width, height);
163 }
164 }
165
166 // ビューポートを更新する
167 instance->updateViewport();
168
169 // ユーザー定義のコールバック関数の呼び出し
170 if (instance->resizeFunc) (*instance->resizeFunc)(instance, width, height);
171 }
172}
173
174//
175// キーボードをタイプした時の処理
176//
177void GgApp::Window::keyboard(GLFWwindow* window, int key, int scancode, int action, int mods)
178{
179#if defined(IMGUI_VERSION)
180 // ImGui がキーボードを使うときはキーボードの処理を行わない
181 if (ImGui::GetIO().WantCaptureKeyboard) return;
182#endif
183
184 // このインスタンスの this ポインタを得る
185 auto* const instance{ static_cast<Window*>(glfwGetWindowUserPointer(window)) };
186
187 if (instance && action)
188 {
189 // ユーザー定義のコールバック関数の呼び出し
190 if (instance->keyboardFunc) (*instance->keyboardFunc)(instance, key, scancode, action, mods);
191
192 // 対象のユーザインタフェース
193 auto& current_if{ instance->interfaceData[instance->interfaceNo] };
194
195 switch (key)
196 {
197 case GLFW_KEY_HOME:
198
199 // トラックボールを初期化する
200 instance->resetRotation();
201 [[fallthrough]];
202
203 case GLFW_KEY_END:
204
205 // 平行移動量を初期化する
206 instance->resetTranslation();
207 break;
208
209 case GLFW_KEY_UP:
210
211 if (mods & GLFW_MOD_SHIFT)
212 current_if.arrow[1][1]++;
213 else if (mods & GLFW_MOD_CONTROL)
214 current_if.arrow[2][1]++;
215 else if (mods & GLFW_MOD_ALT)
216 current_if.arrow[3][1]++;
217 else
218 current_if.arrow[0][1]++;
219 break;
220
221 case GLFW_KEY_DOWN:
222
223 if (mods & GLFW_MOD_SHIFT)
224 current_if.arrow[1][1]--;
225 else if (mods & GLFW_MOD_CONTROL)
226 current_if.arrow[2][1]--;
227 else if (mods & GLFW_MOD_ALT)
228 current_if.arrow[3][1]--;
229 else
230 current_if.arrow[0][1]--;
231 break;
232
233 case GLFW_KEY_RIGHT:
234
235 if (mods & GLFW_MOD_SHIFT)
236 current_if.arrow[1][0]++;
237 else if (mods & GLFW_MOD_CONTROL)
238 current_if.arrow[2][0]++;
239 else if (mods & GLFW_MOD_ALT)
240 current_if.arrow[3][0]++;
241 else
242 current_if.arrow[0][0]++;
243 break;
244
245 case GLFW_KEY_LEFT:
246
247 if (mods & GLFW_MOD_SHIFT)
248 current_if.arrow[1][0]--;
249 else if (mods & GLFW_MOD_CONTROL)
250 current_if.arrow[2][0]--;
251 else if (mods & GLFW_MOD_ALT)
252 current_if.arrow[3][0]--;
253 else
254 current_if.arrow[0][0]--;
255 break;
256
257 default:
258 break;
259 }
260
261 current_if.lastKey = key;
262 }
263}
264
265//
266// マウスボタンを操作したときの処理
267//
268void GgApp::Window::mouse(GLFWwindow* window, int button, int action, int mods)
269{
270#if defined(IMGUI_VERSION)
271 // ImGui がマウスを使うときは Window クラスのマウス位置を更新しない
272 if (ImGui::GetIO().WantCaptureMouse) return;
273#endif
274
275 // このインスタンスの this ポインタを得る
276 auto* const instance{ static_cast<Window*>(glfwGetWindowUserPointer(window)) };
277
278 // マウスボタンの状態を記録する
279 assert(button >= GLFW_MOUSE_BUTTON_1 && button < GLFW_MOUSE_BUTTON_1 + GG_BUTTON_COUNT);
280 instance->status[button] = action != GLFW_RELEASE;
281
282 if (instance)
283 {
284 // ユーザー定義のコールバック関数の呼び出し
285 if (instance->mouseFunc) (*instance->mouseFunc)(instance, button, action, mods);
286
287 // 対象のユーザインタフェース
288 auto& current_if{ instance->interfaceData[instance->interfaceNo] };
289
290 // マウスの現在位置を得る
291 const auto x{ current_if.mouse[0] };
292 const auto y{ current_if.mouse[1] };
293
294 if (x < 0 || x >= instance->size[0] || y < 0 || y >= instance->size[1]) return;
295
296 if (action)
297 {
298 // ドラッグ開始
299 current_if.rotation[button].begin(x, y);
300 }
301 else
302 {
303 // ドラッグ終了
304 current_if.translation[button][0] = current_if.translation[button][1];
305 current_if.rotation[button].end(x, y);
306 }
307 }
308}
309
310//
311// マウスホイールを操作した時の処理
312//
313void GgApp::Window::wheel(GLFWwindow* window, double x, double y)
314{
315#if defined(IMGUI_VERSION)
316 // ImGui がマウスを使うときは Window クラスのマウス位置を更新しない
317 if (ImGui::GetIO().WantCaptureMouse) return;
318#endif
319
320 // このインスタンスの this ポインタを得る
321 auto* const instance{ static_cast<Window*>(glfwGetWindowUserPointer(window)) };
322
323 if (instance)
324 {
325 // ユーザー定義のコールバック関数の呼び出し
326 if (instance->wheelFunc) (*instance->wheelFunc)(instance, x, y);
327
328 // 対象のユーザインタフェース
329 auto& current_if{ instance->interfaceData[instance->interfaceNo] };
330
331 // マウスホイールの回転量の保存
332 current_if.wheel[0] += static_cast<GLfloat>(x);
333 current_if.wheel[1] += static_cast<GLfloat>(y);
334
335 // マウスによる平行移動量の z 値の更新
336 const auto z{ current_if.wheel[1] * instance->velocity[2] };
337 for (auto& t : current_if.translation) t[1][2] = z;
338 }
339}
340
341//
342// Window クラスのコンストラクタ
343//
344GgApp::Window::Window(const std::string& title, int width, int height, int fullscreen, GLFWwindow* share) :
345 size{ width, height },
346 fboSize{ width, height }
347{
348 // ディスプレイの情報
349 GLFWmonitor* monitor{ nullptr };
350
351 // フルスクリーン表示
352 if (fullscreen > 0)
353 {
354 // 接続されているモニタの数を数える
355 int mcount;
356 auto** const monitors{ glfwGetMonitors(&mcount) };
357
358 // セカンダリモニタがあればそれを使う
359 if (fullscreen > mcount) fullscreen = mcount;
360 monitor = monitors[fullscreen - 1];
361
362 // モニタのモードを調べる
363 const auto* mode{ glfwGetVideoMode(monitor) };
364
365 // ウィンドウのサイズをディスプレイのサイズにする
366 width = mode->width;
367 height = mode->height;
368 }
369
370 // GLFW のウィンドウを作成する
371 window = glfwCreateWindow(width, height, title.c_str(), monitor, share);
372
373 // ウィンドウが作成できなければエラー
374 if (!window) throw std::runtime_error("Unable to open the GLFW window.");
375
376 // 現在のウィンドウを処理対象にする
377 glfwMakeContextCurrent(window);
378
379 // ゲームグラフィックス特論の都合による初期化を行う
380 ggInit();
381
382 // このインスタンスの this ポインタを記録しておく
383 glfwSetWindowUserPointer(window, this);
384
385 // キーボードを操作した時の処理を登録する
386 glfwSetKeyCallback(window, keyboard);
387
388 // マウスボタンを操作したときの処理を登録する
389 glfwSetMouseButtonCallback(window, mouse);
390
391 // マウスホイール操作時に呼び出す処理を登録する
392 glfwSetScrollCallback(window, wheel);
393
394 // ウィンドウのサイズ変更時に呼び出す処理を登録する
395 glfwSetFramebufferSizeCallback(window, resize);
396
397 // 垂直同期タイミングに合わせる
398 glfwSwapInterval(1);
399
400 // 実際のフレームバッファのサイズを取得する
401 glfwGetFramebufferSize(window, &width, &height);
402
403 // ビューポートと投影変換行列を初期化する
404 resize(window, width, height);
405
406#if defined(IMGUI_VERSION)
407 // 最初のウィンドウを開いたとき
408 static bool firstTime{ true };
409 if (firstTime)
410 {
411 // Setup Platform/Renderer bindings
412 ImGui_ImplGlfw_InitForOpenGL(window, true);
413 ImGui_ImplOpenGL3_Init(nullptr);
414
415 // 実行済みであることを記録する
416 firstTime = false;
417 }
418#endif
419}
420
421//
422// Window クラスのムーブコンストラクタ
423//
425 window{ w.window },
426 size{ w.size },
427 fboSize{ w.fboSize },
428 interfaceData{ std::move(w.interfaceData) },
429 interfaceNo{ w.interfaceNo },
430 userPointer{ w.userPointer },
431 resizeFunc{ w.resizeFunc },
432 keyboardFunc{ w.keyboardFunc },
433 mouseFunc{ w.mouseFunc },
434 wheelFunc{ w.wheelFunc }
435{
436 w.window = nullptr;
437 if (window)
438 {
439 glfwSetWindowUserPointer(window, this);
440 }
441}
442
443//
444// Window クラスのムーブ代入演算子
445//
447{
448 if (&w != this)
449 {
450 if (window)
451 {
452 glfwDestroyWindow(window);
453 }
454 window = w.window;
455 size = w.size;
456 fboSize = w.fboSize;
457 interfaceData = std::move(w.interfaceData);
458 interfaceNo = w.interfaceNo;
459 userPointer = w.userPointer;
460 resizeFunc = w.resizeFunc;
461 keyboardFunc = w.keyboardFunc;
462 mouseFunc = w.mouseFunc;
463 wheelFunc = w.wheelFunc;
464
465 w.window = nullptr;
466 if (window)
467 {
468 glfwSetWindowUserPointer(window, this);
469 }
470 }
471 return *this;
472}
473
474//
475// イベントを取得してループを継続すべきかどうか調べる
476//
477GgApp::Window::operator bool()
478{
479 // イベントを取り出す
480 glfwPollEvents();
481
482 // ウィンドウを閉じるべきなら false を返す
483 if (shouldClose()) return false;
484
485 // 対象のユーザインタフェース
486 auto& current_if{ interfaceData[interfaceNo] };
487
488#if defined(IMGUI_VERSION)
489 // ImGui の新規フレームを作成する
490 ImGui_ImplOpenGL3_NewFrame();
491 ImGui_ImplGlfw_NewFrame();
492 ImGui::NewFrame();
493
494 // ImGui の状態を取り出す
495 const ImGuiIO& io{ ImGui::GetIO() };
496
497 // ImGui がマウスを使うときは Window クラスのマウス位置を更新しない
498 if (io.WantCaptureMouse) return true;
499
500 // マウスの位置を更新する
501 current_if.mouse = std::array<GLfloat, 2>{ io.MousePos.x, io.MousePos.y };
502#else
503 // マウスの現在位置を調べる
504 double x, y;
505 glfwGetCursorPos(window, &x, &y);
506
507 // マウスの位置を更新する
508 current_if.mouse = std::array<GLfloat, 2>{ static_cast<GLfloat>(x), static_cast<GLfloat>(y) };
509#endif
510
511 // マウスドラッグ
512 for (int button = GLFW_MOUSE_BUTTON_1; button < GLFW_MOUSE_BUTTON_1 + GG_BUTTON_COUNT; ++button)
513 {
514 // マウスボタンを押していたら
515 if (status[button])
516 {
517 // 現在位置と平行移動量を更新する
518 current_if.calcTranslation(button, velocity);
519 }
520 }
521
522 return true;
523}
524
525//
526// カラーバッファを入れ替える
527//
529{
530#if defined(IMGUI_VERSION)
531 // ImGui の描画データがあればフレームをレンダリングする
532 ImGui::Render();
533 ImDrawData* data{ ImGui::GetDrawData() };
534 if (data) ImGui_ImplOpenGL3_RenderDrawData(data);
535#endif
536
537 // エラーチェック
538 ggError();
539
540 // カラーバッファを入れ替える
541 glfwSwapBuffers(window);
542}
543
544//
545// ビューポートのサイズを更新する
546//
548{
549 // フレームバッファの大きさを求める
550 glfwGetFramebufferSize(window, &fboSize[0], &fboSize[1]);
551
552#if defined(IMGUI_VERSION)
553 // フレームバッファの高さからメニューバーの高さを減じる
554 fboSize[1] -= menubarHeight;
555#endif
556
557 // ウィンドウの縦横比を保存する
558 aspect = static_cast<GLfloat>(fboSize[0]) / static_cast<GLfloat>(fboSize[1]);
559
560 // ビューポートを設定する
562}
563
564#if defined(GG_USE_OPENXR)
565
566namespace
567{
568 //
569 // OpenXR の関数の戻り値を文字列にする
570 //
571 std::string xrMessage(XrInstance instance, XrResult result, const std::string& message)
572 {
573 char buffer[XR_MAX_RESULT_STRING_SIZE]{ '\0' };
574 if (instance == XR_NULL_HANDLE
575 || XR_FAILED(xrResultToString(instance, result, buffer))
576 || buffer[0] == '\0')
577 {
578 std::snprintf(buffer, sizeof buffer, "XrResult(%d)", static_cast<int>(result));
579 }
580 return message + ": " + buffer;
581 }
582
583 //
584 // OpenXR の関数の戻り値を検査して, エラーなら例外を投げる
585 //
586 void xrCheck(XrInstance instance, XrResult result, const std::string& message)
587 {
588 if (XR_SUCCEEDED(result)) return;
589 throw std::runtime_error(xrMessage(instance, result, message));
590 }
591
592 //
593 // OpenXR の関数の戻り値を検査して, エラーなら標準エラー出力に報告する
594 //
595 bool xrWarn(XrInstance instance, XrResult result, const std::string& message)
596 {
597 if (XR_SUCCEEDED(result)) return true;
598 std::cerr << "OpenXR: " << xrMessage(instance, result, message) << '\n';
599 return false;
600 }
601
602 //
603 // 固定長の文字列に安全にコピーする
604 //
605 void xrCopyString(char* destination, size_t size, const char* source)
606 {
607 if (size == 0) return;
608 const auto length{ std::min(std::strlen(source), size - 1) };
609 std::memcpy(destination, source, length);
610 destination[length] = '\0';
611 }
612}
613
614//
615// コンストラクタ
616//
617GgApp::OpenXR::OpenXR()
618{
619}
620
621//
622// デストラクタ
623//
624GgApp::OpenXR::~OpenXR()
625{
626 // このオブジェクトは関数内 static なので, 破棄されるのは main() が
627 // 終了した後, すなわち OpenGL のコンテキストが失われた後である.
628 // したがってここでは OpenGL の資源には触れず, OpenXR のハンドルだけを
629 // 解放する (OpenGL の資源は terminate() で解放しておくこと).
630 destroyXr();
631}
632
633//
634// アクションシステムを初期化する
635//
636void GgApp::OpenXR::initActions()
637{
638 // アクションセットの作成
639 XrActionSetCreateInfo actionSetInfo{ XR_TYPE_ACTION_SET_CREATE_INFO };
640 xrCopyString(actionSetInfo.actionSetName, sizeof actionSetInfo.actionSetName, "gameplay");
641 xrCopyString(actionSetInfo.localizedActionSetName, sizeof actionSetInfo.localizedActionSetName, "Gameplay");
642 actionSetInfo.priority = 0;
643 xrCheck(instance, xrCreateActionSet(instance, &actionSetInfo, &actionSet),
644 "Can't create the OpenXR action set");
645
646 // サブアクションパスの取得
647 xrCheck(instance, xrStringToPath(instance, "/user/hand/left", &handSubactionPath[Hand::Left]),
648 "Can't convert the path of the left hand");
649 xrCheck(instance, xrStringToPath(instance, "/user/hand/right", &handSubactionPath[Hand::Right]),
650 "Can't convert the path of the right hand");
651
652 // アクション作成ヘルパー
653 auto createAction = [this](const char* name, const char* localizedName, XrActionType type, XrAction& action)
654 {
655 XrActionCreateInfo createInfo{ XR_TYPE_ACTION_CREATE_INFO };
656 xrCopyString(createInfo.actionName, sizeof createInfo.actionName, name);
657 xrCopyString(createInfo.localizedActionName, sizeof createInfo.localizedActionName, localizedName);
658 createInfo.actionType = type;
659 createInfo.countSubactionPaths = Hand::Count;
660 createInfo.subactionPaths = handSubactionPath;
661 xrCheck(instance, xrCreateAction(actionSet, &createInfo, &action),
662 std::string("Can't create the OpenXR action \"") + name + "\"");
663 };
664
665 createAction("aim_pose", "Aim Pose", XR_ACTION_TYPE_POSE_INPUT, aimPoseAction);
666 createAction("grip_pose", "Grip Pose", XR_ACTION_TYPE_POSE_INPUT, gripPoseAction);
667 createAction("trigger", "Trigger", XR_ACTION_TYPE_FLOAT_INPUT, triggerAction);
668 createAction("grip", "Grip", XR_ACTION_TYPE_FLOAT_INPUT, gripAction);
669 createAction("thumbstick", "Thumbstick", XR_ACTION_TYPE_VECTOR2F_INPUT, thumbstickAction);
670 createAction("thumbstick_click", "Thumbstick Click", XR_ACTION_TYPE_BOOLEAN_INPUT, thumbstickClickAction);
671 createAction("primary_button", "Primary Button", XR_ACTION_TYPE_BOOLEAN_INPUT, primaryButtonAction);
672 createAction("secondary_button", "Secondary Button", XR_ACTION_TYPE_BOOLEAN_INPUT, secondaryButtonAction);
673 createAction("menu_button", "Menu Button", XR_ACTION_TYPE_BOOLEAN_INPUT, menuButtonAction);
674 createAction("haptic", "Haptic Vibration", XR_ACTION_TYPE_VIBRATION_OUTPUT, hapticAction);
675
676 // バインディング設定ヘルパー (対応していない対話プロファイルは読み飛ばす)
677 auto suggestBindings = [this](const char* profileStr, const std::vector<std::pair<XrAction, const char*>>& bindings)
678 {
679 XrPath profilePath{ XR_NULL_PATH };
680 if (XR_FAILED(xrStringToPath(instance, profileStr, &profilePath))) return;
681
682 std::vector<XrActionSuggestedBinding> suggestedBindings;
683 suggestedBindings.reserve(bindings.size());
684 for (const auto& [action, pathStr] : bindings)
685 {
686 XrPath path{ XR_NULL_PATH };
687 if (XR_FAILED(xrStringToPath(instance, pathStr, &path))) continue;
688 suggestedBindings.push_back(XrActionSuggestedBinding{ action, path });
689 }
690 if (suggestedBindings.empty()) return;
691
692 XrInteractionProfileSuggestedBinding profileSuggestedBindings{ XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING };
693 profileSuggestedBindings.interactionProfile = profilePath;
694 profileSuggestedBindings.suggestedBindings = suggestedBindings.data();
695 profileSuggestedBindings.countSuggestedBindings = static_cast<uint32_t>(suggestedBindings.size());
696 xrWarn(instance, xrSuggestInteractionProfileBindings(instance, &profileSuggestedBindings),
697 std::string("Can't suggest the bindings for ") + profileStr);
698 };
699
700 // Simple Controller のバインディング (すべてのランタイムが対応する最小限のもの)
701 suggestBindings("/interaction_profiles/khr/simple_controller", {
702 { aimPoseAction, "/user/hand/left/input/aim/pose" },
703 { aimPoseAction, "/user/hand/right/input/aim/pose" },
704 { gripPoseAction, "/user/hand/left/input/grip/pose" },
705 { gripPoseAction, "/user/hand/right/input/grip/pose" },
706 { triggerAction, "/user/hand/left/input/select/click" },
707 { triggerAction, "/user/hand/right/input/select/click" },
708 { menuButtonAction, "/user/hand/left/input/menu/click" },
709 { menuButtonAction, "/user/hand/right/input/menu/click" },
710 { hapticAction, "/user/hand/left/output/haptic" },
711 { hapticAction, "/user/hand/right/output/haptic" }
712 });
713
714 // Meta (Oculus) Touch コントローラーのバインディング
715 suggestBindings("/interaction_profiles/oculus/touch_controller", {
716 { aimPoseAction, "/user/hand/left/input/aim/pose" },
717 { aimPoseAction, "/user/hand/right/input/aim/pose" },
718 { gripPoseAction, "/user/hand/left/input/grip/pose" },
719 { gripPoseAction, "/user/hand/right/input/grip/pose" },
720 { triggerAction, "/user/hand/left/input/trigger/value" },
721 { triggerAction, "/user/hand/right/input/trigger/value" },
722 { gripAction, "/user/hand/left/input/squeeze/value" },
723 { gripAction, "/user/hand/right/input/squeeze/value" },
724 { thumbstickAction, "/user/hand/left/input/thumbstick" },
725 { thumbstickAction, "/user/hand/right/input/thumbstick" },
726 { thumbstickClickAction, "/user/hand/left/input/thumbstick/click" },
727 { thumbstickClickAction, "/user/hand/right/input/thumbstick/click" },
728 { primaryButtonAction, "/user/hand/left/input/x/click" },
729 { primaryButtonAction, "/user/hand/right/input/a/click" },
730 { secondaryButtonAction, "/user/hand/left/input/y/click" },
731 { secondaryButtonAction, "/user/hand/right/input/b/click" },
732 { menuButtonAction, "/user/hand/left/input/menu/click" },
733 { hapticAction, "/user/hand/left/output/haptic" },
734 { hapticAction, "/user/hand/right/output/haptic" }
735 });
736
737 // HTC Vive コントローラーのバインディング
738 suggestBindings("/interaction_profiles/htc/vive_controller", {
739 { aimPoseAction, "/user/hand/left/input/aim/pose" },
740 { aimPoseAction, "/user/hand/right/input/aim/pose" },
741 { gripPoseAction, "/user/hand/left/input/grip/pose" },
742 { gripPoseAction, "/user/hand/right/input/grip/pose" },
743 { triggerAction, "/user/hand/left/input/trigger/value" },
744 { triggerAction, "/user/hand/right/input/trigger/value" },
745 { gripAction, "/user/hand/left/input/squeeze/click" },
746 { gripAction, "/user/hand/right/input/squeeze/click" },
747 { thumbstickAction, "/user/hand/left/input/trackpad" },
748 { thumbstickAction, "/user/hand/right/input/trackpad" },
749 { thumbstickClickAction, "/user/hand/left/input/trackpad/click" },
750 { thumbstickClickAction, "/user/hand/right/input/trackpad/click" },
751 { menuButtonAction, "/user/hand/left/input/menu/click" },
752 { menuButtonAction, "/user/hand/right/input/menu/click" },
753 { hapticAction, "/user/hand/left/output/haptic" },
754 { hapticAction, "/user/hand/right/output/haptic" }
755 });
756
757 // Valve Index コントローラーのバインディング
758 suggestBindings("/interaction_profiles/valve/index_controller", {
759 { aimPoseAction, "/user/hand/left/input/aim/pose" },
760 { aimPoseAction, "/user/hand/right/input/aim/pose" },
761 { gripPoseAction, "/user/hand/left/input/grip/pose" },
762 { gripPoseAction, "/user/hand/right/input/grip/pose" },
763 { triggerAction, "/user/hand/left/input/trigger/value" },
764 { triggerAction, "/user/hand/right/input/trigger/value" },
765 { gripAction, "/user/hand/left/input/squeeze/value" },
766 { gripAction, "/user/hand/right/input/squeeze/value" },
767 { thumbstickAction, "/user/hand/left/input/thumbstick" },
768 { thumbstickAction, "/user/hand/right/input/thumbstick" },
769 { thumbstickClickAction, "/user/hand/left/input/thumbstick/click" },
770 { thumbstickClickAction, "/user/hand/right/input/thumbstick/click" },
771 { primaryButtonAction, "/user/hand/left/input/a/click" },
772 { primaryButtonAction, "/user/hand/right/input/a/click" },
773 { secondaryButtonAction, "/user/hand/left/input/b/click" },
774 { secondaryButtonAction, "/user/hand/right/input/b/click" },
775 { hapticAction, "/user/hand/left/output/haptic" },
776 { hapticAction, "/user/hand/right/output/haptic" }
777 });
778
779 // Microsoft Mixed Reality モーションコントローラーのバインディング
780 suggestBindings("/interaction_profiles/microsoft/motion_controller", {
781 { aimPoseAction, "/user/hand/left/input/aim/pose" },
782 { aimPoseAction, "/user/hand/right/input/aim/pose" },
783 { gripPoseAction, "/user/hand/left/input/grip/pose" },
784 { gripPoseAction, "/user/hand/right/input/grip/pose" },
785 { triggerAction, "/user/hand/left/input/trigger/value" },
786 { triggerAction, "/user/hand/right/input/trigger/value" },
787 { gripAction, "/user/hand/left/input/squeeze/click" },
788 { gripAction, "/user/hand/right/input/squeeze/click" },
789 { thumbstickAction, "/user/hand/left/input/thumbstick" },
790 { thumbstickAction, "/user/hand/right/input/thumbstick" },
791 { thumbstickClickAction, "/user/hand/left/input/thumbstick/click" },
792 { thumbstickClickAction, "/user/hand/right/input/thumbstick/click" },
793 { menuButtonAction, "/user/hand/left/input/menu/click" },
794 { menuButtonAction, "/user/hand/right/input/menu/click" },
795 { hapticAction, "/user/hand/left/output/haptic" },
796 { hapticAction, "/user/hand/right/output/haptic" }
797 });
798
799 // セッションにアクションセットをアタッチする (これ以降はバインディングを追加できない)
800 XrSessionActionSetsAttachInfo attachInfo{ XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO };
801 attachInfo.countActionSets = 1;
802 attachInfo.actionSets = &actionSet;
803 xrCheck(instance, xrAttachSessionActionSets(session, &attachInfo),
804 "Can't attach the OpenXR action set to the session");
805
806 // アクションスペースの作成
807 for (int i = 0; i < Hand::Count; ++i)
808 {
809 XrActionSpaceCreateInfo spaceInfo{ XR_TYPE_ACTION_SPACE_CREATE_INFO };
810 spaceInfo.poseInActionSpace.orientation.w = 1.0f;
811 spaceInfo.subactionPath = handSubactionPath[i];
812
813 spaceInfo.action = aimPoseAction;
814 xrCheck(instance, xrCreateActionSpace(session, &spaceInfo, &aimSpace[i]),
815 "Can't create the OpenXR action space for the aim pose");
816
817 spaceInfo.action = gripPoseAction;
818 xrCheck(instance, xrCreateActionSpace(session, &spaceInfo, &gripSpace[i]),
819 "Can't create the OpenXR action space for the grip pose");
820 }
821}
822
823//
824// アクション状態を更新する
825//
826void GgApp::OpenXR::pollActions()
827{
828 // 入力を受け付けていなければコントローラーの状態を無効にする
829 if (!isSessionRunning || actionSet == XR_NULL_HANDLE || sessionState != XR_SESSION_STATE_FOCUSED)
830 {
831 for (auto& state : controllerStates) state = ControllerState{};
832 return;
833 }
834
835 XrActiveActionSet activeActionSet{ actionSet, XR_NULL_PATH };
836 XrActionsSyncInfo syncInfo{ XR_TYPE_ACTIONS_SYNC_INFO };
837 syncInfo.countActiveActionSets = 1;
838 syncInfo.activeActionSets = &activeActionSet;
839 if (!xrWarn(instance, xrSyncActions(session, &syncInfo),
840 "Can't synchronize the OpenXR actions")) return;
841
842 // 空間の姿勢を取り出すヘルパー (位置と向きの両方が有効なときだけ更新する)
843 auto locate = [this](XrSpace space, XrPosef& pose)
844 {
845 if (space == XR_NULL_HANDLE) return false;
846
847 XrSpaceLocation location{ XR_TYPE_SPACE_LOCATION };
848 if (XR_FAILED(xrLocateSpace(space, appSpace, frameState.predictedDisplayTime, &location)))
849 return false;
850
851 constexpr XrSpaceLocationFlags valid
852 {
853 XR_SPACE_LOCATION_POSITION_VALID_BIT | XR_SPACE_LOCATION_ORIENTATION_VALID_BIT
854 };
855 if ((location.locationFlags & valid) != valid) return false;
856
857 pose = location.pose;
858 return true;
859 };
860
861 for (int i = 0; i < Hand::Count; ++i)
862 {
863 auto& state = controllerStates[i];
864 const XrPath subaction = handSubactionPath[i];
865
866 XrActionStateGetInfo getInfo{ XR_TYPE_ACTION_STATE_GET_INFO };
867 getInfo.subactionPath = subaction;
868
869 // グリップポーズの取得
870 XrActionStatePose gripPoseState{ XR_TYPE_ACTION_STATE_POSE };
871 getInfo.action = gripPoseAction;
872 const bool gripActive
873 {
874 XR_SUCCEEDED(xrGetActionStatePose(session, &getInfo, &gripPoseState))
875 && gripPoseState.isActive != XR_FALSE
876 };
877
878 // エイムポーズの取得
879 XrActionStatePose aimPoseState{ XR_TYPE_ACTION_STATE_POSE };
880 getInfo.action = aimPoseAction;
881 const bool aimActive
882 {
883 XR_SUCCEEDED(xrGetActionStatePose(session, &getInfo, &aimPoseState))
884 && aimPoseState.isActive != XR_FALSE
885 };
886
887 // どちらかの姿勢が有効ならコントローラーが接続されている
888 state.isTracked = gripActive || aimActive;
889 if (gripActive) locate(gripSpace[i], state.gripPose);
890 if (aimActive) locate(aimSpace[i], state.aimPose);
891
892 // 連続値のアクションを取り出すヘルパー
893 auto getFloat = [this, &getInfo](XrAction action, float& value)
894 {
895 getInfo.action = action;
896 XrActionStateFloat floatState{ XR_TYPE_ACTION_STATE_FLOAT };
897 value = XR_SUCCEEDED(xrGetActionStateFloat(session, &getInfo, &floatState))
898 && floatState.isActive != XR_FALSE ? floatState.currentState : 0.0f;
899 };
900
901 // 論理値のアクションを取り出すヘルパー
902 auto getBoolean = [this, &getInfo](XrAction action, bool& value)
903 {
904 getInfo.action = action;
905 XrActionStateBoolean booleanState{ XR_TYPE_ACTION_STATE_BOOLEAN };
906 value = XR_SUCCEEDED(xrGetActionStateBoolean(session, &getInfo, &booleanState))
907 && booleanState.isActive != XR_FALSE && booleanState.currentState != XR_FALSE;
908 };
909
910 // トリガーとグリップ
911 getFloat(triggerAction, state.trigger);
912 getFloat(gripAction, state.grip);
913
914 // スティック
915 getInfo.action = thumbstickAction;
916 XrActionStateVector2f thumbstickState{ XR_TYPE_ACTION_STATE_VECTOR2F };
917 if (XR_SUCCEEDED(xrGetActionStateVector2f(session, &getInfo, &thumbstickState))
918 && thumbstickState.isActive != XR_FALSE)
919 state.thumbstick = { thumbstickState.currentState.x, thumbstickState.currentState.y };
920 else
921 state.thumbstick = { 0.0f, 0.0f };
922
923 // ボタン
924 getBoolean(thumbstickClickAction, state.thumbstickClick);
925 getBoolean(primaryButtonAction, state.primaryButton);
926 getBoolean(secondaryButtonAction, state.secondaryButton);
927 getBoolean(menuButtonAction, state.menuButton);
928 }
929}
930
931//
932// OpenXR のイベントを処理する
933//
934void GgApp::OpenXR::pollEvents()
935{
936 XrEventDataBuffer eventData{ XR_TYPE_EVENT_DATA_BUFFER };
937
938 while (xrPollEvent(instance, &eventData) == XR_SUCCESS)
939 {
940 switch (eventData.type)
941 {
942 case XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING:
943
944 // OpenXR のインスタンスが失われるのでアプリケーションを終了する
945 isSessionRunning = false;
946 if (window) window->setClose(GLFW_TRUE);
947 break;
948
949 case XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED:
950 {
951 const auto* stateChanged{ reinterpret_cast<const XrEventDataSessionStateChanged*>(&eventData) };
952 sessionState = stateChanged->state;
953
954 switch (sessionState)
955 {
956 case XR_SESSION_STATE_READY:
957 {
958 // セッションを開始する
959 XrSessionBeginInfo beginInfo{ XR_TYPE_SESSION_BEGIN_INFO };
960 beginInfo.primaryViewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO;
961 if (xrWarn(instance, xrBeginSession(session, &beginInfo),
962 "Can't begin the OpenXR session")) isSessionRunning = true;
963 break;
964 }
965
966 case XR_SESSION_STATE_STOPPING:
967
968 // セッションを終了する
969 isSessionRunning = false;
970 frameBegun = false;
971 xrWarn(instance, xrEndSession(session), "Can't end the OpenXR session");
972 break;
973
974 case XR_SESSION_STATE_EXITING:
975 case XR_SESSION_STATE_LOSS_PENDING:
976
977 // アプリケーションを終了する
978 isSessionRunning = false;
979 if (window) window->setClose(GLFW_TRUE);
980 break;
981
982 default:
983 break;
984 }
985 break;
986 }
987
988 default:
989 break;
990 }
991
992 // 次のイベントを取り出す準備をする
993 eventData = XrEventDataBuffer{ XR_TYPE_EVENT_DATA_BUFFER };
994 }
995}
996
997//
998// スワップチェーンを作成する
999//
1000void GgApp::OpenXR::createSwapchains()
1001{
1002 // ビュー構成を取得する
1003 uint32_t viewCount{ 0 };
1004 xrCheck(instance, xrEnumerateViewConfigurationViews(instance, systemId,
1005 XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, 0, &viewCount, nullptr),
1006 "Can't count the OpenXR view configuration views");
1007 views.assign(viewCount, XrViewConfigurationView{ XR_TYPE_VIEW_CONFIGURATION_VIEW });
1008 xrCheck(instance, xrEnumerateViewConfigurationViews(instance, systemId,
1009 XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, viewCount, &viewCount, views.data()),
1010 "Can't enumerate the OpenXR view configuration views");
1011 if (viewCount == 0) throw std::runtime_error("The OpenXR system has no view");
1012
1013 viewStates.assign(viewCount, XrView{ XR_TYPE_VIEW });
1014 currentImageIndex.assign(viewCount, 0);
1015 imageAcquired.assign(viewCount, false);
1016
1017 // 環境の合成方法を取得する (最初のものが最も推奨される)
1018 uint32_t blendModeCount{ 0 };
1019 if (XR_SUCCEEDED(xrEnumerateEnvironmentBlendModes(instance, systemId,
1020 XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, 0, &blendModeCount, nullptr))
1021 && blendModeCount > 0)
1022 {
1023 std::vector<XrEnvironmentBlendMode> blendModes(blendModeCount);
1024 if (XR_SUCCEEDED(xrEnumerateEnvironmentBlendModes(instance, systemId,
1025 XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, blendModeCount,
1026 &blendModeCount, blendModes.data())))
1027 {
1028 blendMode = blendModes[0];
1029 }
1030 }
1031
1032 // 利用可能なスワップチェーンのカラーフォーマットを取得する
1033 uint32_t formatCount{ 0 };
1034 xrCheck(instance, xrEnumerateSwapchainFormats(session, 0, &formatCount, nullptr),
1035 "Can't count the OpenXR swapchain formats");
1036 std::vector<int64_t> formats(formatCount);
1037 xrCheck(instance, xrEnumerateSwapchainFormats(session, formatCount, &formatCount, formats.data()),
1038 "Can't enumerate the OpenXR swapchain formats");
1039 if (formats.empty()) throw std::runtime_error("The OpenXR runtime has no swapchain format");
1040
1041 // 使用したいカラーフォーマットの候補 (前にあるものを優先する)
1042 static const int64_t preferred[]{ GL_SRGB8_ALPHA8, GL_SRGB8, GL_RGBA8, GL_RGB10_A2 };
1043
1044 // 利用可能なカラーフォーマットの中から使用するものを選ぶ
1045 int64_t format{ formats[0] };
1046 for (const auto candidate : preferred)
1047 {
1048 if (std::find(formats.begin(), formats.end(), candidate) != formats.end())
1049 {
1050 format = candidate;
1051 break;
1052 }
1053 }
1054
1055 // sRGB のフォーマットならリニア色空間で描画してガンマ補正をランタイムに任せる
1056 swapchainIsSrgb = format == GL_SRGB8_ALPHA8 || format == GL_SRGB8;
1057
1058 // ビューの数だけ FBO とデプスバッファを作成する
1059 openxrFbo.assign(viewCount, 0);
1060 openxrDepth.assign(viewCount, 0);
1061 glGenFramebuffers(static_cast<GLsizei>(viewCount), openxrFbo.data());
1062 glGenRenderbuffers(static_cast<GLsizei>(viewCount), openxrDepth.data());
1063
1064 for (uint32_t i = 0; i < viewCount; ++i)
1065 {
1066 // スワップチェーンを作成する
1067 XrSwapchainCreateInfo swapchainCreateInfo{ XR_TYPE_SWAPCHAIN_CREATE_INFO };
1068 swapchainCreateInfo.usageFlags = XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT
1069 | XR_SWAPCHAIN_USAGE_SAMPLED_BIT;
1070 swapchainCreateInfo.format = format;
1071 swapchainCreateInfo.sampleCount = 1;
1072 swapchainCreateInfo.width = views[i].recommendedImageRectWidth;
1073 swapchainCreateInfo.height = views[i].recommendedImageRectHeight;
1074 swapchainCreateInfo.faceCount = 1;
1075 swapchainCreateInfo.arraySize = 1;
1076 swapchainCreateInfo.mipCount = 1;
1077
1078 XrSwapchain swapchain{ XR_NULL_HANDLE };
1079 xrCheck(instance, xrCreateSwapchain(session, &swapchainCreateInfo, &swapchain),
1080 "Can't create the OpenXR swapchain");
1081 swapchains.push_back(swapchain);
1082
1083 // スワップチェーンのイメージを取得する
1084 uint32_t imageCount{ 0 };
1085 xrCheck(instance, xrEnumerateSwapchainImages(swapchain, 0, &imageCount, nullptr),
1086 "Can't count the OpenXR swapchain images");
1087 std::vector<XrSwapchainImageOpenGLKHR> images(imageCount,
1088 XrSwapchainImageOpenGLKHR{ XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_KHR });
1089 xrCheck(instance, xrEnumerateSwapchainImages(swapchain, imageCount, &imageCount,
1090 reinterpret_cast<XrSwapchainImageBaseHeader*>(images.data())),
1091 "Can't enumerate the OpenXR swapchain images");
1092 swapchainImages.push_back(std::move(images));
1093
1094 // 隠面消去処理に使うデプスバッファを作成する
1095 glBindRenderbuffer(GL_RENDERBUFFER, openxrDepth[i]);
1096 glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8,
1097 static_cast<GLsizei>(swapchainCreateInfo.width),
1098 static_cast<GLsizei>(swapchainCreateInfo.height));
1099 glBindRenderbuffer(GL_RENDERBUFFER, 0);
1100
1101 // FBO にデプスバッファを取り付けておく (カラーバッファは select() で取り付ける)
1102 glBindFramebuffer(GL_FRAMEBUFFER, openxrFbo[i]);
1103 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
1104 GL_RENDERBUFFER, openxrDepth[i]);
1105 }
1106
1107 glBindFramebuffer(GL_FRAMEBUFFER, 0);
1108}
1109
1110//
1111// OpenXR のセッションを作成する
1112//
1113GgApp::OpenXR& GgApp::OpenXR::initialize(const Window& window,
1114 XrReferenceSpaceType spaceType, const char* appName)
1115{
1116 static OpenXR openxr;
1117
1118 // 初期化済みならそのまま返す
1119 if (openxr.initialized) return openxr;
1120
1121 // 初期化に失敗していた場合に備えて後始末をしておく
1122 openxr.terminate();
1123
1124 openxr.window = &window;
1125 openxr.referenceSpaceType = spaceType;
1126
1127 try
1128 {
1129 // 利用可能な拡張機能を調べる
1130 uint32_t extensionCount{ 0 };
1131 xrCheck(XR_NULL_HANDLE,
1132 xrEnumerateInstanceExtensionProperties(nullptr, 0, &extensionCount, nullptr),
1133 "Can't count the OpenXR instance extensions");
1134 std::vector<XrExtensionProperties> extensionProperties(extensionCount,
1135 XrExtensionProperties{ XR_TYPE_EXTENSION_PROPERTIES });
1136 xrCheck(XR_NULL_HANDLE,
1137 xrEnumerateInstanceExtensionProperties(nullptr, extensionCount,
1138 &extensionCount, extensionProperties.data()),
1139 "Can't enumerate the OpenXR instance extensions");
1140
1141 // OpenGL との連携に必要な拡張機能が使えなければあきらめる
1142 const auto found{ std::any_of(extensionProperties.begin(), extensionProperties.end(),
1143 [](const XrExtensionProperties& p)
1144 {
1145 return std::strcmp(p.extensionName, XR_KHR_OPENGL_ENABLE_EXTENSION_NAME) == 0;
1146 }) };
1147 if (!found)
1148 {
1149 throw std::runtime_error(
1150 "The OpenXR runtime does not support " XR_KHR_OPENGL_ENABLE_EXTENSION_NAME);
1151 }
1152
1153 // XrInstance の作成
1154 XrInstanceCreateInfo createInfo{ XR_TYPE_INSTANCE_CREATE_INFO };
1155 xrCopyString(createInfo.applicationInfo.applicationName,
1156 sizeof createInfo.applicationInfo.applicationName, appName ? appName : "GgApp");
1157 createInfo.applicationInfo.applicationVersion = 1;
1158 xrCopyString(createInfo.applicationInfo.engineName,
1159 sizeof createInfo.applicationInfo.engineName, "GgApp");
1160 createInfo.applicationInfo.engineVersion = 1;
1161 createInfo.applicationInfo.apiVersion = XR_CURRENT_API_VERSION;
1162
1163 const char* const extensions[]{ XR_KHR_OPENGL_ENABLE_EXTENSION_NAME };
1164 createInfo.enabledExtensionCount = 1;
1165 createInfo.enabledExtensionNames = extensions;
1166
1167 xrCheck(XR_NULL_HANDLE, xrCreateInstance(&createInfo, &openxr.instance),
1168 "Can't create the OpenXR instance (is an OpenXR runtime installed and active?)");
1169
1170 // システムの取得
1171 XrSystemGetInfo systemInfo{ XR_TYPE_SYSTEM_GET_INFO };
1172 systemInfo.formFactor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY;
1173 xrCheck(openxr.instance, xrGetSystem(openxr.instance, &systemInfo, &openxr.systemId),
1174 "Can't get the OpenXR system (is the head mounted display connected?)");
1175
1176 // システムの名前の取得
1177 XrSystemProperties systemProperties{ XR_TYPE_SYSTEM_PROPERTIES };
1178 if (XR_SUCCEEDED(xrGetSystemProperties(openxr.instance, openxr.systemId, &systemProperties)))
1179 {
1180 openxr.systemName = systemProperties.systemName;
1181 }
1182
1183 // OpenGL との連携に必要な拡張機能の関数の取得
1184 PFN_xrGetOpenGLGraphicsRequirementsKHR pfnGetOpenGLGraphicsRequirementsKHR{ nullptr };
1185 xrCheck(openxr.instance, xrGetInstanceProcAddr(openxr.instance,
1186 "xrGetOpenGLGraphicsRequirementsKHR",
1187 reinterpret_cast<PFN_xrVoidFunction*>(&pfnGetOpenGLGraphicsRequirementsKHR)),
1188 "Can't get the address of xrGetOpenGLGraphicsRequirementsKHR");
1189 if (!pfnGetOpenGLGraphicsRequirementsKHR)
1190 throw std::runtime_error("Can't get the address of xrGetOpenGLGraphicsRequirementsKHR");
1191
1192 // OpenGL の要件の取得 (セッションの作成前に必ず呼ばなければならない)
1193 XrGraphicsRequirementsOpenGLKHR graphicsRequirements{ XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_KHR };
1194 xrCheck(openxr.instance, pfnGetOpenGLGraphicsRequirementsKHR(openxr.instance,
1195 openxr.systemId, &graphicsRequirements),
1196 "Can't get the OpenGL graphics requirements");
1197
1198 // OpenGL のバージョンが要件を満たしているかどうか調べる
1199 GLint major{ 0 }, minor{ 0 };
1200 glGetIntegerv(GL_MAJOR_VERSION, &major);
1201 glGetIntegerv(GL_MINOR_VERSION, &minor);
1202 if (XR_MAKE_VERSION(major, minor, 0) < graphicsRequirements.minApiVersionSupported)
1203 {
1204 char message[128];
1205 std::snprintf(message, sizeof message,
1206 "The OpenXR runtime requires OpenGL %d.%d or later, but %d.%d is current",
1207 static_cast<int>(XR_VERSION_MAJOR(graphicsRequirements.minApiVersionSupported)),
1208 static_cast<int>(XR_VERSION_MINOR(graphicsRequirements.minApiVersionSupported)),
1209 major, minor);
1210 throw std::runtime_error(message);
1211 }
1212
1213 // OpenGL のコンテキストをセッションに結びつける
1214#if defined(XR_USE_PLATFORM_WIN32)
1215 XrGraphicsBindingOpenGLWin32KHR graphicsBinding{ XR_TYPE_GRAPHICS_BINDING_OPENGL_WIN32_KHR };
1216 graphicsBinding.hDC = wglGetCurrentDC();
1217 graphicsBinding.hGLRC = glfwGetWGLContext(window.get());
1218#elif defined(XR_USE_PLATFORM_XLIB)
1219 XrGraphicsBindingOpenGLXlibKHR graphicsBinding{ XR_TYPE_GRAPHICS_BINDING_OPENGL_XLIB_KHR };
1220 graphicsBinding.xDisplay = glfwGetX11Display();
1221 graphicsBinding.visualid = 0;
1222 graphicsBinding.glxFBConfig = nullptr;
1223 graphicsBinding.glxDrawable = glfwGetGLXWindow(window.get());
1224 graphicsBinding.glxContext = glfwGetGLXContext(window.get());
1225#else
1226# error "GG_USE_OPENXR is not supported on this platform"
1227#endif
1228
1229 // セッションの作成
1230 XrSessionCreateInfo sessionCreateInfo{ XR_TYPE_SESSION_CREATE_INFO };
1231 sessionCreateInfo.next = &graphicsBinding;
1232 sessionCreateInfo.systemId = openxr.systemId;
1233 xrCheck(openxr.instance, xrCreateSession(openxr.instance, &sessionCreateInfo, &openxr.session),
1234 "Can't create the OpenXR session");
1235
1236 // 参照空間の作成 (要求されたものが使えなければ LOCAL にフォールバックする)
1237 XrReferenceSpaceCreateInfo spaceCreateInfo{ XR_TYPE_REFERENCE_SPACE_CREATE_INFO };
1238 spaceCreateInfo.referenceSpaceType = openxr.referenceSpaceType;
1239 spaceCreateInfo.poseInReferenceSpace.orientation.w = 1.0f;
1240 if (XR_FAILED(xrCreateReferenceSpace(openxr.session, &spaceCreateInfo, &openxr.appSpace)))
1241 {
1242 openxr.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL;
1243 spaceCreateInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL;
1244 xrCheck(openxr.instance,
1245 xrCreateReferenceSpace(openxr.session, &spaceCreateInfo, &openxr.appSpace),
1246 "Can't create the OpenXR reference space");
1247 }
1248
1249 // アクションシステムの初期化
1250 openxr.initActions();
1251
1252 // スワップチェーンの作成
1253 openxr.createSwapchains();
1254 }
1255 catch (...)
1256 {
1257 // 途中まで確保した資源を解放してから例外を投げ直す
1258 openxr.terminate();
1259 throw;
1260 }
1261
1262 // OpenXR は xrWaitFrame() でフレームの表示速度を制御するので
1263 // ウィンドウ側の垂直同期の待ち合わせは行わない
1264 glfwSwapInterval(0);
1265
1266 openxr.initialized = true;
1267
1268 return openxr;
1269}
1270
1271//
1272// OpenXR のハンドルを破棄する (OpenGL の資源には触れない)
1273//
1274void GgApp::OpenXR::destroyXr()
1275{
1276 for (int i = 0; i < Hand::Count; ++i)
1277 {
1278 if (aimSpace[i] != XR_NULL_HANDLE) { xrDestroySpace(aimSpace[i]); aimSpace[i] = XR_NULL_HANDLE; }
1279 if (gripSpace[i] != XR_NULL_HANDLE) { xrDestroySpace(gripSpace[i]); gripSpace[i] = XR_NULL_HANDLE; }
1280 }
1281
1282 // アクションはアクションセットと一緒に破棄される
1283 if (actionSet != XR_NULL_HANDLE) xrDestroyActionSet(actionSet);
1284 actionSet = XR_NULL_HANDLE;
1285 aimPoseAction = gripPoseAction = XR_NULL_HANDLE;
1286 triggerAction = gripAction = XR_NULL_HANDLE;
1287 thumbstickAction = thumbstickClickAction = XR_NULL_HANDLE;
1288 primaryButtonAction = secondaryButtonAction = menuButtonAction = XR_NULL_HANDLE;
1289 hapticAction = XR_NULL_HANDLE;
1290
1291 for (auto swapchain : swapchains) xrDestroySwapchain(swapchain);
1292 swapchains.clear();
1293 swapchainImages.clear();
1294
1295 if (appSpace != XR_NULL_HANDLE) { xrDestroySpace(appSpace); appSpace = XR_NULL_HANDLE; }
1296 if (session != XR_NULL_HANDLE) { xrDestroySession(session); session = XR_NULL_HANDLE; }
1297 if (instance != XR_NULL_HANDLE) { xrDestroyInstance(instance); instance = XR_NULL_HANDLE; }
1298
1299 systemId = XR_NULL_SYSTEM_ID;
1300 sessionState = XR_SESSION_STATE_UNKNOWN;
1301 isSessionRunning = false;
1302 frameBegun = false;
1303 viewPoseValid = false;
1304 initialized = false;
1305}
1306
1307//
1308// OpenXR のセッションを破棄する
1309//
1310void GgApp::OpenXR::terminate()
1311{
1312 // 取得中のスワップチェーンイメージがあれば解放する (xrEndFrame() より前に行う)
1313 for (size_t i = 0; i < imageAcquired.size(); ++i)
1314 {
1315 if (!imageAcquired[i]) continue;
1316 XrSwapchainImageReleaseInfo releaseInfo{ XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO };
1317 xrReleaseSwapchainImage(swapchains[i], &releaseInfo);
1318 imageAcquired[i] = false;
1319 }
1320
1321 // 描画中のフレームがあれば完了しておく
1322 if (frameBegun) endFrame();
1323
1324 // OpenGL の資源を解放する
1325 if (!openxrFbo.empty())
1326 {
1327 glDeleteFramebuffers(static_cast<GLsizei>(openxrFbo.size()), openxrFbo.data());
1328 openxrFbo.clear();
1329 }
1330 if (!openxrDepth.empty())
1331 {
1332 glDeleteRenderbuffers(static_cast<GLsizei>(openxrDepth.size()), openxrDepth.data());
1333 openxrDepth.clear();
1334 }
1335
1336 // OpenXR のハンドルを破棄する
1337 destroyXr();
1338
1339 views.clear();
1340 viewStates.clear();
1341 currentImageIndex.clear();
1342 imageAcquired.clear();
1343 systemName.clear();
1344
1345 for (auto& state : controllerStates) state = ControllerState{};
1346
1347 // ウィンドウ側の設定を元に戻す
1348 if (window)
1349 {
1350 glDisable(GL_FRAMEBUFFER_SRGB);
1351 glfwSwapInterval(1);
1352 window = nullptr;
1353 }
1354}
1355
1356//
1357// OpenXR による描画開始
1358//
1359bool GgApp::OpenXR::begin()
1360{
1361 // 初期化されていなければ何もしない
1362 if (instance == XR_NULL_HANDLE) return false;
1363
1364 // OpenXR のイベントを処理する
1365 pollEvents();
1366
1367 // セッションが実行中でなければ描画しない
1368 if (!isSessionRunning) return false;
1369
1370 // 合成器がこのフレームの描画を始めるべき時刻まで待つ
1371 XrFrameWaitInfo waitInfo{ XR_TYPE_FRAME_WAIT_INFO };
1372 frameState = XrFrameState{ XR_TYPE_FRAME_STATE };
1373 if (!xrWarn(instance, xrWaitFrame(session, &waitInfo, &frameState),
1374 "Can't wait for the OpenXR frame")) return false;
1375
1376 // フレームの描画を開始する
1377 XrFrameBeginInfo beginInfo{ XR_TYPE_FRAME_BEGIN_INFO };
1378 if (!xrWarn(instance, xrBeginFrame(session, &beginInfo),
1379 "Can't begin the OpenXR frame")) return false;
1380
1381 // ここから先は必ず xrEndFrame() を呼ばなければならない
1382 frameBegun = true;
1383 viewPoseValid = false;
1384
1385 // コントローラーの状態を更新する
1386 pollActions();
1387
1388 // 描画すべきフレームなら視点の姿勢を取得する
1389 if (frameState.shouldRender != XR_FALSE)
1390 {
1391 XrViewLocateInfo viewLocateInfo{ XR_TYPE_VIEW_LOCATE_INFO };
1392 viewLocateInfo.viewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO;
1393 viewLocateInfo.displayTime = frameState.predictedDisplayTime;
1394 viewLocateInfo.space = appSpace;
1395
1396 XrViewState viewState{ XR_TYPE_VIEW_STATE };
1397 uint32_t viewCount{ 0 };
1398 if (XR_SUCCEEDED(xrLocateViews(session, &viewLocateInfo, &viewState,
1399 static_cast<uint32_t>(viewStates.size()), &viewCount, viewStates.data())))
1400 {
1401 // 位置と向きの両方が有効なときだけ描画する
1402 constexpr XrViewStateFlags valid
1403 {
1404 XR_VIEW_STATE_POSITION_VALID_BIT | XR_VIEW_STATE_ORIENTATION_VALID_BIT
1405 };
1406 viewPoseValid = (viewState.viewStateFlags & valid) == valid
1407 && viewCount == static_cast<uint32_t>(viewStates.size());
1408 }
1409 }
1410
1411 // 描画するなら true を返す
1412 if (viewPoseValid) return true;
1413
1414 // 描画しないフレームでもここで xrEndFrame() を呼んで辻褄を合わせる
1415 endFrame();
1416
1417 return false;
1418}
1419
1420//
1421// 描画対象の目を指定してフレームバッファとビューポートを設定する
1422//
1423void GgApp::OpenXR::select(int eye)
1424{
1425 // 描画すべきフレームでなければ何もしない
1426 if (!frameBegun || !viewPoseValid) return;
1427
1428 // 視点の番号が範囲を外れていたら何もしない
1429 assert(eye >= 0 && eye < static_cast<int>(swapchains.size()));
1430 if (eye < 0 || eye >= static_cast<int>(swapchains.size())) return;
1431
1432 // 取得済みなら描画先を結合し直すだけにする
1433 if (imageAcquired[eye])
1434 {
1435 glBindFramebuffer(GL_FRAMEBUFFER, openxrFbo[eye]);
1436 glViewport(0, 0,
1437 static_cast<GLsizei>(views[eye].recommendedImageRectWidth),
1438 static_cast<GLsizei>(views[eye].recommendedImageRectHeight));
1439 if (swapchainIsSrgb) glEnable(GL_FRAMEBUFFER_SRGB);
1440 return;
1441 }
1442
1443 // 描画可能なスワップチェーンイメージを取得する
1444 XrSwapchainImageAcquireInfo acquireInfo{ XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO };
1445 if (!xrWarn(instance, xrAcquireSwapchainImage(swapchains[eye], &acquireInfo,
1446 &currentImageIndex[eye]), "Can't acquire the OpenXR swapchain image")) return;
1447
1448 // そのスワップチェーンイメージが描画可能になるのを待つ
1449 XrSwapchainImageWaitInfo waitInfo{ XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO };
1450 waitInfo.timeout = XR_INFINITE_DURATION;
1451 if (!xrWarn(instance, xrWaitSwapchainImage(swapchains[eye], &waitInfo),
1452 "Can't wait for the OpenXR swapchain image"))
1453 {
1454 XrSwapchainImageReleaseInfo releaseInfo{ XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO };
1455 xrReleaseSwapchainImage(swapchains[eye], &releaseInfo);
1456 return;
1457 }
1458
1459 imageAcquired[eye] = true;
1460
1461 // 描画先をこのスワップチェーンイメージに切り替える
1462 const GLuint texture{ swapchainImages[eye][currentImageIndex[eye]].image };
1463 glBindFramebuffer(GL_FRAMEBUFFER, openxrFbo[eye]);
1464 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
1465
1466 // フレームバッファオブジェクトが完成しているか確かめる (最初の一度だけ報告する)
1467 static bool reported{ false };
1468 if (!reported && glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
1469 {
1470 reported = true;
1471 std::cerr << "OpenXR: The framebuffer object for the swapchain image is not complete\n";
1472 }
1473
1474 glViewport(0, 0,
1475 static_cast<GLsizei>(views[eye].recommendedImageRectWidth),
1476 static_cast<GLsizei>(views[eye].recommendedImageRectHeight));
1477
1478 // sRGB のスワップチェーンならリニア色空間で描画する
1479 if (swapchainIsSrgb) glEnable(GL_FRAMEBUFFER_SRGB);
1480}
1481
1482//
1483// 描画対象の目を指定する (旧 LibOVR 仕様互換)
1484//
1485void GgApp::OpenXR::select(int eye, GLfloat* screen, GLfloat* position, GLfloat* orientation)
1486{
1487 select(eye);
1488
1489 assert(eye >= 0 && eye < static_cast<int>(viewStates.size()));
1490 const auto& pose = viewStates[eye].pose;
1491 const auto& fov = viewStates[eye].fov;
1492
1493 screen[0] = tanf(fov.angleLeft);
1494 screen[1] = tanf(fov.angleRight);
1495 screen[2] = tanf(fov.angleDown);
1496 screen[3] = tanf(fov.angleUp);
1497
1498 position[0] = pose.position.x;
1499 position[1] = pose.position.y;
1500 position[2] = pose.position.z;
1501
1502 orientation[0] = pose.orientation.x;
1503 orientation[1] = pose.orientation.y;
1504 orientation[2] = pose.orientation.z;
1505 orientation[3] = pose.orientation.w;
1506}
1507
1508//
1509// 指定した目の描画を完了する
1510//
1511void GgApp::OpenXR::commit(int eye)
1512{
1513 if (!frameBegun) return;
1514 assert(eye >= 0 && eye < static_cast<int>(swapchains.size()));
1515 if (eye < 0 || eye >= static_cast<int>(swapchains.size())) return;
1516 if (!imageAcquired[eye]) return;
1517
1518 // ガンマ補正を元に戻して描画先をウィンドウに戻す
1519 if (swapchainIsSrgb) glDisable(GL_FRAMEBUFFER_SRGB);
1520 glBindFramebuffer(GL_FRAMEBUFFER, 0);
1521
1522 // スワップチェーンイメージはミラー表示に使うので, ここでは解放しない
1523 // (解放は submit() の中でミラー表示を行った後に実施する)
1524}
1525
1526//
1527// ミラー表示を行う
1528//
1529void GgApp::OpenXR::blitMirror() const
1530{
1531 // ミラー表示を行わないなら何もしない
1532 if (mirrorView < 0 || !window) return;
1533 const auto eye{ static_cast<size_t>(mirrorView) };
1534 if (eye >= swapchains.size() || !imageAcquired[eye]) return;
1535
1536 // 転送元の大きさ
1537 const auto srcWidth{ static_cast<GLint>(views[eye].recommendedImageRectWidth) };
1538 const auto srcHeight{ static_cast<GLint>(views[eye].recommendedImageRectHeight) };
1539 if (srcWidth <= 0 || srcHeight <= 0) return;
1540
1541 // 転送先 (ウィンドウ) の大きさ
1542 const auto& fboSize{ window->getFboSize() };
1543 if (fboSize[0] <= 0 || fboSize[1] <= 0) return;
1544
1545 // 縦横比を保ったままウィンドウに収まる転送先の矩形を求める
1546 const auto scale{ std::min(
1547 static_cast<float>(fboSize[0]) / static_cast<float>(srcWidth),
1548 static_cast<float>(fboSize[1]) / static_cast<float>(srcHeight)) };
1549 const auto dstWidth{ static_cast<GLint>(static_cast<float>(srcWidth) * scale) };
1550 const auto dstHeight{ static_cast<GLint>(static_cast<float>(srcHeight) * scale) };
1551 const auto dstLeft{ (static_cast<GLint>(fboSize[0]) - dstWidth) / 2 };
1552 const auto dstBottom{ (static_cast<GLint>(fboSize[1]) - dstHeight) / 2 };
1553
1554 // sRGB の再変換を避けるためにガンマ補正を無効にする
1555 if (swapchainIsSrgb) glDisable(GL_FRAMEBUFFER_SRGB);
1556
1557 // 上下左右の余白を黒で塗りつぶす (消去色は元に戻す)
1558 GLfloat clearColor[4];
1559 glGetFloatv(GL_COLOR_CLEAR_VALUE, clearColor);
1560 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
1561 glDisable(GL_SCISSOR_TEST);
1562 glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
1563 glClear(GL_COLOR_BUFFER_BIT);
1564 glClearColor(clearColor[0], clearColor[1], clearColor[2], clearColor[3]);
1565
1566 // スワップチェーンイメージをウィンドウに転送する
1567 glBindFramebuffer(GL_READ_FRAMEBUFFER, openxrFbo[eye]);
1568 glBlitFramebuffer(0, 0, srcWidth, srcHeight,
1569 dstLeft, dstBottom, dstLeft + dstWidth, dstBottom + dstHeight,
1570 GL_COLOR_BUFFER_BIT, GL_LINEAR);
1571 glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
1572}
1573
1574//
1575// 描画中のフレームを合成器に転送する
1576//
1577void GgApp::OpenXR::endFrame()
1578{
1579 if (!frameBegun) return;
1580
1581 // 合成する層
1582 std::vector<XrCompositionLayerProjectionView> projectionViews;
1583 XrCompositionLayerProjection layer{ XR_TYPE_COMPOSITION_LAYER_PROJECTION };
1584 const XrCompositionLayerBaseHeader* layers[1]{ nullptr };
1585
1586 // 描画したのなら層を用意する
1587 if (viewPoseValid && frameState.shouldRender != XR_FALSE)
1588 {
1589 projectionViews.resize(swapchains.size());
1590 for (size_t i = 0; i < swapchains.size(); ++i)
1591 {
1592 auto& projectionView{ projectionViews[i] };
1593 projectionView = XrCompositionLayerProjectionView{ XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW };
1594 projectionView.pose = viewStates[i].pose;
1595 projectionView.fov = viewStates[i].fov;
1596 projectionView.subImage.swapchain = swapchains[i];
1597 projectionView.subImage.imageRect.offset = { 0, 0 };
1598 projectionView.subImage.imageRect.extent = {
1599 static_cast<int32_t>(views[i].recommendedImageRectWidth),
1600 static_cast<int32_t>(views[i].recommendedImageRectHeight)
1601 };
1602 projectionView.subImage.imageArrayIndex = 0;
1603 }
1604
1605 // 環境の合成方法に応じて層の属性を設定する
1606 layer.layerFlags = blendMode == XR_ENVIRONMENT_BLEND_MODE_OPAQUE ? 0
1607 : XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT
1608 | XR_COMPOSITION_LAYER_UNPREMULTIPLIED_ALPHA_BIT;
1609 layer.space = appSpace;
1610 layer.viewCount = static_cast<uint32_t>(projectionViews.size());
1611 layer.views = projectionViews.data();
1612 layers[0] = reinterpret_cast<const XrCompositionLayerBaseHeader*>(&layer);
1613 }
1614
1615 // フレームを合成器に転送する
1616 XrFrameEndInfo endInfo{ XR_TYPE_FRAME_END_INFO };
1617 endInfo.displayTime = frameState.predictedDisplayTime;
1618 endInfo.environmentBlendMode = blendMode;
1619 endInfo.layerCount = layers[0] ? 1u : 0u;
1620 endInfo.layers = layers;
1621 xrWarn(instance, xrEndFrame(session, &endInfo), "Can't end the OpenXR frame");
1622
1623 frameBegun = false;
1624}
1625
1626//
1627// フレームを転送して HMD に表示する
1628//
1629bool GgApp::OpenXR::submit(bool mirror)
1630{
1631 // 描画中のフレームがなければ何もしない
1632 if (!frameBegun) return false;
1633
1634 // ミラー表示の有無を設定する
1635 if (!mirror) mirrorView = -1;
1636 else if (mirrorView < 0) mirrorView = 0;
1637
1638 // スワップチェーンイメージを解放する前にミラー表示を行う
1639 blitMirror();
1640
1641 // ウィンドウのビューポートを復帰して Dear ImGui などの描画に備える
1642 if (window) window->restoreViewport();
1643
1644 // 取得したスワップチェーンイメージを解放する
1645 for (size_t i = 0; i < imageAcquired.size(); ++i)
1646 {
1647 if (!imageAcquired[i]) continue;
1648 XrSwapchainImageReleaseInfo releaseInfo{ XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO };
1649 xrWarn(instance, xrReleaseSwapchainImage(swapchains[i], &releaseInfo),
1650 "Can't release the OpenXR swapchain image");
1651 imageAcquired[i] = false;
1652 }
1653
1654 // 合成器にフレームを転送する
1655 endFrame();
1656
1657 return true;
1658}
1659
1660//
1661// ミラー表示を行うビューの番号を設定する
1662//
1663void GgApp::OpenXR::setMirror(int eye)
1664{
1665 mirrorView = eye;
1666}
1667
1668//
1669// ミラー表示を行うビューの番号を取得する
1670//
1671int GgApp::OpenXR::getMirror() const
1672{
1673 return mirrorView;
1674}
1675
1676//
1677// セッションが実行中かどうか調べる
1678//
1679bool GgApp::OpenXR::isRunning() const
1680{
1681 return isSessionRunning;
1682}
1683
1684//
1685// アプリケーションが入力を受け付けているかどうか調べる
1686//
1687bool GgApp::OpenXR::isFocused() const
1688{
1689 return sessionState == XR_SESSION_STATE_FOCUSED;
1690}
1691
1692//
1693// OpenXR のシステム (HMD) の名前を取得する
1694//
1695const std::string& GgApp::OpenXR::getSystemName() const
1696{
1697 return systemName;
1698}
1699
1700//
1701// 指定した目の透視投影変換行列を取得する
1702//
1703GgMatrix GgApp::OpenXR::getProjectionMatrix(int eye, GLfloat zNear, GLfloat zFar) const
1704{
1705 assert(eye >= 0 && eye < static_cast<int>(viewStates.size()));
1706 const auto& fov{ viewStates[eye].fov };
1707 const GLfloat l{ tanf(fov.angleLeft) * zNear };
1708 const GLfloat r{ tanf(fov.angleRight) * zNear };
1709 const GLfloat b{ tanf(fov.angleDown) * zNear };
1710 const GLfloat t{ tanf(fov.angleUp) * zNear };
1711 return ggFrustum(l, r, b, t, zNear, zFar);
1712}
1713
1714//
1715// 指定した目のビュー変換行列を取得する
1716//
1717GgMatrix GgApp::OpenXR::getViewMatrix(int eye) const
1718{
1719 assert(eye >= 0 && eye < static_cast<int>(viewStates.size()));
1720 const auto& pose{ viewStates[eye].pose };
1721 const GgQuaternion q{ pose.orientation.x, pose.orientation.y, pose.orientation.z, pose.orientation.w };
1722 return q.getConjugateMatrix() * ggTranslate(-pose.position.x, -pose.position.y, -pose.position.z);
1723}
1724
1725//
1726// 指定した目の姿勢行列を取得する
1727//
1728GgMatrix GgApp::OpenXR::getPoseMatrix(int eye) const
1729{
1730 assert(eye >= 0 && eye < static_cast<int>(viewStates.size()));
1731 const auto& pose{ viewStates[eye].pose };
1732 const GgQuaternion q{ pose.orientation.x, pose.orientation.y, pose.orientation.z, pose.orientation.w };
1733 return ggTranslate(pose.position.x, pose.position.y, pose.position.z) * q.getMatrix();
1734}
1735
1736//
1737// 指定した目の視点位置を取得する
1738//
1739GgVector GgApp::OpenXR::getPosition(int eye) const
1740{
1741 assert(eye >= 0 && eye < static_cast<int>(viewStates.size()));
1742 const auto& pos{ viewStates[eye].pose.position };
1743 return GgVector{ pos.x, pos.y, pos.z, 1.0f };
1744}
1745
1746//
1747// 指定した目の視線方向の回転四元数を取得する
1748//
1749GgQuaternion GgApp::OpenXR::getOrientation(int eye) const
1750{
1751 assert(eye >= 0 && eye < static_cast<int>(viewStates.size()));
1752 const auto& ori{ viewStates[eye].pose.orientation };
1753 return GgQuaternion{ ori.x, ori.y, ori.z, ori.w };
1754}
1755
1756//
1757// 指定した目の視野角情報 (XrFovf) を取得する
1758//
1759const XrFovf& GgApp::OpenXR::getFov(int eye) const
1760{
1761 assert(eye >= 0 && eye < static_cast<int>(viewStates.size()));
1762 return viewStates[eye].fov;
1763}
1764
1765//
1766// 指定した目の姿勢情報 (XrPosef) を取得する
1767//
1768const XrPosef& GgApp::OpenXR::getPose(int eye) const
1769{
1770 assert(eye >= 0 && eye < static_cast<int>(viewStates.size()));
1771 return viewStates[eye].pose;
1772}
1773
1774//
1775// 視点の姿勢が有効かどうか調べる
1776//
1777bool GgApp::OpenXR::isPoseValid() const
1778{
1779 return viewPoseValid;
1780}
1781
1782//
1783// レンダリング推奨解像度の横幅を取得する
1784//
1785GLsizei GgApp::OpenXR::getWidth(int eye) const
1786{
1787 assert(eye >= 0 && eye < static_cast<int>(views.size()));
1788 return static_cast<GLsizei>(views[eye].recommendedImageRectWidth);
1789}
1790
1791//
1792// レンダリング推奨解像度の高さを取得する
1793//
1794GLsizei GgApp::OpenXR::getHeight(int eye) const
1795{
1796 assert(eye >= 0 && eye < static_cast<int>(views.size()));
1797 return static_cast<GLsizei>(views[eye].recommendedImageRectHeight);
1798}
1799
1800//
1801// アスペクト比を取得する
1802//
1803GLfloat GgApp::OpenXR::getAspect(int eye) const
1804{
1805 assert(eye >= 0 && eye < static_cast<int>(views.size()));
1806 return static_cast<GLfloat>(views[eye].recommendedImageRectWidth)
1807 / static_cast<GLfloat>(views[eye].recommendedImageRectHeight);
1808}
1809
1810//
1811// ビューの総数を取得する
1812//
1813uint32_t GgApp::OpenXR::getViewCount() const
1814{
1815 return static_cast<uint32_t>(views.size());
1816}
1817
1818//
1819// 現在の参照空間タイプを取得する
1820//
1821XrReferenceSpaceType GgApp::OpenXR::getReferenceSpaceType() const
1822{
1823 return referenceSpaceType;
1824}
1825
1826//
1827// コントローラーがトラッキングされているか取得する
1828//
1829bool GgApp::OpenXR::isTracked(int hand) const
1830{
1831 assert(hand >= 0 && hand < Hand::Count);
1832 return controllerStates[hand].isTracked;
1833}
1834
1835//
1836// コントローラーのグリップ変換行列を取得する
1837//
1838GgMatrix GgApp::OpenXR::getGripMatrix(int hand) const
1839{
1840 assert(hand >= 0 && hand < Hand::Count);
1841 const auto& pose = controllerStates[hand].gripPose;
1842 const GgQuaternion q{ pose.orientation.x, pose.orientation.y, pose.orientation.z, pose.orientation.w };
1843 return ggTranslate(pose.position.x, pose.position.y, pose.position.z) * q.getMatrix();
1844}
1845
1846//
1847// コントローラーのエイム変換行列を取得する
1848//
1849GgMatrix GgApp::OpenXR::getAimMatrix(int hand) const
1850{
1851 assert(hand >= 0 && hand < Hand::Count);
1852 const auto& pose = controllerStates[hand].aimPose;
1853 const GgQuaternion q{ pose.orientation.x, pose.orientation.y, pose.orientation.z, pose.orientation.w };
1854 return ggTranslate(pose.position.x, pose.position.y, pose.position.z) * q.getMatrix();
1855}
1856
1857//
1858// コントローラーのグリップ位置を取得する
1859//
1860GgVector GgApp::OpenXR::getGripPosition(int hand) const
1861{
1862 assert(hand >= 0 && hand < Hand::Count);
1863 const auto& pos = controllerStates[hand].gripPose.position;
1864 return GgVector{ pos.x, pos.y, pos.z, 1.0f };
1865}
1866
1867//
1868// コントローラーのグリップ回転四元数を取得する
1869//
1870GgQuaternion GgApp::OpenXR::getGripOrientation(int hand) const
1871{
1872 assert(hand >= 0 && hand < Hand::Count);
1873 const auto& ori = controllerStates[hand].gripPose.orientation;
1874 return GgQuaternion{ ori.x, ori.y, ori.z, ori.w };
1875}
1876
1877//
1878// コントローラーのエイム位置を取得する
1879//
1880GgVector GgApp::OpenXR::getAimPosition(int hand) const
1881{
1882 assert(hand >= 0 && hand < Hand::Count);
1883 const auto& pos = controllerStates[hand].aimPose.position;
1884 return GgVector{ pos.x, pos.y, pos.z, 1.0f };
1885}
1886
1887//
1888// コントローラーのエイム回転四元数を取得する
1889//
1890GgQuaternion GgApp::OpenXR::getAimOrientation(int hand) const
1891{
1892 assert(hand >= 0 && hand < Hand::Count);
1893 const auto& ori = controllerStates[hand].aimPose.orientation;
1894 return GgQuaternion{ ori.x, ori.y, ori.z, ori.w };
1895}
1896
1897//
1898// トリガーの押し込み量を取得する
1899//
1900float GgApp::OpenXR::getTrigger(int hand) const
1901{
1902 assert(hand >= 0 && hand < Hand::Count);
1903 return controllerStates[hand].trigger;
1904}
1905
1906//
1907// グリップの押し込み量を取得する
1908//
1909float GgApp::OpenXR::getGrip(int hand) const
1910{
1911 assert(hand >= 0 && hand < Hand::Count);
1912 return controllerStates[hand].grip;
1913}
1914
1915//
1916// アナログスティックの入力値を取得する
1917//
1918std::array<float, 2> GgApp::OpenXR::getThumbstick(int hand) const
1919{
1920 assert(hand >= 0 && hand < Hand::Count);
1921 return controllerStates[hand].thumbstick;
1922}
1923
1924//
1925// アナログスティックのクリック状態を取得する
1926//
1927bool GgApp::OpenXR::getThumbstickClick(int hand) const
1928{
1929 assert(hand >= 0 && hand < Hand::Count);
1930 return controllerStates[hand].thumbstickClick;
1931}
1932
1933//
1934// プライマリボタンの押下状態を取得する
1935//
1936bool GgApp::OpenXR::getPrimaryButton(int hand) const
1937{
1938 assert(hand >= 0 && hand < Hand::Count);
1939 return controllerStates[hand].primaryButton;
1940}
1941
1942//
1943// セカンダリボタンの押下状態を取得する
1944//
1945bool GgApp::OpenXR::getSecondaryButton(int hand) const
1946{
1947 assert(hand >= 0 && hand < Hand::Count);
1948 return controllerStates[hand].secondaryButton;
1949}
1950
1951//
1952// メニューボタンの押下状態を取得する
1953//
1954bool GgApp::OpenXR::getMenuButton(int hand) const
1955{
1956 assert(hand >= 0 && hand < Hand::Count);
1957 return controllerStates[hand].menuButton;
1958}
1959
1960//
1961// コントローラーに振動を出力する
1962//
1963void GgApp::OpenXR::applyHapticVibration(int hand, float durationSeconds, float frequency, float amplitude)
1964{
1965 assert(hand >= 0 && hand < Hand::Count);
1966 if (session == XR_NULL_HANDLE || hapticAction == XR_NULL_HANDLE) return;
1967
1968 XrHapticVibration vibration{ XR_TYPE_HAPTIC_VIBRATION };
1969 vibration.duration = durationSeconds > 0.0f
1970 ? static_cast<XrDuration>(static_cast<double>(durationSeconds) * 1.0e9)
1971 : XR_MIN_HAPTIC_DURATION;
1972 vibration.frequency = frequency;
1973 vibration.amplitude = std::min(std::max(amplitude, 0.0f), 1.0f);
1974
1975 XrHapticActionInfo actionInfo{ XR_TYPE_HAPTIC_ACTION_INFO };
1976 actionInfo.action = hapticAction;
1977 actionInfo.subactionPath = handSubactionPath[hand];
1978
1979 xrWarn(instance, xrApplyHapticFeedback(session, &actionInfo,
1980 reinterpret_cast<const XrHapticBaseHeader*>(&vibration)),
1981 "Can't apply the haptic feedback");
1982}
1983
1984#endif
1985
1986#if defined(_WIN32)
1987# if !defined(_INC_WINDOWS) && !defined(_WINDOWS_)
1988# include <windows.h>
1989# endif
1990#else
1991# include <pwd.h>
1992# include <unistd.h>
1993#endif
1994//
1995// ユーザ名を得る
1996//
1998{
1999 // 環境変数からユーザ名を得る
2000 const char* user{
2001#if defined(_WIN32)
2002 std::getenv("USERNAME")
2003#else
2004 std::getenv("USER")
2005#endif
2006 };
2007
2008 // 環境変数からユーザ名が得られたらそれを返す
2009 if (user) return user;
2010
2011#if defined(_WIN32)
2012 // Win32 API を使ってユーザ名を得る
2013 char username[256];
2014 DWORD size{ sizeof(username) };
2015 if (GetUserNameA(username, &size)) return std::string(username);
2016#else
2017 struct passwd* pw{ getpwuid(getuid()) };
2018 if (pw) return std::string(pw->pw_name);
2019#endif
2020
2021 // ユーザ名が得られなかった
2022 return "unknown";
2023}
#define GG_BUTTON_COUNT
Definition GgApp.h:49
Window(const std::string &title="GLFW Window", int width=640, int height=480, int fullscreen=0, GLFWwindow *share=nullptr)
Definition GgApp.cpp:344
void swapBuffers() const
Definition GgApp.cpp:528
void restoreViewport() const
Definition GgApp.h:370
void updateViewport()
Definition GgApp.cpp:547
bool shouldClose() const
Definition GgApp.h:349
Window & operator=(const Window &w)=delete
GgApp(int major=0, int minor=1)
Definition GgApp.cpp:49
virtual ~GgApp()
Definition GgApp.cpp:91
static std::string getUsername()
Definition GgApp.cpp:1997
void getConjugateMatrix(GLfloat *a) const
Definition gg.h:4541
void getMatrix(GLfloat *a) const
Definition gg.h:4509
#define ggError()
Definition gg.h:1398
Definition gg.h:1351
GgMatrix ggTranslate(GLfloat x, GLfloat y, GLfloat z, GLfloat w=1.0f)
Definition gg.h:3159
GgMatrix ggFrustum(GLfloat left, GLfloat right, GLfloat bottom, GLfloat top, GLfloat zNear, GLfloat zFar)
Definition gg.h:3419
void ggInit()
Definition gg.cpp:1354